diff options
author | chai <chaifix@163.com> | 2021-11-15 11:34:17 +0800 |
---|---|---|
committer | chai <chaifix@163.com> | 2021-11-15 11:34:17 +0800 |
commit | 4c36bed53fe63ae6056730b3ecad2573f03d88f8 (patch) | |
tree | 264fa97bb8a7a6275b6e4a3693b45e0c27fb0cfc | |
parent | 14fa5cd8b00cf10d53c829caa0d5f9b40a3d8d3f (diff) |
+lua addons
508 files changed, 130852 insertions, 25 deletions
diff --git a/Data/DefaultContent/Libraries/GameLab/Class.lua b/Data/DefaultContent/Libraries/GameLab/Class.lua index 005a7ce..96865eb 100644 --- a/Data/DefaultContent/Libraries/GameLab/Class.lua +++ b/Data/DefaultContent/Libraries/GameLab/Class.lua @@ -1,6 +1,6 @@ -- Declare class
--- 保留方法名:New, Ctor, Is
+-- 保留方法:New, Ctor, Is
-- 保留字段:_type, _base
local TrackClassInstances = false
diff --git a/Data/DefaultContent/Libraries/addons/.gitattributes b/Data/DefaultContent/Libraries/addons/.gitattributes new file mode 100644 index 0000000..c4f806b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/.gitattributes @@ -0,0 +1,25 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.lua text +*.json text + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/Data/DefaultContent/Libraries/addons/.gitignore b/Data/DefaultContent/Libraries/addons/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/Data/DefaultContent/Libraries/addons/README.md b/Data/DefaultContent/Libraries/addons/README.md new file mode 100644 index 0000000..502b97c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/README.md @@ -0,0 +1,36 @@ +Lua +=== +Lua Addons and Scripts +Lua documentation can be found at the Windower Development wiki: http://dev.windower.net/doku.php?id=lua:start + +Licensing +--------- +We require that all submissions to the Windower/Lua repository be licensed under the BSD license, reproduced below. To do this, simply place the license text in a comment at the top of each of your source files, and replace `<year>`, `<addon name>` and both instances of `<your name>` with appropriate values. + +``` +Copyright © <year>, <your name> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/AnnounceTarget.lua b/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/AnnounceTarget.lua new file mode 100644 index 0000000..8c966cc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/AnnounceTarget.lua @@ -0,0 +1,114 @@ +-- Copyright © 2015, JoshK6656, Sechs +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of AnnounceTarget nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Sechs 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. + +config = require ('config') +require ('logger') + +_addon.name = 'AnnounceTarget' +_addon.author = 'JoshK6656, Sechs' +_addon.version = '1.2.1' +_addon.commands = {'announcetarget','at'} + +defaults = T{} +defaults.AnnounceMode = 'party' --this can be say/party/linkshell/linkshell2/shout/echo/s/p/l/l2/sh +defaults.AutoAnnounce = false + +settings = config.load(defaults) + +adherents_map = {['Steadfast Adherent']="PLD, DEF+", ['Furtive Adherent']="WHM, MDB+", ['Occult Adherent']="WAR, EVA+", + ['Fleet Adherent']="WAR, Haste+", ['Brawny Adherent']="DRK, ATK+", ['Martial Adherent']="DRK,Regain+", + ['Honed Adherent']="RDM, Fast Cast+", ['Insidious Adherent']="RDM, MEVA+", ['Hexbreaking Adherent']="BLM, MAB+"} +chatmodes = S{'say','party','linkshell','linkshell2','shout','echo','s','p','l','l2','sh'} +false_values = S{'false','off','f','0'} +true_values = S{'true','on','t','1'} +moblist = S{} +mob = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') + +windower.register_event('addon command', function (command,...) + command = command and command:lower() or 'help' + local args = T{...} + if command == 'reload' then + windower.send_command('lua reload AnnounceTarget') + elseif command == 'unload' then + windower.send_command('lua unload AnnounceTarget') + elseif command == 'chatmode' or command == 'cm' then + if args[1] ~= nil and chatmodes:contains(args[1]) then + log(' ***** Chat Mode changed to "'..args[1]..'" *****') + settings.AnnounceMode = args[1] + config.save(settings) + else + error(' ***** That is not a valid chat mode *****') + end + elseif command == 'announce' or command == 'a' then + announce(mob.name) + elseif command == 'autoannounce' or command == 'aa' then + local value = args[1] and args[1]:lower() or nil + if not value then + settings.AutoAnnounce = not settings.AutoAnnounce + elseif false_values:contains(value) or true_values:contains(value) then + settings.AutoAnnounce = not false_values:contains(args[1]:lower()) + else + error(' ***** "'..args[1]..'" is not a valid setting for AutoAnnounce *****') + return + end + log(' ***** AutoAnnounce changed to "',settings.AutoAnnounce,'" *****') + config.save(settings) + elseif command == 'clear' or command == 'c' then + moblist:clear() + log(' ***** Previously announced targets table cleared *****') + elseif command == 'help' then + log(' *** '.._addon.name..' v'.._addon.version..' - Authors: '.._addon.author..' ***') + log(' help -> Displays this message') + log(' chatmode -> Changes chat output mode. Available settings: say/party/linkshell/linkshell2/shout/echo') + log(' autoannounce -> Turns AutoAnnounce on or off. Accepted settings: on/true/false/off') + log(' announce -> Manually announces for the current target') + log(' clear -> Clears the list of announced mobs during AutoAnnounce mode on') + else + error(' ***** That is not a valid AnnounceTarget command. See //at help. *****') + end +end) + +function announce(name) + if adherents_map[name] then + windower.send_command('input /'..settings.AnnounceMode..' '..name..' buff is ==> '..adherents_map[name]) + else + log(' ***** Target is not an Adherent *****') + end +end + +windower.register_event('target change',function(index) + mob = windower.ffxi.get_mob_by_index(index) + if settings.AutoAnnounce and index ~= 0 then + if adherents_map[mob.name] and not moblist:contains(mob.id) then + moblist:add(mob.id) + announce(mob.name) + end + end +end) + +windower.register_event('zone change',function(...) + moblist:clear() +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/ReadMe.md new file mode 100644 index 0000000..a904251 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/AnnounceTarget/ReadMe.md @@ -0,0 +1,35 @@ +**Authors:** JoshK6656, Sechs +**Version:** 1.2.1 +**Date:** 12/02/2015 + +**Description:** +AnnounceTarget checks your current target and produces various chat messages according to settings. +At the moment it works for Incursion: if target is an Incursion Adherent it will send a message to the preferred chat mode saying the Adherent's job and the buffs it gives to the linked Boss. + + +**Commands:** +//announcetarget reload +//announcetarget unload +//announcetarget chatmode say|party|linkshell|linkshell2|shout +//announcetarget autoannounce on|true|off|false +//announcetarget announce +//announcetarget clear + + +**Short commands:** +//at reload +//at unload +//at cm s|p|l|l2|sh +//at aa t|f|0|1 +//at a +//at c + + +**Commands details:** +*Reload/Unload: Reloads or Unloads the addon, respectively +*Chatmode: Used to change the output chat mode. By default it's set to party. +*AutoAnnounce: If AutoAnnounce is turned on the addon will automatically announce in the selected chat mode whenever you target an Adherent. +It will remember, through mob_id, the Adherents it has already announced, so there won't be any risk of spamming chat with the same Adherent over and over. +This list is temporary and clears up each time you reload the addon, otherwise you can manually clear it by using the specific command +*Announce: Manual command to announce the current target. Works regardles of AutoAnnounce being on or off +*Clear: Used to clear the list of "already announced mobs". This list already clears each time you zone or reload the addon. diff --git a/Data/DefaultContent/Libraries/addons/addons/AutoRA/AutoRA.lua b/Data/DefaultContent/Libraries/addons/addons/AutoRA/AutoRA.lua new file mode 100644 index 0000000..49dd1bb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/AutoRA/AutoRA.lua @@ -0,0 +1,127 @@ +_addon.author = 'Banggugyangu' +_addon.version = '3.0.0' +_addon.commands = {'autora', 'ara'} + +require('functions') +local config = require('config') + +local defaults = { + HaltOnTp = true, + Delay = 1.5 +} + +local settings = config.load(defaults) + +local auto = false +local player_id + +windower.send_command('bind ^d ara start') +windower.send_command('bind !d ara stop') + +local shoot = function() + windower.send_command('input /shoot <t>') +end + +local start = function() + auto = true + windower.add_to_chat(17, 'AutoRA STARTING~~~~~~~~~~~~~~') + + shoot() +end + +local stop = function() + auto = false + windower.add_to_chat(17, 'AutoRA STOPPING ~~~~~~~~~~~~~~') +end + +local haltontp = function() + settings.HaltOnTp = not settings.HaltOnTp + + if settings.HaltOnTp then + windower.add_to_chat(17, 'AutoRA will halt upon reaching 1000 TP') + else + windower.add_to_chat(17, 'AutoRA will no longer halt upon reaching 1000 TP') + end +end + +local check = function() + if not auto then + return + end + + local player = windower.ffxi.get_player() + if not player or not player.target_index then + return + end + + if player.vitals.tp >= 1000 and settings.HaltOnTp then + auto = false + windower.add_to_chat(17, 'AutoRA HALTING AT 1000 TP ~~~~~~~~~~~~~~') + elseif player.status == 1 then + shoot() + end +end + +windower.register_event('action', function(action) + if auto and action.actor_id == player_id and action.category == 2 then + check:schedule(settings.Delay) + end +end) + +windower.register_event('addon command', function(command) + command = command and command:lower() or 'help' + + if command == 'start' then + start() + elseif command == 'stop' then + stop() + elseif command == 'shoot' then + shoot() + elseif command == 'reload' then + setDelay() + elseif command == 'haltontp' then + haltontp() + elseif command == 'help' then + windower.add_to_chat(17, 'AutoRA v' .. _addon.version .. 'commands:') + windower.add_to_chat(17, '//ara [options]') + windower.add_to_chat(17, ' start - Starts auto attack with ranged weapon') + windower.add_to_chat(17, ' stop - Stops auto attack with ranged weapon') + windower.add_to_chat(17, ' haltontp - Toggles automatic halt upon reaching 1000 TP') + windower.add_to_chat(17, ' help - Displays this help text') + windower.add_to_chat(17, ' ') + windower.add_to_chat(17, 'AutoRA will only automate ranged attacks if your status is "Engaged". Otherwise it will always fire a single ranged attack.') + windower.add_to_chat(17, 'To start auto ranged attacks without commands use the key: Ctrl+D') + windower.add_to_chat(17, 'To stop auto ranged attacks in the same manner: Atl+D') + end +end) + +windower.register_event('load', 'login', 'logout', function() + local player = windower.ffxi.get_player() + player_id = player and player.id +end) + +--Copyright © 2013, Banggugyangu +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. diff --git a/Data/DefaultContent/Libraries/addons/addons/AutoRA/README.md b/Data/DefaultContent/Libraries/addons/addons/AutoRA/README.md new file mode 100644 index 0000000..14ad45c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/AutoRA/README.md @@ -0,0 +1,23 @@ +AutoRA v. 2.0.0 + +AutoRA simply causes ranged attacks to behave like the melee Auto-Attack while your weapon is drawn. If no weapon is drawn or you put your weapon away, your ranged attacks will cease. + +||||||||||||||||||||||DISCLAIMER|||||||||||||||||||||||| +AutoRA will not consider any ammo you have equipped! +Be warned. If you have your equipment window open, spellcast WILL NOT change your ammo!!! +I am not responsible for any lost ammo due to AutoRA. + +||||||||||||||||||||||IMPORTANT!!||||||||||||||||||||||| +AutoRA will change any keybinds for the following keys: +Ctrl + D: Start Ranged attacking. (If no weapon drawn, fires a single ranged attack) +Alt + D: Force stop Ranged attacking. (Automatically done when weapon is put away.) + + +Commands: + +ara [options] + start - Starts Ranged Attacking (Identical to CTRL + D) + stop - Stops Ranged Attacking (Identical to ALT + D) + shoot - Fires a ranged attack + haltontp - Toggles automatic halt upon reaching 1000 TP + help - Shows Version info and available commands diff --git a/Data/DefaultContent/Libraries/addons/addons/BLUAlert/BLUAlert.lua b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/BLUAlert.lua new file mode 100644 index 0000000..0018943 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/BLUAlert.lua @@ -0,0 +1,89 @@ +--[[ +BLUAlert v1.0.0.0 + +Copyright 2017, Christopher Szewczyk +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of BLUAlert nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Christopher Szewczyk 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. +]] + +require('sets') +res = require('resources') +chat = require('chat') + +_addon.name = 'BLUAlert' +_addon.author = 'Kainsin' +_addon.version = '1.0.0.0' + +-- Some BLU spells have a different name then the monster abilities they come from. +blu_different_names = { + ["Everyone's Grudge"] = "Evryone. Grudge", + ["Nature's Meditation"] = "Nat. Meditation", + ["Orcish Counterstance"] = "O. Counterstance", + ["Tempestuous Upheaval"] = "Tem. Upheaval", + ["Atramentous Libations"] = "Atra. Libations", + ["Winds of Promyvion"] = "Winds of Promy.", + ["Quadratic Continuum"] = "Quad. Continuum", +} + +-- Traverse through all of the BLU spells looking for the one with the given name. +blu_spells = res.spells:type('BlueMagic') +function find_blu_spell(monster_ability_name) + for i,v in pairs(blu_spells) do + if (v.english == monster_ability_name) then + return v.id + end + end +end + +-- Since the action packet gives monster abilities by ID, we'll want to create a +-- Monster Ability -> BLU Spell mapping to quickly find out which monster ability +-- corresponds to which spell. +spell_id_map = {} +for i,v in pairs(res.monster_abilities) do + local monster_ability_name = blu_different_names[v.english] or v.english + spell_id_map[i] = find_blu_spell(monster_ability_name) +end + +function get_action_id(targets) + for i,v in pairs(targets) do + for i2,v2 in pairs(v['actions']) do + if v2['param'] then + return v2['param'] + end + end + end +end + +windower.register_event('action', function(action) + -- Category 7 is the readies message for abilities. + if (action['category'] == 7) then + local action_id = get_action_id(action['targets']) + local spell_id = spell_id_map[action_id] + if spell_id and not windower.ffxi.get_spells()[spell_id] then + windower.add_to_chat(123, "Unknown Blue Magic Used!") + windower.play_sound(windower.addon_path..'sounds/UnknownBlueMagicUsed.wav') + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/BLUAlert/README.md b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/README.md new file mode 100644 index 0000000..ea67ae4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/README.md @@ -0,0 +1,7 @@ +Are you sick and tired of watching your chat log like a hawk when hunting for new Blue Magic spells, waiting for the monster to use that one ability? Are you sick and tired of going AFK until the monster dies, losing out on precious time that could be better spent in other areas of FFXI? + +If so then your savior is here! BLUAlert! + +This add-on will monitor monster abilities for Blue Magic spells that you don't know. When one is detected it will write a message into chat and play a sound to notify you. + +Now you can relax while you AFK with the knowledge that you'll be able to come back and kill the monster right after it uses that one ability!
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/BLUAlert/sounds/UnknownBlueMagicUsed.wav b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/sounds/UnknownBlueMagicUsed.wav Binary files differnew file mode 100644 index 0000000..bacf3d2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BLUAlert/sounds/UnknownBlueMagicUsed.wav diff --git a/Data/DefaultContent/Libraries/addons/addons/BattleStations/LICENSE b/Data/DefaultContent/Libraries/addons/addons/BattleStations/LICENSE new file mode 100644 index 0000000..d719b9e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BattleStations/LICENSE @@ -0,0 +1,25 @@ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Battle Stations nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/BattleStations/README.md b/Data/DefaultContent/Libraries/addons/addons/BattleStations/README.md new file mode 100644 index 0000000..83fd082 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BattleStations/README.md @@ -0,0 +1,225 @@ +**Author:** [Sjshovan (Apogee)](https://github.com/Ap0gee) +**Version:** v0.9.1 + + +# Battle Stations + +> A Windower 4 addon that allows the user to change or remove the default battle music in Final Fantasy 11 Online. + + +### Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Aliases](#aliases) +- [Usage](#usage) +- [Commands](#commands) +- [Support](#support) +- [Change Log](#change-log) +- [Known Issues](#known-issues) +- [TODOs](#todos) +- [License](#license) + +___ +### Prerequisites +1. [Final Fantasy 11 Online](http://www.playonline.com/ff11us/index.shtml) +2. [Windower 4](http://windower.net/) + +___ +### Installation + +**Windower:** +1. Navigate to the `Addons` section at the top of Windower. +2. Locate the `BattleStations` addon. +3. Click the download button. +4. Ensure the addon is switched on. + +**Manual:** +1. Navigate to <https://github.com/Ap0gee/BattleStations>. +2. Click on `Releases`. +3. Click on the `Source code (zip)` link within the latest release to download. +4. Extract the zipped folder to `Windower4/addons/`. +5. Rename the folder to remove the version tag (`-v0.9.0`). The folder should be named `BattleStations`. + +___ +### Aliases +The following aliases are available to Battle Stations commands: + +**battlestations:** stations | bs +**list:** l +**set:** s +**get:** g +**default:** d +**normal:** n +**reload:** r +**about:** a +**help:** h +**stations:** station | s +**radios:** receivers | receiver | radio | r +**all:** all | a | * + +___ +### Usage + +Manually load the addon by using one of the following commands: + + //lua load battlestations + //lua l battlestations + +___ +### Commands + +**help** + +Displays the available Battle Stations commands. Below are the equivalent ways of calling the command: + + //battlestations help + //stations help + //bs help + + //battlestations h + //stations h + //bs h + +**list _[radios|stations] [category#]_** + +Displays the available radios and or stations. Below are some useage examples of this command: + + //bs list + //bs l + + //bs list radios + //bs l radios + //bs l r + + //bs list stations + //bs l stations + //bs l s + + //bs l s 100 + +* _**[radios|stations]:**_ Optional parameter used to filter the list display to show only available radios or stations. If neither filter type is present, all available stations and radios will be listed. +* _**[category#]:**_ Optional parameter used to filter the list of stations by the given category number. The available category numbers are 100-107. + +**set _\<station> [radio]_** + +Sets the radio(s) to the given station. Below are some useage examples of this command: + + //bs set 100.1 + //bs s 100.1 + + //bs s 100.1 solo + //bs s 100.1 party + +* _**\<station>:**_ Required parameter. +* _**[radio]:**_ Optional parameter used to specify which radio to set the given station to. If no radio type is present both radios will be set to the given station. + +**get _[radio]_** + +Displays the currently set station on the given radio(s). Below are some useage examples of this command: + + //bs get + //bs g + + //bs g solo + //bs g party + +* _**[radio]:**_ Optional parameter used to specify the radio for which you would like to display the currently set station. If no radio type is present, the currently set station for both radios will be displayed. + +**default _[radio]_** + +Sets the given radio(s) to the default station (Current Zone Music). Below are some useage examples of this command: + + //bs default + //bs d + + //bs d solo + //bs d party + +* _**[radio]:**_ Optional parameter used to specify which radio to set the default station to. If no radio type is present, both radios will be set to the default station. + + +**normal _[radio]_** + +Sets the given radio(s) to the original game music. Below are some useage examples of this command: + + //bs normal + //bs n + + //bs n solo + //bs n party + +* _**[radio]:**_ Optional parameter used to specify which radio to set the normal station to. If no radio type is present, both radios will be set to the normal station. + +**reload** + +Reloads the Battle Stations addon. Below are the equivalent ways of calling the command: + + //battlestations reload + //stations reload + //bs reload + + //battlestations r + //stations r + //bs r + +**about** + +Displays information about the Battle Stations addon. Below are the equivalent ways of calling the command: + + /battlestations about + //stations about + //bs about + + //battlestations a + //stations a + //bs a + +___ +### Support +**Having Issues with this addon?** +* Please let me know [here](https://github.com/Ap0gee/BattleStations/issues/new). + +**Have something to say?** +* Send me some feedback here: <sjshovan@gmail.com> + +**Want to stay in the loop with my work?** +* You can follow me at: <https://twitter.com/Sjshovan> + +**Want to toss a coin to your modder?** +* You can do so here: <https://www.paypal.me/Sjshovan> + +___ +### Change Log + +**v0.9.1** - 12/29/2019 +- **Fix:** Resolved mismatched setting type error within the `bs normal` command. +- **Add:** New command added (about). +- **Update:** Silent song id changed to help prevent future game updates from overriding. +- **Update:** README Commands updated. +- **Update:** README Aliases updated. +- **Update:** README Known Issues updated. +- **Update:** README TODOS updated. + +**v0.9.0** - 6/19/2018 +- Initial release + +___ +### Known Issues + +- **Issue:** During campaign battles in the past, the music switches from the campaign music to the normal zone music while stations are set to `107.3`. + +___ +### TODOS + +- **TODO:** Consider providing aliases to stations to make references easier. +- **TODO:** Consider adding categories as a list type. +- **TODO:** Investigate methods for resolving the campaign battle music issue. +___ + +### License + +Copyright © 2018, [Sjshovan (Apogee)](https://github.com/Ap0gee). +Released under the [BSD License](LICENSE). + +***
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/BattleStations/battlestations.lua b/Data/DefaultContent/Libraries/addons/addons/BattleStations/battlestations.lua new file mode 100644 index 0000000..5c2922a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BattleStations/battlestations.lua @@ -0,0 +1,597 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Battle Stations nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'Battle Stations' +_addon.description = 'Change or remove the default battle music.' +_addon.author = 'Sjshovan (Apogee) sjshovan@gmail.com' +_addon.version = '0.9.1' +_addon.commands = {'battlestations', 'stations', 'bs'} + +local _logger = require('logger') +local _config = require('config') +local _packets = require('packets') + +require('functions') +require('constants') +require('helpers') + +local defaults = { + stations = { + solo = 107.3, + party = 107.3 + } +} + +local settings = _config.load(defaults) + +local help = { + commands = { + buildHelpSeperator('=', 28), + buildHelpTitle('Commands'), + buildHelpSeperator('=', 28), + buildHelpCommandEntry('list [radios|stations] [category#]', 'Display the available radios and or stations.'), + buildHelpCommandEntry('set <station> [radio]', 'Set radio(s) to the given station.'), + buildHelpCommandEntry('get [radio]', 'Display currently set station on the given radio(s).'), + buildHelpCommandEntry('default [radio]', 'Set radio(s) to the default station (Current Zone Music).'), + buildHelpCommandEntry('normal [radio]', 'Set radio(s) to the original game music.'), + buildHelpCommandEntry('reload', 'Reload Battle Stations.'), + buildHelpCommandEntry('about', 'Display information about Battle Stations.'), + buildHelpCommandEntry('help', 'Display Battle Stations commands.'), + buildHelpSeperator('=', 28), + }, + + radios = { + buildHelpSeperator('=', 25), + buildHelpTitle('Radios'), + buildHelpSeperator('=', 25), + buildHelpRadioEntry(stations.receivers.solo:ucfirst(), 'Plays Solo Battle Music'), + buildHelpRadioEntry(stations.receivers.party:ucfirst(), 'Plays Party Battle Music'), + buildHelpSeperator('=', 25), + }, + + about = { + buildHelpSeperator('=', 23), + buildHelpTitle('About'), + buildHelpSeperator('=', 23), + buildHelpTypeEntry('Name', _addon.name), + buildHelpTypeEntry('Description', _addon.description), + buildHelpTypeEntry('Author', _addon.author), + buildHelpTypeEntry('Version', _addon.version), + buildHelpSeperator('=', 23), + }, + + aliases = { + list = { + stations = T{ + 's', + 'station', + 'stations', + }, + radios = T{ + 'r', + 'radio', + 'radios', + 'receiver', + 'receivers' + }, + categories = T{ + 'c', + 'cat', + 'category', + 'categories' + }, + all = T{ + '*', + 'a', + 'all', + } + } + } +} + +function displayHelp(table_help) + for index, command in pairs(table_help) do + displayResponse(command) + end +end + +function displayStations(range) + displayResponse(buildHelpSeperator('=', 27)) + displayResponse(buildHelpTitle('Stations')) + displayResponse(buildHelpSeperator('=', 27)) + + if range ~= nil then + if categoryValid(range) then + displayRangeFrequencies(range) + end + else + for i=100, 107, 1 do + range = tostring(i) + displayRangeFrequencies(range) + end + end + displayResponse(buildHelpSeperator('=', 26)) +end + +function displayRangeFrequencies(range, name) + local categories = stations.categories + + if categoryValid(range) then + local name = categories[range] + displayResponse(buildHelpStationCategoryEntry(range, name)) + displayFrequencies(range) + end +end + +function displayFrequencies(range) + for i=1, 9, 1 do + local frequency = range .. '.%s':format(tostring(i)) + if frequencyValid(frequency) then + local frequencyObj = getFrequencyObjByValue(frequency) + local response = buildHelpStationEntry(frequency, frequencyObj.callSign) + displayResponse(response) + end + end +end + +function displayCategories() + displayResponse(buildHelpSeperator('=', 27)) + displayResponse(buildHelpTitle('Categories')) + displayResponse(buildHelpSeperator('=', 27)) + for i=100, 107, 1 do + local range = tostring(i) + if categoryValid(range) then + local name = stations.categories[range] + displayResponse(buildHelpStationCategoryEntry(range, name)) + end + end + displayResponse(buildHelpSeperator('=', 27)) +end + +function getStations() + return settings.stations +end + +function setStation(radio, frequency) + if radio == stations.receivers.solo then + settings.stations.solo = frequency + elseif radio == stations.receivers.party then + settings.stations.party = frequency + else + settings.stations.solo = frequency + settings.stations.party = frequency + end + + settings:save() +end + +function resolveCurrentStations() + local current_stations = getStations() + local radio = 'solo' + local frequency = tostring(defaults.stations.solo) + local message_template = '%s station found in settings was not valid and was set to the default %s (%s).' + + if not frequencyValid(current_stations.solo) then + current_stations.solo = frequency + setStation(radio, frequency) + + displayResponse( + buildWarningMessage( + message_template:format( + radio:ucfirst():color(colors.secondary), + frequency:color(colors.primary), + getFrequencyObjByValue(frequency).callSign + ) + ) + ) + end + + if not frequencyValid(current_stations.party) then + radio = 'party' + frequency = tostring(defaults.stations.party) + current_stations.party = party_default + + setStation(radio, frequency) + + displayResponse( + buildWarningMessage( + message_template:format( + radio:ucfirst():color(colors.secondary), + frequency:color(colors.primary), + getFrequencyObjByValue(frequency).callSign + ) + ) + ) + end + + return current_stations +end + +function injectBattleMusic() + local current_stations = resolveCurrentStations() + local song = getFrequencyObjByValue(current_stations.solo).song + local music_type = music.types.battle_solo + + if playerInParty() then + music_type = music.types.battle_party + song = getFrequencyObjByValue(current_stations.party).song + end + + song = getConditionalSongTranslation(song) + + _packets.inject(_packets.new('incoming', packets.inbound.music_change.id, { + ['BGM Type'] = music_type, + ['Song ID'] = song + })) +end + +function getFrequencyObjByValue(frequency) + return stations.frequencies[tostring(frequency)] +end + +function getCurrentTime(formatted) + local timestamp = tostring(windower.ffxi.get_info().time) + local hours = (timestamp / 60):floor() + local minutes = timestamp % 60 + if formatted then + return "%s:%s":format(hours, minutes) + end + return timestamp +end + +function getZoneBGMTable() + local data = windower.packets.last_incoming(packets.inbound.zone_update.id) + local packet = _packets.parse('incoming', data) + return { + day = packet['Day Music'], + night = packet['Night Music'], + solo = packet['Solo Combat Music'], + party = packet['Party Combat Music'] + } +end + +function getConditionalSongTranslation(song) + local zone_bgm_table = getZoneBGMTable() + if song == music.songs.others.zone then + if timeIsDaytime() then + song = zone_bgm_table.day + else + song = zone_bgm_table.night + end + + if playerInReive() then + song = music.songs.seekers_of_adoulin.breaking_ground + end + + elseif song == music.songs.others.normal then + if playerInParty() then + song = zone_bgm_table.party + else + song = zone_bgm_table.solo + end + end + return song +end + +function getPlayerBuffs() + return T(windower.ffxi.get_player().buffs) +end + +function timeIsDaytime() + local current_time = tonumber(getCurrentTime()) + return current_time >= 6*60 and current_time <= 18*60 +end + +function playerIsFighting() + return windower.ffxi.get_player().status == player.statuses.fighting +end + +function playerInParty() + return windower.ffxi.get_party().alliance_count > 1 +end + +function playerInReive() + return getPlayerBuffs():contains(player.buffs.reiveMark) +end + +function frequencyValid(frequency) + return stations.frequencies[tostring(frequency)] ~= nil +end + +function radioValid(radio) + return stations.receivers[radio] ~= nil or radio == '*' +end + +function categoryValid(category) + return stations.categories[category] ~= nil +end + +function listTypeValid(list_type) + return help.lists[list_type] ~= nil +end + +function handleInjectionNeeds() + if needs_inject then + injectBattleMusic() + needs_inject = false; + end +end + +windower.register_event('load', function () + injectBattleMusic() +end) + +windower.register_event('unload', function() + local music_type = music.types.battle_solo + local zone_bgm_table = getZoneBGMTable() + local song = zone_bgm_table.solo + + if playerInParty() then + music_type = music.types.battle_party + song = zone_bgm_table.solo + end + + _packets.inject(_packets.new('incoming', packets.inbound.music_change.id, { + ['BGM Type'] = music_type, + ['Song ID'] = song, + })) +end) + +windower.register_event('action', function(act) + if act.actor_id == windower.ffxi.get_player().id then + if act.category == 4 and act.recast == 225 and act.targets[1].actions[1].animation == 939 then + if not playerInParty() then + functions.loop(injectBattleMusic, 1, 5) + end + end + end +end) + +windower.register_event('outgoing chunk', function(id, data) + if id == packets.outbound.action.id then + local packet = _packets.parse('outgoing', data) + if packet.Category == packets.outbound.action.categories.engage then + injectBattleMusic() + end + end +end) + +windower.register_event('addon command', function(command, ...) + if command then + command = command:lower() + else + displayHelp(help.commands) + return + end + + local command_args = {...} + + local respond = false + local response_message = '' + local success = true + + if command == 'list' or command == 'l' then + local list_type = (command_args[1] or '*'):lower() + local category = command_args[2] + + if help.aliases.list.stations:contains(list_type) then + if category then + if categoryValid(category) then + displayStations(category) + + else + respond = true + success = false + response_message = 'Category not recognized.' + end + else + displayStations() + end + + elseif help.aliases.list.radios:contains(list_type) then + displayHelp(help.radios) + + elseif help.aliases.list.categories:contains(list_type) then + displayCategories() + + elseif help.aliases.list.all:contains(list_type) then + displayHelp(help.radios) + displayStations() + + else + respond = true + success = false + response_message = 'List type not recognized.' + end + + elseif command == 'set' or command == 's' then + respond = true + + local frequency = command_args[1]:lower() + + local radio = (command_args[2] or '*'):lower() + + if not frequencyValid(frequency) then + success = false + response_message = 'Frequency not recognized.' + + elseif not radioValid(radio) then + success = false + response_message = 'Radio not recognized.' + else + needs_inject = true + + local context = 'radio' + + setStation(radio, tonumber(frequency)) + + if radio == '*' then + context = 'radios' + radio = 'Solo and Party' + end + + response_message = buildSetResponseMessage( + radio:ucfirst(), + context, + frequency, + getFrequencyObjByValue(frequency).callSign + )..'.' + end + + elseif command == 'get' or command == 'g' then + respond = true + + local current_stations = resolveCurrentStations() + local radio = (command_args[1] or '*'):lower() + local frequency = current_stations[radio] + local frequency2 = current_stations.party + local individual = false + + if not radioValid(radio) then + success = false + response_message = 'Radio not recognized.' + else + local context = 'radio is' + + if radio == '*' then + frequency = current_stations.solo + + if current_stations.party ~= current_stations.solo then + individual = true + else + context = 'radios are' + radio = 'Solo and Party' + end + end + + if not individual then + response_message = buildGetResponseMessage( + radio:ucfirst(), + context, + frequency, + getFrequencyObjByValue(frequency).callSign + )..'.' + + else + local solo_message = buildGetResponseMessage( + stations.receivers.solo:ucfirst(), + context, + frequency, + getFrequencyObjByValue(frequency).callSign + ) + + local party_message = buildGetResponseMessage( + stations.receivers.party:ucfirst(), + context, + frequency2, + getFrequencyObjByValue(frequency2).callSign + ) + + response_message = solo_message..' and '..party_message..'.' + end + end + + elseif command == 'default' or command == 'd' then + respond = true + + local radio = (command_args[1] or '*'):lower() + + if not radioValid(radio) then + success = false + response_message = 'Radio not recognized.' + else + needs_inject = true + + local frequency = defaults.stations.solo + local context = 'radio' + + setStation(radio, tonumber(frequency)) + + if radio == '*' then + context = 'radios' + radio = 'Solo and Party' + end + + response_message = buildSetResponseMessage( + radio:ucfirst(), + context, + frequency, + getFrequencyObjByValue(frequency).callSign + )..'.' + end + + elseif command == 'normal' or command == 'n' then + respond = true + + local radio = (command_args[1] or '*'):lower() + + if not radioValid(radio) then + success = false + response_message = 'Radio not recognized.' + else + needs_inject = true + + local frequency = '107.2' + local context = 'radio' + + setStation(radio, tonumber(frequency)) + + if radio == '*' then + context = 'radios' + radio = 'Solo and Party' + end + + response_message = buildSetResponseMessage( + radio:ucfirst(), + context, + frequency, + getFrequencyObjByValue(frequency).callSign + )..'.' + end + + elseif command == 'reload' or command == 'r' then + windower.send_command('lua r battlestations') + + elseif command == 'about' or command == 'a' then + displayHelp(help.about) + + elseif command == 'help' or command == 'h' then + displayHelp(help.commands) + + else + displayHelp(help.commands) + end + + if respond then + displayResponse( + buildCommandResponse(response_message, success) + ) + end + + handleInjectionNeeds() +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/BattleStations/constants.lua b/Data/DefaultContent/Libraries/addons/addons/BattleStations/constants.lua new file mode 100644 index 0000000..950ecc1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BattleStations/constants.lua @@ -0,0 +1,382 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Battle Stations nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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. +]] + +packets = { + inbound = { + music_change = { + id = 0x05F + }, + zone_update = { + id = 0x00A + } + }, + outbound = { + action = { + id = 0x1A, + categories = { + engage = 2, + disengage = 4 + + }, + } + }, +} + +player = { + statuses = { + idle = 0x00, + fighting = 0x01, + }, + buffs = { + reiveMark = 511 + } +} + +colors = { + primary = 200, + secondary = 207, + info = 0, + warn = 140, + danger = 167, + success = 158 +} + +music = { + songs = { + final_fantasy_xi = { + battle_theme = 101, + battle_theme_2 = 103, + battle_in_the_dungeon = 115, + battle_in_the_dungeon_2 = 102, + tough_battle = 125, + awakening = 119 + }, + rise_of_the_zilart = { + battle_theme_3 = 191, + battle_in_the_dungeon_3 = 192, + tough_battle_2 = 193, + fighters_of_the_crystal = 196, + ealdnarche = 198, + belief = 195 + }, + chains_of_promathia = { + onslaught = 219, + depths_of_the_soul = 218, + turmoil = 220, + ruler_of_the_skies = 232, + dusk_and_dawn = 224, + a_realm_of_emptiness = 137 + }, + treasures_of_aht_urhgan = { + mercenaries_delight = 138, + delve = 139, + rapid_onslaught_assault = 144, + fated_strife_besieged = 142, + hellriders = 143, + black_coffin = 172, + iron_colossus = 186, + ragnarok = 187 + }, + wings_of_the_goddess = { + clash_of_standards = 215, + on_this_blade = 216, + roar_of_the_battle_drums = 247, + run_maggot_run = 249, + under_a_clouded_moon = 250, + kindred_cry = 217, + provenance_watcher = 55, + goddess_divine = 46 + }, + seekers_of_adoulin = { + steel_sings_blades_dance = 57, + breaking_ground = 64, + buccaneers = 170, + keepers_of_the_wild = 62 + }, + add_ons = { + echoes_of_creation = 47, + luck_of_the_mog = 49, + a_feast_for_ladies = 50, + melodies_errant = 52, + shinryu = 53, + wail_of_the_void = 82 + }, + others = { + silent = 9999, + normal = 09, + zone = 00 + }, + }, + types = { + battle_solo = 2, + battle_party = 3, + idle_day = 0, + idle_night = 1 + } +} + +stations = { + receivers = T{ + solo = 'solo', + party = 'party' + }, + + categories = T{ + ['100'] = 'Final Fantasy XI', + ['101'] = 'Rise of the Zilart', + ['102'] = 'Chains of Promathia', + ['103'] = 'Treasures of Aht Urhgan', + ['104'] = 'Wings of the Goddess', + ['105'] = 'Seekers of Adoulin', + ['106'] = 'Add-Ons', + ['107'] = 'Others' + }, + + frequencies = T{ + + --(100) Final Fantasy XI -- + + ['100.1'] = { + callSign = 'Battle Theme', + song = music.songs.final_fantasy_xi.battle_theme + }, + ['100.2'] = { + callSign = 'Battle Theme 2', + song = music.songs.final_fantasy_xi.battle_theme_2 + }, + ['100.3'] = { + callSign = 'Battle in the Dungeon', + song = music.songs.final_fantasy_xi.battle_in_the_dungeon + }, + ['100.4'] = { + callSign = 'Battle in the Dungeon 2', + song = music.songs.final_fantasy_xi.battle_in_the_dungeon_2 + }, + ['100.5'] = { + callSign = 'Tough Battle', + song = music.songs.final_fantasy_xi.tough_battle + }, + ['100.6'] = { + callSign = 'Awakening', + song = music.songs.final_fantasy_xi.awakening + }, + + --(101) Rise of the Zilart -- + + ['101.1'] = { + callSign = 'Battle Theme 3', + song = music.songs.rise_of_the_zilart.battle_theme_3 + }, + ['101.2'] = { + callSign = 'Battle in the Dungeon 3', + song = music.songs.rise_of_the_zilart.battle_in_the_dungeon_3 + }, + ['101.3'] = { + callSign = 'Tough Battle 2', + song = music.songs.rise_of_the_zilart.tough_battle_2 + }, + ['101.4'] = { + callSign = 'Fighters of the Crystal', + song = music.songs.rise_of_the_zilart.fighters_of_the_crystal + }, + ['101.5'] = { + callSign = "Eald'narche", + song = music.songs.rise_of_the_zilart.ealdnarche + }, + ['101.6'] = { + callSign = 'Belief', + song = music.songs.rise_of_the_zilart.belief + }, + + --(102) Chains of Promathia -- + + ['102.1'] = { + callSign = 'Onslaught', + song = music.songs.chains_of_promathia.onslaught + }, + ['102.2'] = { + callSign = 'Depths of the Soul', + song = music.songs.chains_of_promathia.depths_of_the_soul + }, + ['102.3'] = { + callSign = 'Turmoil', + song = music.songs.chains_of_promathia.turmoil + }, + ['102.4'] = { + callSign = 'Ruler of the Skies', + song = music.songs.chains_of_promathia.ruler_of_the_skies + }, + ['102.5'] = { + callSign = 'Dusk and Dawn', + song = music.songs.chains_of_promathia.dusk_and_dawn + }, + ['102.6'] = { + callSign = 'A Realm of Emptiness', + song = music.songs.chains_of_promathia.a_realm_of_emptiness + }, + + --(103) Treasures of Aht Urhgan -- + + ['103.1'] = { + callSign = "Mercenaries' Delight", + song = music.songs.treasures_of_aht_urhgan.mercenaries_delight + }, + ['103.2'] = { + callSign = 'Delve', + song = music.songs.treasures_of_aht_urhgan.delve + }, + ['103.3'] = { + callSign = 'Rapid Onslaught -Assult-', + song = music.songs.treasures_of_aht_urhgan.rapid_onslaught_assault + }, + ['103.4'] = { + callSign = 'Fated Strife -Besieged-', + song = music.songs.treasures_of_aht_urhgan.fated_strife_besieged + }, + ['103.5'] = { + callSign = 'Hellriders', + song = music.songs.treasures_of_aht_urhgan.hellriders + }, + ['103.6'] = { + callSign = 'Black Coffin', + song = music.songs.treasures_of_aht_urhgan.black_coffin + }, + ['103.7'] = { + callSign = 'Iron Colossus', + song = music.songs.treasures_of_aht_urhgan.iron_colossus + }, + ['103.8'] = { + callSign = 'Ragnarok', + song = music.songs.treasures_of_aht_urhgan.ragnarok + }, + + --(104) Wings of the Goddess -- + + ['104.1'] = { + callSign = 'Clash of Standards', + song = music.songs.wings_of_the_goddess.clash_of_standards + }, + ['104.2'] = { + callSign = 'On this Blade', + song = music.songs.wings_of_the_goddess.on_this_blade + }, + ['104.3'] = { + callSign = 'Roar of the Battle Drums', + song = music.songs.wings_of_the_goddess.roar_of_the_battle_drums + }, + ['104.4'] = { + callSign = 'Run Maggot, Run!', + song = music.songs.wings_of_the_goddess.run_maggot_run + }, + ['104.5'] = { + callSign = 'Under a Clouded Moon', + song = music.songs.wings_of_the_goddess.under_a_clouded_moon + }, + ['104.6'] = { + callSign = 'Kindred Cry', + song = music.songs.wings_of_the_goddess.kindred_cry + }, + ['104.7'] = { + callSign = 'Provenance Watcher', + song = music.songs.wings_of_the_goddess.provenance_watcher + }, + ['104.8'] = { + callSign = 'Goddess Divine', + song = music.songs.wings_of_the_goddess.goddess_divine + }, + + --(105) Seekers of Adoulin -- + + ['105.1'] = { + callSign = 'Steel Sings, Blades Dance', + song = music.songs.seekers_of_adoulin.steel_sings_blades_dance + }, + ['105.2'] = { + callSign = 'Braking Ground', + song = music.songs.seekers_of_adoulin.breaking_ground + }, + ['105.3'] = { + callSign = 'Buccaneers', + song = music.songs.seekers_of_adoulin.buccaneers + }, + ['105.4'] = { + callSign = 'Keepers of the Wild', + song = music.songs.seekers_of_adoulin.keepers_of_the_wild + }, + + --(106) Add-Ons -- + + ['106.1'] = { + callSign = 'Echoes of Creation', + song = music.songs.add_ons.echoes_of_creation + }, + ['106.2'] = { + callSign = 'Luck of the Mog', + song = music.songs.add_ons.luck_of_the_mog + }, + ['106.3'] = { + callSign = 'A Feast for Ladies', + song = music.songs.add_ons.a_feast_for_ladies + }, + ['106.4'] = { + callSign = 'Melodies Errant', + song = music.songs.add_ons.melodies_errant + }, + ['106.5'] = { + callSign = 'Shinryu', + song = music.songs.add_ons.shinryu + }, + ['106.6'] = { + callSign = 'Wail of the Void', + song = music.songs.add_ons.wail_of_the_void + }, + + --(107) Others -- + + ['107.1'] = { + callSign = 'No Music', + song = music.songs.others.silent + }, + ['107.2'] = { + callSign = 'Original Music', + song = music.songs.others.normal + }, + ['107.3'] = { + callSign = 'Current Zone Music (Default)', + song = music.songs.others.zone + } + } +} + +return { + packets = packets, + player = player, + colors = colors, + music = music, + stations = stations +}
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/BattleStations/helpers.lua b/Data/DefaultContent/Libraries/addons/addons/BattleStations/helpers.lua new file mode 100644 index 0000000..17b2acf --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/BattleStations/helpers.lua @@ -0,0 +1,121 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Battle Stations nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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. +]] + +local colors = require("constants").colors + +function buildHelpCommandEntry(command, description) + local entry_template = "%s %s %s %s" + local short_name = "bs":color(colors.primary) + local command = command:color(colors.secondary) + local sep = "=>":color(colors.primary) + local description = description:color(colors.info) + return entry_template:format(short_name, command, sep, description) +end + +function buildHelpTypeEntry(name, description) + local entry_template = "%s %s %s" + local name = name:color(colors.secondary) + local sep = "=>":color(colors.primary) + local description = description:color(colors.info) + return entry_template:format(name, sep, description) +end + +function buildHelpRadioEntry(name, description) + local entry_template = "%s %s %s" + local name = name:color(colors.secondary) + local sep = "=>":color(colors.primary) + local description = description:color(colors.info) + return entry_template:format(name, sep, description) +end + +function buildHelpStationEntry(frequency, callSign) + local entry_template = " %s %s %s" + local frequency = frequency:color(colors.secondary) + local sep = "=>":color(colors.primary) + local callSign = callSign:color(colors.info) + return entry_template:format(frequency, sep, callSign) +end + +function buildHelpStationCategoryEntry(range, name) + local entry_template = "(%s) %s" + local range = range:color(colors.primary) + local name = name:color(colors.danger) + return entry_template:format(range, name) +end + +function buildHelpTitle(context) + local title_template = "%s Help: %s" + local context = context:color(colors.danger) + return title_template:color(colors.primary):format(_addon.name, context) +end + +function buildHelpSeperator(character, count) + local sep = '' + for i = 1, count do + sep = sep .. character + end + return sep:color(colors.warn) +end + +function buildCommandResponse(message, success) + local response_template = '%s: %s' + local response_color = colors.success + local response_type = 'Success' + + if not success then + response_type = 'Error' + response_color = colors.danger + end + return response_template:format(response_type:color(response_color), message) +end + +function buildWarningMessage(message) + local message_template = '%s: %s' + local response_type = 'Note' + return message_template:format(response_type:color(colors.warn), message) +end + +function buildGetResponseMessage(radio, context, frequency, callSign) + local message_template = '%s %s currently set to %s (%s)' + radio = radio:color(colors.secondary) + frequency = tostring(frequency):color(colors.primary) + return message_template:format(radio, context, frequency, callSign) +end + +function buildSetResponseMessage(radio, context, frequency, callSign) + local message_template = '%s %s updated to station %s (%s)' + radio = radio:color(colors.secondary) + frequency = tostring(frequency):color(colors.primary) + return message_template:format(radio, context, frequency, callSign) +end + +function displayResponse(response, color) + color = color or colors.info + windower.add_to_chat(color, response) + windower.console.write(response:strip_colors()) +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/Bonanza/Bonanza.lua b/Data/DefaultContent/Libraries/addons/addons/Bonanza/Bonanza.lua new file mode 100644 index 0000000..0508fea --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Bonanza/Bonanza.lua @@ -0,0 +1,192 @@ +--[[ +Copyright © 2018, from20020516 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Bonanza nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL from20020516 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'Bonanza' +_addon.author = 'from20020516' +_addon.version = '1.1' +_addon.command = 'bonanza' + +extdata = require('extdata') +packets = require('packets') +require('logger') + +marble = {id=2559,price=2000,limit=10} --general settings + +win_numbers = {} +math.randomseed(os.time()) + +--search your bags for bonanza marble and get number then add prefix 0 +function search_marbles() + local marbles = {} + local bags = {0,1,2,4,5,6,7,9} + local items = windower.ffxi.get_items + for bag_ind=1,#bags do + for index=1,windower.ffxi.get_bag_info(bags[bag_ind]).max do + local item = items(bags[bag_ind],index) + if item.id == marble.id then --bonanza marble + local five_digits = string.format('%05s',extdata.decode(item).number) + table.insert(marbles,{bags[bag_ind],index,five_digits}) + end + end + end + return marbles; +end + +windower.register_event('addon command',function(...) + moogle = windower.ffxi.get_mob_by_name('Bonanza Moogle') + decides = {} + local cmd={...} + local count = marble.limit-#search_marbles() + for i=1,#cmd do + math.random() --shuffle rand + cmd[i] = tonumber(cmd[i]) and cmd[i]*1 or cmd[i] + end + if not cmd[1] or cmd[1] == 'help' then + local chat = windower.add_to_chat + local color = string.color + chat(1,'Bonanza - Command List:') + chat(207,'//bonanza '..color('<number> [number] ...',166)) + chat(160,' purchase specified marble(s).') + chat(207,'//bonanza '..color('random',166)) + chat(160,' purchase up to 10 marbles with at random.') + chat(207,'//bonanza '..color('sequence <number>',166)) + chat(160,' purchase up to 10 marbles with consecutive number.') + chat(160,' e.g. '..color('15250',166)..' then buying '..color('15250',166)..' to '..color('15259',166)..'.') + chat(207,'//bonanza '..color('last <number>',166)) + chat(160,' purchase up to 10 random marbles with tail of specified 0-9.') + local zero = color('0',166) + chat(160,' e.g. '..zero..' then buying 2224'..zero..', 6231'..zero..', 4586'..zero..'...') + elseif cmd[1] == 'judge' then + windower.chat.input('/smes') + elseif count == 0 then + error('you have already '..marble.limit..' marbles.') + elseif cmd[1] == 'random' then + for i=1,count do + table.insert(decides,math.random(0,99)+math.random(0,99)*100+math.random(0,9)*10000) + end + elseif cmd[1] == 'sequence' then + local n = math.min(cmd[2],10000-count) + for i=n,n+count-1 do + table.insert(decides,i) + end + elseif cmd[1] == 'last' and 10 > cmd[2] then + for i=1,count do + table.insert(decides,tonumber(math.random(0,9999)..cmd[2])) + end + elseif 100000 > cmd[1] and not cmd[marble.limit+1] then + decides = cmd + end + + if #decides > 0 then + talk_moogle() + end +end) + +function talk_moogle() + if #decides > 0 then + hide_ui = true + local packet = packets.new('outgoing',0x01A,{ + ['Target']=moogle.id, + ['Target Index']=moogle.index}) + packets.inject(packet) + end +end + +windower.register_event('incoming chunk',function(id,data) + if S{0x020,0x034}[id] and hide_ui then + --got a marble. + local p = packets.parse('incoming',data) + if id == 0x020 and p['Item'] == marble.id and p['Bag'] == 0 then + local item = windower.ffxi.get_items(0,p['Index']) + if extdata.decode(item).number == decides[1] then + hide_ui = false + table.remove(decides,1) + talk_moogle() + end + --responced to talk. + elseif id == 0x034 and p['NPC'] == moogle.id then + if moogle.distance > 36 then + error('not close enough to Bonanza Moogle.') + elseif marble.price > windower.ffxi.get_items().gil then + error('not have enough gils.') + else + local packet = packets.new('outgoing',0x05B) + local i = decides[1] + log('Purchase a Bonanza Marble #'..string.format('%05s',i)) + --packet part 1 + packet['Target']=moogle.id + packet['Option Index']=2+i%256*256 + packet['Target Index']=moogle.index + packet['Automated Message']=true + packet['_unknown1']=(i-i%256)/256 + packet['Zone']=p['Zone'] + packet['Menu ID']=p['Menu ID'] + packets.inject(packet) + --packet part 2 + packet['Option Index']=3 + packet['Automated Message']=false + packets.inject(packet) + return true; --hide ui + end + end + end +end) + +--get winning numbers(str) from /smes. +windower.register_event('incoming text',function(original,modified,mode) + local jis = windower.from_shift_jis(original) --valid in english environment + if mode == 200 and windower.regex.match(jis,'digit|けた') then + local numbers = windower.regex.match(jis,'["「]([0-9]+)[」"]')[1][1] + table.insert(win_numbers,6-string.len(numbers),numbers) + if #win_numbers == 5 then + scan_marbles() + end + end +end) + +function scan_marbles() + log('Winning numbers is..',unpack(win_numbers)) + local marbles = search_marbles() + local colors = {51,18,166,207,208,161} + for m=1,#marbles do + local number = marbles[m][3] + if S{1,2,4,9}[marbles[m][1]] and windower.ffxi.get_info().mog_house + or S{5,6,7}[marbles[m][1]] then + windower.ffxi.get_item(marbles[m][1],marbles[m][2]) + end + for r=1,5 do + local text = _addon.name..': #'..string.format("%02s",m)..' '..number..' <Rank' + if win_numbers[r] == windower.regex.match(number,"[0-9]{1,"..(6-r).."}$")[1][0] then + windower.add_to_chat(colors[r],text..r..'>') + break; + elseif r == 5 then + windower.add_to_chat(colors[6],text..'6'..'>') + end + end + end + win_numbers = {} +end diff --git a/Data/DefaultContent/Libraries/addons/addons/Bonanza/README.md b/Data/DefaultContent/Libraries/addons/addons/Bonanza/README.md new file mode 100644 index 0000000..b4be251 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Bonanza/README.md @@ -0,0 +1,27 @@ +# Bonanza + +- Judge your Bonanza Marbles. activate with when you recive system message (/smes) with winning numbers. +- Purchase Bonanza Marbles with any combination of numbers. + +#### Japanese + +- モグボナンザの当せんを判定。当せん番号を含むシステムメッセージ (/smes) を受信するとボナンザマーブルを鑑定し、結果を出力します。 +- 任意の数字の組み合わせでボナンザマーブルを購入します。 + +## Commands + +- //bonanza judge + - same as in-game /smes. + +### Purchase marble (inject packets) + +- //bonanza `<number>` `[number]`... + - purchase specified marble(s). +- //bonanza random + - purchase up to 10 marbles with at random. +- //bonanza sequence `<number>` + - purchase up to 10 marbles with consecutive number. + - e.g. `15250` then buying 15250 to 15259. +- //bonanza last `<number>` + - purchase up to 10 random marbles with tail of specified `0-9`. + - e.g. `0` then 2224**0**, 6231**0**, 4586**0**, 9078**0**... diff --git a/Data/DefaultContent/Libraries/addons/addons/ChatLink/ChatLink.lua b/Data/DefaultContent/Libraries/addons/addons/ChatLink/ChatLink.lua new file mode 100644 index 0000000..b2dc811 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ChatLink/ChatLink.lua @@ -0,0 +1,87 @@ +_addon.name = 'ChatLink' +_addon.author = 'Aureus' +_addon.version = '1.2.0.0' +_addon.commands = {'chatlink', 'clink'} + +require('pack') +require('lists') +require('logger') +require('strings') + +urls = L{} +ids = {} + +pattern = L{ + -- Matches mail addresses + '[\\w.-]+@[\\w.%-]+\\.[a-z]{2,5}\\b', + -- Matches domain names preceded by a scheme + '\\w+://[\\w%-]+(?:\\.[\\w%-]+)*\\.\\w{2,5}(?:\\:\\d{1,5}(?!\\d))?(?:/[^\\s]*)?', + -- Matches IPv4, optionally preceded by a scheme + '(?:\\w+://)?\\d{1,3}(?:\\.\\d{1,3}){3}(?:\\:\\d{1,5}(?!\\d))?(?:/[^\\s]*)?', + -- Matches domain names without scheme. Only a few select TLDs allowed to avoid false positives + '[\\w%-]+(?:\\.[\\w%-]+)*\\.(?:com|net|org|jp|uk|de|fr|it|es|ru|be|io)(?:\\:\\d{1,5}(?!\\d))?(?:/[^\\s]*)?', +}:concat('|') + +replace = function(url) + if not ids[url] then + urls:append(url) + ids[url] = #urls + end + + return '[%u]%s':format(ids[url], url) +end + +identifier = ('ChatLink' .. 0x01:char()):map(function(char) + return 'h':pack(char:byte() * 0x100 + 0x1E) +end) + +windower.register_event('incoming text', function(_, text, color) + return not text:match(identifier) and windower.regex.replace(text, pattern, replace) or nil +end) + +windower.register_event('addon command', function(command, id) + command = command and command:lower() or 'help' + + if command == 'list' or command == 'l' then + if urls:empty() then + return log('No URLs found.') + end + + log('%u %s found.':format(#urls, 'URL':plural(urls))) + for url, key in urls:it() do + log('%s [%u]: %s':format(identifier, key, url)) + end + + else + local key = tonumber(id) + if not key then + return error('The ID "%s" is not a number.':format(id)) + end + + if not urls[key] then + return error('The ID "%s" was not found. Currently the highest ID is %u: %s':format(#urls, urls[#urls])) + end + + if command == 'open' or command == 'o' then + windower.open_url(urls[key]) + + elseif command == 'copy' or command == 'c' then + windower.copy_to_clipboard(urls[key]) + + end + + end +end) + +--[[ +Copyright 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/ChatLink/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/ChatLink/ReadMe.md new file mode 100644 index 0000000..a61b864 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ChatLink/ReadMe.md @@ -0,0 +1,29 @@ +# ChatLink + +Allows opening of posted URLs through the chatlog. When URLs appear in the chatlog they are prepended by a number. This number is used to identify the link and is used in commands to copy or open them. + +### Commands + +#### Copy + +``` +chatlink copy <nr> +``` + +Where `nr` is the number prepended to the respective URL. + +#### Open + +``` +chatlink open <nr> +``` + +Where `nr` is the number prepended to the respective URL. Note that this has historically had some issues with Firefox, although these may be gone by now. + +#### List + +``` +chatlink list +``` + +Displays all currently saved URLs and the number required to open them. diff --git a/Data/DefaultContent/Libraries/addons/addons/Clock/Clock.lua b/Data/DefaultContent/Libraries/addons/addons/Clock/Clock.lua new file mode 100644 index 0000000..9a66f68 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Clock/Clock.lua @@ -0,0 +1,189 @@ +_addon.name = 'Clock' +_addon.author = 'StarHawk' +_addon.version = '1.0.1.1' +_addon.command = 'clock' + +require('tables') +require('lists') +require('strings') +require('logger') +config = require('config') +texts = require('texts') + +time_zones = T(require('time_zones')) + +for key, val in pairs(time_zones) do + time_zones[key] = val * 3600 +end + +local tz_format = {} +for tz in time_zones:keyset():it() do + tz_format[tz:upper()] = tz:gsub('%d', '') +end + +defaults = {} +defaults.Format = '%H:%M:%S' +defaults.TimeZones = L{'UTC', 'JST'} +defaults.Display = T{} +defaults.ShowTimeZones = true +defaults.Separator = '\\n' +defaults.Sort = 'None' +defaults.Clock = {} + +settings = config.load(defaults) + +clock = texts.new('', settings.Clock, settings) + +sort = T{ + time = function(t1, t2) + return time_zones[t1] < time_zones[t2] + end, + alphabetical = function(t1, t2) + return t1 < t2 + end, +} + +redraw = function() + local sorted = settings.Sort ~= 'None' and settings.TimeZones:sort(sort[settings.Sort:lower()]) or settings.TimeZones + local width = settings.TimeZones:reduce(function(acc, tz) + return math.max(acc, #(settings.Display[tz] or tz_format[tz])) + end, 0) + local format_string = settings.ShowTimeZones and '%s%s: ${%s}' or '${%s}' + local strings = sorted:map(function(tz) + local display = settings.Display[tz] or tz_format[tz] + return format_string:format(display, ' ':rep(width - #display), tz) + end) + + -- Use loadstring to let Lua interpret things like \n for us + clock:text(strings:concat(loadstring('return \'%s\'':format(settings.Separator))())) +end + +config.register(settings, redraw) + +clock:show() + +utc_diff = os.difftime(os.time(), os.time(os.date('!*t', os.time()))) + +windower.register_event('prerender', function() + local utc_now = os.time() - utc_diff + for var in clock:it() do + if tz_format[var] then + clock[var] = os.date(settings.Format, utc_now + time_zones[var]) + end + end +end) + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + local args = L{...} + + if command == 'help' or command == 'h' then + print(_addon.name .. ' v.' .. _addon.version) + print(' \\cs(51, 153, 255)f\\cs(153, 204, 255)ormat\\cr - Displays the current or sets a new format to use') + print(' \\cs(51, 153, 255)a\\cs(153, 204, 255)dd\\cr - Adds a new time zone to the list') + print(' \\cs(51, 153, 255)r\\cs(153, 204, 255)emove\\cr - Removes a time zone to the current list') + + elseif command == 'format' or command == 'f' then + if args[1] then + settings.Format = args:concat(' ') + config.save(settings) + end + + log('Format set to: %s':format(settings.Format)) + + elseif command == 'add' or command == 'a' then + if not args[1] then + error('Invalid syntax: //clock add <timezones...>') + return + end + + while args[1] do + local arg = args:remove(1):upper() + local tz = tz_format[arg] + if not tz then + error('Unknown time zone identifier: %s':format(args[1])) + return + end + + if settings.TimeZones:contains(arg) then + notice('Time zone "%s" is already being displayed.':format(tz)) + return + end + + settings.TimeZones:append(arg) + end + + config.save(settings) + redraw() + + elseif command == 'remove' or command == 'r' then + if not args[1] then + error('Invalid syntax: //clock add <timezones...>') + return + end + + while args[1] do + local arg = args:remove(1):upper() + local tz = tz_format[arg] + if not tz then + error('Unknown time zone identifier: %s':format(args[1])) + return + end + + if not settings.TimeZones:contains(arg) then + notice('Time zone "%s" is not being displayed.':format(tz)) + return + end + + settings.TimeZones:remove(settings.TimeZones:find(arg)) + end + + config.save(settings) + redraw() + + elseif command == 'sort' or command == 's' then + if args[1] and not sort[args[1]:lower()] then + error('Invalid sorting specified. Choose one of: %s':format((L{'None'} + sort:keyset():sort()):map(string.capitalize):format('or'))) + return + end + + if args[1] then + settings.Sort = args[1]:capitalize() + config.save(settings) + end + + log('Sorting set to: %s':format(settings.Sort:capitalize())) + redraw() + + elseif command == 'display' or command == 'd' then + if not args[1] or not args[2] then + error('Invalid syntax: //clock display <timezone> <name>') + return + end + + local arg = args:remove(1):upper() + local tz = tz_format[arg] + if not tz then + error('Unknown time zone identifier: %s':format(args[1])) + return + end + + settings.Display[arg] = args:concat(' ') + config.save(settings) + redraw() + + end +end) + +--[[ +Copyright � 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/Clock/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Clock/ReadMe.md new file mode 100644 index 0000000..72237d3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Clock/ReadMe.md @@ -0,0 +1,48 @@ +# Clock + +Displays the current time in various time zones around the world in a customizable format on the screen. + +### Commands + +#### Time format + +``` +clock format [new] +``` + +If `new` is provided, will set that as the new format, according to [these rules](http://www.cplusplus.com/reference/ctime/strftime/). If omitted, will print out the current format. + +#### Sorting + +``` +clock sort [order] +``` + +If `order` is provided, will set that as the new sorting order. If omitted, will print out the current sorting order. Valid values are: +* `None`: Leaves the order as it is defined in the file +* `Alphabetical`: Sorts them alphabetically by their time zone abbreviation +* `Time`: Sorts them according to the time they display in ascending order + +#### Add time zone + +``` +clock add <timezones...> +``` + +Appends a time zone to the list of currently displayed time zones. The `timezone` parameter needs to be one of [these abbreviations](https://github.com/Windower/Lua/blob/4.1-dev/addons/Clock/time_zones.lua). + +#### Remove time zone + +``` +clock remove <timezones...> +``` + +Removes a time zone from the list of currently displayed time zones. The `timezone` parameter needs to be one of [these abbreviations](https://github.com/Windower/Lua/blob/4.1-dev/addons/Clock/time_zones.lua). + +#### Set up time zone display + +``` +clock display <timezone> <name> +``` + +Sets the display name for a time zone, instead of displaying the time zone identifier. So `clock display jst JP clock` would display `JP clock` before the time instead of `JST`. diff --git a/Data/DefaultContent/Libraries/addons/addons/Clock/time_zones.lua b/Data/DefaultContent/Libraries/addons/addons/Clock/time_zones.lua new file mode 100644 index 0000000..9895072 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Clock/time_zones.lua @@ -0,0 +1,215 @@ +return { + UTC = +00.00, -- Coordinated Universal Time + + ACDT = +10.50, -- Australian Central Daylight Savings Time + ACST = +09.50, -- Australian Central Standard Time + ACT = +08.00, -- ASEAN Common Time + ADT = -03.00, -- Atlantic Daylight Time + AEDT = +11.00, -- Australian Eastern Daylight Savings Time + AEST = +10.00, -- Australian Eastern Standard Time + AFT = +04.50, -- Afghanistan Time + AKDT = -08.00, -- Alaska Daylight Time + AKST = -09.00, -- Alaska Standard Time + AMST = -03.00, -- Amazon Summer Time (Brazil)[1] + AMST2 = +05.00, -- Armenia Summer Time + AMT = -04.00, -- Amazon Time (Brazil)[2] + AMT2 = +04.00, -- Armenia Time + ART = -03.00, -- Argentina Time + AST = -04.00, -- Atlantic Standard Time + AST2 = +03.00, -- Arabia Standard Time + AWDT = +09.00, -- Australian Western Daylight Time + AWST = +08.00, -- Australian Western Standard Time + AZOST = -01.00, -- Azores Standard Time + AZT = +04.00, -- Azerbaijan Time + BDT = +08.00, -- Brunei Time + BIOT = +06.00, -- British Indian Ocean Time + BIT = -12.00, -- Baker Island Time + BOT = -04.00, -- Bolivia Time + BRT = -03.00, -- Brasilia Time + BST = +01.00, -- British Summer Time (British Standard Time from Feb 1968 to Oct 1971) + BST2 = +06.00, -- Bangladesh Standard Time + BTT = +06.00, -- Bhutan Time + CAT = +02.00, -- Central Africa Time + CCT = +06.50, -- Cocos Islands Time + CDT = -05.00, -- Central Daylight Time (North America) + CDT2 = -04.00, -- Cuba Daylight Time[3] + CEDT = +02.00, -- Central European Daylight Time + CEST = +02.00, -- Central European Summer Time (Cf. HAEC) + CET = +01.00, -- Central European Time + CHADT = +13.75, -- Chatham Daylight Time + CHAST = +12.75, -- Chatham Standard Time + CHOT = +08.00, -- Choibalsan + ChST = +10.00, -- Chamorro Standard Time + CHUT = +10.00, -- Chuuk Time + CIST = -08.00, -- Clipperton Island Standard Time + CIT = +08.00, -- Central Indonesia Time + CKT = -10.00, -- Cook Island Time + CLST = -03.00, -- Chile Summer Time + CLT = -04.00, -- Chile Standard Time + COST = -04.00, -- Colombia Summer Time + COT = -05.00, -- Colombia Time + CST = -06.00, -- Central Standard Time (North America) + CST2 = -05.00, -- Cuba Standard Time + CST3 = +08.00, -- China Standard Time + CST4 = +09.50, -- Central Standard Time (Australia) + CST5 = +10.50, -- Central Summer Time (Australia) + CT = +08.00, -- China time + CVT = -01.00, -- Cape Verde Time + CWST = +08.75, -- Central Western Standard Time (Australia) unofficial + CXT = +07.00, -- Christmas Island Time + DAVT = +07.00, -- Davis Time + DDUT = +10.00, -- Dumont d'Urville Time + DFT = +01.00, -- AIX specific equivalent of Central European Time[4] + EASST = -05.00, -- Easter Island Standard Summer Time + EAST = -06.00, -- Easter Island Standard Time + EAT = +03.00, -- East Africa Time + ECT = -04.00, -- Eastern Caribbean Time (does not recognise DST) + ECT = -05.00, -- Ecuador Time + EDT = -04.00, -- Eastern Daylight Time (North America) + EEDT = +03.00, -- Eastern European Daylight Time + EEST = +03.00, -- Eastern European Summer Time + EET = +02.00, -- Eastern European Time + EGST = +00.00, -- Eastern Greenland Summer Time + EGT = -01.00, -- Eastern Greenland Time + EIT = +09.00, -- Eastern Indonesian Time + EST = -05.00, -- Eastern Standard Time (North America) + EST2 = +10.00, -- Eastern Standard Time (Australia) + FET = +03.00, -- Further-eastern European Time + FJT = +12.00, -- Fiji Time + FKST = -03.00, -- Falkland Islands Standard Time + FKST = -03.00, -- Falkland Islands Summer Time + FKT = -04.00, -- Falkland Islands Time + FNT = -02.00, -- Fernando de Noronha Time + GALT = -06.00, -- Galapagos Time + GAMT = -09.00, -- Gambier Islands + GET = +04.00, -- Georgia Standard Time + GFT = -03.00, -- French Guiana Time + GILT = +12.00, -- Gilbert Island Time + GIT = -09.00, -- Gambier Island Time + GMT = +00.00, -- Greenwich Mean Time + GST = -02.00, -- South Georgia and the South Sandwich Islands + GST2 = +04.00, -- Gulf Standard Time + GYT = -04.00, -- Guyana Time + HADT = -09.00, -- Hawaii-Aleutian Daylight Time + HAEC = +02.00, -- Heure Avanc�e d'Europe Centrale francised name for CEST + HAST = -10.00, -- Hawaii-Aleutian Standard Time + HKT = +08.00, -- Hong Kong Time + HMT = +05.00, -- Heard and McDonald Islands Time + HOVT = +07.00, -- Khovd Time + HST = -10.00, -- Hawaii Standard Time + ICT = +07.00, -- Indochina Time + IDT = +03.00, -- Israel Daylight Time + IOT = +03.00, -- Indian Ocean Time + IRDT = +04.50, -- Iran Daylight Time + IRKT = +08.00, -- Irkutsk Time + IRST = +03.50, -- Iran Standard Time + IST = +01.00, -- Irish Standard Time[5] + IST2 = +02.00, -- Israel Standard Time + IST3 = +05.50, -- Indian Standard Time + JST = +09.00, -- Japan Standard Time + KGT = +06.00, -- Kyrgyzstan time + KOST = +11.00, -- Kosrae Time + KRAT = +07.00, -- Krasnoyarsk Time + KST = +09.00, -- Korea Standard Time + LHST = +10.50, -- Lord Howe Standard Time + LHST2 = +11.00, -- Lord Howe Summer Time + LINT = +14.00, -- Line Islands Time + MAGT = +12.00, -- Magadan Time + MART = -09.50, -- Marquesas Islands Time + MAWT = +05.00, -- Mawson Station Time + MDT = -06.00, -- Mountain Daylight Time (North America) + MET = +01.00, -- Middle European Time Same zone as CET + MEST = +02.00, -- Middle European Saving Time Same zone as CEST + MHT = +12.00, -- Marshall Islands + MIST = +11.00, -- Macquarie Island Station Time + MIT = -09.50, -- Marquesas Islands Time + MMT = +06.50, -- Myanmar Time + MSK = +03.00, -- Moscow Time + MST = -07.00, -- Mountain Standard Time (North America) + MST2 = +06.50, -- Myanmar Standard Time + MST3 = +08.00, -- Malaysia Standard Time + MUT = +04.00, -- Mauritius Time + MVT = +05.00, -- Maldives Time + MYT = +08.00, -- Malaysia Time + NCT = +11.00, -- New Caledonia Time + NDT = -02.50, -- Newfoundland Daylight Time + NFT = +11.50, -- Norfolk Time + NPT = +05.75, -- Nepal Time + NST = -03.50, -- Newfoundland Standard Time + NT = -03.50, -- Newfoundland Time + NUT = -11.00, -- Niue Time + NZDT = +13.00, -- New Zealand Daylight Time + NZST = +12.00, -- New Zealand Standard Time + OMST = +06.00, -- Omsk Time + ORAT = +05.00, -- Oral Time + PDT = -07.00, -- Pacific Daylight Time (North America) + PET = -05.00, -- Peru Time + PETT = +12.00, -- Kamchatka Time + PGT = +10.00, -- Papua New Guinea Time + PHOT = +13.00, -- Phoenix Island Time + PKT = +05.00, -- Pakistan Standard Time + PMDT = -02.00, -- Saint Pierre and Miquelon Daylight time + PMST = -03.00, -- Saint Pierre and Miquelon Standard Time + PONT = +11.00, -- Pohnpei Standard Time + PST = -08.00, -- Pacific Standard Time (North America) + PST2 = +08.00, -- Philippine Standard Time + PYST = -03.00, -- Paraguay Summer Time (South America)[6] + PYT = -04.00, -- Paraguay Time (South America)[7] + RET = +04.00, -- R�union Time + ROTT = -03.00, -- Rothera Research Station Time + SAKT = +11.00, -- Sakhalin Island time + SAMT = +04.00, -- Samara Time + SAST = +02.00, -- South African Standard Time + SBT = +11.00, -- Solomon Islands Time + SCT = +04.00, -- Seychelles Time + SGT = +08.00, -- Singapore Time + SLST = +05.50, -- Sri Lanka Time + SRET = +11.00, -- Srednekolymsk Time + SRT = -03.00, -- Suriname Time + SST = -11.00, -- Samoa Standard Time + SST2 = +08.00, -- Singapore Standard Time + SYOT = +03.00, -- Showa Station Time + TAHT = -10.00, -- Tahiti Time + THA = +07.00, -- Thailand Standard Time + TFT = +05.00, -- Indian/Kerguelen + TJT = +05.00, -- Tajikistan Time + TKT = +13.00, -- Tokelau Time + TLT = +09.00, -- Timor Leste Time + TMT = +05.00, -- Turkmenistan Time + TOT = +13.00, -- Tonga Time + TVT = +12.00, -- Tuvalu Time + ULAT = +08.00, -- Ulaanbaatar Time + USZ1 = +02.00, -- Kaliningrad Time + UYST = -02.00, -- Uruguay Summer Time + UYT = -03.00, -- Uruguay Standard Time + UZT = +05.00, -- Uzbekistan Time + VET = -04.50, -- Venezuelan Standard Time + VLAT = +10.00, -- Vladivostok Time + VOLT = +04.00, -- Volgograd Time + VOST = +06.00, -- Vostok Station Time + VUT = +11.00, -- Vanuatu Time + WAKT = +12.00, -- Wake Island Time + WAST = +02.00, -- West Africa Summer Time + WAT = +01.00, -- West Africa Time + WEDT = +01.00, -- Western European Daylight Time + WEST = +01.00, -- Western European Summer Time + WET = +00.00, -- Western European Time + WIT = +07.00, -- Western Indonesian Time + WST = +08.00, -- Western Standard Time + YAKT = +09.00, -- Yakutsk Time + YEKT = +05.00, -- Yekaterinburg Time + Z = +00.00, -- Zulu Time (Coordinated Universal Time) +} + +--[[ +Copyright � 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ConsoleBG.lua b/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ConsoleBG.lua new file mode 100644 index 0000000..db6ed5c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ConsoleBG.lua @@ -0,0 +1,114 @@ +_addon.name = 'ConsoleBG' +_addon.author = 'StarHawk' +_addon.version = '0.9.0.1' +_addon.command = 'consolebg' + +config = require('config') +require('logger') + +defaults = {} +defaults.bg = {} +defaults.bg.alpha = 192 +defaults.bg.red = 0 +defaults.bg.green = 0 +defaults.bg.blue = 0 +defaults.pos = {} +defaults.pos.x = 0 +defaults.pos.y = 0 +defaults.extents = {} +defaults.extents.x = 7680 +defaults.extents.y = 360 + +settings = config.load(defaults) + +windower.prim.create('ConsoleBG') + +consolesetting_commands = T{ + color = 'Color', + c = 'Color', + size = 'Size', + s = 'Size', + position = 'Position', + p = 'Position', +} + +config.register(settings, function(settings) + windower.prim.set_color('ConsoleBG', settings.bg.alpha, settings.bg.red, settings.bg.green, settings.bg.blue) + windower.prim.set_position('ConsoleBG', settings.pos.x, settings.pos.y) + windower.prim.set_size('ConsoleBG', settings.extents.x, settings.extents.y) +end) + +function consolesettings(command1, ...) + local values = L{...} + local command1 = command1:lower() + if command1 == 'color' then + log('Colors changed! Alpha: ' .. values[1] .. ' Red: ' .. values[2] .. ' Green: ' .. values[3] .. ' Blue: ' .. values[4]) + settings.bg.alpha = tonumber(values[1]) + settings.bg.red = tonumber(values[2]) + settings.bg.green = tonumber(values[3]) + settings.bg.blue = tonumber(values[4]) + elseif command1 == 'position' then + log('Position changed! X: ' .. values[1] .. ' Y: ' .. values[2]) + settings.pos.x = tonumber(values[1]) + settings.pos.y = tonumber(values[2]) + elseif command1 == 'size' then + log('Size changed! Width: ' .. values[1] .. ' Height: ' .. values[2]) + settings.extents.x = tonumber(values[1]) + settings.extents.y = tonumber(values[2]) + end + + config.save(settings) + config.reload(settings) +end + +windower.register_event('addon command', function(command1, ...) + local argcount = select('#', ...) + + command1 = command1 and command1:lower() or 'help' + + if consolesetting_commands:containskey(command1) then + command1 = consolesetting_commands[command1] + if command1 == 'Color' then + if ((4 > argcount) or (argcount > 4)) then + error('Invalid syntax. Check the "help" command.') + else + consolesettings(command1, ...) + end + elseif (command1 == 'Position' or command1 == 'Size') then + if ((2 > argcount) or (argcount > 2)) then + error('Invalid syntax. Check the "help" command.') + else + consolesettings(command1, ...) + end + + end + + elseif command1 == 'help' then + print('%s v%s':format(_addon.name, _addon.version)) + print(' \\cs(255,255,255)color <values>\\cr - Changes background color and transparency Valid range: 0-255') + print(' \\crExample:\\cs(255,255,255) color 255 0 1 2\\cr - Alpha: 255 Red: 0 Green: 1 Blue: 2') + print(' \\cs(255,255,255)position <values>\\cr - Set anchor points') + print(' \\crExample:\\cs(255,255,255) position 1 25 \\cr - X: 1 Y: 25') + print(' \\cs(255,255,255)size <values>\\cr - Set size') + print(' \\crExample:\\cs(255,255,255) size 700 310\\cr - Width: 700 Height: 310') + + else + error('Unknown command! Use the "help" command for a list of commands.') + + end +end) + + +windower.register_event('prerender', function() + windower.prim.set_visibility('ConsoleBG', windower.console.visible()) +end) + +--[[ +Copyright © 2015, Windower +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ReadMe.md new file mode 100644 index 0000000..4c9136d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ConsoleBG/ReadMe.md @@ -0,0 +1,3 @@ +# ConsoleBG + +Creates a background shadow for the console window to make it more readable. diff --git a/Data/DefaultContent/Libraries/addons/addons/Debuffed/Debuffed.lua b/Data/DefaultContent/Libraries/addons/addons/Debuffed/Debuffed.lua new file mode 100644 index 0000000..d3c177e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Debuffed/Debuffed.lua @@ -0,0 +1,305 @@ +--[[ +Copyright © 2019, Xathe +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Debuffed nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Xathe BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'Debuffed' +_addon.author = 'Xathe (Asura)' +_addon.version = '1.0.0.4' +_addon.commands = {'dbf','debuffed'} + +config = require('config') +packets = require('packets') +res = require('resources') +texts = require('texts') +require('logger') + +defaults = {} +defaults.interval = .1 +defaults.mode = 'blacklist' +defaults.timers = true +defaults.hide_below_zero = false +defaults.whitelist = S{} +defaults.blacklist = S{} +defaults.colors = {} +defaults.colors.player = {} +defaults.colors.player.red = 255 +defaults.colors.player.green = 255 +defaults.colors.player.blue = 255 +defaults.colors.others = {} +defaults.colors.others.red = 255 +defaults.colors.others.green = 255 +defaults.colors.others.blue = 0 + +settings = config.load(defaults) +box = texts.new('${current_string}', settings) +box:show() + +list_commands = T{ + w = 'whitelist', + wlist = 'whitelist', + white = 'whitelist', + whitelist = 'whitelist', + b = 'blacklist', + blist = 'blacklist', + black = 'blacklist', + blacklist = 'blacklist' +} + +sort_commands = T{ + a = 'add', + add = 'add', + ['+'] = 'add', + r = 'remove', + remove = 'remove', + ['-'] = 'remove' +} + +player_id = 0 +frame_time = 0 +debuffed_mobs = {} + +function update_box() + local lines = L{} + local target = windower.ffxi.get_mob_by_target('t') + + if target and target.valid_target and (target.claim_id ~= 0 or target.spawn_type == 16) then + local data = debuffed_mobs[target.id] + + if data then + for effect, spell in pairs(data) do + local name = res.spells[spell.id].name + local remains = math.max(0, spell.timer - os.clock()) + + if settings.mode == 'whitelist' and settings.whitelist:contains(name) or settings.mode == 'blacklist' and not settings.blacklist:contains(name) then + if settings.timers and remains > 0 then + lines:append('\\cs(%s)%s: %.0f\\cr':format(get_color(spell.actor), name, remains)) + elseif remains < 0 and settings.hide_below_zero then + debuffed_mobs[target.id][effect] = nil + else + lines:append('\\cs(%s)%s\\cr':format(get_color(spell.actor), name)) + end + end + end + end + end + + if lines:length() == 0 then + box.current_string = '' + else + box.current_string = 'Debuffed [' .. target.name .. ']\n\n' .. lines:concat('\n') + end +end + +function get_color(actor) + if actor == player_id then + return '%s,%s,%s':format(settings.colors.player.red, settings.colors.player.green, settings.colors.player.blue) + else + return '%s,%s,%s':format(settings.colors.others.red, settings.colors.others.green, settings.colors.others.blue) + end +end + +function handle_overwrites(target, new, t) + if not debuffed_mobs[target] then + return true + end + + for effect, spell in pairs(debuffed_mobs[target]) do + local old = res.spells[spell.id].overwrites or {} + + -- Check if there isn't a higher priority debuff active + if table.length(old) > 0 then + for _,v in ipairs(old) do + if new == v then + return false + end + end + end + + -- Check if a lower priority debuff is being overwritten + if table.length(t) > 0 then + for _,v in ipairs(t) do + if spell.id == v then + debuffed_mobs[target][effect] = nil + end + end + end + end + return true +end + +function apply_debuff(target, effect, spell, actor) + if not debuffed_mobs[target] then + debuffed_mobs[target] = {} + end + + -- Check overwrite conditions + local overwrites = res.spells[spell].overwrites or {} + if not handle_overwrites(target, spell, overwrites) then + return + end + + -- Create timer + debuffed_mobs[target][effect] = {id=spell, timer=(os.clock() + (res.spells[spell].duration or 0)), actor=actor} +end + +function handle_shot(target) + if not debuffed_mobs[target] or not debuffed_mobs[target][134] then + return true + end + + local current = debuffed_mobs[target][134].id + if current < 26 then + debuffed_mobs[target][134].id = current + 1 + end +end + +function inc_action(act) + if act.category ~= 4 then + if act.category == 6 and act.param == 131 then + handle_shot(act.targets[1].id) + end + return + end + + -- Damaging spells + if S{2,252}:contains(act.targets[1].actions[1].message) then + local target = act.targets[1].id + local spell = act.param + local effect = res.spells[spell].status + local actor = act.actor_id + + if effect then + apply_debuff(target, effect, spell, actor) + end + + -- Non-damaging spells + elseif S{236,237,268,271}:contains(act.targets[1].actions[1].message) then + local target = act.targets[1].id + local effect = act.targets[1].actions[1].param + local spell = act.param + local actor = act.actor_id + + if res.spells[spell].status and res.spells[spell].status == effect then + apply_debuff(target, effect, spell, actor) + end + end +end + +function inc_action_message(arr) + + -- Unit died + if S{6,20,113,406,605,646}:contains(arr.message_id) then + debuffed_mobs[arr.target_id] = nil + + -- Debuff expired + elseif S{64,204,206,350,531}:contains(arr.message_id) then + if debuffed_mobs[arr.target_id] then + debuffed_mobs[arr.target_id][arr.param_1] = nil + end + end +end + +windower.register_event('login','load', function() + player_id = (windower.ffxi.get_player() or {}).id +end) + +windower.register_event('logout','zone change', function() + debuffed_mobs = {} +end) + +windower.register_event('incoming chunk', function(id, data) + if id == 0x028 then + inc_action(windower.packets.parse_action(data)) + elseif id == 0x029 then + local arr = {} + arr.target_id = data:unpack('I',0x09) + arr.param_1 = data:unpack('I',0x0D) + arr.message_id = data:unpack('H',0x19)%32768 + + inc_action_message(arr) + end +end) + +windower.register_event('prerender', function() + local curr = os.clock() + if curr > frame_time + settings.interval then + frame_time = curr + update_box() + end +end) + +windower.register_event('addon command', function(command1, command2, ...) + local args = L{...} + command1 = command1 and command1:lower() or nil + command2 = command2 and command2:lower() or nil + + local name = args:concat(' ') + if command1 == 'm' or command1 == 'mode' then + if settings.mode == 'blacklist' then + settings.mode = 'whitelist' + else + settings.mode = 'blacklist' + end + log('Changed to %s mode.':format(settings.mode)) + settings:save() + elseif command1 == 't' or command1 == 'timers' then + settings.timers = not settings.timers + log('Timer display %s.':format(settings.timers and 'enabled' or 'disabled')) + settings:save() + elseif command1 == 'i' or command1 == 'interval' then + settings.interval = tonumber(command2) or .1 + log('Refresh interval set to %s seconds.':format(settings.interval)) + settings:save() + elseif command1 == 'h' or command1 == 'hide' then + settings.hide_below_zero = not settings.hide_below_zero + log('Timers that reach 0 will be %s.':format(settings.hide_below_zero and 'hidden' or 'shown')) + settings:save() + elseif list_commands:containskey(command1) then + if sort_commands:containskey(command2) then + local spell = res.spells:with('name', windower.wc_match-{name}) + command1 = list_commands[command1] + command2 = sort_commands[command2] + + if spell == nil then + error('No spells found that match: %s':format(name)) + elseif command2 == 'add' then + settings[command1]:add(spell.name) + log('Added spell to %s: %s':format(command1, spell.name)) + else + settings[command1]:remove(spell.name) + log('Removed spell from %s: %s':format(command1, spell.name)) + end + settings:save() + end + else + print('%s (v%s)':format(_addon.name, _addon.version)) + print(' \\cs(255,255,255)mode\\cr - Switches between blacklist and whitelist mode (default: blacklist)') + print(' \\cs(255,255,255)timers\\cr - Toggles display of debuff timers (default: true)') + print(' \\cs(255,255,255)interval <value>\\cr - Allows you to change the refresh interval (default: 0.1)') + print(' \\cs(255,255,255)blacklist|whitelist add|remove <name>\\cr - Adds or removes the spell <name> to the specified list') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Debuffed/README.md b/Data/DefaultContent/Libraries/addons/addons/Debuffed/README.md new file mode 100644 index 0000000..ae986b1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Debuffed/README.md @@ -0,0 +1,38 @@ +# Debuffed + +An addon that tracks and displays debuffs on your current target. Filters are available to customise which debuffs are shown. + +### Commands + +`//debuffed mode` + +This will switch between blacklist and whitelist mode for debuff filtering. + +`//debuffed timers` + +This toggles the display of timers for debuffs. + +`//debuffed interval <value>` + +This allows you to adjust the refresh interval for the textbox. It will be updated every \<value\> number of seconds. + +`//debuffed hide` + +This toggles the automatic removal of effects when their timer reaches zero. + +`//debuffed blacklist|whitelist add|remove <name>` + +This adds or removes the spell \<name\> to the specified filter. + +### Abbreviations + +The following abbreviations are available for addon commands: +* `debuffed` to `dbf` +* `mode` to `m` +* `timers` to `t` +* `interval` to `i` +* `hide` to `h` +* `blacklist` to `b` or `blist` or `black` +* `whitelist` to `w` or `wlist` or `white` +* `add` to `a` or `+` +* `remove` to `r` or `-` diff --git a/Data/DefaultContent/Libraries/addons/addons/Dimmer/Dimmer.lua b/Data/DefaultContent/Libraries/addons/addons/Dimmer/Dimmer.lua new file mode 100644 index 0000000..7b9f814 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Dimmer/Dimmer.lua @@ -0,0 +1,115 @@ +--[[ +Copyright © 2018, Chiaia +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Dimmer nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 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.]] + +--Complete addon is almost a direct copy of MyHome from "from20020516" but for warping to a different area. + +_addon.name = 'Dimmer' +_addon.author = 'Chiaia' +_addon.version = '1.1.1' +_addon.commands = {'dim','dimmer'} + +require('logger') +extdata = require('extdata') +res_bags = require('resources').bags + +log_flag = true + +lang = string.lower(windower.ffxi.get_info().language) +item_info = { + [1]={id=26176,japanese='D.ホラリング',english='"Dim. Ring (Holla)"',slot=13}, + [2]={id=26177,japanese='D.デムリング',english='"Dim. Ring (Dem)"',slot=13}, + [3]={id=26178,japanese='D.メアリング',english='"Dim. Ring (Mea)"',slot=13}, + [4]={id=10385,japanese="キュムラスマスク+1",english="Cumulus Masque +1",slot=4}, +} + +function search_item() + if windower.ffxi.get_player().status > 1 then + log('You cannot use items at this time.') + return + end + + local item_array = {} + local get_items = windower.ffxi.get_items + local set_equip = windower.ffxi.set_equip + + for bag_id in pairs(res_bags:equippable(true)) do + local bag = get_items(bag_id) + for _,item in ipairs(bag) do + if item.id > 0 then + item_array[item.id] = item + item_array[item.id].bag = bag_id + item_array[item.id].bag_enabled = bag.enabled + end + end + end + for index,stats in pairs(item_info) do + local item = item_array[stats.id] + if item and item.bag_enabled then + local ext = extdata.decode(item) + local enchant = ext.type == 'Enchanted Equipment' + local recast = enchant and ext.charges_remaining > 0 and math.max(ext.next_use_time+18000-os.time(),0) + local usable = recast and recast == 0 + log(stats[lang],usable and '' or recast and recast..' sec recast.') + if usable or ext.type == 'General' then + if enchant and item.status ~= 5 then --not equipped + set_equip(item.slot,stats.slot,item.bag) + repeat --waiting cast delay + coroutine.sleep(1) + local ext = extdata.decode(get_items(item.bag,item.slot)) + local delay = ext.activation_time+18000-os.time() + if delay > 0 then + log(stats[lang],delay) + elseif log_flag then + log_flag = false + log('Item use within 3 seconds..') + end + until ext.usable or delay > 30 + end + windower.chat.input('/item '..windower.to_shift_jis(stats[lang])..' <me>') + break; + end + elseif item and not item.bag_enabled then + log('You cannot access '..stats[lang]..' from ' .. res_bags[item.bag].name ..' at this time.') + else + log('You don\'t have '..stats[lang]..'.') + end + end +end + +windower.register_event('addon command',function(...) + local args = T{...} + local cmd = args[1] + if cmd == 'all' then + windower.chat.input('//dimmer') + windower.send_ipc_message('dimmer') + else + search_item() + end +end) + +windower.register_event('ipc message',function (msg) + if msg == 'dimmer' then + windower.chat.input('//dimmer') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Dimmer/README.md b/Data/DefaultContent/Libraries/addons/addons/Dimmer/README.md new file mode 100644 index 0000000..ea808e7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Dimmer/README.md @@ -0,0 +1,14 @@ +# Dimmer +## English +- Automatically choose and uses the first Dimensional Ring on cool down to warp to Reisenjima for you. + +### Command +- `//dim` OR `//dimmer` +- `//dim all` OR `//dimmer all` will use ring on all characters. +- Priorities are: + 1. Dim. Ring (Holla) + 2. Dim. Ring (Dem) + 3. Dim. Ring (Mea) + +## 日本語 +- Should support the Japanesse client too.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/DistancePlus/DistancePlus.lua b/Data/DefaultContent/Libraries/addons/addons/DistancePlus/DistancePlus.lua new file mode 100644 index 0000000..760924a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DistancePlus/DistancePlus.lua @@ -0,0 +1,423 @@ +--[[ +Copyright © 2017, Sammeh of Quetzalcoatl +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of DistancePlus nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sammeh BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'DistancePlus' +_addon.author = 'Sammeh' +_addon.version = '1.3.0.10' +_addon.command = 'dp' + +-- 1.3.0.2 Fixed up nil's per recommendation on submission to Windower +-- 1.3.0.3 Replaced all tabs for 4 spaces to normalize indentations. +-- 1.3.0.4 Moving some expensive functions to on-load vs per-render. +-- 1.3.0.5 Implement config plugin. +-- 1.3.0.6 Fix ability list on job change. +-- 1.3.0.7 Implement ranged fix w/o ja_distance +-- 1.3.0.8 Wasn't refreshing 'self' upon job change. Fixed up spacing. +-- 1.3.0.9 Fixup MaxDecimal from config plugin addition. +-- 1.3.0.10 Changed slightly some variable scopes for lower mem usage. + +require('tables') + +res = require 'resources' +config = require('config') +texts = require('texts') + +defaults = {} +defaults.main = {} +defaults.main.pos = {} +defaults.main.pos.x = -178 +defaults.main.pos.y = 21 +defaults.main.text = {} +defaults.main.text.font = 'Arial' +defaults.main.text.size = 14 +defaults.main.flags = {} +defaults.main.flags.right = true + +defaults.pettxt = {} +defaults.pettxt.pos = {} +defaults.pettxt.pos.x = -178 +defaults.pettxt.pos.y = 45 +defaults.pettxt.text = {} +defaults.pettxt.text.font = 'Arial' +defaults.pettxt.text.size = 14 +defaults.pettxt.flags = {} +defaults.pettxt.flags.right = true + + +defaults.abilitytxt = {} +defaults.abilitytxt.pos = {} +defaults.abilitytxt.pos.x = -80 +defaults.abilitytxt.pos.y = 45 +defaults.abilitytxt.text = {} +defaults.abilitytxt.text.font = 'Arial' +defaults.abilitytxt.text.size = 10 +defaults.abilitytxt.flags = {} +defaults.abilitytxt.flags.right = true + +defaults.heighttxt = {} +defaults.heighttxt.pos = {} +defaults.heighttxt.pos.x = -238 +defaults.heighttxt.pos.y = 21 +defaults.heighttxt.text = {} +defaults.heighttxt.text.font = 'Arial' +defaults.heighttxt.text.size = 14 +defaults.heighttxt.flags = {} +defaults.heighttxt.flags.right = true + +height_upper_threshold = 8.5 +height_lower_threshold = -7.5 + + +settings = config.load(defaults) +distance = texts.new('${value||%.2f}', settings.main) +petdistance = texts.new('${value||%.2f}', settings.pettxt) +abilities = texts.new('${value}', settings.abilitytxt) +height = texts.new('${value||%.2f}', settings.heighttxt) + + +option = "Default" +showabilities = false +showheight = false + +function displayabilities(distance,master_pet_distance,s,t) + local range_mult = { + [2] = 1.55, + [3] = 1.490909, + [4] = 1.44, + [5] = 1.377778, + [6] = 1.30, + [7] = 1.15, + [8] = 1.25, + [9] = 1.377778, + [10] = 1.45, + [11] = 1.454545454545455, + [12] = 1.666666666666667, + } + local list = 'Abilities:\n' + if abilitylist then + for key,ability in pairs(abilitylist) do + ability_en = res.job_abilities[ability].name + ability_type = res.job_abilities[ability].type + ability_targets = res.job_abilities[ability].targets + ability_distance = res.job_abilities[ability].range + if distance and ability_en and (ability_type == 'JobAbility' or ability_type == 'PetCommand' or ability_type == 'BloodPactRage' or ability_type == 'BloodPactWard' or ability_type == 'Monster' or ability_type == 'Step') and ability_en ~= "Flourishes II" then + if ability_targets.Self ~= true then + if distance < (t.model_size + ability_distance * range_mult[ability_distance] + s.model_size) and distance ~= 0 then + list = list..'\\cs(0,255,0)'..ability_en..'\\cs(255,255,255)'..'\n' + else + list = list..'\\cs(255,255,255)'..ability_en..'\n' + end + --[[ too much crap on screen!!! + elseif ability_targets.Self == true and (ability_type == 'Monster' or ability_type == 'PetCommand') and master_pet_distance then + if master_pet_distance < (4 + s.model_size + t.model_size) and distance ~= 0 then + list = list..'\\cs(0,255,0)'..ability_en..'\\cs(255,255,255)'..'\n' + else + list = list..'\\cs(255,255,255)'..ability_en..'\n' + end + --]] + end + end + end + end + abilities.value = list + abilities:visible(showabilities) +end + +function check_job() + windower.add_to_chat(8,'*****DP Job Selection:'..self.main_job..'*****') + if self.main_job == 'RDM' or self.main_job == 'BLM' or self.main_job == 'GEO' or self.main_job == 'SCH' or self.main_job == 'WHM' or self.main_job == 'BRD' then + option = "Magic" + windower.add_to_chat(8,'Mode: Magic.') + windower.add_to_chat(8,' White = Can not cast.') + windower.add_to_chat(8,' Green = Casting Range') + MaxDistance = 20 + elseif self.main_job == 'COR' then + windower.add_to_chat(8,'Mode: Gun.') + windower.add_to_chat(8,' White = Can not shoot.') + windower.add_to_chat(8,' Yellow = Ranged Attack Capable (No Buff)') + windower.add_to_chat(8,' Green = Shoots Squarely (Good)') + windower.add_to_chat(8,' Blue = True Shot (Best)') + option = "Gun" + MaxDistance = 25 + elseif self.main_job == 'RNG' then + windower.add_to_chat(8,'RANGER should do //dp Bow, //dp XBow, or //dp Gun') + windower.add_to_chat(8,'Mode: Default.') + option = "Default" + MaxDistance = 25 + elseif self.main_job == 'NIN' then + option = "Ninjutsu" + windower.add_to_chat(8,'Mode: Ninjutsu.') + windower.add_to_chat(8,' White = Can not cast.') + windower.add_to_chat(8,' Green = Casting Range') + else + windower.add_to_chat(8,'Mode: Default.') + option = "Default" + MaxDistance = 25 + end +end + + +windower.register_event('prerender', function() + local t = windower.ffxi.get_mob_by_target('t') or windower.ffxi.get_mob_by_target('st') + local s = windower.ffxi.get_mob_by_target('me') + if windower.ffxi.get_mob_by_target('pet') then + pet = windower.ffxi.get_mob_by_target('pet') + else + pet = nil + end + if pet and self.main_job ~= 'DRG' then + if self.main_job == 'BST' then + local PetMaxDistance = 4 + local pettargetdistance = PetMaxDistance + pet.model_size + s.model_size + if pet.model_size > 1.6 then + pettargetdistance = PetMaxDistance + pet.model_size + s.model_size + 0.1 + end + if pet.distance:sqrt() < pettargetdistance then + petdistance:color(0,255,0) -- Green + else + petdistance:color(255,255,255) -- White + end + --else + -- may add some stuff here for SMN + end + petdistance.value = pet.distance:sqrt() + petdistance:visible(pet ~= nil) + else + petdistance:visible(false) + end + if t then + if pet then + displayabilities(t.distance:sqrt(),pet.distance:sqrt(),s,t) + else + displayabilities(t.distance:sqrt(),nil,s,t) + end + if t.distance:sqrt() == 0 then + distance:color(255,255,255) + else + if option == 'Default' then + distance:color(255,255,255) + elseif option == 'Bow' then + MaxDistance = 25 + trueshotmax = s.model_size + t.model_size + 9.5199 + trueshotmin = s.model_size + t.model_size + 6.02 + squareshot_far_max = s.model_size + t.model_size + 14.5199 + squareshot_close_min = s.model_size + t.model_size + 4.62 + if t.model_size > 1.6 then + trueshotmax = trueshotmax + 0.1 + trueshotmin = trueshotmin + 0.1 + squareshot_far_max = squareshot_far_max + 0.1 + squareshot_close_min = squareshot_close_min + 0.1 + end + if t.distance:sqrt() < MaxDistance and (t.distance:sqrt() > squareshot_far_max or t.distance:sqrt() < squareshot_close_min) then + distance:color(255,255,0) -- Yellow (No Ranged Boost) + elseif (t.distance:sqrt() <= squareshot_far_max and t.distance:sqrt() > trueshotmax) or (t.distance:sqrt() < trueshotmin and t.distance:sqrt() >= squareshot_close_min) then + distance:color(0,255,0) -- Green (Square Shot) + elseif (t.distance:sqrt() <= trueshotmax and t.distance:sqrt() >= trueshotmin) then + distance:color(0,0,255) -- Blue (Strikes True) + else + distance:color(255,255,255) -- White (Can't Shoot) + end + elseif option == 'Xbow' then + MaxDistance = 25 + trueshotmax = s.model_size + t.model_size + 8.3999 + trueshotmin = s.model_size + t.model_size + 5.0007 + squareshot_far_max = s.model_size + t.model_size + 11.7199 + squareshot_close_min = s.model_size + t.model_size + 3.6199 + if t.model_size > 1.6 then + trueshotmax = trueshotmax + 0.1 + trueshotmin = trueshotmin + 0.1 + squareshot_far_max = squareshot_far_max + 0.1 + squareshot_close_min = squareshot_close_min + 0.1 + end + if t.distance:sqrt() < MaxDistance and (t.distance:sqrt() > squareshot_far_max or t.distance:sqrt() < squareshot_close_min) then + distance:color(255,255,0) -- Yellow (No Ranged Boost) + elseif (t.distance:sqrt() <= squareshot_far_max and t.distance:sqrt() > trueshotmax) or (t.distance:sqrt() < trueshotmin and t.distance:sqrt() >= squareshot_close_min) then + distance:color(0,255,0) -- Green (Square Shot) + elseif (t.distance:sqrt() <= trueshotmax and t.distance:sqrt() >= trueshotmin) then + distance:color(0,0,255) -- Blue (Strikes True) + else + distance:color(255,255,255) -- White (Can't Shoot) + end + elseif option == 'Gun' then + MaxDistance = 25 + trueshotmax = s.model_size + t.model_size + 4.3189 + trueshotmin = s.model_size + t.model_size + 3.0209 + squareshot_far_max = s.model_size + t.model_size + 6.8199 + squareshot_close_min = s.model_size + t.model_size + 2.2219 + if t.model_size > 1.6 then + trueshotmax = trueshotmax + 0.1 + trueshotmin = trueshotmin + 0.1 + squareshot_far_max = squareshot_far_max + 0.1 + squareshot_close_min = squareshot_close_min + 0.1 + end + if t.distance:sqrt() < MaxDistance and (t.distance:sqrt() > squareshot_far_max or t.distance:sqrt() < squareshot_close_min) then + distance:color(255,255,0) -- Yellow (No Ranged Boost) + elseif (t.distance:sqrt() <= squareshot_far_max and t.distance:sqrt() > trueshotmax) or (t.distance:sqrt() < trueshotmin and t.distance:sqrt() >= squareshot_close_min) then + distance:color(0,255,0) -- Green (Square Shot) + elseif (t.distance:sqrt() <= trueshotmax and t.distance:sqrt() >= trueshotmin) then + distance:color(0,0,255) -- Blue (Strikes True) + else + distance:color(255,255,255) -- White (Can't Shoot) + end + elseif option == 'Magic' then + MaxDistance = 20 + if t.model_size > 2 then + MaxDistance = MaxDistance + 0.1 + elseif math.floor(t.model_size * 10) == 44 then + MaxDistance = 20.0666 + elseif math.floor(t.model_size * 10) == 53 then + MaxDistance = 20 + end + targetdistance = MaxDistance + t.model_size + s.model_size + if t.distance:sqrt() < targetdistance then + distance:color(0,255,0) -- Green + else + distance:color(255,255,255) -- White can't Cast + end + elseif option == 'Ninjutsu' then + MaxDistance = 16.1 + if t.model_size > 2 then + MaxDistance = MaxDistance + 0.1 + elseif math.floor(t.model_size * 10) == 44 then + MaxDistance = 16.1 + elseif math.floor(t.model_size * 10) == 53 then + MaxDistance = 16.1 + end + targetdistance = MaxDistance + t.model_size + s.model_size + if t.distance:sqrt() < targetdistance then + distance:color(0,255,0) -- Green + else + distance:color(255,255,255) -- White can't Cast + end + else + distance:color(255,255,255) + end + end + distance.value = t.distance:sqrt() + + height.value = t.z - s.z + if (t.z - s.z) >= height_upper_threshold or (t.z - s.z) <= height_lower_threshold then + height:color(0,255,0) -- green + else + height:color(255,0,0) -- red + end + + end + distance:visible(t ~= nil) + height:visible(t ~= nil and showheight) +end) + + +windower.register_event('addon command', function(command) + if command:lower() == 'help' then + windower.add_to_chat(8,'DistancePlus: Valid Modes are //DP <command>:') + windower.add_to_chat(8,' Gun, Bow, Xbow, Magic, JA') + windower.add_to_chat(8,' MaxDecimal - Expand MaxDecimal for Max Accuracy. DP Calculates to the Thousand') + windower.add_to_chat(8,' Default - Reset to Defaults') + windower.add_to_chat(8,' Pets - Not a command. If a pet is out another dialog will pop up with distance between you and Pet.') + elseif command:lower() == 'gun' then + windower.add_to_chat(8,'Mode: Gun.') + windower.add_to_chat(8,' White = Can not shoot.') + windower.add_to_chat(8,' Yellow = Ranged Attack Capable (No Buff)') + windower.add_to_chat(8,' Green = Shoots Squarely (Good)') + windower.add_to_chat(8,' Blue = True Shot (Best)') + option = "Gun" + elseif command:lower() == 'xbow' then + option = "Xbow" + windower.add_to_chat(8,'Mode: XBOW.') + windower.add_to_chat(8,' White = Can not shoot.') + windower.add_to_chat(8,' Yellow = Ranged Attack Capable (No Buff)') + windower.add_to_chat(8,' Green = Shoots Squarely (Good)') + windower.add_to_chat(8,' Blue = True Shot (Best)') + elseif command:lower() == 'bow' then + option = "Bow" + windower.add_to_chat(8,'Mode: BOW.') + windower.add_to_chat(8,' White = Can not shoot.') + windower.add_to_chat(8,' Yellow = Ranged Attack Capable (No Buff)') + windower.add_to_chat(8,' Green = Shoots Squarely (Good)') + windower.add_to_chat(8,' Blue = True Shot (Best)') + elseif command:lower() == 'magic' then + option = "Magic" + windower.add_to_chat(8,'Mode: Magic.') + windower.add_to_chat(8,' White = Can not cast.') + windower.add_to_chat(8,' Green = Casting Range') + elseif command:lower() == 'ninjutsu' then + option = "Ninjutsu" + windower.add_to_chat(8,'Mode: Ninjutsu.') + windower.add_to_chat(8,' White = Can not cast.') + windower.add_to_chat(8,' Green = Casting Range') + elseif command:lower() == 'default' then + windower.add_to_chat(8,'Mode: Default.') + option = "Default" + MaxDistance = 25 + distance:visible(false) + distance = texts.new('${value||%.2f}', settings.main) + elseif command:lower() == 'maxdecimal' then + distance:visible(false) + distance = texts.new('${value||%.12f}', settings.main) + elseif command:lower() == 'abilitylist' or command:lower() == 'ja' then + if showabilities then + showabilities = false + else + windower.add_to_chat(8,'Mode: JA.') + showabilities = true + displayabilities() + end + elseif command:lower() == 'height' then + showheight = true + end +end) + +windower.register_event('job change', function() + coroutine.sleep(2) -- sleeping because jobchange too fast doesn't show new abilities + self = windower.ffxi.get_player() + check_job() + abilitylist = windower.ffxi.get_abilities().job_abilities + abilities:visible(false) + abilities.value = "" + displayabilities() +end) + +windower.register_event('load', function() + if windower.ffxi.get_player() then + coroutine.sleep(2) -- sleeping because jobchange too fast doesn't show new abilities + self = windower.ffxi.get_player() + check_job() + abilitylist = windower.ffxi.get_abilities().job_abilities + displayabilities() + end +end) + + +windower.register_event('login', function() + coroutine.sleep(2) -- sleeping because jobchange too fast doesn't show new abilities + self = windower.ffxi.get_player() + check_job() + abilitylist = windower.ffxi.get_abilities().job_abilities + displayabilities() +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/DistancePlus/readme.md b/Data/DefaultContent/Libraries/addons/addons/DistancePlus/readme.md new file mode 100644 index 0000000..5821201 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DistancePlus/readme.md @@ -0,0 +1,16 @@ + +Enhances the functionality of the Distance AddOn. + +Distance Plus adds the following features: +* Distance measured to hundredth vs 10th. +* Distance offset to Model size correlation +* Color coordination for JA's, Ranged Attacks, Magic, etc +* Pet distance for distance-from-Pet + +Usage: +* //dp help - Shows help +* //dp MaxDecimal - Extends decimal out 12 points - really only useful for debugging. +* //dp height - Shows a height delta - Green - Can avoid AOE's, Red - danger of being hit by AOE +* //dp Magic - Sets defaults for magic casting +* //dp Gun|Bow|Xbow - sets defaults for shooting +* //dp ja - Shows a Job ability list and correlates distance/color for them. diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/DressUp.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/DressUp.lua new file mode 100644 index 0000000..85ea1e4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/DressUp.lua @@ -0,0 +1,481 @@ +-- Copyright © 2013-2017, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn BE LIABLE FOR ANY +-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'DressUp' +_addon.author = 'Cair' +_addon.version = '1.21' +_addon.commands = {'DressUp','du'} + + +packets = require('packets') +require('luau') +require('helper_functions') +require('static_variables') + + +models = {} +require('head') +require('body') +require('hands') +require('legs') +require('feet') + +settings = config.load(defaults) +info = T{ + names = T{}, + self = T{}, + party = S{} +} + +model_names = S{"Face","Race","Head","Body","Hands","Legs","Feet","Main","Sub","Ranged"} + +local initialize = function() + local player = windower.ffxi.get_player() + info.self.name = player.name:lower() + info.self.id = player.id + info.self.index = player.index + + if not settings[info.self.name] then + settings[info.self.name] = {} + end + + print_blink_settings("global") + if load_profile(player.main_job) then + notice('Loaded profile: ' .. player.main_job) + end + + update_model(info.self.index) +end + +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in then + initialize() + end +end) + +windower.register_event('login', initialize) + +windower.register_event('logout', function() + info.self:clear() +end) + +windower.register_event('job change',function(job) + if load_profile(res.jobs[job].name) then + update_model(info.self.index) + notice('Loaded profile: ' .. res.jobs[job].name) + end +end) + + +local modify_gear = function(packet, name, freeze, models) + local modified = false + + for k, v in pairs(packet) do + if model_names[k] then + if rawget(settings, name) and settings[name][k:lower()] then + -- Settings for individuals + packet[k] = settings[name][k:lower()] + modified = true + elseif table.containskey(settings.replacements[k:lower()], tostring(v)) then + -- Replace specific gear + packet[k] = settings.replacements[k:lower()][tostring(v)] + modified = true + elseif freeze and models and models[k] then + -- Swap the model values from memory to the packet to prevent blinking + packet[k] = models[k] + modified = true + end + end + end + + return modified, packet +end + +windower.register_event('incoming chunk',function (id, _, data) + + if id ~= 0x00A and id ~= 0x00D and id ~= 0x051 then + return + end + + local packet = packets.parse('incoming', data) + local modified + + -- Processing based on packet type + if id == 0x00A then + info.self.id = packet['Player'] + info.self.index = packet['Player Index'] + info.self.name = packet['Player Name']:lower() + modified,packet = modify_gear(packet, info.self.name) + return modified and packets.build(packet) + end + + if id == 0x0D and not packet['Update Model'] then + return + end + + local char_id = packet.Player or info.self.id + local char_index = packet.Index or info.self.index + local character = windower.ffxi.get_mob_by_index(char_index or -1) + local blink_type, name = 'others' + local models + + if character and character.models and table.length(character.models) == 9 and + (id == 0x051 or (id == 0x00D and character.id == packet.Player) ) then + models = T{ + Race = character.race, + Face = character.models[1], + Head = character.models[2]+0x1000, + Body = character.models[3]+0x2000, + Hands = character.models[4]+0x3000, + Legs = character.models[5]+0x4000, + Feet = character.models[6]+0x5000, + Main = character.models[7]+0x6000, + Sub = character.models[8]+0x7000, + Ranged = character.models[9]+0x8000} + end + + if not info.names[char_id] then + if packet['Update Name'] then + info.names[char_id] = packet['Character Name']:lower() + elseif character then + info.names[char_id] = character.name:lower() + else + return + end + end + + local player = windower.ffxi.get_player() + + if player.follow_index == char_index then + blink_type = "follow" + elseif character and character.in_alliance then + blink_type = "party" + else + blink_type = "others" + end + + if info.names[char_id] == info.self.name then + name = info.self.name + blink_type = "self" + elseif settings[info.names[char_id]] then + name = info.names[char_id] + else + name = "others" + end + + -- Model ID 0xFFFF in ranged slot signifies a monster. This prevents undesired results. + modified,packet = modify_gear(packet, name, blink_logic(blink_type, char_index, player), models) + return packet['Ranged'] ~= 0xFFFF and modified and packets.build(packet) +end) + +--[[windower.register_event('outgoing chunk',function (id, data) + if id == 0x17 then + -- Block the NPC/armor mismatch error packet + return true + end +end) +-- It appear that blocking this packet might have been causing people to not show up occasionally. +-- Rather than an unnatural error packet, it might be a normal part of client-server communication.]] + +windower.register_event('addon command', function (command,...) + command = command and command:lower() or 'help' + local args = T{...}:map(string.lower) + local _clear = nil + + if command == 'help' then + print(helptext) + elseif command == "eval" then + assert(loadstring(L{...}:concat(' ')))() + + elseif command == "autoupdate" or command == "au" then + settings.autoupdate = not settings.autoupdate + notice("AutoUpdate setting is now "..tostring(settings.autoupdate)..".") + + elseif command == "save" or command == "s" then + save_profile(args:concat('')) + + elseif command == "load" or command == "l" then + if load_profile(args:concat('')) then + notice('Loaded profile: ' .. args:concat('')) + else + error('Failed to find a profile named: ' .. args:concat('')) + end + + elseif command == "delete" or command == "d" then + if settings.profiles[args:concat(''):lower()] then + settings.profiles[args:concat(''):lower()] = nil + notice('Deleted profile: ' .. args:concat('')) + else + error('Failed to find a profile named: ' .. args:concat('')) + end + ---------------------------------------------------------- + --------------- Commands for model changes --------------- + ---------------------------------------------------------- + + elseif T{"self","others","player"}:contains(command) then + if not args[1] then + error("That is not a valid selection.") + return + end + + if command == "player" then + command = args:remove(1) + elseif command == "self" then + command = info.self.name + end + + if not settings[command] then + settings[command] = {} + end + + local _selection = S{"head","body","hands","legs","feet","main","sub","ranged","race","face"}:contains(args[1]) and args:remove(1) + + if not _selection then + error("That is not a valid selection.") + return + elseif _selection == "race" then + if not args[1] then + error("Please specify a race.") + return + elseif table.containskey(_races,args[1]) then + if args[1] == "mithra" or args[1] == "galka" then + settings[command]["race"] = _races[args[1]] + elseif args[2] and S{"male","female","m","f"}:contains(args[2]) then + settings[command]["race"] = _races[args[1]][args[2]] + else + error("Please specify male or female.") + return + end + + elseif S{0,1,2,3,4,5,6,7,8}:contains(tonumber(args[1])) then + settings[command]["race"] = tonumber(args[1]) + end + + elseif _selection == "face" then + if not args[1] then + error("Please specify a face.") + return + elseif table.containskey(_faces,args[1]) then + settings[command]["face"] = _faces[args[1]] + elseif S{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,29,30}:contains(tonumber(args[1])) then + settings[command]["face"] = tonumber(args[1]) + end + + else + if not args[1] then + error("Please specify an item.") + return + else + local item_id = tonumber(args[1]) or get_item_id(args[1],_selection) + if not item_id then + error("That item is not recognized.") + return + elseif table.containskey(models[_selection],item_id) then + if models[_selection][item_id] == ' ' then + error("That item has not been identified.") + return + else + settings[command][_selection] = models[_selection][item_id].model + end + else + error("That is not the correct item type.") + return + end + end + end + + ---------------------------------------------------------- + ---------------- Commands for blink rules ---------------- + ---------------------------------------------------------- + + elseif S{"blinking","blinkmenot","bmn"}:contains(command) then + if not args[1] or args[1] == "settings" then + _print = S{"self","others","party","all","follow"}:contains(args[2]) and args[2] or "global" + print_blink_settings(_print) + return + else + local _one = S{"self","others","party","follow","all"}:contains(args[1]) and args[1] + local _two = S{"target","always","combat","all"}:contains(args[2]) and args[2] + local _blinkbool + if args[3] and S{"on","off","true","false","t","f"}:contains(args[3]) then + _blinkbool = (S{"on","true","t"}:contains(args[3]) and true) or (S{"off","false","f"}:contains(args[3]) and false) + else + _blinkbool = "flip" + end + + if _one and _two then + if _blinkbool == "flip" then + if _two == "all" then + error("Specify [on/off] for selection 'all'.") + return + else + settings.blinking[_one][_two] = not settings.blinking[_one][_two] + print_blink_settings(_one) + end + else + if _two == "all" then + settings.blinking[_one]["target"] = _blinkbool + settings.blinking[_one]["always"] = _blinkbool + settings.blinking[_one]["combat"] = _blinkbool + else + settings.blinking[_one][_two] = _blinkbool + end + print_blink_settings(_one) + end + else + error("Invalid selections for blinking.") + return + end + end + + ---------------------------------------------------------- + ------------- Commands for clearing settings ------------- + ---------------------------------------------------------- + + elseif S{"clear","remove","delete"}:contains(command) then + if not args[1] then + error("Please specify something to clear.") + return + end + _clear = S{"replacements","self","others","player"}:contains(args[1]) and args:remove(1) + if _clear == "player" then + _clear = args:remove(1) + elseif _clear == "self" then + _clear = info.self.name + end + + local _selection = S{"head","body","hands","legs","feet","main","sub","ranged","race","face"}:contains(args[1]) and args:remove(1) + if not _clear then + error("Invalid clearing selection.") + return + elseif _clear == "replacements" then + if not _selection and settings[_clear] then + settings[_clear] = { face = {}, race = {}, head = {}, body = {}, hands = {}, legs = {}, feet = {}, main = {}, sub = {}, ranged = {} } + elseif not args[1] then + settings[_clear][_selection] = {} + elseif args[1] and settings[_clear][_selection] then + --To do: Expand on this to lookup keys for specified choices + settings[_clear][_selection][args[1]] = nil + else + error("The specified settings do not exist.") + return + end + else + if not _selection and settings[_clear] then + settings[_clear] = {} + elseif settings[_clear][_selection] then + settings[_clear][_selection] = nil + + else + error("The specified settings do not exist.") + return + end + end + + ---------------------------------------------------------- + -------------- Commands for 1:1 replacement -------------- + ---------------------------------------------------------- + elseif S{"replacements","replace","switch"}:contains(command) then + if not args[1] then + error("Please specify something to replace.") + return + end + local _models = {} + local _selection = S{"head","body","hands","legs","feet","main","sub","ranged","race","face"}:contains(args[1]) and args:remove(1) + + if not _selection then + error("That is not a valid selection.") + return + elseif _selection == "race" then + local _working = true + while #_models ~= 2 do + if not args[1] then + error("Please specify a race for #"..#_models + 1) + return + elseif table.containskey(_races,args[1]) then + if args[1] == "mithra" or args[1] == "galka" then + table.append(_models,_races[args:remove(1)]) + elseif args[2] and S{"male","female","m","f"}:contains(args[2]) then + table.append(_models,_races[args:remove(1)][args:remove(1)]) + else + error("Please specify male or female for #"..#_models + 1) + return + end + + elseif S{0,1,2,3,4,5,6,7,8}:contains(tonumber(args[1])) then + table.append(_models,tonumber(args:remove(1))) + end + end + elseif _selection == "face" then + while #_models ~= 2 do + if not args[1] then + error("Please specify a face for #"..#_models + 1) + return + elseif table.containskey(_faces,args[1]) then + table.append(_models,_faces[args:remove(1)]) + elseif S{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,29,30}:contains(tonumber(args[1])) then + table.append(_models,tonumber(args:remove(1))) + end + end + else + while #_models ~= 2 do + if not args[1] then + error("Please specify an item.") + return + else + local item_id = tonumber(args[1]) or get_item_id(args[1],_selection) + args:remove(1) + if not item_id then + error("Item #".. #_models + 1 .." is not recognized.") + return + elseif table.containskey(models[_selection],item_id) then + if models[_selection][item_id] == ' ' then + error("Item #".. #_models + 1 .." has not been identified.") + return + else + table.append(_models,models[_selection][item_id].model) + end + else + error("Item #".. #_models + 1 .." is not the correct type.") + return + end + end + end + end + + if #_models == 2 then + settings.replacements[_selection][tostring(_models[1])] = tostring(_models[2]) + else + error("Something went wrong!") + return + end + end + if settings.autoupdate and ((command == info.self.name) or (_clear == info.self.name)) then + update_model(windower.ffxi.get_player().index) + end + + settings:save('all') +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/DressUp/ReadMe.md new file mode 100644 index 0000000..13f83be --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/ReadMe.md @@ -0,0 +1,44 @@ +**Author:** Cairthenn<br> +**Version:** 1.1<br> +**Date:** Nov. 11, 2015<br> + +# DressUp # + +* Allows you to specify custom gear models for yourself, others, or individual players. Also allows for 1:1 model replacement similar to the functionality of .DAT swapping. +* Emulates BlinkMeNot functionality to prevent model blinking. +* **Uses packets**. + +---- + +#### Settings #### + +* DressUp uses 'settings.xml' in its data folder for all settings related to models and blinking. +* If you choose to edit this manually, this file uses **model IDs** for gear. You can look up the appropriate model IDs in the files in the main directory. + +**Abbreviation:** //du + +#### Commands: #### +1. help : Shows a menu of commands in game +2. self/others/player <player name (if player was selected)> [race/face/<item slot>] [<item name>/<race nam>/<face>] + - Assigns models to yourself, others, or an individual player as specified. + - Supports IDs as well as names for items/races. Specify male or female if necessary. +3. clear [self/others/player] <player name> [race/face/<item slot>] + - Clears settings for the selection. Player name specific to player option. +4. replace [race/face/<item slot>] <selection1> <selection2> + - Handles 1:1 replacement, similar to .DAT swapping. +5. blinking [self/others/party/follow/all] [always/target/combat/all] [on/off] + - Changes blinking settings. Toggles if nothing is specified. + - Also accepts "bmn" and "blinkmenot" as command prefix. +6. autoupdate + - Your character's appearance will update as you type commands if this setting is on. +7. load/save/delete <profile name> + - Creates a profile to be used for your own characters appearance. + - Profiles named JOB or NAME_JOB will be checked for on job changes. + +-- + +#### To do: #### +* Add weapons, ranged weapons, and shield models. +* Allow for monster models. + * While this is currently possible, others seem to lose their animations if transformed into a monster. +* Refine profile system diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/body.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/body.lua new file mode 100644 index 0000000..d91a3c7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/body.lua @@ -0,0 +1,1024 @@ +-- Copyright © 2013, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +models.body = {} + +models.body["None"] = { name = "empty", model = 8192 } + +models.body[10240] = { name = "Hexed Haubert" , enl = "hexed haubert", model = 8197} +models.body[10241] = { name = "Hexed Domaru" , enl = "hexed domaru", model = 8201} +models.body[10242] = { name = "Hexed Jacket" , enl = "hexed jacket", model = 8363} +models.body[10243] = { name = "Hexed Doublet" , enl = "hexed doublet", model = 8212} +models.body[10244] = { name = "Hexed Bliaut" , enl = "hexed bliaut", model = 8120} +models.body[10245] = { name = "Hexed Haubert -1" , enl = "hexed haubert -1", model = 8197} +models.body[10246] = { name = "Hexed Domaru -1" , enl = "hexed domaru -1", model = 8201} +models.body[10247] = { name = "Hexed Jacket -1" , enl = "hexed jacket -1", model = 8363} +models.body[10248] = { name = "Hexed Doublet -1" , enl = "hexed doublet -1", model = 8212} +models.body[10249] = { name = "Hexed Bliaut -1" , enl = "hexed bliaut -1", model = 8120} +models.body[10250] = { name = "Moogle Suit" , enl = "moogle suit", model = 8499} --8507 Taru M +models.body[10251] = { name = "Decennial Coat" , enl = "decennial coat", model = 8523} +models.body[10252] = { name = "Decennial Dress" , enl = "decennial dress", model = 8524} +models.body[10253] = { name = "Decennial Coat +1" , enl = "decennial coat +1", model = 8523} +models.body[10254] = { name = "Decennial Dress +1" , enl = "decennial dress +1", model = 8524} +models.body[10255] = { name = "Matanca Harness" , enl = "matanca harness", model = 8525} +models.body[10256] = { name = "Marine Gilet" , enl = "marine gilet", model = 8526} +models.body[10257] = { name = "Marine Top" , enl = "marine top", model = 8526} +models.body[10258] = { name = "Woodsy Gilet" , enl = "woodsy gilet", model = 8526} +models.body[10259] = { name = "Woodsy Top" , enl = "woodsy top", model = 8526} +models.body[10260] = { name = "Creek Maillot" , enl = "creek maillot", model = 8527} +models.body[10261] = { name = "Creek Top" , enl = "creek top", model = 8527} +models.body[10262] = { name = "River Top" , enl = "river top", model = 8526} +models.body[10263] = { name = "Dune Gilet" , enl = "dune gilet", model = 8526} +models.body[10264] = { name = "Marine Gilet +1" , enl = "marine gilet +1", model = 8526} +models.body[10265] = { name = "Marine Top +1" , enl = "marine top +1", model = 8526} +models.body[10266] = { name = "Woodsy Gilet +1" , enl = "woodsy gilet +1", model = 8526} +models.body[10267] = { name = "Woodsy Top +1" , enl = "woodsy top +1", model = 8526} +models.body[10268] = { name = "Creek Maillot +1" , enl = "creek maillot +1", model = 8527} +models.body[10269] = { name = "Creek Top +1" , enl = "creek top +1", model = 8527} +models.body[10270] = { name = "River Top +1" , enl = "river top +1", model = 8526} +models.body[10271] = { name = "Dune Gilet +1" , enl = "dune gilet +1", model = 8526} +models.body[10272] = { name = "Dux Scale Mail" , enl = "dux scale mail", model = 8528} +models.body[10273] = { name = "Dux Sc. Mail +1" , enl = "dux scale mail +1", model = 8528} +models.body[10274] = { name = "Chelona Blazer" , enl = "chelona blazer", model = 8529} +models.body[10275] = { name = "Chelona Blazer +1" , enl = "chelona blazer +1", model = 8529} +models.body[10276] = { name = "Enif Corazza" , enl = "enif corazza", model = 8351} +models.body[10277] = { name = "Adhara Manteel" , enl = "adhara manteel", model = 8352} +models.body[10278] = { name = "Murzim Corazza" , enl = "murzim corazza", model = 8351} +models.body[10279] = { name = "Shedir Manteel" , enl = "shedir manteel", model = 8351} +models.body[10280] = { name = "Laeradr Breastplate" , enl = "laeradr breastplate", model = 8391} +models.body[10281] = { name = "Manasa Chasuble" , enl = "manasa chasuble", model = 8335} +models.body[10282] = { name = "Krabat Jacket" , enl = "krabat jacket", model = 8396} +models.body[10283] = { name = "Vara Brigandine" , enl = "vara brigandine armor", model = 8235} +models.body[10284] = { name = "Grandoyne's Mail" , enl = "grandoyne's mail", model = 8252} +models.body[10285] = { name = "Beguiler's Jerkin" , enl = "beguiler's jerkin", model = 8246} +models.body[10286] = { name = "Seidr Cotehardie" , enl = "seidr cotehardie", model = 8240} +models.body[10287] = { name = "Feverish Korazin" , enl = "feverish korazin", model = 8366} +models.body[10288] = { name = "Baalmuian Robe" , enl = "baalmuian robe", model = 8538} +models.body[10289] = { name = "Sublime Breastplate" , enl = "sublime breastplate", model = 8289} +models.body[10290] = { name = "Laudan Cuirass" , enl = "laudan cuirass", model = 8394} +models.body[10291] = { name = "Febro Kaftan" , enl = "febro kaftan", model = 8426} +models.body[10292] = { name = "Wikyo Cloak" , enl = "wikyo cloak", model = 8239} +models.body[10293] = { name = "Chocobo Shirt" , enl = "chocobo shirt", model = 8501} -- 8509 Taru M +models.body[10450] = { name = "Ogier's Surcoat" , enl = "ogier's surcoat", model = 8517} +models.body[10451] = { name = "Athos's Tabard" , enl = "athos's tabard", model = 8518} +models.body[10452] = { name = "Rubeus Jacket" , enl = "rubeus jacket", model = 8519} +models.body[10453] = { name = "Praeco Doublet" , enl = "praeco doublet", model = 8521} +models.body[10454] = { name = "Asura Samue" , enl = "asura samue", model = 8290} +models.body[10455] = { name = "Asura Samue +1" , enl = "asura samue +1", model = 8290} +models.body[10456] = { name = "Avant Mail" , enl = "avant mail", model = 8221} +models.body[10457] = { name = "Avant Mail +1" , enl = "avant mail +1", model = 8221} +models.body[10458] = { name = "Kacura Harness" , enl = "kacura harness", model = 8302} +models.body[10459] = { name = "Kacura Harness +1" , enl = "kacura harness +1", model = 8302} +models.body[10460] = { name = "Sweven Coat" , enl = "sweven coat", model = 8323} +models.body[10461] = { name = "Sweven Coat +1" , enl = "sweven coat +1", model = 8323} +models.body[10462] = { name = "Calma Breastplate" , enl = "calma breastplate", model = 8473} +models.body[10463] = { name = "Mustela Harness" , enl = "mustela harness", model = 8440} +models.body[10464] = { name = "Magavan Frock" , enl = "magavan frock", model = 8461} +models.body[10465] = { name = "Fulad-Zereh" , enl = "fulad-zereh", model = 8520} +models.body[10466] = { name = "Gunman Gambison" , enl = "gunman gambison", model = 8354} +models.body[10467] = { name = "Pharmakeia Robe" , enl = "pharmakeia robe", model = 8412} +models.body[10468] = { name = "Kumarbi's Akar" , enl = "kumarbi's akar", model = 8395} +models.body[10469] = { name = "Eirene's Manteel" , enl = "eirene's manteel", model = 8352} +models.body[10470] = { name = "Rheic Korazin" , enl = "rheic korazin", model = 8387} +models.body[10471] = { name = "Rheic Korazin +1" , enl = "rheic korazin +1", model = 8387} +models.body[10472] = { name = "Rheic Korazin +2" , enl = "rheic korazin +2", model = 8387} +models.body[10473] = { name = "Rheic Korazin +3" , enl = "rheic korazin +3", model = 8387} +models.body[10474] = { name = "Phorcys Korazin" , enl = "phorcys korazin", model = 8387} +models.body[10475] = { name = "Euxine Coat" , enl = "euxine coat", model = 8388} +models.body[10476] = { name = "Euxine Coat +1" , enl = "euxine coat +1", model = 8388} +models.body[10477] = { name = "Euxine Coat +2" , enl = "euxine coat +2", model = 8388} +models.body[10478] = { name = "Euxine Coat +3" , enl = "euxine coat +3", model = 8388} +models.body[10479] = { name = "Thaumas Coat" , enl = "thaumas coat", model = 8388} +models.body[10480] = { name = "Tethyan Saio" , enl = "tethyan saio", model = 8389} +models.body[10481] = { name = "Tethyan Saio +1" , enl = "tethyan saio +1", model = 8389} +models.body[10482] = { name = "Tethyan Saio +2" , enl = "tethyan saio +2", model = 8389} +models.body[10483] = { name = "Tethyan Saio +3" , enl = "tethyan saio +3", model = 8389} +models.body[10484] = { name = "Nares Saio" , enl = "nares saio", model = 8389} +models.body[10485] = { name = "Tessera Saio" , enl = "tessera saio", model = 8522} +models.body[10486] = { name = "Porthos Byrnie" , enl = "porthos byrnie", model = 8233} +models.body[10487] = { name = "Hedera Cotehardie" , enl = "hedera cotehardie", model = 8241} +models.body[10488] = { name = "Kudzu Aketon" , enl = "kudzu aketon", model = 8288} +models.body[10489] = { name = "Huginn Haubert" , enl = "huginn haubert", model = 8515} +models.body[10490] = { name = "Tenryu Domaru +1" , enl = "tenryu domaru +1", model = 8516} +models.body[10491] = { name = "Khepri Jacket" , enl = "khepri jacket", model = 8512} +models.body[10492] = { name = "Spurrina Doublet" , enl = "spurrina doublet", model = 8514} +models.body[10493] = { name = "Iaso Bliaut" , enl = "iaso bliaut", model = 8513} +models.body[10494] = { name = "Ugol Haubert" , enl = "ugol haubert", model = 8342} +models.body[10495] = { name = "Mavros Haubert" , enl = "mavros haubert", model = 8342} +models.body[10496] = { name = "Urja Jerkin" , enl = "urja jerkin", model = 8242} +models.body[10497] = { name = "Sthira Jerkin" , enl = "sthira jerkin", model = 8242} +models.body[10498] = { name = "Spolia Saio" , enl = "spolia saio", model = 8341} +models.body[10499] = { name = "Opima Saio" , enl = "opima saio", model = 8341} +models.body[10670] = { name = "War. Lorica +2" , enl = "warrior's lorica +2", model = 8257} +models.body[10671] = { name = "Mel. Cyclas +2" , enl = "melee cyclas +2", model = 8259} +models.body[10672] = { name = "Clr. Briault +2" , enl = "cleric's briault +2", model = 8261} +models.body[10673] = { name = "Src. Coat +2" , enl = "sorcerer's coat +2", model = 8263} +models.body[10674] = { name = "Dls. Tabard +2" , enl = "duelist's tabard +2", model = 8265} +models.body[10675] = { name = "Asn. Vest +2" , enl = "assassin's vest +2", model = 8267} +models.body[10676] = { name = "Vlr. Surcoat +2" , enl = "valor surcoat +2", model = 8269} +models.body[10677] = { name = "Abs. Cuirass +2" , enl = "abyss cuirass +2", model = 8271} +models.body[10678] = { name = "Mst. Jackcoat +2" , enl = "monster jackcoat +2", model = 8273} +models.body[10679] = { name = "Brd. Jstcorps +2" , enl = "bard's justaucorps +2", model = 8275} +models.body[10680] = { name = "Sct. Jerkin +2" , enl = "scout's jerkin +2", model = 8277} +models.body[10681] = { name = "Sao. Domaru +2" , enl = "saotome domaru +2", model = 8279} +models.body[10682] = { name = "Kog. Chainmail +2" , enl = "koga chainmail +2", model = 8281} +models.body[10683] = { name = "Wym. Mail +2" , enl = "wyrm mail +2", model = 8283} +models.body[10684] = { name = "Smn. Doublet +2" , enl = "summoner's doublet +2", model = 8285} +models.body[10685] = { name = "Mirage Jubbah +2" , enl = "mirage jubbah +2", model = 8358} +models.body[10686] = { name = "Comm. Frac +2" , enl = "commodore frac +2", model = 8360} +models.body[10687] = { name = "Pantin Tobe +2" , enl = "pantin tobe +2", model = 8362} +models.body[10688] = { name = "Etoile Casaque +2" , enl = "etoile casaque +2", model = 8496} -- 8504 Taru M +models.body[10689] = { name = "Argute Gown +2" , enl = "argute gown +2", model = 8407} +models.body[11084] = { name = "Rvg. Lorica +2" , enl = "ravager's lorica +2", model = 8474} +models.body[11085] = { name = "Tantra Cyclas +2" , enl = "tantra cyclas +2", model = 8475} +models.body[11086] = { name = "Orison Bliaud +2" , enl = "orison bliaud +2", model = 8476} +models.body[11087] = { name = "Goetia Coat +2" , enl = "goetia coat +2", model = 8477} +models.body[11088] = { name = "Estq. Sayon +2" , enl = "estoqueur's sayon +2", model = 8478} +models.body[11089] = { name = "Raider's Vest +2" , enl = "raider's vest +2", model = 8479} +models.body[11090] = { name = "Creed Cuirass +2" , enl = "creed cuirass +2", model = 8480} +models.body[11091] = { name = "Bale Cuirass +2" , enl = "bale cuirass +2", model = 8481} +models.body[11092] = { name = "Ferine Gausape +2" , enl = "ferine gausape +2", model = 8482} +models.body[11093] = { name = "Aoidos' Hngrln. +2" , enl = "aoidos' hongreline +2", model = 8483} +models.body[11094] = { name = "Sylvan Caban +2" , enl = "sylvan caban +2", model = 8484} +models.body[11095] = { name = "Unkai Domaru +2" , enl = "unkai domaru +2", model = 8485} +models.body[11096] = { name = "Iga Ningi +2" , enl = "iga ningi +2", model = 8486} +models.body[11097] = { name = "Lncr. Plackart +2" , enl = "lancer's plackart +2", model = 8487} +models.body[11098] = { name = "Call. Doublet +2" , enl = "caller's doublet +2", model = 8488} +models.body[11099] = { name = "Mavi Mintan +2" , enl = "mavi mintan +2", model = 8489} +models.body[11100] = { name = "Nvrch. Frac +2" , enl = "navarch's frac +2", model = 8490} +models.body[11101] = { name = "Cirque Farsetto +2" , enl = "cirque farsetto +2", model = 8491} +models.body[11102] = { name = "Charis Casaque +2" , enl = "charis casaque +2", model = 8497} -- 8505 Taru M +models.body[11103] = { name = "Savant's Gown +2" , enl = "savant's gown +2", model = 8498} -- 8606 Taru M +models.body[11184] = { name = "Rvg. Lorica +1" , enl = "ravager's lorica +1", model = 8474} +models.body[11185] = { name = "Tantra Cyclas +1" , enl = "tantra cyclas +1", model = 8475} +models.body[11186] = { name = "Orison Bliaud +1" , enl = "orison bliaud +1", model = 8476} +models.body[11187] = { name = "Goetia Coat +1" , enl = "goetia coat +1", model = 8477} +models.body[11188] = { name = "Estq. Sayon +1" , enl = "estoqueur's sayon +1", model = 8478} +models.body[11189] = { name = "Raider's Vest +1" , enl = "raider's vest +1", model = 8479} +models.body[11190] = { name = "Creed Cuirass +1" , enl = "creed cuirass +1", model = 8480} +models.body[11191] = { name = "Bale Cuirass +1" , enl = "bale cuirass +1", model = 8481} +models.body[11192] = { name = "Ferine Gausape +1" , enl = "ferine gausape +1", model = 8482} +models.body[11193] = { name = "Aoidos' Hngrln. +1" , enl = "aoidos' hongreline +1", model = 8483} +models.body[11194] = { name = "Sylvan Caban +1" , enl = "sylvan caban +1", model = 8484} +models.body[11195] = { name = "Unkai Domaru +1" , enl = "unkai domaru +1", model = 8485} +models.body[11196] = { name = "Iga Ningi +1" , enl = "iga ningi +1", model = 8486} +models.body[11197] = { name = "Lncr. Plackart +1" , enl = "lancer's plackart +1", model = 8487} +models.body[11198] = { name = "Caller's Doublet +1" , enl = "caller's doublet +1", model = 8488} +models.body[11199] = { name = "Mavi Mintan +1" , enl = "mavi mintan +1", model = 8489} +models.body[11200] = { name = "Navarch's Frac +1" , enl = "navarch's frac +1", model = 8490} +models.body[11201] = { name = "Cirque Farsetto +1" , enl = "cirque farsetto +1", model = 8491} +models.body[11202] = { name = "Charis Casaque +1" , enl = "charis casaque +1", model = 8497} -- 8505 Taru M +models.body[11203] = { name = "Savant's Gown +1" , enl = "savant's gown +1", model = 8498} -- 8606 Taru M +models.body[11265] = { name = "Custom Gilet" , enl = "custom gilet", model = 8417} +models.body[11266] = { name = "Custom Top" , enl = "custom top", model = 8417} +models.body[11267] = { name = "Magna Gilet" , enl = "magna gilet", model = 8417} +models.body[11268] = { name = "Magna Top" , enl = "magna top", model = 8417} +models.body[11269] = { name = "Wonder Maillot" , enl = "wonder maillot", model = 8418} +models.body[11270] = { name = "Wonder Top" , enl = "wonder top", model = 8418} +models.body[11271] = { name = "Savage Top" , enl = "savage top", model = 8417} +models.body[11272] = { name = "Elder Gilet" , enl = "elder gilet", model = 8417} +models.body[11273] = { name = "Custom Gilet +1" , enl = "custom gilet +1", model = 8417} +models.body[11274] = { name = "Custom Top +1" , enl = "custom top +1", model = 8417} +models.body[11275] = { name = "Magna Gilet +1" , enl = "magna gilet +1", model = 8417} +models.body[11276] = { name = "Magna Top +1" , enl = "magna top +1", model = 8417} +models.body[11277] = { name = "Wonder Maillot +1" , enl = "wonder maillot +1", model = 8418} +models.body[11278] = { name = "Wonder Top +1" , enl = "wonder top +1", model = 8418} +models.body[11279] = { name = "Savage Top +1" , enl = "savage top +1", model = 8417} +models.body[11280] = { name = "Elder Gilet +1" , enl = "elder gilet +1", model = 8417} +models.body[11281] = { name = "Hachiryu Haramaki" , enl = "hachiryu haramaki", model = 8419} +models.body[11282] = { name = "Aurum Cuirass" , enl = "aurum cuirass", model = 8228} +models.body[11283] = { name = "Oracle's Robe" , enl = "oracle's robe", model = 8335} +models.body[11284] = { name = "Enkidu's Harness" , enl = "enkidu's harness", model = 8248} +models.body[11285] = { name = "Mrgn. Cotehardie" , enl = "morgana's cotehardie", model = 8241} +models.body[11286] = { name = "Avalon Breastplate" , enl = "avalon breastplate", model = 8306} +models.body[11287] = { name = "Antares Harness" , enl = "antares harness", model = 8226} +models.body[11288] = { name = "Zahak's Mail" , enl = "zahak's mail", model = 8252} +models.body[11289] = { name = "Ixion Cloak" , enl = "ixion cloak", model = 8238} +models.body[11290] = { name = "Tidal Talisman" , enl = "tidal talisman", model = 8420} +models.body[11291] = { name = "Magus Jubbah +1" , enl = "magus jubbah +1", model = 8357} +models.body[11292] = { name = "Mirage Jubbah" , enl = "mirage jubbah", model = 8358} +models.body[11293] = { name = "Mirage Jubbah +1" , enl = "mirage jubbah +1", model = 8358} +models.body[11294] = { name = "Corsair's Frac +1" , enl = "corsair's frac +1", model = 8359} +models.body[11295] = { name = "Commodore Frac" , enl = "commodore frac", model = 8360} +models.body[11296] = { name = "Comm. Frac +1" , enl = "commodore frac +1", model = 8360} +models.body[11297] = { name = "Pup. Tobe +1" , enl = "puppetry tobe +1", model = 8361} +models.body[11298] = { name = "Pantin Tobe" , enl = "pantin tobe", model = 8362} +models.body[11299] = { name = "Pantin Tobe +1" , enl = "pantin tobe +1", model = 8362} +models.body[11300] = { name = "Eerie Cloak" , enl = "eerie cloak", model = 8421} +models.body[11301] = { name = "Eerie Cloak +1" , enl = "eerie cloak +1", model = 8421} +models.body[11302] = { name = "Dnc. Casaque +1" , enl = "dancer's casaque +1", model = 8402} +models.body[11303] = { name = "Dnc. Casaque +1" , enl = "dancer's casaque +1", model = 8403} +models.body[11304] = { name = "Scholar's Gown +1" , enl = "scholar's gown +1", model = 8406} +models.body[11305] = { name = "Etoile Casaque" , enl = "etoile casaque", model = 8496} -- 8504 Taru M +models.body[11306] = { name = "Etoile Casaque +1" , enl = "etoile casaque +1", model = 8496} -- 8504 Taru M +models.body[11307] = { name = "Argute Gown" , enl = "argute gown", model = 8407} +models.body[11308] = { name = "Argute Gown +1" , enl = "argute gown +1", model = 8407} +models.body[11309] = { name = "Benedight Coat" , enl = "benedight coat", model = 8424} +models.body[11310] = { name = "Argent Coat" , enl = "argent coat", model = 8424} +models.body[11311] = { name = "Platino Coat" , enl = "platino coat", model = 8424} +models.body[11312] = { name = "Rambler's Cloak" , enl = "rambler's cloak", model = 8202} +models.body[11313] = { name = "Nuevo Coselete" , enl = "nuevo coselete", model = 8428} +models.body[11314] = { name = "Mirke Wardecors" , enl = "mirke wardecors", model = 8429} +models.body[11315] = { name = "Royal Redingote" , enl = "royal redingote", model = 8430} +models.body[11316] = { name = "Otokogusa Yukata" , enl = "otokogusa yukata", model = 8435} +models.body[11317] = { name = "Onnagusa Yukata" , enl = "onnagusa yukata", model = 8436} +models.body[11318] = { name = "Otokoeshi Yukata" , enl = "otokoeshi yukata", model = 8435} +models.body[11319] = { name = "Ominaeshi Yukata" , enl = "ominaeshi yukata", model = 8436} +models.body[11320] = { name = "Skeleton Robe" , enl = "skeleton robe", model = 8210} +models.body[11321] = { name = "Orange Race Silks" , enl = "orange racing silks", model = 8379} +models.body[11322] = { name = "Black Race Silks" , enl = "black racing silks", model = 8380} +models.body[11323] = { name = "Purple Race Silks" , enl = "purple racing silks", model = 8381} +models.body[11324] = { name = "S. Blue Race Silks" , enl = "sky blue racing silks", model = 8382} +models.body[11325] = { name = "Blue Race Silks" , enl = "blue racing silks", model = 8383} +models.body[11326] = { name = "Red Race Silks" , enl = "red racing silks", model = 8384} +models.body[11327] = { name = "White Race Silks" , enl = "white racing silks", model = 8385} +models.body[11328] = { name = "Green Race Silks" , enl = "green racing silks", model = 8386} +models.body[11329] = { name = "Carpenter's Smock" , enl = "carpenter's smock", model = 8336} +models.body[11330] = { name = "Blksmith. Smock" , enl = "blacksmith's smock", model = 8337} +models.body[11331] = { name = "Goldsmith's Smock" , enl = "goldsmith's smock", model = 8348} +models.body[11332] = { name = "Weaver's Smock" , enl = "weaver's smock", model = 8338} +models.body[11333] = { name = "Tanner's Smock" , enl = "tanner's smock", model = 8346} +models.body[11334] = { name = "Bonewrk. Smock" , enl = "boneworker's smock", model = 8344} +models.body[11335] = { name = "Alchemist's Smock" , enl = "alchemist's smock", model = 8339} +models.body[11336] = { name = "Culinarian's Smock" , enl = "culinarian's smock", model = 8347} +models.body[11337] = { name = "Fisherman's Smock" , enl = "fisherman's smock", model = 8345} +models.body[11338] = { name = "Aega's Doublet" , enl = "aega's doublet", model = 8213} +models.body[11339] = { name = "Channeling Robe" , enl = "channeling robe", model = 8212} +models.body[11340] = { name = "Salutary Robe" , enl = "salutary robe", model = 8210} +models.body[11341] = { name = "Vivacity Coat" , enl = "vivacity coat", model = 8211} +models.body[11342] = { name = "Restorer Cloak" , enl = "restorer cloak", model = 8203} +models.body[11343] = { name = "Thrk. Breastplate" , enl = "thrakon breastplate", model = 8300} +models.body[11344] = { name = "Styrne Byrnie" , enl = "styrne byrnie", model = 8233} +models.body[11345] = { name = "Alacer Aketon" , enl = "alacer aketon", model = 8231} +models.body[11346] = { name = "Vela Justaucorps" , enl = "vela justaucorps", model = 8236} +models.body[11347] = { name = "Menetrier's Alb" , enl = "menetrier's alb", model = 8341} +models.body[11348] = { name = "Salutary Robe +1" , enl = "salutary robe +1", model = 8210} +models.body[11349] = { name = "Vivacity Coat +1" , enl = "vivacity coat +1", model = 8211} +models.body[11350] = { name = "Styrne Byrnie +1" , enl = "styrne byrnie +1", model = 8233} +models.body[11351] = { name = "Alacer Aketon +1" , enl = "alacer aketon +1", model = 8231} +models.body[11352] = { name = "Vela Jstcorps +1" , enl = "vela justaucorps +1", model = 8236} +models.body[11353] = { name = "Menetrier's Alb +1" , enl = "menetrier's alb +1", model = 8341} +models.body[11354] = { name = "Nocturnus Mail" , enl = "nocturnus mail", model = 8423} +models.body[11355] = { name = "Dinner Jacket" , enl = "dinner jacket", model = 8422} +models.body[11356] = { name = "Ryl.Grd. Livery" , enl = "royal guard livery", model = 8303} +models.body[11357] = { name = "Myth.Msk. Livery" , enl = "mythril musketeer livery", model = 8305} +models.body[11358] = { name = "Ptr.Prt. Livery" , enl = "patriarch protector livery", model = 8304} +models.body[11359] = { name = "Velox Harness" , enl = "velox harness", model = 8302} +models.body[11360] = { name = "Taranis's Harness" , enl = "taranis's harness", model = 8340} +models.body[11361] = { name = "Pluviale" , enl = "pluviale", model = 8352} +models.body[11362] = { name = "Twilight Mail" , enl = "twilight mail", model = 8373} +models.body[11363] = { name = "Twilight Cloak" , enl = "twilight cloak", model = 8404} +models.body[11828] = { name = "Gules Harness" , enl = "gules harness", model = 8248} +models.body[11829] = { name = "Gules Harness +1" , enl = "gules harness +1", model = 8248} +models.body[11830] = { name = "Versa Hauberk" , enl = "versa hauberk", model = 8250} +models.body[11831] = { name = "Versa Hauberk +1" , enl = "versa hauberk +1", model = 8250} +models.body[11832] = { name = "Lore Robe" , enl = "lore robe", model = 8307} +models.body[11833] = { name = "Lore Robe +1" , enl = "lore robe +1", model = 8307} +models.body[11834] = { name = "Bellicus Cuirass" , enl = "bellicus cuirass", model = 8462} +models.body[11835] = { name = "Bestia Mail" , enl = "bestia mail", model = 8465} +models.body[11836] = { name = "Paragon Haubert" , enl = "paragon haubert", model = 8468} +models.body[11837] = { name = "Skopos Jerkin" , enl = "skopos jerkin", model = 8441} +models.body[11838] = { name = "Kokugetsu Togi" , enl = "kokugetsu togi", model = 8444} +models.body[11839] = { name = "Spry Vest" , enl = "spry vest", model = 8447} +models.body[11840] = { name = "Mederi Talar" , enl = "mederi talar", model = 8450} +models.body[11841] = { name = "Literae Coat" , enl = "literae coat", model = 8453} +models.body[11842] = { name = "Facio Bliaut" , enl = "facio bliaut", model = 8456} +models.body[11843] = { name = "Tristan's Brstplate" , enl = "tristan's breastplate", model = 8224} +models.body[11844] = { name = "Alcide's Harness" , enl = "alcide's harness", model = 8302} +models.body[11845] = { name = "Alcd. Harness +1" , enl = "alcide's harness +1", model = 8302} +models.body[11846] = { name = "Nemus Peti" , enl = "nemus peti", model = 8364} +models.body[11847] = { name = "Nemus Peti +1" , enl = "nemus peti +1", model = 8364} +models.body[11848] = { name = "Nebula Hpl." , enl = "nebula houppelande", model = 8293} +models.body[11849] = { name = "Nebula Hpl. +1" , enl = "nebula houppelande +1", model = 8293} +models.body[11850] = { name = "Fazheluo Mail" , enl = "fazheluo mail", model = 8398} +models.body[11851] = { name = "Cuauhtli Harness" , enl = "cuauhtli harness", model = 8390} +models.body[11852] = { name = "Hyksos Robe" , enl = "hyksos robe", model = 8397} +models.body[11853] = { name = "Novennial Coat" , enl = "novennial coat", model = 8427} +models.body[11854] = { name = "Novennial Dress" , enl = "novennial dress", model = 8437} +models.body[11855] = { name = "Mextli Harness" , enl = "mextli harness", model = 8399} +models.body[11856] = { name = "Anhur Robe" , enl = "anhur robe", model = 8400} +models.body[11857] = { name = "Fazheluo R. Mail" , enl = "fazheluo radiant mail", model = 8401} +models.body[11858] = { name = "Fazheluo Mail +1" , enl = "fazheluo mail +1", model = 8398} +models.body[11859] = { name = "Cuauh. Harness +1" , enl = "cuauhtli harness +1", model = 8390} +models.body[11860] = { name = "Hyksos Robe +1" , enl = "hyksos robe +1", model = 8397} +models.body[11861] = { name = "Hikogami Yukata" , enl = "hikogami yukata", model = 8416} +models.body[11862] = { name = "Himegami Yukata" , enl = "himegami yukata", model = 8425} +models.body[11863] = { name = "Ocelomeh Harness" , enl = "ocelomeh harness", model = 8492} +models.body[11864] = { name = "Nefer Kalasiris" , enl = "nefer kalasiris", model = 8493} +models.body[11865] = { name = "Mekira Toshugai" , enl = "mekira toshugai", model = 8494} +models.body[11866] = { name = "Toci's Harness" , enl = "toci's harness", model = 8243} +models.body[11867] = { name = "Heka's Kalasiris" , enl = "heka's kalasiris", model = 8405} +models.body[11868] = { name = "Mekira Meikogai" , enl = "mekira meikogai", model = 8495} +models.body[11869] = { name = "Ocelm. Harness +1" , enl = "ocelomeh harness +1", model = 8492} +models.body[11870] = { name = "Nefer Kalasiris +1" , enl = "nefer kalasiris +1", model = 8492} +models.body[11871] = { name = "Mekira Toshugai +1" , enl = "mekira toshugai +1", model = 8494} +models.body[11872] = { name = "Ace's Mail" , enl = "ace's mail", model = 8244} +models.body[11873] = { name = "Hrafn Haubert" , enl = "hrafn haubert", model = 8515} +models.body[11874] = { name = "Tenryu Domaru" , enl = "tenryu domaru", model = 8516} +models.body[11875] = { name = "Kheper Jacket" , enl = "kheper jacket", model = 8512} +models.body[11876] = { name = "Auspex Doublet" , enl = "auspex doublet", model = 8514} +models.body[11877] = { name = "Paean Bliaut" , enl = "paean bliaut", model = 8513} +models.body[12028] = { name = "Ravager's Lorica" , enl = "ravager's lorica", model = 8474} +models.body[12029] = { name = "Tantra Cyclas" , enl = "tantra cyclas", model = 8475} +models.body[12030] = { name = "Orison Bliaud" , enl = "orison bliaud", model = 8476} +models.body[12031] = { name = "Goetia Coat" , enl = "goetia coat", model = 8477} +models.body[12032] = { name = "Estoqueur's Sayon" , enl = "estoqueur's sayon", model = 8478} +models.body[12033] = { name = "Raider's Vest" , enl = "raider's vest", model = 8479} +models.body[12034] = { name = "Creed Cuirass" , enl = "creed cuirass", model = 8480} +models.body[12035] = { name = "Bale Cuirass" , enl = "bale cuirass", model = 8481} +models.body[12036] = { name = "Ferine Gausape" , enl = "ferine gausape", model = 8482} +models.body[12037] = { name = "Aoidos' Hongreline" , enl = "aoidos' hongreline", model = 8483} +models.body[12038] = { name = "Sylvan Caban" , enl = "sylvan caban", model = 8484} +models.body[12039] = { name = "Unkai Domaru" , enl = "unkai domaru", model = 8485} +models.body[12040] = { name = "Iga Ningi" , enl = "iga ningi", model = 8486} +models.body[12041] = { name = "Lncr. Plackart" , enl = "lancer's plackart", model = 8487} +models.body[12042] = { name = "Caller's Doublet" , enl = "caller's doublet", model = 8488} +models.body[12043] = { name = "Mavi Mintan" , enl = "mavi mintan", model = 8489} +models.body[12044] = { name = "Navarch's Frac" , enl = "navarch's frac", model = 8490} +models.body[12045] = { name = "Cirque Farsetto" , enl = "cirque farsetto", model = 8491} +models.body[12046] = { name = "Charis Casaque" , enl = "charis casaque", model = 8497} -- 8505 Taru M +models.body[12047] = { name = "Savant's Gown" , enl = "savant's gown", model = 8498} -- 8606 Taru M +models.body[12144] = { name = "Ebon Cuirass" , enl = "ebon cuirass", model = 8462} +models.body[12145] = { name = "Furia Cuirass" , enl = "furia cuirass", model = 8463} +models.body[12146] = { name = "Ebur Cuirass" , enl = "ebur cuirass", model = 8464} +models.body[12147] = { name = "Ebon Mail" , enl = "ebon mail", model = 8465} +models.body[12148] = { name = "Furia Mail" , enl = "furia mail", model = 8466} +models.body[12149] = { name = "Ebur Mail" , enl = "ebur mail", model = 8467} +models.body[12150] = { name = "Ebon Haubert" , enl = "ebon haubert", model = 8468} +models.body[12151] = { name = "Furia Haubert" , enl = "furia haubert", model = 8469} +models.body[12152] = { name = "Ebur Haubert" , enl = "ebur haubert", model = 8470} +models.body[12153] = { name = "Ebon Breastplate" , enl = "ebon breastplate", model = 8471} +models.body[12154] = { name = "Furia Breastplate" , enl = "furia breastplate", model = 8472} +models.body[12155] = { name = "Ebur Breastplate" , enl = "ebur breastplate", model = 8473} +models.body[12156] = { name = "Ebon Harness" , enl = "ebon harness", model = 8438} +models.body[12157] = { name = "Furia Harness" , enl = "furia harness", model = 8439} +models.body[12158] = { name = "Ebur Harness" , enl = "ebur harness", model = 8440} +models.body[12159] = { name = "Ebon Jerkin" , enl = "ebon jerkin", model = 8441} +models.body[12160] = { name = "Furia Jerkin" , enl = "furia jerkin", model = 8442} +models.body[12161] = { name = "Ebur Jerkin" , enl = "ebur jerkin", model = 8443} +models.body[12162] = { name = "Shikkoku Togi" , enl = "shikkoku togi", model = 8444} +models.body[12163] = { name = "Shinku Togi" , enl = "shinku togi", model = 8445} +models.body[12164] = { name = "Ginhaku Togi" , enl = "ginhaku togi", model = 8446} +models.body[12165] = { name = "Ebon Vest" , enl = "ebon vest", model = 8447} +models.body[12166] = { name = "Furia Vest" , enl = "furia vest", model = 8448} +models.body[12167] = { name = "Ebur Vest" , enl = "ebur vest", model = 8449} +models.body[12168] = { name = "Ebon Talar" , enl = "ebon talar", model = 8450} +models.body[12169] = { name = "Furia Talar" , enl = "furia talar", model = 8451} +models.body[12170] = { name = "Ebur Talar" , enl = "ebur talar", model = 8452} +models.body[12171] = { name = "Ebon Coat" , enl = "ebon coat", model = 8453} +models.body[12172] = { name = "Furia Coat" , enl = "furia coat", model = 8454} +models.body[12173] = { name = "Ebur Coat" , enl = "ebur coat", model = 8455} +models.body[12174] = { name = "Ebon Bliaut" , enl = "ebon bliaut", model = 8456} +models.body[12175] = { name = "Furia Bliaut" , enl = "furia bliaut", model = 8457} +models.body[12176] = { name = "Ebur Bliaut" , enl = "ebur bliaut", model = 8458} +models.body[12177] = { name = "Ebon Frock" , enl = "ebon frock", model = 8459} +models.body[12178] = { name = "Furia Frock" , enl = "furia frock", model = 8460} +models.body[12179] = { name = "Ebur Frock" , enl = "ebur frock", model = 8461} +models.body[12544] = { name = "Breastplate" , enl = "breastplate", model = 8194} +models.body[12545] = { name = "Mtl. Breastplate" , enl = "mythril breastplate", model = 8221} +models.body[12546] = { name = "Gold Cuirass" , enl = "gold cuirass", model = 8217} +models.body[12547] = { name = "Darksteel Cuirass" , enl = "darksteel cuirass", model = 8214} +models.body[12548] = { name = "Adaman Cuirass" , enl = "adaman cuirass", model = 8247} +models.body[12549] = { name = "Koenig Cuirass" , enl = "koenig cuirass", model = 8287} +models.body[12550] = { name = "Irn.Msk. Cuirass" , enl = "iron musketeer's cuirass", model = 8217} +models.body[12551] = { name = "Judge's Cuirass" , enl = "judge's cuirass", model = 8222} +models.body[12552] = { name = "Chainmail" , enl = "chainmail", model = 8197} +models.body[12553] = { name = "Silver Mail" , enl = "silver mail", model = 8197} +models.body[12554] = { name = "Banded Mail" , enl = "banded mail", model = 8204} +models.body[12555] = { name = "Haubergeon" , enl = "haubergeon", model = 8232} +models.body[12556] = { name = "Hauberk" , enl = "hauberk", model = 8250} +models.body[12557] = { name = "Adaman Hauberk" , enl = "adaman hauberk", model = 8255} +models.body[12558] = { name = "Ryl.Kgt. Chainmail" , enl = "royal knight's chainmail", model = 8205} +models.body[12559] = { name = "Ryl.Sqr. Chainmail" , enl = "royal squire's chainmail", model = 8204} +models.body[12560] = { name = "Scale Mail" , enl = "scale mail", model = 8220} +models.body[12561] = { name = "Brass Scale Mail" , enl = "brass scale mail", model = 8220} +models.body[12562] = { name = "Kirin's Osode" , enl = "kirin's osode", model = 8291} +models.body[12563] = { name = "Coral Scale Mail" , enl = "coral scale mail", model = 8218} +models.body[12564] = { name = "Dragon Mail" , enl = "dragon mail", model = 8252} +models.body[12565] = { name = "Gavial Mail" , enl = "gavial mail", model = 8219} +models.body[12566] = { name = "Ctr. Scale Mail" , enl = "centurion's scale mail", model = 8218} +models.body[12567] = { name = "Steam Scale Mail" , enl = "steam scale mail", model = 8220} +models.body[12568] = { name = "Leather Vest" , enl = "leather vest", model = 8193} +models.body[12569] = { name = "Lizard Jerkin" , enl = "lizard jerkin", model = 8198} +models.body[12570] = { name = "Studded Vest" , enl = "studded vest", model = 8193} +models.body[12571] = { name = "Cuir Bouilli" , enl = "cuir bouilli", model = 8193} +models.body[12572] = { name = "Raptor Jerkin" , enl = "raptor jerkin", model = 8199} +models.body[12573] = { name = "Dusk Jerkin" , enl = "dusk jerkin", model = 8301} +models.body[12574] = { name = "Tiger Jerkin" , enl = "tiger jerkin", model = 8242} +models.body[12575] = { name = "Coeurl Jerkin" , enl = "coeurl jerkin", model = 8246} +models.body[12576] = { name = "Bronze Harness" , enl = "bronze harness", model = 8207} +models.body[12577] = { name = "Brass Harness" , enl = "brass harness", model = 8207} +models.body[12578] = { name = "Padded Armor" , enl = "padded armor", model = 8206} +models.body[12579] = { name = "Scorpion Harness" , enl = "scorpion harness", model = 8226} +models.body[12580] = { name = "Darksteel Harness" , enl = "darksteel harness", model = 8206} +models.body[12581] = { name = "Coral Harness" , enl = "coral harness", model = 8248} +models.body[12582] = { name = "Bone Harness" , enl = "bone harness", model = 8208} +models.body[12583] = { name = "Beetle Harness" , enl = "beetle harness", model = 8208} +models.body[12584] = { name = "Kenpogi" , enl = "kenpogi", model = 8209} +models.body[12585] = { name = "Cotton Dogi" , enl = "cotton dogi", model = 8209} +models.body[12586] = { name = "Soil Gi" , enl = "soil gi", model = 8209} +models.body[12587] = { name = "Hara-Ate" , enl = "hara-ate", model = 8201} +models.body[12588] = { name = "Shinobi Gi" , enl = "shinobi gi", model = 8196} +models.body[12589] = { name = "Scp. Brstplate +1" , enl = "scorpion breastplate +1", model = 8306} +models.body[12590] = { name = "Power Gi" , enl = "power gi", model = 8209} +models.body[12591] = { name = "Doublet +1" , enl = "doublet +1", model = 8213} +models.body[12592] = { name = "Doublet" , enl = "doublet", model = 8213} +models.body[12593] = { name = "Cotton Doublet" , enl = "cotton doublet", model = 8213} +models.body[12594] = { name = "Gambison" , enl = "gambison", model = 8215} +models.body[12595] = { name = "Wool Gambison" , enl = "wool gambison", model = 8215} +models.body[12596] = { name = "Battle Jupon" , enl = "battle jupon", model = 8216} +models.body[12597] = { name = "War Aketon" , enl = "war aketon", model = 8288} +models.body[12598] = { name = "Mrc.Cpt. Doublet" , enl = "mercenary captain's doublet", model = 8213} +models.body[12599] = { name = "Leather Vest +1" , enl = "leather vest +1", model = 8193} +models.body[12600] = { name = "Robe" , enl = "robe", model = 8212} +models.body[12601] = { name = "Linen Robe" , enl = "linen robe", model = 8212} +models.body[12602] = { name = "Wool Robe" , enl = "wool robe", model = 8210} +models.body[12603] = { name = "Velvet Robe" , enl = "velvet robe", model = 8210} +models.body[12604] = { name = "Silk Coat" , enl = "silk coat", model = 8211} +models.body[12605] = { name = "Noble's Tunic" , enl = "noble's tunic", model = 8249} +models.body[12606] = { name = "Tct.Mgc. Coat" , enl = "tactician magician's coat", model = 8211} +models.body[12607] = { name = "Bronze Harness +1" , enl = "bronze harness +1", model = 8207} +models.body[12608] = { name = "Tunic" , enl = "tunic", model = 8195} +models.body[12609] = { name = "Black Tunic" , enl = "black tunic", model = 8195} +models.body[12610] = { name = "Cloak" , enl = "cloak", model = 8202} +models.body[12611] = { name = "White Cloak" , enl = "white cloak", model = 8203} +models.body[12612] = { name = "Silk Cloak" , enl = "silk cloak", model = 8203} +models.body[12613] = { name = "Jurfu Cloak" , enl = "jurfu cloak", model = 8203} +models.body[12614] = { name = "Cmb.Cst. Cloak" , enl = "combat caster's cloak", model = 8202} +models.body[12615] = { name = "Robe +1" , enl = "robe +1", model = 8212} +models.body[12616] = { name = "Tunic +1" , enl = "tunic +1", model = 8195} +models.body[12617] = { name = "War Shinobi Gi" , enl = "war shinobi gi", model = 8234} +models.body[12618] = { name = "Yasha Samue" , enl = "yasha samue", model = 8290} +models.body[12620] = { name = "Grim Cuirass +1" , enl = "grim cuirass +1", model = 8394} +models.body[12621] = { name = "Scp. Breastplate" , enl = "scorpion breastplate", model = 8306} +models.body[12622] = { name = "Juogi +1" , enl = "juogi +1", model = 8409} +models.body[12623] = { name = "Yhel Jacket +1" , enl = "yhel jacket +1", model = 8396} +models.body[12624] = { name = "Cotton Dogi +1" , enl = "cotton dogi +1", model = 8209} +models.body[12625] = { name = "Gambison +1" , enl = "gambison +1", model = 8215} +models.body[12626] = { name = "Linen Robe +1" , enl = "linen robe +1", model = 8212} +models.body[12627] = { name = "Wool Robe +1" , enl = "wool robe +1", model = 8210} +models.body[12628] = { name = "Battle Jupon +1" , enl = "battle jupon +1", model = 8216} +models.body[12629] = { name = "Lgn. Harness" , enl = "legionnaire's harness", model = 8207} +models.body[12630] = { name = "Ryl.Ftm. Vest" , enl = "royal footman's vest", model = 8193} +models.body[12631] = { name = "Hume Tunic" , enl = "hume tunic", model = 8200} +models.body[12632] = { name = "Hume Vest" , enl = "hume vest", model = 8200} +models.body[12633] = { name = "Elvaan Jerkin" , enl = "elvaan jerkin", model = 8200} +models.body[12634] = { name = "Elvaan Bodice" , enl = "elvaan bodice", model = 8200} +models.body[12635] = { name = "Tarutaru Kaftan" , enl = "tarutaru kaftan", model = 8200} +models.body[12636] = { name = "Mithran Separates" , enl = "mithran separates", model = 8200} +models.body[12637] = { name = "Galkan Surcoat" , enl = "galkan surcoat", model = 8200} +models.body[12638] = { name = "Fighter's Lorica" , enl = "fighter's lorica", model = 8256} +models.body[12639] = { name = "Temple Cyclas" , enl = "temple cyclas", model = 8258} +models.body[12640] = { name = "Healer's Briault" , enl = "healer's briault", model = 8260} +models.body[12641] = { name = "Wizard's Coat" , enl = "wizard's coat", model = 8262} +models.body[12642] = { name = "Warlock's Tabard" , enl = "warlock's tabard", model = 8264} +models.body[12643] = { name = "Rogue's Vest" , enl = "rogue's vest", model = 8266} +models.body[12644] = { name = "Gallant Surcoat" , enl = "gallant surcoat", model = 8268} +models.body[12645] = { name = "Chaos Cuirass" , enl = "chaos cuirass", model = 8270} +models.body[12646] = { name = "Beast Jackcoat" , enl = "beast jackcoat", model = 8272} +models.body[12647] = { name = "Choral Jstcorps" , enl = "choral justaucorps", model = 8274} +models.body[12648] = { name = "Hunter's Jerkin" , enl = "hunter's jerkin", model = 8276} +models.body[12649] = { name = "Drachen Mail" , enl = "drachen mail", model = 8282} +models.body[12650] = { name = "Evoker's Doublet" , enl = "evoker's doublet", model = 8284} +models.body[12651] = { name = "White Cloak +1" , enl = "white cloak +1", model = 8203} +models.body[12652] = { name = "Silk Coat +1" , enl = "silk coat +1", model = 8211} +models.body[12653] = { name = "Mercenary's Gi" , enl = "mercenary's gi", model = 8209} +models.body[12654] = { name = "Custom Tunic" , enl = "custom tunic", model = 8223} +models.body[12655] = { name = "Custom Vest" , enl = "custom vest", model = 8223} +models.body[12656] = { name = "Magna Jerkin" , enl = "magna jerkin", model = 8223} +models.body[12657] = { name = "Magna Bodice" , enl = "magna bodice", model = 8223} +models.body[12658] = { name = "Wonder Kaftan" , enl = "wonder kaftan", model = 8223} +models.body[12659] = { name = "Savage Separates" , enl = "savage separates", model = 8223} +models.body[12660] = { name = "Elder's Surcoat" , enl = "elder's surcoat", model = 8223} +models.body[12661] = { name = "Solid Mail" , enl = "solid mail", model = 8220} +models.body[12662] = { name = "Chainmail +1" , enl = "chainmail +1", model = 8197} +models.body[12663] = { name = "Strong Harness" , enl = "strong harness", model = 8206} +models.body[12664] = { name = "Brass Harness +1" , enl = "brass harness +1", model = 8207} +models.body[12665] = { name = "Brs. Scale Mail +1" , enl = "brass scale mail +1", model = 8220} +models.body[12666] = { name = "Silver Mail +1" , enl = "silver mail +1", model = 8197} +models.body[12667] = { name = "Banded Mail +1" , enl = "banded mail +1", model = 8204} +models.body[12668] = { name = "Kenpogi +1" , enl = "kenpogi +1", model = 8209} +models.body[12669] = { name = "Great Doublet" , enl = "great doublet", model = 8213} +models.body[12670] = { name = "Cloak +1" , enl = "cloak +1", model = 8202} +models.body[12671] = { name = "Soil Gi +1" , enl = "soil gi +1", model = 8209} +models.body[13696] = { name = "Wool Gambison +1" , enl = "wool gambison +1", model = 8215} +models.body[13697] = { name = "Fine Jerkin" , enl = "fine jerkin", model = 8198} +models.body[13699] = { name = "Beak Jerkin" , enl = "beak jerkin", model = 8198} +models.body[13703] = { name = "Brigandine" , enl = "brigandine armor", model = 8235} +models.body[13705] = { name = "Ogre Jerkin" , enl = "ogre jerkin", model = 8286} +models.body[13707] = { name = "Strong Vest" , enl = "strong vest", model = 8193} +models.body[13709] = { name = "Cuir Bouilli +1" , enl = "cuir bouilli +1", model = 8193} +models.body[13710] = { name = "Brigandine +1" , enl = "brigandine armor +1", model = 8235} +models.body[13712] = { name = "Carapace Harness" , enl = "carapace harness", model = 8208} +models.body[13714] = { name = "Cpc. Harness +1" , enl = "carapace harness +1", model = 8208} +models.body[13716] = { name = "Bone Harness +1" , enl = "bone harness +1", model = 8208} +models.body[13717] = { name = "Beetle Harness +1" , enl = "beetle harness +1", model = 8208} +models.body[13718] = { name = "Ryl.Ftm. Tunic" , enl = "royal footman's tunic", model = 8195} +models.body[13719] = { name = "Ryl.Sqr. Robe" , enl = "royal squire's robe", model = 8210} +models.body[13720] = { name = "Ryl.Kgt. Cloak" , enl = "royal knight's cloak", model = 8203} +models.body[13721] = { name = "Irn.Msk. Gambison" , enl = "iron musketeer's gambison", model = 8215} +models.body[13722] = { name = "Ryl.Kgt. Aketon" , enl = "royal knight's aketon", model = 8230} +models.body[13723] = { name = "Pyro Robe" , enl = "pyro robe", model = 8212} +models.body[13724] = { name = "Breastplate +1" , enl = "breastplate +1", model = 8194} +models.body[13725] = { name = "Mage's Tunic" , enl = "mage's tunic", model = 8195} +models.body[13726] = { name = "Mage's Robe" , enl = "mage's robe", model = 8210} +models.body[13727] = { name = "Dino Jerkin" , enl = "dino jerkin", model = 8199} +models.body[13728] = { name = "Jujitsu Gi" , enl = "jujitsu gi", model = 8229} +models.body[13729] = { name = "Priest's Robe" , enl = "priest's robe", model = 8212} +models.body[13730] = { name = "Frost Robe" , enl = "frost robe", model = 8210} +models.body[13731] = { name = "Faerie Tunic" , enl = "faerie tunic", model = 8195} +models.body[13732] = { name = "Earth Doublet" , enl = "earth doublet", model = 8235} +models.body[13733] = { name = "Shinobi Gi +1" , enl = "shinobi gi +1", model = 8196} +models.body[13734] = { name = "Scp. Harness +1" , enl = "scorpion harness +1", model = 8226} +models.body[13735] = { name = "Haubergeon +1" , enl = "haubergeon +1", model = 8232} +models.body[13736] = { name = "Stolid Breastplate" , enl = "stolid breastplate", model = 8194} +models.body[13737] = { name = "Mtl. Brstplate +1" , enl = "mythril breastplate +1", model = 8221} +models.body[13738] = { name = "Gilt Cuirass" , enl = "gilt cuirass", model = 8217} +models.body[13739] = { name = "Beak Jerkin +1" , enl = "beak jerkin +1", model = 8198} +models.body[13740] = { name = "Byrnie" , enl = "byrnie", model = 8233} +models.body[13741] = { name = "Byrnie +1" , enl = "byrnie +1", model = 8233} +models.body[13742] = { name = "Aketon" , enl = "aketon", model = 8231} +models.body[13743] = { name = "Aketon +1" , enl = "aketon +1", model = 8231} +models.body[13744] = { name = "Justaucorps" , enl = "justaucorps", model = 8236} +models.body[13745] = { name = "Justaucorps +1" , enl = "justaucorps +1", model = 8236} +models.body[13746] = { name = "Gem Cuirass" , enl = "gem cuirass", model = 8247} +models.body[13747] = { name = "Gavial Mail +1" , enl = "gavial mail +1", model = 8219} +models.body[13748] = { name = "Vermillion Cloak" , enl = "vermillion cloak", model = 8239} +models.body[13749] = { name = "Royal Cloak" , enl = "royal cloak", model = 8239} +models.body[13750] = { name = "Linen Doublet" , enl = "linen doublet", model = 8213} +models.body[13751] = { name = "Linen Doublet +1" , enl = "linen doublet +1", model = 8213} +models.body[13752] = { name = "Wool Doublet" , enl = "wool doublet", model = 8213} +models.body[13753] = { name = "Wool Doublet +1" , enl = "wool doublet +1", model = 8213} +models.body[13754] = { name = "Black Cotehardie" , enl = "black cotehardie", model = 8241} +models.body[13755] = { name = "Flora Cotehardie" , enl = "flora cotehardie", model = 8241} +models.body[13756] = { name = "Dst. Cuirass +1" , enl = "darksteel cuirass +1", model = 8214} +models.body[13757] = { name = "Lord's Cuirass" , enl = "lord's cuirass", model = 8228} +models.body[13758] = { name = "King's Cuirass" , enl = "king's cuirass", model = 8228} +models.body[13759] = { name = "Perle Hauberk" , enl = "perle hauberk", model = 8431} +models.body[13760] = { name = "Aurore Doublet" , enl = "aurore doublet", model = 8433} +models.body[13761] = { name = "Cor. Scale Mail +1" , enl = "coral scale mail +1", model = 8218} +models.body[13762] = { name = "Dragon Mail +1" , enl = "dragon mail +1", model = 8252} +models.body[13763] = { name = "Feral Jerkin" , enl = "feral jerkin", model = 8242} +models.body[13764] = { name = "Torama Jerkin" , enl = "torama jerkin", model = 8246} +models.body[13765] = { name = "Dst. Harness +1" , enl = "darksteel harness +1", model = 8206} +models.body[13766] = { name = "Merman's Harness" , enl = "merman's harness", model = 8248} +models.body[13767] = { name = "Demon's Harness" , enl = "demon's harness", model = 8227} +models.body[13768] = { name = "Dmn. Harness +1" , enl = "demon's harness +1", model = 8227} +models.body[13769] = { name = "Hara-Ate +1" , enl = "hara-ate +1", model = 8201} +models.body[13770] = { name = "War Shinobi Gi +1" , enl = "war shinobi gi +1", model = 8234} +models.body[13771] = { name = "War Aketon +1" , enl = "war aketon +1", model = 8288} +models.body[13772] = { name = "Bloody Aketon" , enl = "bloody aketon", model = 8230} +models.body[13773] = { name = "Carnage Aketon" , enl = "carnage aketon", model = 8230} +models.body[13774] = { name = "Aristocrat's Coat" , enl = "aristocrat's coat", model = 8249} +models.body[13775] = { name = "Blue Cotehardie" , enl = "blue cotehardie", model = 8240} +models.body[13776] = { name = "Blue Cotehard. +1" , enl = "blue cotehardie +1", model = 8240} +models.body[13777] = { name = "Silk Cloak +1" , enl = "silk cloak +1", model = 8203} +models.body[13778] = { name = "Teal Saio" , enl = "teal saio", model = 8432} +models.body[13779] = { name = "Black Cloak" , enl = "black cloak", model = 8238} +models.body[13780] = { name = "Demon's Cloak" , enl = "demon's cloak", model = 8238} +models.body[13781] = { name = "Myochin Domaru" , enl = "myochin domaru", model = 8278} +models.body[13782] = { name = "Ninja Chainmail" , enl = "ninja chainmail", model = 8280} +models.body[13783] = { name = "Iron Scale Mail" , enl = "iron scale mail", model = 8244} +models.body[13784] = { name = "Iron Scale Mail +1" , enl = "iron scale mail +1", model = 8244} +models.body[13785] = { name = "Steel Scale Mail" , enl = "steel scale mail", model = 8245} +models.body[13786] = { name = "Stl. Scale Mail +1" , enl = "steel scale mail +1", model = 8245} +models.body[13787] = { name = "Dalmatica" , enl = "dalmatica", model = 8299} +models.body[13788] = { name = "Dalmatica +1" , enl = "dalmatica +1", model = 8299} +models.body[13789] = { name = "Cpc. Breastplate" , enl = "carapace breastplate", model = 8300} +models.body[13790] = { name = "Cpc. Brstplate +1" , enl = "carapace breastplate +1", model = 8300} +models.body[13791] = { name = "Timarli Jawshan" , enl = "timarli jawshan", model = 8395} +models.body[13792] = { name = "Grim Cuirass" , enl = "grim cuirass", model = 8394} +models.body[13793] = { name = "Hauberk +1" , enl = "hauberk +1", model = 8250} +models.body[13794] = { name = "Heavy Cuirass" , enl = "heavy cuirass", model = 8214} +models.body[13795] = { name = "Arhat's Gi" , enl = "arhat's gi", model = 8251} +models.body[13796] = { name = "Bishop's Robe" , enl = "bishop's robe", model = 8212} +models.body[13797] = { name = "Bishop's Robe +1" , enl = "bishop's robe +1", model = 8212} +models.body[13798] = { name = "Gaia Doublet" , enl = "gaia doublet", model = 8235} +models.body[13799] = { name = "Gaia Doublet +1" , enl = "gaia doublet +1", model = 8235} +models.body[13800] = { name = "Master's Gi" , enl = "master's gi", model = 8229} +models.body[13801] = { name = "Master's Gi +1" , enl = "master's gi +1", model = 8229} +models.body[13802] = { name = "Arhat's Gi +1" , enl = "arhat's gi +1", model = 8251} +models.body[13803] = { name = "Shaman's Cloak" , enl = "shaman's cloak", model = 8203} +models.body[13804] = { name = "Minstrel's Coat" , enl = "minstrel's coat", model = 8211} +models.body[13805] = { name = "Assault Jerkin" , enl = "assault jerkin", model = 8246} +models.body[13806] = { name = "Vgd. Tunica" , enl = "vagabond's tunica", model = 8296} +models.body[13807] = { name = "Nomad's Tunica" , enl = "nomad's tunica", model = 8296} +models.body[13808] = { name = "Fsh. Tunica" , enl = "fisherman's tunica", model = 8294} +models.body[13809] = { name = "Angler's Tunica" , enl = "angler's tunica", model = 8294} +models.body[13810] = { name = "Choc. Jack Coat" , enl = "chocobo jack coat", model = 8295} +models.body[13811] = { name = "Rider's Jack Coat" , enl = "rider's jack coat", model = 8295} +models.body[13812] = { name = "Holy Breastplate" , enl = "holy breastplate", model = 8289} +models.body[13813] = { name = "Divine Breastplate" , enl = "divine breastplate", model = 8289} +models.body[13814] = { name = "Austere Robe" , enl = "austere robe", model = 8307} +models.body[13815] = { name = "Penance Robe" , enl = "penance robe", model = 8307} +models.body[13816] = { name = "Narasimha's Vest" , enl = "narasimha's vest", model = 8225} +models.body[13817] = { name = "Vishnu's Vest" , enl = "vishnu's vest", model = 8225} +models.body[13818] = { name = "Garrison Tunica" , enl = "garrison tunica", model = 8296} +models.body[13819] = { name = "Onoko Yukata" , enl = "onoko yukata", model = 8318} +models.body[13820] = { name = "Omina Yukata" , enl = "omina yukata", model = 8319} +models.body[13821] = { name = "Lord's Yukata" , enl = "lord's yukata", model = 8318} +models.body[13822] = { name = "Lady's Yukata" , enl = "lady's yukata", model = 8319} +models.body[13823] = { name = "Regen Cuirass" , enl = "regen cuirass", model = 8228} +models.body[14284] = { name = "Northern Jerkin" , enl = "northern jerkin", model = 8242} +models.body[14285] = { name = "Tundra Jerkin" , enl = "tundra jerkin", model = 8242} +models.body[14336] = { name = "Juogi" , enl = "juogi", model = 8409} +models.body[14337] = { name = "Loki's Kaftan" , enl = "loki's kaftan", model = 8426} +models.body[14338] = { name = "Bastokan Harness" , enl = "bastokan harness", model = 8207} +models.body[14339] = { name = "Republic Harness" , enl = "republic harness", model = 8207} +models.body[14340] = { name = "Ryl.Sqr. Chnml. +1" , enl = "royal squire's chainmail +1", model = 8204} +models.body[14341] = { name = "Ryl.Sqr. Chnml. +2" , enl = "royal squire's chainmail +2", model = 8204} +models.body[14342] = { name = "I.M. Cuirass +1" , enl = "iron musketeer's cuirass +1", model = 8217} +models.body[14343] = { name = "I.M. Cuirass +2" , enl = "iron musketeer's cuirass +2", model = 8217} +models.body[14344] = { name = "San d'Orian Vest" , enl = "san d'orian vest", model = 8193} +models.body[14345] = { name = "Kingdom Vest" , enl = "kingdom vest", model = 8193} +models.body[14346] = { name = "Bas. Scale Mail" , enl = "bastokan scale mail", model = 8218} +models.body[14347] = { name = "Rep. Scale Mail" , enl = "republic scale mail", model = 8218} +models.body[14348] = { name = "San d'Orian Tunic" , enl = "san d'orian tunic", model = 8195} +models.body[14349] = { name = "Kingdom Tunic" , enl = "kingdom tunic", model = 8195} +models.body[14350] = { name = "Windurstian Gi" , enl = "windurstian gi", model = 8209} +models.body[14351] = { name = "Federation Gi" , enl = "federation gi", model = 8209} +models.body[14352] = { name = "Win. Doublet" , enl = "windurstian doublet", model = 8213} +models.body[14353] = { name = "Fed. Doublet" , enl = "federation doublet", model = 8213} +models.body[14354] = { name = "C.C. Cloak +1" , enl = "combat caster's cloak +1", model = 8202} +models.body[14355] = { name = "C.C. Cloak +2" , enl = "combat caster's cloak +2", model = 8202} +models.body[14356] = { name = "Irn.Msk.Gmbsn. +1" , enl = "iron musketeer's gambison +1", model = 8215} +models.body[14357] = { name = "Irn.Msk.Gmbsn. +2" , enl = "iron musketeer's gambison +2", model = 8215} +models.body[14358] = { name = "Ryl.Sqr. Robe +1" , enl = "royal squire's robe +1", model = 8210} +models.body[14359] = { name = "Ryl.Sqr. Robe +2" , enl = "royal squire's robe +2", model = 8210} +models.body[14360] = { name = "R.K. Cloak +1" , enl = "royal knight's cloak +1", model = 8203} +models.body[14361] = { name = "R.K. Cloak +2" , enl = "royal knight's cloak +2", model = 8203} +models.body[14362] = { name = "T.M. Coat +1" , enl = "tactician magician's coat +1", model = 8211} +models.body[14363] = { name = "T.M. Coat +2" , enl = "tactician magician's coat +2", model = 8211} +models.body[14364] = { name = "Yhel Jacket" , enl = "yhel jacket", model = 8396} +models.body[14365] = { name = "Augur's Jaseran" , enl = "augur's jaseran", model = 8415} +models.body[14366] = { name = "Ogre Jerkin +1" , enl = "ogre jerkin +1", model = 8286} +models.body[14367] = { name = "Crm. Scale Mail" , enl = "crimson scale mail", model = 8254} +models.body[14368] = { name = "Blood Scale Mail" , enl = "blood scale mail", model = 8254} +models.body[14369] = { name = "Arcane Robe" , enl = "arcane robe", model = 8341} +models.body[14370] = { name = "Kaiser Cuirass" , enl = "kaiser cuirass", model = 8287} +models.body[14371] = { name = "Armada Hauberk" , enl = "armada hauberk", model = 8255} +models.body[14372] = { name = "Cardinal Vest" , enl = "cardinal vest", model = 8225} +models.body[14373] = { name = "Bachelor Vest" , enl = "bachelor vest", model = 8225} +models.body[14374] = { name = "Field Tunica" , enl = "field tunica", model = 8297} +models.body[14375] = { name = "Worker Tunica" , enl = "worker tunica", model = 8297} +models.body[14376] = { name = "Rasetsu Samue" , enl = "rasetsu samue", model = 8252} +models.body[14377] = { name = "Rasetsu Samue +1" , enl = "rasetsu samue +1", model = 8252} +models.body[14378] = { name = "Hecatomb Harness" , enl = "hecatomb harness", model = 8292} +models.body[14379] = { name = "Hct. Harness +1" , enl = "hecatomb harness +1", model = 8292} +models.body[14380] = { name = "Errant Hpl." , enl = "errant houppelande", model = 8293} +models.body[14381] = { name = "Mahatma Hpl." , enl = "mahatma houppelande", model = 8293} +models.body[14382] = { name = "Plastron" , enl = "plastron", model = 8343} +models.body[14383] = { name = "Plastron +1" , enl = "plastron +1", model = 8343} +models.body[14384] = { name = "Opaline Dress" , enl = "opaline dress", model = 8308} +models.body[14385] = { name = "Ceremonial Dress" , enl = "ceremonial dress", model = 8308} +models.body[14386] = { name = "Wedding Dress" , enl = "wedding dress", model = 8308} +models.body[14387] = { name = "Shura Togi" , enl = "shura togi", model = 8298} +models.body[14388] = { name = "Shura Togi +1" , enl = "shura togi +1", model = 8298} +models.body[14389] = { name = "Dragon Harness" , enl = "dragon harness", model = 8302} +models.body[14390] = { name = "Dragon Harness +1" , enl = "dragon harness +1", model = 8302} +models.body[14391] = { name = "Dusk Jerkin +1" , enl = "dusk jerkin +1", model = 8301} +models.body[14392] = { name = "Carpenter's Apron" , enl = "carpenter's apron", model = 8309} +models.body[14393] = { name = "Blacksmith's Apn." , enl = "blacksmith's apron", model = 8310} +models.body[14394] = { name = "Goldsmith's Apron" , enl = "goldsmith's apron", model = 8317} +models.body[14395] = { name = "Weaver's Apron" , enl = "weaver's apron", model = 8311} +models.body[14396] = { name = "Tanner's Apron" , enl = "tanner's apron", model = 8315} +models.body[14397] = { name = "Boneworker's Apn." , enl = "boneworker's apron", model = 8313} +models.body[14398] = { name = "Alchemist's Apron" , enl = "alchemist's apron", model = 8312} +models.body[14399] = { name = "Culinarian's Apron" , enl = "culinarian's apron", model = 8316} +models.body[14400] = { name = "Fisherman's Apron" , enl = "fisherman's apron", model = 8314} +models.body[14401] = { name = "Duende Cotehardie" , enl = "duende cotehardie", model = 8240} +models.body[14402] = { name = "Nokizaru Gi" , enl = "nokizaru gi", model = 8196} +models.body[14403] = { name = "Rapparee Harness" , enl = "rapparee harness", model = 8227} +models.body[14404] = { name = "Shm. Hara-Ate" , enl = "shinimusha hara-ate", model = 8201} +models.body[14405] = { name = "Wyvern Mail" , enl = "wyvern mail", model = 8220} +models.body[14406] = { name = "Shikaree Aketon" , enl = "shikaree aketon", model = 8288} +models.body[14407] = { name = "Cerise Doublet" , enl = "cerise doublet", model = 8324} +models.body[14408] = { name = "Glamor Jupon" , enl = "glamor jupon", model = 8216} +models.body[14409] = { name = "Gloom Breastplate" , enl = "gloom breastplate", model = 8221} +models.body[14410] = { name = "Nimbus Doublet" , enl = "nimbus doublet", model = 8235} +models.body[14411] = { name = "Aikido Gi" , enl = "aikido gi", model = 8229} +models.body[14412] = { name = "Parade Cuirass" , enl = "parade cuirass", model = 8228} +models.body[14413] = { name = "Gaudy Harness" , enl = "gaudy harness", model = 8248} +models.body[14414] = { name = "Sha'ir Manteel" , enl = "sha'ir manteel", model = 8327} +models.body[14415] = { name = "Sheikh Manteel" , enl = "sheikh manteel", model = 8327} +models.body[14416] = { name = "Barone Corazza" , enl = "barone corazza", model = 8328} +models.body[14417] = { name = "Conte Corazza" , enl = "conte corazza", model = 8328} +models.body[14418] = { name = "Bison Jacket" , enl = "bison jacket", model = 8326} +models.body[14419] = { name = "Brave's Jacket" , enl = "brave's jacket", model = 8326} +models.body[14420] = { name = "Igqira Weskit" , enl = "igqira weskit", model = 8329} +models.body[14421] = { name = "Genie Weskit" , enl = "genie weskit", model = 8329} +models.body[14422] = { name = "Noct Doublet" , enl = "noct doublet", model = 8325} +models.body[14423] = { name = "Mist Tunic" , enl = "mist tunic", model = 8322} +models.body[14424] = { name = "Seer's Tunic" , enl = "seer's tunic", model = 8323} +models.body[14425] = { name = "Garish Tunic" , enl = "garish tunic", model = 8322} +models.body[14426] = { name = "Shade Harness" , enl = "shade harness", model = 8321} +models.body[14427] = { name = "Seer's Tunic +1" , enl = "seer's tunic +1", model = 8323} +models.body[14428] = { name = "Kingdom Aketon" , enl = "kingdom aketon", model = 8303} +models.body[14429] = { name = "Republic Aketon" , enl = "republic aketon", model = 8305} +models.body[14430] = { name = "Federation Aketon" , enl = "federation aketon", model = 8304} +models.body[14431] = { name = "Eisenbrust" , enl = "eisenbrust", model = 8330} +models.body[14432] = { name = "Rubious Tunic" , enl = "rubious tunic", model = 8322} +models.body[14433] = { name = "Shade Harness +1" , enl = "shade harness +1", model = 8321} +models.body[14434] = { name = "Noct Doublet +1" , enl = "noct doublet +1", model = 8325} +models.body[14435] = { name = "Kampfbrust" , enl = "kampfbrust", model = 8330} +models.body[14436] = { name = "Blessed Briault" , enl = "blessed briault", model = 8334} +models.body[14437] = { name = "Hachiman Domaru" , enl = "hachiman domaru", model = 8333} +models.body[14438] = { name = "Blessed Briault +1" , enl = "blessed briault +1", model = 8334} +models.body[14439] = { name = "Hmn. Domaru +1" , enl = "hachiman domaru +1", model = 8333} +models.body[14440] = { name = "Chasuble" , enl = "chasuble", model = 8335} +models.body[14441] = { name = "Chasuble +1" , enl = "chasuble +1", model = 8335} +models.body[14442] = { name = "Yasha Samue +1" , enl = "yasha samue +1", model = 8290} +models.body[14443] = { name = "Vampire Cloak" , enl = "vampire cloak", model = 8238} +models.body[14444] = { name = "Alumine Haubert" , enl = "alumine haubert", model = 8342} +models.body[14445] = { name = "Luisant Haubert" , enl = "luisant haubert", model = 8342} +models.body[14446] = { name = "Trader's Saio" , enl = "trader's saio", model = 8341} +models.body[14447] = { name = "Baron's Saio" , enl = "baron's saio", model = 8341} +models.body[14448] = { name = "Unicorn Harness" , enl = "unicorn harness", model = 8340} +models.body[14449] = { name = "Ucn. Harness +1" , enl = "unicorn harness +1", model = 8340} +models.body[14450] = { name = "Hume Gilet" , enl = "hume gilet", model = 8349} +models.body[14451] = { name = "Hume Top" , enl = "hume top", model = 8349} +models.body[14452] = { name = "Elvaan Gilet" , enl = "elvaan gilet", model = 8349} +models.body[14453] = { name = "Elvaan Top" , enl = "elvaan top", model = 8349} +models.body[14454] = { name = "Tarutaru Maillot" , enl = "tarutaru maillot", model = 8349} +models.body[14455] = { name = "Mithra Top" , enl = "mithra top", model = 8349} +models.body[14456] = { name = "Galka Gilet" , enl = "galka gilet", model = 8349} +models.body[14457] = { name = "Hume Gilet +1" , enl = "hume gilet +1", model = 8349} +models.body[14458] = { name = "Hume Top +1" , enl = "hume top +1", model = 8349} +models.body[14459] = { name = "Elvaan Gilet +1" , enl = "elvaan gilet +1", model = 8349} +models.body[14460] = { name = "Elvaan Top +1" , enl = "elvaan top +1", model = 8349} +models.body[14461] = { name = "Taru. Maillot +1" , enl = "tarutaru maillot +1", model = 8349} +models.body[14462] = { name = "Mithra Top +1" , enl = "mithra top +1", model = 8349} +models.body[14463] = { name = "Galka Gilet +1" , enl = "galka gilet +1", model = 8349} +models.body[14464] = { name = "Trailer's Tunica" , enl = "trailer's tunica", model = 8297} +models.body[14465] = { name = "Nanban Kariginu" , enl = "nanban kariginu", model = 8294} +models.body[14466] = { name = "Fomor Tunic" , enl = "fomor tunic", model = 8195} +models.body[14467] = { name = "Archer's Jupon" , enl = "archer's jupon", model = 8216} +models.body[14468] = { name = "Yinyang Robe" , enl = "yinyang robe", model = 8229} +models.body[14469] = { name = "Reverend Mail" , enl = "reverend mail", model = 8232} +models.body[14470] = { name = "Assault Brstplate" , enl = "assault breastplate", model = 8224} +models.body[14471] = { name = "Tarutaru Top" , enl = "tarutaru top", model = 8349} +models.body[14472] = { name = "Tarutaru Top +1" , enl = "tarutaru top +1", model = 8349} +models.body[14473] = { name = "Ftr. Lorica +1" , enl = "fighter's lorica +1", model = 8256} +models.body[14474] = { name = "Tpl. Cyclas +1" , enl = "temple cyclas +1", model = 8258} +models.body[14475] = { name = "Hlr. Briault +1" , enl = "healer's briault +1", model = 8260} +models.body[14476] = { name = "Wzd. Coat +1" , enl = "wizard's coat +1", model = 8262} +models.body[14477] = { name = "Wlk. Tabard +1" , enl = "warlock's tabard +1", model = 8264} +models.body[14478] = { name = "Rog. Vest +1" , enl = "rogue's vest +1", model = 8266} +models.body[14479] = { name = "Glt. Surcoat +1" , enl = "gallant surcoat +1", model = 8268} +models.body[14480] = { name = "Chs. Cuirass +1" , enl = "chaos cuirass +1", model = 8270} +models.body[14481] = { name = "Bst. Jackcoat +1" , enl = "beast jackcoat +1", model = 8272} +models.body[14482] = { name = "Chl. Jstcorps +1" , enl = "choral justaucorps +1", model = 8274} +models.body[14483] = { name = "Htr. Jerkin +1" , enl = "hunter's jerkin +1", model = 8276} +models.body[14484] = { name = "Myn. Domaru +1" , enl = "myochin domaru +1", model = 8278} +models.body[14485] = { name = "Nin. Chainmail +1" , enl = "ninja chainmail +1", model = 8280} +models.body[14486] = { name = "Drn. Mail +1" , enl = "drachen mail +1", model = 8282} +models.body[14487] = { name = "Evk. Doublet +1" , enl = "evoker's doublet +1", model = 8284} +models.body[14488] = { name = "Homam Corazza" , enl = "homam corazza", model = 8351} +models.body[14489] = { name = "Nashira Manteel" , enl = "nashira manteel", model = 8352} +models.body[14490] = { name = "Mana Tunic" , enl = "mana tunic", model = 8195} +models.body[14491] = { name = "Mana Cloak" , enl = "mana cloak", model = 8202} +models.body[14492] = { name = "High Mana Cloak" , enl = "high mana cloak", model = 8203} +models.body[14493] = { name = "Healing Vest" , enl = "healing vest", model = 8193} +models.body[14494] = { name = "Healing Mail" , enl = "healing mail", model = 8252} +models.body[14495] = { name = "Healing Harness" , enl = "healing harness", model = 8208} +models.body[14496] = { name = "Healing Jstcorps" , enl = "healing justaucorps", model = 8236} +models.body[14497] = { name = "High Heal. Harness" , enl = "high healing harness", model = 8208} +models.body[14498] = { name = "Crow Jupon" , enl = "crow jupon", model = 8354} +models.body[14499] = { name = "Raven Jupon" , enl = "raven jupon", model = 8354} +models.body[14500] = { name = "War. Lorica +1" , enl = "warrior's lorica +1", model = 8257} +models.body[14501] = { name = "Mel. Cyclas +1" , enl = "melee cyclas +1", model = 8259} +models.body[14502] = { name = "Clr. Briault +1" , enl = "cleric's briault +1", model = 8261} +models.body[14503] = { name = "Src. Coat +1" , enl = "sorcerer's coat +1", model = 8263} +models.body[14504] = { name = "Dls. Tabard +1" , enl = "duelist's tabard +1", model = 8265} +models.body[14505] = { name = "Asn. Vest +1" , enl = "assassin's vest +1", model = 8267} +models.body[14506] = { name = "Vlr. Surcoat +1" , enl = "valor surcoat +1", model = 8269} +models.body[14507] = { name = "Abs. Cuirass +1" , enl = "abyss cuirass +1", model = 8271} +models.body[14508] = { name = "Mst. Jackcoat +1" , enl = "monster jackcoat +1", model = 8273} +models.body[14509] = { name = "Brd. Jstcorps +1" , enl = "bard's justaucorps +1", model = 8275} +models.body[14510] = { name = "Sct. Jerkin +1" , enl = "scout's jerkin +1", model = 8277} +models.body[14511] = { name = "Sao. Domaru +1" , enl = "saotome domaru +1", model = 8279} +models.body[14512] = { name = "Kog. Chainmail +1" , enl = "koga chainmail +1", model = 8281} +models.body[14513] = { name = "Wym. Mail +1" , enl = "wyrm mail +1", model = 8283} +models.body[14514] = { name = "Smn. Doublet +1" , enl = "summoner's doublet +1", model = 8285} +models.body[14515] = { name = "Hydra Doublet" , enl = "hydra doublet", model = 8324} +models.body[14516] = { name = "Hydra Harness" , enl = "hydra harness", model = 8320} +models.body[14517] = { name = "Hydra Haubert" , enl = "hydra haubert", model = 8356} +models.body[14518] = { name = "Hydra Jupon" , enl = "hydra jupon", model = 8355} +models.body[14519] = { name = "Dream Robe" , enl = "dream robe", model = 8332} +models.body[14520] = { name = "Dream Robe +1" , enl = "dream robe +1", model = 8332} +models.body[14521] = { name = "Magus Jubbah" , enl = "magus jubbah", model = 8357} +models.body[14522] = { name = "Corsair's Frac" , enl = "corsair's frac", model = 8359} +models.body[14523] = { name = "Pup. Tobe" , enl = "puppetry tobe", model = 8361} +models.body[14524] = { name = "Sipahi Jawshan" , enl = "sipahi jawshan", model = 8363} +models.body[14525] = { name = "Amir Korazin" , enl = "amir korazin", model = 8366} +models.body[14526] = { name = "Jaridah Peti" , enl = "jaridah peti", model = 8364} +models.body[14527] = { name = "Yigit Gomlek" , enl = "yigit gomlek", model = 8368} +models.body[14528] = { name = "Abtal Jawshan" , enl = "abtal jawshan", model = 8363} +models.body[14529] = { name = "Akinji Peti" , enl = "akinji peti", model = 8364} +models.body[14530] = { name = "Pln. Khazagand" , enl = "pahluwan khazagand", model = 8367} +models.body[14531] = { name = "Bannaret Mail" , enl = "bannaret mail", model = 8197} +models.body[14532] = { name = "Otoko Yukata" , enl = "otoko yukata", model = 8318} +models.body[14533] = { name = "Onago Yukata" , enl = "onago yukata", model = 8319} +models.body[14534] = { name = "Otokogimi Yukata" , enl = "otokogimi yukata", model = 8318} +models.body[14535] = { name = "Onnagimi Yukata" , enl = "onnagimi yukata", model = 8319} +models.body[14536] = { name = "Arakan Samue" , enl = "arakan samue", model = 8251} +models.body[14537] = { name = "Hydra Mail" , enl = "hydra mail", model = 8219} +models.body[14538] = { name = "Hydra Mail +1" , enl = "hydra mail +1", model = 8219} +models.body[14539] = { name = "Kyudogi" , enl = "kyudogi", model = 8372} +models.body[14540] = { name = "Kyudogi +1" , enl = "kyudogi +1", model = 8372} +models.body[14541] = { name = "Taikyoku Kenpogi" , enl = "taikyoku kenpogi", model = 8209} +models.body[14542] = { name = "Silken Coat" , enl = "silken coat", model = 8211} +models.body[14543] = { name = "Magi Coat" , enl = "magi coat", model = 8211} +models.body[14544] = { name = "Corselet" , enl = "corselet", model = 8371} +models.body[14545] = { name = "Corselet +1" , enl = "corselet +1", model = 8371} +models.body[14546] = { name = "Ares' Cuirass" , enl = "ares' cuirass", model = 8374} +models.body[14547] = { name = "Enyo's Brstplate" , enl = "enyo's breastplate", model = 8294} +models.body[14548] = { name = "Phobos's Cuirass" , enl = "phobos's cuirass", model = 8217} +models.body[14549] = { name = "Deimos's Cuirass" , enl = "deimos's cuirass", model = 8214} +models.body[14550] = { name = "Skadi's Cuirie" , enl = "skadi's cuirie", model = 8375} +models.body[14551] = { name = "Njord's Jerkin" , enl = "njord's jerkin", model = 8198} +models.body[14552] = { name = "Freyr's Jerkin" , enl = "freyr's jerkin", model = 8199} +models.body[14553] = { name = "Freya's Jerkin" , enl = "freya's jerkin", model = 8242} +models.body[14554] = { name = "Usukane Haramaki" , enl = "usukane haramaki", model = 8376} +models.body[14555] = { name = "Hoshikazu Gi" , enl = "hoshikazu gi", model = 8209} +models.body[14556] = { name = "Tsukikazu Togi" , enl = "tsukikazu togi", model = 8196} +models.body[14557] = { name = "Hikazu Hara-Ate" , enl = "hikazu hara-ate", model = 8201} +models.body[14558] = { name = "Marduk's Jubbah" , enl = "marduk's jubbah", model = 8377} +models.body[14559] = { name = "Anu's Doublet" , enl = "anu's doublet", model = 8213} +models.body[14560] = { name = "Ea's Doublet" , enl = "ea's doublet", model = 8325} +models.body[14561] = { name = "Enlil's Gambison" , enl = "enlil's gambison", model = 8215} +models.body[14562] = { name = "Morrigan's Robe" , enl = "morrigan's robe", model = 8378} +models.body[14563] = { name = "Nemain's Robe" , enl = "nemain's robe", model = 8212} +models.body[14564] = { name = "Bodb's Robe" , enl = "bodb's robe", model = 8210} +models.body[14565] = { name = "Macha's Coat" , enl = "macha's coat", model = 8211} +models.body[14566] = { name = "Khimaira Jacket" , enl = "khimaira jacket", model = 8326} +models.body[14567] = { name = "Stout Jacket" , enl = "stout jacket", model = 8326} +models.body[14568] = { name = "Askar Korazin" , enl = "askar korazin", model = 8387} +models.body[14569] = { name = "Denali Jacket" , enl = "denali jacket", model = 8388} +models.body[14570] = { name = "Goliard Saio" , enl = "goliard saio", model = 8389} +models.body[14571] = { name = "Tabin Jupon" , enl = "tabin jupon", model = 8216} +models.body[14572] = { name = "Tabin Jupon +1" , enl = "tabin jupon +1", model = 8216} +models.body[14573] = { name = "Shadow Brstplate" , enl = "shadow breastplate", model = 8392} +models.body[14574] = { name = "Valk. Breastplate" , enl = "valkyrie's breastplate", model = 8392} +models.body[14575] = { name = "Shadow Coat" , enl = "shadow coat", model = 8393} +models.body[14576] = { name = "Valkyrie's Coat" , enl = "valkyrie's coat", model = 8393} +models.body[14577] = { name = "Valhalla Brstplate" , enl = "valhalla breastplate", model = 8391} +models.body[14578] = { name = "Dancer's Casaque" , enl = "dancer's casaque", model = 8402} +models.body[14579] = { name = "Dancer's Casaque" , enl = "dancer's casaque", model = 8403} +models.body[14580] = { name = "Scholar's Gown" , enl = "scholar's gown", model = 8406} +models.body[14581] = { name = "I.R. Chainmail" , enl = "iron ram chainmail", model = 8204} +models.body[14582] = { name = "Fourth Cuirass" , enl = "fourth division cuirass", model = 8217} +models.body[14583] = { name = "Cobra Coat" , enl = "cobra unit coat", model = 8211} +models.body[14584] = { name = "I.R. Jack Coat" , enl = "iron ram jack coat", model = 8295} +models.body[14585] = { name = "Fourth Tunica" , enl = "fourth division tunica", model = 8294} +models.body[14586] = { name = "Cobra Tunica" , enl = "cobra unit tunica", model = 8297} +models.body[14587] = { name = "Pilgrim Tunica" , enl = "pilgrim tunica", model = 8296} +models.body[14588] = { name = "Iron Ram Hauberk" , enl = "iron ram hauberk", model = 8413} +models.body[14589] = { name = "Fourth Brunne" , enl = "fourth division brunne", model = 8414} +models.body[14590] = { name = "Cobra Harness" , enl = "cobra unit harness", model = 8411} +models.body[14591] = { name = "Cobra Robe" , enl = "cobra unit robe", model = 8412} +models.body[15087] = { name = "Warrior's Lorica" , enl = "warrior's lorica", model = 8257} +models.body[15088] = { name = "Melee Cyclas" , enl = "melee cyclas", model = 8259} +models.body[15089] = { name = "Cleric's Briault" , enl = "cleric's briault", model = 8261} +models.body[15090] = { name = "Sorcerer's Coat" , enl = "sorcerer's coat", model = 8263} +models.body[15091] = { name = "Duelist's Tabard" , enl = "duelist's tabard", model = 8265} +models.body[15092] = { name = "Assassin's Vest" , enl = "assassin's vest", model = 8267} +models.body[15093] = { name = "Valor Surcoat" , enl = "valor surcoat", model = 8269} +models.body[15094] = { name = "Abyss Cuirass" , enl = "abyss cuirass", model = 8271} +models.body[15095] = { name = "Monster Jackcoat" , enl = "monster jackcoat", model = 8273} +models.body[15096] = { name = "Bard's Jstcorps" , enl = "bard's justaucorps", model = 8275} +models.body[15097] = { name = "Scout's Jerkin" , enl = "scout's jerkin", model = 8277} +models.body[15098] = { name = "Saotome Domaru" , enl = "saotome domaru", model = 8279} +models.body[15099] = { name = "Koga Chainmail" , enl = "koga chainmail", model = 8281} +models.body[15100] = { name = "Wyrm Mail" , enl = "wyrm mail", model = 8283} +models.body[15101] = { name = "Summoner's Dblt." , enl = "summoner's doublet", model = 8285} +models.body[27788] = { name = "Ares' Cuirass +1" , enl = "ares' cuirass +1", model = 8374} +models.body[27789] = { name = "Skadi's Cuirie +1" , enl = "skadi's cuirie +1", model = 8375} +models.body[27790] = { name = "Usk. Haramaki +1" , enl = "usukane haramaki +1", model = 8376} +models.body[27791] = { name = "Marduk's Jubbah +1" , enl = "marduk's jubbah +1", model = 8377} +models.body[27792] = { name = "Morrigan's Robe +1" , enl = "morrigan's robe +1", model = 8378} +models.body[27793] = { name = "Ker's Cuirass" , enl = "ker's cuirass", model = 8374} +models.body[27794] = { name = "Sigyn's Cuirie" , enl = "sigyn's cuirie", model = 8375} +models.body[27795] = { name = "Omodaka Haramaki" , enl = "omodaka haramaki", model = 8376} +models.body[27796] = { name = "Nabu's Jubbah" , enl = "nabu's jubbah", model = 8377} +models.body[27797] = { name = "Fea's Robe" , enl = "fea's robe", model = 8378} +models.body[27798] = { name = "Ate's Cuirass" , enl = "ate's cuirass", model = 8214} +models.body[27799] = { name = "Idi's Jerkin" , enl = "idi's jerkin", model = 8242} +models.body[27800] = { name = "Genta Hara-Ate" , enl = "genta hara-ate", model = 8201} +models.body[27801] = { name = "Namru's Jubbah" , enl = "namru's jubbah", model = 8215} +models.body[27802] = { name = "Neit's Coat" , enl = "neit's coat", model = 8211} +models.body[27803] = { name = "Rustic Maillot" , enl = "rustic maillot", model = 8550} +models.body[27804] = { name = "Shoal Maillot" , enl = "shoal maillot", model = 8551} +models.body[27805] = { name = "Rustic Maillot +1" , enl = "rustic maillot +1", model = 8550} +models.body[27806] = { name = "Shoal Maillot +1" , enl = "shoal maillot +1", model = 8551} +models.body[27807] = { name = "Pummeler's Lorica" , enl = "pummeler's lorica" , model = 8256 } +models.body[27808] = { name = "Anchorite's Cyclas" , enl = "anchorite's cyclas" , model = 8258 } +models.body[27809] = { name = "Theo. Briault" , enl = "theophany briault" , model = 8260 } +models.body[27810] = { name = "Spaekona's Coat" , enl = "spaekona's coat" , model = 8262 } +models.body[27811] = { name = "Atrophy Tabard" , enl = "atrophy tabard" , model = 8264 } +models.body[27812] = { name = "Pillager's Vest" , enl = "pillager's vest" , model = 8266 } +models.body[27813] = { name = "Reverence Surcoat" , enl = "reverence surcoat" , model = 8268 } +models.body[27814] = { name = "Ignominy Cuirass" , enl = "ignominy cuirass" , model = 8270 } +models.body[27815] = { name = "Totemic Jackcoat" , enl = "totemic jackcoat" , model = 8272 } +models.body[27816] = { name = "Brioso Justaucorps" , enl = "brioso justaucorps" , model = 8274 } +models.body[27817] = { name = "Orion Jerkin" , enl = "orion jerkin" , model = 8276 } +models.body[27818] = { name = "Wakido Domaru" , enl = "wakido domaru" , model = 8278 } +models.body[27819] = { name = "Hachi. Chainmail" , enl = "hachiya chainmail" , model = 8280 } +models.body[27820] = { name = "Vishap Mail" , enl = "suit of vishap mail" , model = 8282 } +models.body[27821] = { name = "Convo. Doublet" , enl = "convoker's doublet" , model = 8284 } +models.body[27822] = { name = "Assim. Jubbah" , enl = "assimilator's jubbah" , model = 8357 } +models.body[27823] = { name = "Laksamana Frac" , enl = "laksmana's frac" , model = 8359 } +models.body[27824] = { name = "Foire Tobe" , enl = "foire tobe" , model = 8361 } +models.body[27825] = { name = "Maxixi Casaque" , enl = "maxixi casaque" , model = 8402 } +models.body[27826] = { name = "Maxixi Casaque" , enl = "maxixi casaque" , model = 8403 } +models.body[27827] = { name = "Academic's Gown" , enl = "academic's gown" , model = 8406 } +models.body[27828] = { name = "Pumm. Lorica +1" , enl = "pummeler's lorica +1" , model = 8256 } +models.body[27829] = { name = "Anch. Cyclas +1" , enl = "anchorite's cyclas +1" , model = 8258 } +models.body[27830] = { name = "Theo. Briault +1" , enl = "theophany briault +1" , model = 8260 } +models.body[27831] = { name = "Spae. Coat +1" , enl = "spaekona's coat +1" , model = 8262 } +models.body[27832] = { name = "Atrophy Tabard +1" , enl = "atrophy tabard +1" , model = 8264 } +models.body[27833] = { name = "Pillager's Vest +1" , enl = "pillager's vest +1" , model = 8266 } +models.body[27834] = { name = "Rev. Surcoat +1" , enl = "reverence surcoat +1" , model = 8268 } +models.body[27835] = { name = "Igno. Cuirass +1" , enl = "ignominy cuirass +1" , model = 8270 } +models.body[27836] = { name = "Tot. Jackcoat +1" , enl = "totemic jackcoat +1" , model = 8272 } +models.body[27837] = { name = "Brioso Just. +1" , enl = "brioso justaucorps +1" , model = 8274 } +models.body[27838] = { name = "Orion Jerkin +1" , enl = "orion jerkin +1" , model = 8276 } +models.body[27839] = { name = "Wakido Domaru +1" , enl = "wakido domaru +1" , model = 8278 } +models.body[27840] = { name = "Hachi. Chain. +1" , enl = "hachiya chainmail +1" , model = 8280 } +models.body[27841] = { name = "Vishap Mail +1" , enl = "vishap mail +1" , model = 8282 } +models.body[27842] = { name = "Con. Doublet +1" , enl = "convoker's doublet +1" , model = 8284 } +models.body[27843] = { name = "Assim. Jubbah +1" , enl = "assimilator's jubbah +1" , model = 8357 } +models.body[27844] = { name = "Lak. Frac +1" , enl = "laksamana's frac +1" , model = 8359 } +models.body[27845] = { name = "Foire Tobe +1" , enl = "foire tobe +1" , model = 8361 } +models.body[27846] = { name = "Maxixi Casaque +1" , enl = "maxixi casaque +1" , model = 8402 } +models.body[27847] = { name = "Maxixi Casaque +1" , enl = "maxixi casaque +1" , model = 8403 } +models.body[27848] = { name = "Acad. Gown +1" , enl = "academic's gown +1" , model = 8406 } +models.body[27849] = { name = "Geo. Tunic +1" , enl = "geomancy tunic +1" , model = 8500 } +models.body[27850] = { name = "Runeist Coat +1" , enl = "runeist coat +1" , model = 8530 } +models.body[27879] = { name = "Overalls" , enl = "overalls" , model = 8558 } +models.body[27881] = { name = "Outrider Mail" , enl = "outrider mail" , model = 8197 } +models.body[27882] = { name = "Espial Gambison" , enl = "espial gambison" , model = 8215 } +models.body[27883] = { name = "Wayfarer Robe" , enl = "wayfarer robe" , model = 8212 } +models.body[27884] = { name = "Temachtiani Shirt" , enl = "temachtiani shirt" , model = 8294 } +models.body[27888] = { name = "Kyujutsugi" , enl = "kyujutsugi" , model = 8409 } +models.body[27895] = { name = "Karieyh Haubert +1" , enl = "Karieyh haubert +1" , model = 8534 } +models.body[27896] = { name = "Thur. Tabard +1" , enl = "Thurandaut tabard +1" , model = 8535 } +models.body[27897] = { name = "Orvail Robe +1" , enl = "Orvail robe +1" , model = 8536 } +models.body[27898] = { name = "Alliance Shirt +1" , enl = "alliance shirt +1" , model = ' ' } +models.body[27899] = { name = "Alliance Shirt" , enl = "alliance shirt" , model = ' ' } +models.body[27902] = { name = "G. Spriggan Coat" , enl = "green spriggan coat" , model = 8453 } +models.body[27903] = { name = "Trench Tunic" , enl = "trench tunic", model = 8297} +models.body[27904] = { name = "Morass Tunic" , enl = "morass tunic", model = 8539} +models.body[27905] = { name = "Woodland Tunic" , enl = "woodland tunic", model = 8540} +models.body[27906] = { name = "Chocobo Suit +1" , enl = "chocobo suit +1", model = 8545} +models.body[27907] = { name = "Gorney Haubert" , enl = "gorney haubert", model = 8546} +models.body[27908] = { name = "Shneddick Tabard" , enl = "shneddick tabard", model = 8547} +models.body[27909] = { name = "Weatherspoon Robe" , enl = "weatherspoon robe", model = 8543} +models.body[27911] = { name = "Chocobo Suit" , enl = "chocobo suit", model = 8545} +models.body[27912] = { name = "Cizin Mail" , enl = "cizin mail", model = 8466} +models.body[27913] = { name = "Otronif Harness" , enl = "otronif harness", model = 8439} +models.body[27914] = { name = "Iuitl Vest" , enl = "iuitl vest", model = 8448} +models.body[27915] = { name = "Gendewitha Bliaut" , enl = "gendewitha bliaut", model = 8457} +models.body[27916] = { name = "Hagondes Coat" , enl = "hagondes coat", model = 8454} +models.body[27917] = { name = "Miki. Breastplate" , enl = "mikinaak breastplate", model = 8541} +models.body[27918] = { name = "Manibozho Jerkin" , enl = "manibozho jerkin", model = 8542} +models.body[27919] = { name = "Bokwus Robe" , enl = "bokwus robe", model = 8543} +models.body[27920] = { name = "Pak Corselet" , enl = "pak corselet", model = 8371} +models.body[27921] = { name = "Pak Corselet +1" , enl = "pak corselet +1", model = 8371} +models.body[27922] = { name = "Orvail Robe" , enl = "orvail robe", model = 8536} +models.body[27924] = { name = "Thurandaut Tabard" , enl = "thurandaut tabard", model = 8535} +models.body[27925] = { name = "Karieyh Haubert" , enl = "karieyh haubert", model = 8534} +models.body[27926] = { name = "Geomancy Tunic" , enl = "geomancy tunic", model = 8500} --8508 Taru M +models.body[27927] = { name = "Runeist Coat" , enl = "runeist coat", model = 8530} + +models.body[50000] = { name = "Volunteer Solider Body" , model = 8365 } +models.body[50001] = { name = "Chasuble (Male Only)", model = 8369 } +models.body[50002] = { name = "Haudrale's Tabard" , model = 8434 } +models.body[50003] = { name = "Geomancer Relic", model = 8502 } --8510 Taru M +models.body[50004] = { name = "Rune Fencer Relic", model = 8531 } + +models.body["Unknown"] = { 8410 } +models.body["Unused"] = { 8370, 8409, 8532, 8533, 8544, 8549, 8552, 8553, 8511 } diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/feet.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/feet.lua new file mode 100644 index 0000000..4893ece --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/feet.lua @@ -0,0 +1,867 @@ +-- Copyright © 2013, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + + +models.feet = {} + +models.feet["None"] = { name = "empty" , enl = "empty", model = 20480 } + +models.feet[10350] = { name = "Wohpe's Sabots" , enl = "wohpe's sabots", model = 20595} +models.feet[10351] = { name = "Ihwa Huaraches" , enl = "ihwa huaraches", model = 20617} +models.feet[10352] = { name = "Ghadhab Nails" , enl = "ghadhab nails", model = 20652} +models.feet[10353] = { name = "Hexed Gambieras" , enl = "hexed gambieras", model = 20485} +models.feet[10354] = { name = "Hexed Sune-Ate" , enl = "hexed sune-ate", model = 20489} +models.feet[10355] = { name = "Hexed Gamashes" , enl = "hexed gamashes", model = 20651} +models.feet[10356] = { name = "Hexed Nails" , enl = "hexed nails", model = 20500} +models.feet[10357] = { name = "Hexed Boots" , enl = "hexed boots", model = 20498} +models.feet[10358] = { name = "Hexed Gamb. -1" , enl = "hexed gambieras -1", model = 20485} +models.feet[10359] = { name = "Hexed Sune-Ate -1" , enl = "hexed sune-ate -1", model = 20489} +models.feet[10360] = { name = "Hexed Gamash. -1" , enl = "hexed gamashes -1", model = 20651} +models.feet[10361] = { name = "Hexed Nails -1" , enl = "hexed nails -1", model = 20500} +models.feet[10362] = { name = "Hexed Boots -1" , enl = "hexed boots -1", model = 20498} +models.feet[10363] = { name = "Dux Greaves" , enl = "dux greaves", model = 20816} +models.feet[10364] = { name = "Dux Greaves +1" , enl = "dux greaves +1", model = 20816} +models.feet[10365] = { name = "Chelona Boots" , enl = "chelona boots", model = 20817} +models.feet[10366] = { name = "Chelona Boots +1" , enl = "chelona boots +1", model = 20817} +models.feet[10367] = { name = "Enif Gambieras" , enl = "enif gambieras", model = 20639} +models.feet[10368] = { name = "Adhara Crackows" , enl = "adhara crackows", model = 20640} +models.feet[10369] = { name = "Murzim Gambieras" , enl = "murzim gambieras", model = 20639} +models.feet[10370] = { name = "Shedir Crackows" , enl = "shedir crackows", model = 20640} +models.feet[10371] = { name = "Veikr Pumps" , enl = "veikr pumps", model = 20610} +models.feet[10372] = { name = "Plumb Boots" , enl = "plumb boots", model = 20590} +models.feet[10373] = { name = "Ocular Boots" , enl = "ocular boots", model = 20679} +models.feet[10600] = { name = "Ogier's Leggings" , enl = "ogier's leggings", model = 20805} +models.feet[10601] = { name = "Athos's Boots" , enl = "athos's boots", model = 20806} +models.feet[10602] = { name = "Rubeus Boots" , enl = "rubeus boots", model = 20807} +models.feet[10603] = { name = "Scopuli Nails" , enl = "scopuli nails", model = 20652} +models.feet[10604] = { name = "Avant Leggings" , enl = "avant leggings", model = 20509} +models.feet[10605] = { name = "Avant Leggings +1" , enl = "avant leggings +1", model = 20509} +models.feet[10606] = { name = "Kacura Leggings" , enl = "kacura leggings", model = 20590} +models.feet[10607] = { name = "Kacura Leggings +1" , enl = "kacura leggings +1", model = 20590} +models.feet[10608] = { name = "Sweven Boots" , enl = "sweven boots", model = 20611} +models.feet[10609] = { name = "Sweven Boots +1" , enl = "sweven boots +1", model = 20611} +models.feet[10610] = { name = "Calma Leggings" , enl = "calma leggings", model = 20761} +models.feet[10611] = { name = "Mustela Boots" , enl = "mustela boots", model = 20728} +models.feet[10612] = { name = "Magavan Clogs" , enl = "magavan clogs", model = 20749} +models.feet[10613] = { name = "Muddle Pumps" , enl = "muddle pumps", model = 20622} +models.feet[10614] = { name = "Wrathwing Nails" , enl = "wrathwing nails", model = 20494} +models.feet[10615] = { name = "Wurrukatte Boots" , enl = "wurrukatte boots", model = 20574} +models.feet[10616] = { name = "Rheic Schuhs" , enl = "rheic schuhs", model = 20675} +models.feet[10617] = { name = "Rheic Schuhs +1" , enl = "rheic schuhs +1", model = 20675} +models.feet[10618] = { name = "Rheic Schuhs +2" , enl = "rheic schuhs +2", model = 20675} +models.feet[10619] = { name = "Rheic Schuhs +3" , enl = "rheic schuhs +3", model = 20675} +models.feet[10620] = { name = "Phorcys Schuhs" , enl = "phorcys schuhs", model = 20675} +models.feet[10621] = { name = "Euxine Nails" , enl = "euxine nails", model = 20676} +models.feet[10622] = { name = "Euxine Nails +1" , enl = "euxine nails +1", model = 20676} +models.feet[10623] = { name = "Euxine Nails +2" , enl = "euxine nails +2", model = 20676} +models.feet[10624] = { name = "Euxine Nails +3" , enl = "euxine nails +3", model = 20676} +models.feet[10625] = { name = "Thaumas Nails" , enl = "thaumas nails", model = 20676} +models.feet[10626] = { name = "Tethyan Clogs" , enl = "tethyan clogs", model = 20677} +models.feet[10627] = { name = "Tethyan Clogs +1" , enl = "tethyan clogs +1", model = 20677} +models.feet[10628] = { name = "Tethyan Clogs +2" , enl = "tethyan clogs +2", model = 20677} +models.feet[10629] = { name = "Tethyan Clogs +3" , enl = "tethyan clogs +3", model = 20677} +models.feet[10630] = { name = "Nares Clogs" , enl = "nares clogs", model = 20677} +models.feet[10631] = { name = "Hrafn Gambieras" , enl = "hrafn gambieras", model = 20803} +models.feet[10632] = { name = "Tenryu Sune-Ate" , enl = "tenryu sune-ate", model = 20804} +models.feet[10633] = { name = "Kheper Gamashes" , enl = "kheper gamashes", model = 20800} +models.feet[10634] = { name = "Auspex Nails" , enl = "auspex nails", model = 20802} +models.feet[10635] = { name = "Paean Boots" , enl = "paean boots", model = 20801} +models.feet[10636] = { name = "Huginn Gambieras" , enl = "huginn gambieras", model = 20803} +models.feet[10637] = { name = "Tenryu Sune-Ate +1" , enl = "tenryu sune-ate +1", model = 20804} +models.feet[10638] = { name = "Khepri Gamashes" , enl = "khepri gamashes", model = 20800} +models.feet[10639] = { name = "Spurrina Nails" , enl = "spurrina nails", model = 20802} +models.feet[10640] = { name = "Iaso Boots" , enl = "iaso boots", model = 20801} +models.feet[10641] = { name = "Ugol Solerets" , enl = "ugol solerets", model = 20630} +models.feet[10642] = { name = "Mavros Solerets" , enl = "mavros solerets", model = 20630} +models.feet[10643] = { name = "Urja Ledelsens" , enl = "urja ledelsens", model = 20530} +models.feet[10644] = { name = "Sthira Ledelsens" , enl = "sthira ledelsens", model = 20530} +models.feet[10645] = { name = "Spolia Pigaches" , enl = "spolia pigaches", model = 20629} +models.feet[10646] = { name = "Opima Pigaches" , enl = "opima pigaches", model = 20629} +models.feet[10647] = { name = "Areion Boots" , enl = "areion boots", model = 20491} +models.feet[10648] = { name = "Areion Boots +1" , enl = "areion boots +1", model = 20491} +models.feet[10730] = { name = "War. Calligae +2" , enl = "warrior's calligae +2", model = 20545} +models.feet[10731] = { name = "Mel. Gaiters +2" , enl = "melee gaiters +2", model = 20547} +models.feet[10732] = { name = "Clr. Duckbills +2" , enl = "cleric's duckbills +2", model = 20549} +models.feet[10733] = { name = "Src. Sabots +2" , enl = "sorcerer's sabots +2", model = 20551} +models.feet[10734] = { name = "Dls. Boots +2" , enl = "duelist's boots +2", model = 20553} +models.feet[10735] = { name = "Asn. Poulaines +2" , enl = "assassin's poulaines +2", model = 20555} +models.feet[10736] = { name = "Vlr. Leggings +2" , enl = "valor leggings +2", model = 20557} +models.feet[10737] = { name = "Abs. Sollerets +2" , enl = "abyss sollerets +2", model = 20559} +models.feet[10738] = { name = "Mst. Gaiters +2" , enl = "monster gaiters +2", model = 20561} +models.feet[10739] = { name = "Brd. Slippers +2" , enl = "bard's slippers +2", model = 20563} +models.feet[10740] = { name = "Sct. Socks +2" , enl = "scout's socks +2", model = 20565} +models.feet[10741] = { name = "Sao. Sune-Ate +2" , enl = "saotome sune-ate +2", model = 20567} +models.feet[10742] = { name = "Kog. Kyahan +2" , enl = "koga kyahan +2", model = 20569} +models.feet[10743] = { name = "Wym. Greaves +2" , enl = "wyrm greaves +2", model = 20571} +models.feet[10744] = { name = "Smn. Pigaches +2" , enl = "summoner's pigaches +2", model = 20573} +models.feet[10745] = { name = "Mirage Charuqs +2" , enl = "mirage charuqs +2", model = 20648} +models.feet[10746] = { name = "Comm. Bottes +2" , enl = "commodore bottes +2", model = 20649} +models.feet[10747] = { name = "Ptn. Babouches +2" , enl = "pantin babouches +2", model = 20650} +models.feet[10748] = { name = "Etoile Shoes +2" , enl = "etoile toe shoes +2", model = 20784} +models.feet[10749] = { name = "Argute Loafers +2" , enl = "argute loafers +2", model = 20695} +models.feet[11144] = { name = "Rvg. Calligae +2" , enl = "ravager's calligae +2", model = 20762} +models.feet[11145] = { name = "Tantra Gaiters +2" , enl = "tantra gaiters +2", model = 20763} +models.feet[11146] = { name = "Orsn. Duckbills +2" , enl = "orison duckbills +2", model = 20764} +models.feet[11147] = { name = "Goetia Sabots +2" , enl = "goetia sabots +2", model = 20765} +models.feet[11148] = { name = "Estq. Houseaux +2" , enl = "estoqueur's houseaux +2", model = 20766} +models.feet[11149] = { name = "Raid. Poulaines +2" , enl = "raider's poulaines +2", model = 20767} +models.feet[11150] = { name = "Creed Sabatons +2" , enl = "creed sabatons +2", model = 20768} +models.feet[11151] = { name = "Bale Sollerets +2" , enl = "bale sollerets +2", model = 20769} +models.feet[11152] = { name = "Ferine Ocreae +2" , enl = "ferine ocreae +2", model = 20770} +models.feet[11153] = { name = "Aoidos' Cothrn. +2" , enl = "aoidos' cothurnes +2", model = 20771} +models.feet[11154] = { name = "Sylvan Bottln. +2" , enl = "sylvan bottillons +2", model = 20772} +models.feet[11155] = { name = "Unkai Sune-Ate +2" , enl = "unkai sune-ate +2", model = 20773} +models.feet[11156] = { name = "Iga Kyahan +2" , enl = "iga kyahan +2", model = 20774} +models.feet[11157] = { name = "Lncr. Schynbld. +2" , enl = "lancer's schynbalds +2", model = 20775} +models.feet[11158] = { name = "Caller's Pgch. +2" , enl = "caller's pigaches +2", model = 20776} +models.feet[11159] = { name = "Mavi Basmak +2" , enl = "mavi basmak +2", model = 20777} +models.feet[11160] = { name = "Nvrch. Bottes +2" , enl = "navarch's bottes +2", model = 20778} +models.feet[11161] = { name = "Cirque Scarpe +2" , enl = "cirque scarpe +2", model = 20779} +models.feet[11162] = { name = "Charis Shoes +2" , enl = "charis toeshoes +2", model = 20785} +models.feet[11163] = { name = "Svnt. Loafers +2" , enl = "savant's loafers +2", model = 20786} +models.feet[11244] = { name = "Rvg. Calligae +1" , enl = "ravager's calligae +1", model = 20762} +models.feet[11245] = { name = "Tantra Gaiters +1" , enl = "tantra gaiters +1", model = 20763} +models.feet[11246] = { name = "Orsn. Duckbills +1" , enl = "orison duckbills +1", model = 20764} +models.feet[11247] = { name = "Goetia Sabots +1" , enl = "goetia sabots +1", model = 20765} +models.feet[11248] = { name = "Estq. Houseaux +1" , enl = "estoqueur's houseaux +1", model = 20766} +models.feet[11249] = { name = "Raid. Poulaines +1" , enl = "raider's poulaines +1", model = 20767} +models.feet[11250] = { name = "Creed Sabatons +1" , enl = "creed sabatons +1", model = 20768} +models.feet[11251] = { name = "Bale Sollerets +1" , enl = "bale sollerets +1", model = 20769} +models.feet[11252] = { name = "Ferine Ocreae +1" , enl = "ferine ocreae +1", model = 20770} +models.feet[11253] = { name = "Aoidos' Cothrn. +1" , enl = "aoidos' cothurnes +1", model = 20771} +models.feet[11254] = { name = "Sylvan Bottln. +1" , enl = "sylvan bottillons +1", model = 20772} +models.feet[11255] = { name = "Unkai Sune-Ate +1" , enl = "unkai sune-ate +1", model = 20773} +models.feet[11256] = { name = "Iga Kyahan +1" , enl = "iga kyahan +1", model = 20774} +models.feet[11257] = { name = "Lncr. Schynbld. +1" , enl = "lancer's schynbalds +1", model = 20775} +models.feet[11258] = { name = "Caller's Pgch. +1" , enl = "caller's pigaches +1", model = 20776} +models.feet[11259] = { name = "Mavi Basmak +1" , enl = "mavi basmak +1", model = 20777} +models.feet[11260] = { name = "Nvrch. Bottes +1" , enl = "navarch's bottes +1", model = 20778} +models.feet[11261] = { name = "Cirque Scarpe +1" , enl = "cirque scarpe +1", model = 20779} +models.feet[11262] = { name = "Charis Shoes +1" , enl = "charis toeshoes +1", model = 20785} +models.feet[11263] = { name = "Svnt. Loafers +1" , enl = "savant's loafers +1", model = 20786} +models.feet[11364] = { name = "Hachiryu Sune-Ate" , enl = "hachiryu sune-ate", model = 20707} +models.feet[11365] = { name = "Karasutengu" , enl = "karasutengu kogake", model = 20696} +models.feet[11366] = { name = "Avocat Pigaches" , enl = "avocat pigaches", model = 20623} +models.feet[11367] = { name = "Nobushi Kyahan" , enl = "nobushi kyahan", model = 20586} +models.feet[11368] = { name = "Hakke Habaki" , enl = "hakke habaki", model = 20497} +models.feet[11369] = { name = "Numerist Pumps" , enl = "numerist pumps", model = 20587} +models.feet[11370] = { name = "Junkenshi Habaki" , enl = "junkenshi habaki", model = 20497} +models.feet[11371] = { name = "Seikenshi Habaki" , enl = "seikenshi habaki", model = 20497} +models.feet[11372] = { name = "Junrenshi Habaki" , enl = "junrenshi habaki", model = 20497} +models.feet[11373] = { name = "Seirenshi Habaki" , enl = "seirenshi habaki", model = 20497} +models.feet[11374] = { name = "Junhanshi Habaki" , enl = "junhanshi habaki", model = 20497} +models.feet[11375] = { name = "Seihanshi Habaki" , enl = "seihanshi habaki", model = 20497} +models.feet[11376] = { name = "Aurum Sabatons" , enl = "aurum sabatons", model = 20619} +models.feet[11377] = { name = "Oracle's Pigaches" , enl = "oracle's pigaches", model = 20623} +models.feet[11378] = { name = "Enkidu's Leggings" , enl = "enkidu's leggings", model = 20536} +models.feet[11379] = { name = "Hermes' Sandals" , enl = "hermes' sandals", model = 20483} +models.feet[11380] = { name = "Hermes' Sandals +1" , enl = "hermes' sandals +1", model = 20483} +models.feet[11381] = { name = "Magus Charuqs +1" , enl = "magus charuqs +1", model = 20645} +models.feet[11382] = { name = "Mirage Charuqs" , enl = "mirage charuqs", model = 20646} +models.feet[11383] = { name = "Mirage Charuqs +1" , enl = "mirage charuqs +1", model = 20646} +models.feet[11384] = { name = "Cor. Bottes +1" , enl = "corsair's bottes +1", model = 20647} +models.feet[11385] = { name = "Comm. Bottes" , enl = "commodore bottes", model = 20648} +models.feet[11386] = { name = "Comm. Bottes +1" , enl = "commodore bottes +1", model = 20648} +models.feet[11387] = { name = "Pup. Babouches +1" , enl = "puppetry babouches +1", model = 20649} +models.feet[11388] = { name = "Pantin Babouches" , enl = "pantin babouches", model = 20650} +models.feet[11389] = { name = "Ptn. Babouches +1" , enl = "pantin babouches +1", model = 20650} +models.feet[11390] = { name = "Llwyd's Clogs" , enl = "llwyd's clogs", model = 20500} +models.feet[11391] = { name = "Emissary Boots" , enl = "emissary boots", model = 20576} +models.feet[11392] = { name = "Koschei Crackows" , enl = "koschei crackows", model = 20640} +models.feet[11393] = { name = "Dancer's Shoes +1" , enl = "dancer's toe shoes +1", model = 20690} +models.feet[11394] = { name = "Dancer's Shoes +1" , enl = "dancer's toe shoes +1", model = 20691} +models.feet[11395] = { name = "Sch. Loafers +1" , enl = "scholar's loafers +1", model = 20694} +models.feet[11396] = { name = "Etoile Shoes" , enl = "etoile toe shoes", model = 20784} +models.feet[11397] = { name = "Etoile Shoes +1" , enl = "etoile toe shoes +1", model = 20784} +models.feet[11398] = { name = "Argute Loafers" , enl = "argute loafers", model = 20695} +models.feet[11399] = { name = "Argute Loafers +1" , enl = "argute loafers +1", model = 20695} +models.feet[11400] = { name = "Noble Poulaines" , enl = "noble poulaines", model = 20711} +models.feet[11401] = { name = "Rambler's Gaiters" , enl = "rambler's gaiters", model = 20501} +models.feet[11402] = { name = "Gothic Sabatons" , enl = "gothic sabatons", model = 20714} +models.feet[11403] = { name = "Talaria" , enl = "talaria", model = 20483} +models.feet[11404] = { name = "Promptitude Solea" , enl = "promptitude solea", model = 20483} +models.feet[11405] = { name = "Kyoshu Kyahan" , enl = "kyoshu kyahan", model = 20497} +models.feet[11406] = { name = "Stimulus Sabots" , enl = "stimulus sabots", model = 20498} +models.feet[11407] = { name = "Mettle Leggings" , enl = "mettle leggings", model = 20496} +models.feet[11408] = { name = "Morana Pigaches" , enl = "morana's pigaches", model = 20629} +models.feet[11409] = { name = "Aoide's Pumps" , enl = "aoide's pumps", model = 20611} +models.feet[11410] = { name = "Setanta's Led." , enl = "setanta's ledelsens", model = 20574} +models.feet[11411] = { name = "Shrewd Pumps" , enl = "shrewd pumps", model = 20610} +models.feet[11412] = { name = "Mtt. Leggings +1" , enl = "mettle leggings +1", model = 20496} +models.feet[11413] = { name = "Perle Solerets" , enl = "perle solerets", model = 20719} +models.feet[11414] = { name = "Aurore Gaiters" , enl = "aurore gaiters", model = 20721} +models.feet[11415] = { name = "Teal Pigaches" , enl = "teal pigaches", model = 20720} +models.feet[11416] = { name = "Clout Boots" , enl = "clout boots", model = 20580} +models.feet[11417] = { name = "Theurgia Clogs" , enl = "theurgia clogs", model = 20677} +models.feet[11418] = { name = "Poise Shoes" , enl = "poise shoes", model = 20483} +models.feet[11419] = { name = "Lithe Boots" , enl = "lithe boots", model = 20487} +models.feet[11420] = { name = "Ruffian Leggings" , enl = "ruffian leggings", model = 20509} +models.feet[11421] = { name = "Missile Boots" , enl = "missile boots", model = 20486} +models.feet[11422] = { name = "Conduit Shoes" , enl = "conduit shoes", model = 20491} +models.feet[11423] = { name = "Rager Ledelsens" , enl = "rager ledelsens", model = 20481} +models.feet[11424] = { name = "Gules Leggings" , enl = "gules leggings", model = 20536} +models.feet[11425] = { name = "Gules Leggings +1" , enl = "gules leggings +1", model = 20536} +models.feet[11426] = { name = "Versa Solerets" , enl = "versa solerets", model = 20538} +models.feet[11427] = { name = "Versa Solerets +1" , enl = "versa solerets +1", model = 20538} +models.feet[11428] = { name = "Lore Sabots" , enl = "lore sabots", model = 20595} +models.feet[11429] = { name = "Lore Sabots +1" , enl = "lore sabots +1", model = 20595} +models.feet[11430] = { name = "Tandava Crackows" , enl = "tandava crackows", model = 20640} +models.feet[11431] = { name = "Ballerines" , enl = "ballerines", model = 20613} +models.feet[11432] = { name = "Ace's Leggings" , enl = "ace's leggings", model = 20496} +models.feet[11433] = { name = "Dornen Schuhs" , enl = "dornen schuhs", model = 20575} +models.feet[11434] = { name = "Sapientia Sabots" , enl = "sapientia sabots", model = 20498} +models.feet[11435] = { name = "Bellicus Sabatons" , enl = "bellicus sabatons", model = 20750} +models.feet[11436] = { name = "Bestia Greaves" , enl = "bestia greaves", model = 20753} +models.feet[11437] = { name = "Paragon Sollerets" , enl = "paragon sollerets", model = 20756} +models.feet[11438] = { name = "Skopos Socks" , enl = "skopos socks", model = 20729} +models.feet[11439] = { name = "Koku. Sune-Ate" , enl = "kokugetsu sune-ate", model = 20732} +models.feet[11440] = { name = "Spry Gaiters" , enl = "spry gaiters", model = 20735} +models.feet[11441] = { name = "Mederi Brogues" , enl = "mederi brogues", model = 20738} +models.feet[11442] = { name = "Literae Sabots" , enl = "literae sabots", model = 20741} +models.feet[11443] = { name = "Facio Galoshes" , enl = "facio galoshes", model = 20744} +models.feet[11444] = { name = "Frisky Sabots" , enl = "frisky sabots", model = 20747} +models.feet[11445] = { name = "Etamin Gambieras" , enl = "etamin gambieras", model = 20654} +models.feet[11446] = { name = "Areion's Gamashes" , enl = "areion's gamashes", model = 20503} +models.feet[11447] = { name = "Alcide's Leggings" , enl = "alcide's leggings", model = 20590} +models.feet[11448] = { name = "Alcd. Leggings +1" , enl = "alcide's leggings +1", model = 20590} +models.feet[11449] = { name = "Nemus Nails" , enl = "nemus nails", model = 20652} +models.feet[11450] = { name = "Nemus Nails +1" , enl = "nemus nails +1", model = 20652} +models.feet[11451] = { name = "Nebula Pigaches" , enl = "nebula pigaches", model = 20581} +models.feet[11452] = { name = "Nebula Pgch. +1" , enl = "nebula pigaches +1", model = 20581} +models.feet[11453] = { name = "Calmecac Leggings" , enl = "calmecac leggings", model = 20687} +models.feet[11454] = { name = "Jingang Greaves" , enl = "jingang greaves", model = 20688} +models.feet[11455] = { name = "Menhit Leggings" , enl = "menhit leggings", model = 20689} +models.feet[11456] = { name = "Ryuga Sune-Ate" , enl = "ryuga sune-ate", model = 20586} +models.feet[11457] = { name = "Eurus' Ledelsens" , enl = "eurus' ledelsens", model = 20530} +models.feet[11458] = { name = "Dilaram's Sollerets" , enl = "dilaram's sollerets", model = 20535} +models.feet[11459] = { name = "Savateur's Gaiters" , enl = "savateur's gaiters", model = 20642} +models.feet[11460] = { name = "Fajin Boots" , enl = "fajin boots", model = 20576} +models.feet[11461] = { name = "Aife's Pumps" , enl = "aife's pumps", model = 20537} +models.feet[11462] = { name = "Gigantes Boots" , enl = "gigantes boots", model = 20574} +models.feet[12088] = { name = "Ravager's Calligae" , enl = "ravager's calligae", model = 20762} +models.feet[12089] = { name = "Tantra Gaiters" , enl = "tantra gaiters", model = 20763} +models.feet[12090] = { name = "Orison Duckbills" , enl = "orison duckbills", model = 20764} +models.feet[12091] = { name = "Goetia Sabots" , enl = "goetia sabots", model = 20765} +models.feet[12092] = { name = "Estq. Houseaux" , enl = "estoqueur's houseaux", model = 20766} +models.feet[12093] = { name = "Raider's Poulaines" , enl = "raider's poulaines", model = 20767} +models.feet[12094] = { name = "Creed Sabatons" , enl = "creed sabatons", model = 20768} +models.feet[12095] = { name = "Bale Sollerets" , enl = "bale sollerets", model = 20769} +models.feet[12096] = { name = "Ferine Ocreae" , enl = "ferine ocreae", model = 20770} +models.feet[12097] = { name = "Aoidos' Cothurnes" , enl = "aoidos' cothurnes", model = 20771} +models.feet[12098] = { name = "Sylvan Bottillons" , enl = "sylvan bottillons", model = 20772} +models.feet[12099] = { name = "Unkai Sune-Ate" , enl = "unkai sune-ate", model = 20773} +models.feet[12100] = { name = "Iga Kyahan" , enl = "iga kyahan", model = 20774} +models.feet[12101] = { name = "Lncr. Schynbalds" , enl = "lancer's schynbalds", model = 20775} +models.feet[12102] = { name = "Caller's Pigaches" , enl = "caller's pigaches", model = 20776} +models.feet[12103] = { name = "Mavi Basmak" , enl = "mavi basmak", model = 20777} +models.feet[12104] = { name = "Navarch's Bottes" , enl = "navarch's bottes", model = 20778} +models.feet[12105] = { name = "Cirque Scarpe" , enl = "cirque scarpe", model = 20779} +models.feet[12106] = { name = "Charis Toeshoes" , enl = "charis toeshoes", model = 20785} +models.feet[12107] = { name = "Savant's Loafers" , enl = "savant's loafers", model = 20786} +models.feet[12252] = { name = "Ebon Sabatons" , enl = "ebon sabatons", model = 20750} +models.feet[12253] = { name = "Furia Sabatons" , enl = "furia sabatons", model = 20751} +models.feet[12254] = { name = "Ebur Sabatons" , enl = "ebur sabatons", model = 20752} +models.feet[12255] = { name = "Ebon Greaves" , enl = "ebon greaves", model = 20753} +models.feet[12256] = { name = "Furia Greaves" , enl = "furia greaves", model = 20754} +models.feet[12257] = { name = "Ebur Greaves" , enl = "ebur greaves", model = 20755} +models.feet[12258] = { name = "Ebon Sollerets" , enl = "ebon sollerets", model = 20756} +models.feet[12259] = { name = "Furia Sollerets" , enl = "furia sollerets", model = 20757} +models.feet[12260] = { name = "Ebur Sollerets" , enl = "ebur sollerets", model = 20758} +models.feet[12261] = { name = "Ebon Leggings" , enl = "ebon leggings", model = 20759} +models.feet[12262] = { name = "Furia Leggings" , enl = "furia leggings", model = 20760} +models.feet[12263] = { name = "Ebur Leggings" , enl = "ebur leggings", model = 20761} +models.feet[12264] = { name = "Ebon Boots" , enl = "ebon boots", model = 20726} +models.feet[12265] = { name = "Furia Boots" , enl = "furia boots", model = 20727} +models.feet[12266] = { name = "Ebur Boots" , enl = "ebur boots", model = 20728} +models.feet[12267] = { name = "Ebon Socks" , enl = "ebon socks", model = 20729} +models.feet[12268] = { name = "Furia Socks" , enl = "furia socks", model = 20730} +models.feet[12269] = { name = "Ebur Socks" , enl = "ebur socks", model = 20731} +models.feet[12270] = { name = "Shikkoku Sune-Ate" , enl = "shikkoku sune-ate", model = 20732} +models.feet[12271] = { name = "Shinku Sune-Ate" , enl = "shinku sune-ate", model = 20733} +models.feet[12272] = { name = "Ginhaku Sune-Ate" , enl = "ginhaku sune-ate", model = 20734} +models.feet[12273] = { name = "Ebon Gaiters" , enl = "ebon gaiters", model = 20735} +models.feet[12274] = { name = "Furia Gaiters" , enl = "furia gaiters", model = 20736} +models.feet[12275] = { name = "Ebur Gaiters" , enl = "ebur gaiters", model = 20737} +models.feet[12276] = { name = "Ebon Brogues" , enl = "ebon brogues", model = 20738} +models.feet[12277] = { name = "Furia Brogues" , enl = "furia brogues", model = 20739} +models.feet[12278] = { name = "Ebur Brogues" , enl = "ebur brogues", model = 20740} +models.feet[12279] = { name = "Ebon Sabots" , enl = "ebon sabots", model = 20741} +models.feet[12280] = { name = "Furia Sabots" , enl = "furia sabots", model = 20742} +models.feet[12281] = { name = "Ebur Sabots" , enl = "ebur sabots", model = 20743} +models.feet[12282] = { name = "Ebon Galoshes" , enl = "ebon galoshes", model = 20744} +models.feet[12283] = { name = "Furia Galoshes" , enl = "furia galoshes", model = 20745} +models.feet[12284] = { name = "Ebur Galoshes" , enl = "ebur galoshes", model = 20746} +models.feet[12285] = { name = "Ebon Clogs" , enl = "ebon clogs", model = 20747} +models.feet[12286] = { name = "Furia Clogs" , enl = "furia clogs", model = 20748} +models.feet[12287] = { name = "Ebur Clogs" , enl = "ebur clogs", model = 20749} +models.feet[12928] = { name = "Plate Leggings" , enl = "plate leggings", model = 20482} +models.feet[12929] = { name = "Mythril Leggings" , enl = "mythril leggings", model = 20509} +models.feet[12930] = { name = "Gold Sabatons" , enl = "gold sabatons", model = 20505} +models.feet[12931] = { name = "Dst. Sabatons" , enl = "darksteel sabatons", model = 20502} +models.feet[12932] = { name = "Adaman Sabatons" , enl = "adaman sabatons", model = 20535} +models.feet[12933] = { name = "Koenig Schuhs" , enl = "koenig schuhs", model = 20575} +models.feet[12934] = { name = "Irn.Msk. Sabatons" , enl = "iron musketeer's sabatons", model = 20505} +models.feet[12935] = { name = "Judge's Greaves" , enl = "judge's greaves", model = 20510} +models.feet[12936] = { name = "Greaves" , enl = "greaves", model = 20485} +models.feet[12937] = { name = "Silver Greaves" , enl = "silver greaves", model = 20485} +models.feet[12938] = { name = "Sollerets" , enl = "sollerets", model = 20493} +models.feet[12939] = { name = "Dst. Sollerets" , enl = "darksteel sollerets", model = 20493} +models.feet[12940] = { name = "Thick Sollerets" , enl = "thick sollerets", model = 20538} +models.feet[12941] = { name = "Adaman Sollerets" , enl = "adaman sollerets", model = 20543} +models.feet[12942] = { name = "Ryl.Kgt. Sollerets" , enl = "royal knight's sollerets", model = 20493} +models.feet[12943] = { name = "Ryl.Sqr. Sollerets" , enl = "royal squire's sollerets", model = 20493} +models.feet[12944] = { name = "Scale Greaves" , enl = "scale greaves", model = 20508} +models.feet[12945] = { name = "Brass Greaves" , enl = "brass greaves", model = 20508} +models.feet[12946] = { name = "Suzaku's Sune-Ate" , enl = "suzaku's sune-ate", model = 20579} +models.feet[12947] = { name = "Coral Greaves" , enl = "coral greaves", model = 20506} +models.feet[12948] = { name = "Dragon Greaves" , enl = "dragon greaves", model = 20540} +models.feet[12949] = { name = "Gavial Greaves" , enl = "gavial greaves", model = 20507} +models.feet[12950] = { name = "Ctr. Greaves" , enl = "centurion's greaves", model = 20506} +models.feet[12951] = { name = "Brz. Leggings +1" , enl = "bronze leggings +1", model = 20495} +models.feet[12952] = { name = "Leather Highboots" , enl = "leather highboots", model = 20481} +models.feet[12953] = { name = "Lizard Ledelsens" , enl = "lizard ledelsens", model = 20486} +models.feet[12954] = { name = "Studded Boots" , enl = "studded boots", model = 20481} +models.feet[12955] = { name = "Cuir Highboots" , enl = "cuir highboots", model = 20481} +models.feet[12956] = { name = "Raptor Ledelsens" , enl = "raptor ledelsens", model = 20487} +models.feet[12957] = { name = "Dusk Ledelsens" , enl = "dusk ledelsens", model = 20589} +models.feet[12958] = { name = "Tiger Ledelsens" , enl = "tiger ledelsens", model = 20530} +models.feet[12959] = { name = "Coeurl Ledelsens" , enl = "coeurl ledelsens", model = 20534} +models.feet[12960] = { name = "Bronze Leggings" , enl = "bronze leggings", model = 20495} +models.feet[12961] = { name = "Brass Leggings" , enl = "brass leggings", model = 20495} +models.feet[12962] = { name = "Leggings" , enl = "leggings", model = 20494} +models.feet[12963] = { name = "Scorpion Leggings" , enl = "scorpion leggings", model = 20496} +models.feet[12964] = { name = "Dst. Leggings" , enl = "darksteel leggings", model = 20494} +models.feet[12965] = { name = "Coral Leggings" , enl = "coral leggings", model = 20536} +models.feet[12966] = { name = "Bone Leggings" , enl = "bone leggings", model = 20496} +models.feet[12967] = { name = "Beetle Leggings" , enl = "beetle leggings", model = 20496} +models.feet[12968] = { name = "Kyahan" , enl = "kyahan", model = 20497} +models.feet[12969] = { name = "Cotton Kyahan" , enl = "cotton kyahan", model = 20497} +models.feet[12970] = { name = "Soil Kyahan" , enl = "soil kyahan", model = 20497} +models.feet[12971] = { name = "Lth. Highboots +1" , enl = "leather highboots +1", model = 20481} +models.feet[12972] = { name = "Shinobi Kyahan" , enl = "shinobi kyahan", model = 20484} +models.feet[12973] = { name = "Mannequin Pumps" , enl = "mannequin pumps", model = 20610} +models.feet[12974] = { name = "Sune-Ate" , enl = "sune-ate", model = 20489} +models.feet[12975] = { name = "Mrc. Kyahan" , enl = "mercenary's kyahan", model = 20497} +models.feet[12976] = { name = "Gaiters" , enl = "gaiters", model = 20501} +models.feet[12977] = { name = "Cotton Gaiters" , enl = "cotton gaiters", model = 20501} +models.feet[12978] = { name = "Socks" , enl = "socks", model = 20503} +models.feet[12979] = { name = "Wool Socks" , enl = "wool socks", model = 20503} +models.feet[12980] = { name = "Battle Boots" , enl = "battle boots", model = 20504} +models.feet[12981] = { name = "War Boots" , enl = "war boots", model = 20576} +models.feet[12982] = { name = "Mrc.Cpt. Gaiters" , enl = "mercenary captain's gaiters", model = 20501} +models.feet[12983] = { name = "Ash Clogs +1" , enl = "ash clogs +1", model = 20500} +models.feet[12984] = { name = "Ash Clogs" , enl = "ash clogs", model = 20500} +models.feet[12985] = { name = "Holly Clogs" , enl = "holly clogs", model = 20500} +models.feet[12986] = { name = "Chestnut Sabots" , enl = "chestnut sabots", model = 20498} +models.feet[12987] = { name = "Ebony Sabots" , enl = "ebony sabots", model = 20747} +models.feet[12988] = { name = "Pigaches" , enl = "pigaches", model = 20499} +models.feet[12989] = { name = "Noble's Pumps" , enl = "noble's pumps", model = 20537} +models.feet[12990] = { name = "Tct.Mgc. Pigaches" , enl = "tactician magician's pigaches", model = 20499} +models.feet[12991] = { name = "Holly Clogs +1" , enl = "holly clogs +1", model = 20500} +models.feet[12992] = { name = "Solea" , enl = "solea", model = 20483} +models.feet[12993] = { name = "Sandals" , enl = "sandals", model = 20483} +models.feet[12994] = { name = "Shoes" , enl = "shoes", model = 20490} +models.feet[12995] = { name = "Moccasins" , enl = "moccasins", model = 20491} +models.feet[12996] = { name = "Silk Pumps" , enl = "silk pumps", model = 20491} +models.feet[12997] = { name = "Danzo Sune-Ate" , enl = "danzo sune-ate", model = 20578} +models.feet[12998] = { name = "Cmb.Cst. Shoes" , enl = "combat caster's shoes", model = 20490} +models.feet[12999] = { name = "Susurrus Sabatons" , enl = "susurrus sabatons", model = 20714} +models.feet[13000] = { name = "Praeda Sabatons" , enl = "praeda sabatons", model = 20682} +models.feet[13001] = { name = "Augur's Gaiters" , enl = "augur's gaiters", model = 20703} +models.feet[13002] = { name = "Yasha Sune-Ate" , enl = "yasha sune-ate", model = 20578} +models.feet[13003] = { name = "Lgn. Leggings" , enl = "legionnaire's leggings", model = 20495} +models.feet[13004] = { name = "Ryl.Ftm. Boots" , enl = "royal footman's boots", model = 20481} +models.feet[13005] = { name = "Hume M Boots" , enl = "hume m boots", model = 20488} +models.feet[13006] = { name = "Elv. M Ledelsens" , enl = "elvaan m ledelsens", model = 20488} +models.feet[13007] = { name = "Tarutaru Clomps" , enl = "tarutaru clomps", model = 20488} +models.feet[13008] = { name = "Mithran Gaiters" , enl = "mithran gaiters", model = 20488} +models.feet[13009] = { name = "Galkan Sandals" , enl = "galkan sandals", model = 20488} +models.feet[13010] = { name = "Hume F Boots" , enl = "hume f boots", model = 20488} +models.feet[13011] = { name = "Elv. F Ledelsens" , enl = "elvaan f ledelsens", model = 20488} +models.feet[13012] = { name = "Power Sandals" , enl = "power sandals", model = 20483} +models.feet[13013] = { name = "Stumbling Sandals" , enl = "stumbling sandals", model = 20483} +models.feet[13014] = { name = "Leaping Boots" , enl = "leaping boots", model = 20486} +models.feet[13015] = { name = "Custom M Boots" , enl = "custom m boots", model = 20511} +models.feet[13016] = { name = "Custom F Boots" , enl = "custom f boots", model = 20511} +models.feet[13017] = { name = "Mgn. M Ledelsens" , enl = "magna m ledelsens", model = 20511} +models.feet[13018] = { name = "Mgn. F Ledelsens" , enl = "magna f ledelsens", model = 20511} +models.feet[13019] = { name = "Wonder Clomps" , enl = "wonder clomps", model = 20511} +models.feet[13020] = { name = "Savage Gaiters" , enl = "savage gaiters", model = 20511} +models.feet[13021] = { name = "Elder's Sandals" , enl = "elder's sandals", model = 20511} +models.feet[13022] = { name = "Chs. Sabots +1" , enl = "chestnut sabots +1", model = 20498} +models.feet[13023] = { name = "Ebony Sabots +1" , enl = "ebony sabots +1", model = 20747} +models.feet[13024] = { name = "Solid Greaves" , enl = "solid greaves", model = 20508} +models.feet[13025] = { name = "Greaves +1" , enl = "greaves +1", model = 20485} +models.feet[13026] = { name = "Leggings +1" , enl = "leggings +1", model = 20494} +models.feet[13027] = { name = "Brass Leggings +1" , enl = "brass leggings +1", model = 20495} +models.feet[13028] = { name = "Brass Greaves +1" , enl = "brass greaves +1", model = 20508} +models.feet[13029] = { name = "Silver Greaves +1" , enl = "silver greaves +1", model = 20485} +models.feet[13030] = { name = "Gaiters +1" , enl = "gaiters +1", model = 20501} +models.feet[13031] = { name = "Kyahan +1" , enl = "kyahan +1", model = 20497} +models.feet[13032] = { name = "Great Gaiters" , enl = "great gaiters", model = 20501} +models.feet[13033] = { name = "Cotton Kyahan +1" , enl = "cotton kyahan +1", model = 20497} +models.feet[13034] = { name = "Socks +1" , enl = "socks +1", model = 20504} +models.feet[13035] = { name = "Soil Kyahan +1" , enl = "soil kyahan +1", model = 20497} +models.feet[13036] = { name = "Wool Socks +1" , enl = "wool socks +1", model = 20503} +models.feet[13037] = { name = "Solea +1" , enl = "solea +1", model = 20483} +models.feet[13038] = { name = "Fine Ledelsens" , enl = "fine ledelsens", model = 20486} +models.feet[13039] = { name = "Strong Boots" , enl = "strong boots", model = 20481} +models.feet[13040] = { name = "Shoes +1" , enl = "shoes +1", model = 20490} +models.feet[13041] = { name = "Cuir Highboots +1" , enl = "cuir highboots +1", model = 20481} +models.feet[13042] = { name = "Bone Leggings +1" , enl = "bone leggings +1", model = 20496} +models.feet[13043] = { name = "Btl. Leggings +1" , enl = "beetle leggings +1", model = 20496} +models.feet[13044] = { name = "Cpc. Leggings +1" , enl = "carapace leggings +1", model = 20496} +models.feet[13045] = { name = "Ryl.Ftm. Clogs" , enl = "royal footman's clogs", model = 20500} +models.feet[13046] = { name = "Plate Leggings +1" , enl = "plate leggings +1", model = 20482} +models.feet[13047] = { name = "Sollerets +1" , enl = "sollerets +1", model = 20493} +models.feet[13048] = { name = "Mage's Sandals" , enl = "mage's sandals", model = 20483} +models.feet[13049] = { name = "Dino Ledelsens" , enl = "dino ledelsens", model = 20487} +models.feet[13050] = { name = "Moccasins +1" , enl = "moccasins +1", model = 20491} +models.feet[13051] = { name = "Coarse Leggings" , enl = "coarse leggings", model = 20482} +models.feet[13052] = { name = "Light Soleas" , enl = "light soleas", model = 20483} +models.feet[13053] = { name = "Calm Pigaches" , enl = "calm pigaches", model = 20629} +models.feet[13054] = { name = "Fuma Kyahan" , enl = "fuma kyahan", model = 20484} +models.feet[13055] = { name = "Spirit Moccasins" , enl = "spirit moccasins", model = 20491} +models.feet[13702] = { name = "Beak Ledelsens" , enl = "beak ledelsens", model = 20486} +models.feet[13708] = { name = "Ogre Ledelsens" , enl = "ogre ledelsens", model = 20574} +models.feet[13715] = { name = "Cpc. Leggings" , enl = "carapace leggings", model = 20496} +models.feet[14080] = { name = "Strider Boots" , enl = "strider boots", model = 20491} +models.feet[14081] = { name = "Pigaches +1" , enl = "pigaches +1", model = 20499} +models.feet[14082] = { name = "Shn. Kyahan +1" , enl = "shinobi kyahan +1", model = 20484} +models.feet[14083] = { name = "Scp. Leggings +1" , enl = "scorpion leggings +1", model = 20496} +models.feet[14084] = { name = "Dst. Sollerets +1" , enl = "darksteel sollerets +1", model = 20493} +models.feet[14085] = { name = "Serpentes Sabots" , enl = "serpentes sabots", model = 20659} +models.feet[14086] = { name = "Mtl. Leggings +1" , enl = "mythril leggings +1", model = 20509} +models.feet[14087] = { name = "Gilt Sabatons" , enl = "gilt sabatons", model = 20505} +models.feet[14088] = { name = "Bk. Ledelsens +1" , enl = "beak ledelsens +1", model = 20486} +models.feet[14089] = { name = "Fighter's Calligae" , enl = "fighter's calligae", model = 20544} +models.feet[14090] = { name = "Temple Gaiters" , enl = "temple gaiters", model = 20546} +models.feet[14091] = { name = "Healer's Duckbills" , enl = "healer's duckbills", model = 20548} +models.feet[14092] = { name = "Wizard's Sabots" , enl = "wizard's sabots", model = 20550} +models.feet[14093] = { name = "Warlock's Boots" , enl = "warlock's boots", model = 20552} +models.feet[14094] = { name = "Rogue's Poulaines" , enl = "rogue's poulaines", model = 20554} +models.feet[14095] = { name = "Gallant Leggings" , enl = "gallant leggings", model = 20556} +models.feet[14096] = { name = "Chaos Sollerets" , enl = "chaos sollerets", model = 20558} +models.feet[14097] = { name = "Beast Gaiters" , enl = "beast gaiters", model = 20560} +models.feet[14098] = { name = "Choral Slippers" , enl = "choral slippers", model = 20562} +models.feet[14099] = { name = "Hunter's Socks" , enl = "hunter's socks", model = 20564} +models.feet[14100] = { name = "Myochin Sune-Ate" , enl = "myochin sune-ate", model = 20566} +models.feet[14101] = { name = "Ninja Kyahan" , enl = "ninja kyahan", model = 20568} +models.feet[14102] = { name = "Drachen Greaves" , enl = "drachen greaves", model = 20570} +models.feet[14103] = { name = "Evoker's Pigaches" , enl = "evoker's pigaches", model = 20572} +models.feet[14104] = { name = "Battle Boots +1" , enl = "battle boots +1", model = 20504} +models.feet[14105] = { name = "Dst. Sabatons +1" , enl = "darksteel sabatons +1", model = 20502} +models.feet[14106] = { name = "Coral Greaves +1" , enl = "coral greaves +1", model = 20506} +models.feet[14107] = { name = "Drg. Greaves +1" , enl = "dragon greaves +1", model = 20540} +models.feet[14108] = { name = "Feral Ledelsens" , enl = "feral ledelsens", model = 20530} +models.feet[14109] = { name = "Torama Ledelsens" , enl = "torama ledelsens", model = 20534} +models.feet[14110] = { name = "Dst. Leggings +1" , enl = "darksteel leggings +1", model = 20494} +models.feet[14111] = { name = "Mmn. Leggings" , enl = "merman's leggings", model = 20536} +models.feet[14112] = { name = "Sune-Ate +1" , enl = "sune-ate +1", model = 20489} +models.feet[14113] = { name = "War Boots +1" , enl = "war boots +1", model = 20576} +models.feet[14114] = { name = "Aristo. Pumps" , enl = "aristocrat's pumps", model = 20537} +models.feet[14115] = { name = "Silk Pumps +1" , enl = "silk pumps +1", model = 20491} +models.feet[14116] = { name = "Opaline Boots" , enl = "opaline boots", model = 20596} +models.feet[14117] = { name = "Rusty Leggings" , enl = "rusty leggings", model = 20495} +models.feet[14118] = { name = "Iron Greaves" , enl = "iron greaves", model = 20532} +models.feet[14119] = { name = "Iron Greaves +1" , enl = "iron greaves +1", model = 20532} +models.feet[14120] = { name = "Steel Greaves" , enl = "steel greaves", model = 20533} +models.feet[14121] = { name = "Steel Greaves +1" , enl = "steel greaves +1", model = 20533} +models.feet[14122] = { name = "Monsoon Kyahan" , enl = "monsoon kyahan", model = 20497} +models.feet[14123] = { name = "Zenith Pumps" , enl = "zenith pumps", model = 20587} +models.feet[14124] = { name = "Zenith Pumps +1" , enl = "zenith pumps +1", model = 20587} +models.feet[14125] = { name = "Ceremonial Boots" , enl = "ceremonial boots", model = 20596} +models.feet[14126] = { name = "Wedding Boots" , enl = "wedding boots", model = 20596} +models.feet[14127] = { name = "Thick Sollerets +1" , enl = "thick sollerets +1", model = 20538} +models.feet[14128] = { name = "Kung Fu Shoes" , enl = "kung fu shoes", model = 20490} +models.feet[14129] = { name = "Arhat's Sune-Ate" , enl = "arhat's sune-ate", model = 20539} +models.feet[14130] = { name = "Wulong Shoes" , enl = "wulong shoes", model = 20490} +models.feet[14131] = { name = "Wulong Shoes +1" , enl = "wulong shoes +1", model = 20490} +models.feet[14132] = { name = "Winged Boots" , enl = "winged boots", model = 20486} +models.feet[14133] = { name = "Winged Boots +1" , enl = "winged boots +1", model = 20486} +models.feet[14134] = { name = "Air Solea" , enl = "air solea", model = 20483} +models.feet[14135] = { name = "Air Solea +1" , enl = "air solea +1", model = 20483} +models.feet[14136] = { name = "Arh. Sune-Ate +1" , enl = "arhat's sune-ate +1", model = 20539} +models.feet[14137] = { name = "R.K. Sollerets +1" , enl = "royal knight's sollerets +1", model = 20493} +models.feet[14138] = { name = "R.K. Sollerets +2" , enl = "royal knight's sollerets +2", model = 20493} +models.feet[14139] = { name = "Bas. Leggings" , enl = "bastokan leggings", model = 20495} +models.feet[14140] = { name = "Republic Leggings" , enl = "republic leggings", model = 20495} +models.feet[14141] = { name = "San. Sollerets" , enl = "san d'orian sollerets", model = 20492} +models.feet[14142] = { name = "Kingdom Sollerets" , enl = "kingdom sollerets", model = 20492} +models.feet[14143] = { name = "Irn.Msk. Sbtn. +1" , enl = "iron musketeer's sabatons +1", model = 20505} +models.feet[14144] = { name = "Irn.Msk. Sbtn. +2" , enl = "iron musketeer's sabatons +2", model = 20505} +models.feet[14145] = { name = "San d'Orian Boots" , enl = "san d'orian boots", model = 20481} +models.feet[14146] = { name = "Kingdom Boots" , enl = "kingdom boots", model = 20481} +models.feet[14147] = { name = "Bastokan Greaves" , enl = "bastokan greaves", model = 20506} +models.feet[14148] = { name = "Republic Greaves" , enl = "republic greaves", model = 20506} +models.feet[14149] = { name = "San d'Orian Clogs" , enl = "san d'orian clogs", model = 20500} +models.feet[14150] = { name = "Kingdom Clogs" , enl = "kingdom clogs", model = 20500} +models.feet[14151] = { name = "Win. Kyahan" , enl = "windurstian kyahan", model = 20497} +models.feet[14152] = { name = "Fed. Kyahan" , enl = "federation kyahan", model = 20497} +models.feet[14153] = { name = "Win. Gaiters" , enl = "windurstian gaiters", model = 20501} +models.feet[14154] = { name = "Federation Gaiters" , enl = "federation gaiters", model = 20501} +models.feet[14155] = { name = "C.C. Shoes +1" , enl = "combat caster's shoes +1", model = 20490} +models.feet[14156] = { name = "C.C. Shoes +2" , enl = "combat caster's shoes +2", model = 20490} +models.feet[14157] = { name = "T.M. Pigaches +1" , enl = "tactician magician's pigaches +1", model = 20499} +models.feet[14158] = { name = "T.M. Pigaches +2" , enl = "tactician magician's pigaches +2", model = 20499} +models.feet[14159] = { name = "Ogre Ledelsens +1" , enl = "ogre ledelsens +1", model = 20574} +models.feet[14160] = { name = "Crimson Greaves" , enl = "crimson greaves", model = 20542} +models.feet[14161] = { name = "Blood Greaves" , enl = "blood greaves", model = 20542} +models.feet[14162] = { name = "Agrona's Leggings" , enl = "agrona's leggings", model = 20496} +models.feet[14163] = { name = "Kaiser Schuhs" , enl = "kaiser schuhs", model = 20575} +models.feet[14164] = { name = "Inferno Sabots" , enl = "inferno sabots", model = 20747} +models.feet[14165] = { name = "Inferno Sabots +1" , enl = "inferno sabots +1", model = 20747} +models.feet[14166] = { name = "Desert Boots" , enl = "desert boots", model = 20481} +models.feet[14167] = { name = "Desert Boots +1" , enl = "desert boots +1", model = 20481} +models.feet[14168] = { name = "Dune Boots" , enl = "dune boots", model = 20481} +models.feet[14169] = { name = "Vagabond's Boots" , enl = "vagabond's boots", model = 20584} +models.feet[14170] = { name = "Nomad's Boots" , enl = "nomad's boots", model = 20584} +models.feet[14171] = { name = "Fisherman's Boots" , enl = "fisherman's boots", model = 20582} +models.feet[14172] = { name = "Angler's Boots" , enl = "angler's boots", model = 20582} +models.feet[14173] = { name = "Chocobo Boots" , enl = "chocobo boots", model = 20583} +models.feet[14174] = { name = "Rider's Boots" , enl = "rider's boots", model = 20583} +models.feet[14175] = { name = "Armada Sollerets" , enl = "armada sollerets", model = 20543} +models.feet[14176] = { name = "Field Boots" , enl = "field boots", model = 20585} +models.feet[14177] = { name = "Worker Boots" , enl = "worker boots", model = 20585} +models.feet[14178] = { name = "Rasetsu Sune-Ate" , enl = "rasetsu sune-ate", model = 20541} +models.feet[14179] = { name = "Rst. Sune-Ate +1" , enl = "rasetsu sune-ate +1", model = 20541} +models.feet[14180] = { name = "Hct. Leggings" , enl = "hecatomb leggings", model = 20580} +models.feet[14181] = { name = "Hct. Leggings +1" , enl = "hecatomb leggings +1", model = 20580} +models.feet[14182] = { name = "Errant Pigaches" , enl = "errant pigaches", model = 20581} +models.feet[14183] = { name = "Mahatma Pigaches" , enl = "mahatma pigaches", model = 20581} +models.feet[14184] = { name = "Shura Sune-Ate" , enl = "shura sune-ate", model = 20586} +models.feet[14185] = { name = "Shr. Sune-Ate +1" , enl = "shura sune-ate +1", model = 20586} +models.feet[14186] = { name = "Dragon Leggings" , enl = "dragon leggings", model = 20590} +models.feet[14187] = { name = "Drn. Leggings +1" , enl = "dragon leggings +1", model = 20590} +models.feet[14188] = { name = "Dusk Ledelsens +1" , enl = "dusk ledelsens +1", model = 20589} +models.feet[14189] = { name = "Austere Sabots" , enl = "austere sabots", model = 20595} +models.feet[14190] = { name = "Penance Sabots" , enl = "penance sabots", model = 20595} +models.feet[14191] = { name = "Heroic Boots" , enl = "heroic boots", model = 20504} +models.feet[14192] = { name = "Heroic Boots +1" , enl = "heroic boots +1", model = 20504} +models.feet[14193] = { name = "Gem Sabatons" , enl = "gem sabatons", model = 20535} +models.feet[14194] = { name = "Gavial Greaves +1" , enl = "gavial greaves +1", model = 20507} +models.feet[14195] = { name = "Waders" , enl = "waders", model = 20602} +models.feet[14196] = { name = "Dance Shoes" , enl = "dance shoes", model = 20491} +models.feet[14197] = { name = "Dance Shoes +1" , enl = "dance shoes +1", model = 20491} +models.feet[14198] = { name = "Marine M Boots" , enl = "marine m boots", model = 20488} +models.feet[14199] = { name = "Marine F Boots" , enl = "marine f boots", model = 20488} +models.feet[14200] = { name = "Wood M Ledelsens" , enl = "wood m ledelsens", model = 20488} +models.feet[14201] = { name = "Wood F Ledelsens" , enl = "wood f ledelsens", model = 20488} +models.feet[14202] = { name = "Creek M Clomps" , enl = "creek m clomps", model = 20488} +models.feet[14203] = { name = "Creek F Clomps" , enl = "creek f clomps", model = 20488} +models.feet[14204] = { name = "River Gaiters" , enl = "river gaiters", model = 20488} +models.feet[14205] = { name = "Dune Sandals" , enl = "dune sandals", model = 20488} +models.feet[14206] = { name = "Garrison Boots" , enl = "garrison boots", model = 20584} +models.feet[14207] = { name = "Noct Gaiters +1" , enl = "noct gaiters +1", model = 20613} +models.feet[15132] = { name = "Warrior's Calligae" , enl = "warrior's calligae", model = 20545} +models.feet[15133] = { name = "Melee Gaiters" , enl = "melee gaiters", model = 20547} +models.feet[15134] = { name = "Cleric's Duckbills" , enl = "cleric's duckbills", model = 20549} +models.feet[15135] = { name = "Sorcerer's Sabots" , enl = "sorcerer's sabots", model = 20551} +models.feet[15136] = { name = "Duelist's Boots" , enl = "duelist's boots", model = 20553} +models.feet[15137] = { name = "Assassin's Pouln." , enl = "assassin's poulaines", model = 20555} +models.feet[15138] = { name = "Valor Leggings" , enl = "valor leggings", model = 20557} +models.feet[15139] = { name = "Abyss Sollerets" , enl = "abyss sollerets", model = 20559} +models.feet[15140] = { name = "Monster Gaiters" , enl = "monster gaiters", model = 20561} +models.feet[15141] = { name = "Bard's Slippers" , enl = "bard's slippers", model = 20563} +models.feet[15142] = { name = "Scout's Socks" , enl = "scout's socks", model = 20565} +models.feet[15143] = { name = "Saotome Sune-Ate" , enl = "saotome sune-ate", model = 20567} +models.feet[15144] = { name = "Koga Kyahan" , enl = "koga kyahan", model = 20569} +models.feet[15145] = { name = "Wyrm Greaves" , enl = "wyrm greaves", model = 20571} +models.feet[15146] = { name = "Summoner's Pgch." , enl = "summoner's pigaches", model = 20573} +models.feet[15303] = { name = "Sha'ir Crackows" , enl = "sha'ir crackows", model = 20615} +models.feet[15304] = { name = "Sheikh Crackows" , enl = "sheikh crackows", model = 20615} +models.feet[15305] = { name = "Barone Gambieras" , enl = "barone gambieras", model = 20616} +models.feet[15306] = { name = "Conte Gambieras" , enl = "conte gambieras", model = 20616} +models.feet[15307] = { name = "Bison Gamashes" , enl = "bison gamashes", model = 20614} +models.feet[15308] = { name = "Brave's Gamashes" , enl = "brave's gamashes", model = 20614} +models.feet[15309] = { name = "Igqira Huaraches" , enl = "igqira huaraches", model = 20617} +models.feet[15310] = { name = "Genie Huaraches" , enl = "genie huaraches", model = 20617} +models.feet[15311] = { name = "Noct Gaiters" , enl = "noct gaiters", model = 20613} +models.feet[15312] = { name = "Mist Pumps" , enl = "mist pumps", model = 20610} +models.feet[15313] = { name = "Seer's Pumps" , enl = "seer's pumps", model = 20611} +models.feet[15314] = { name = "Garish Pumps" , enl = "garish pumps", model = 20610} +models.feet[15315] = { name = "Shade Leggings" , enl = "shade leggings", model = 20609} +models.feet[15316] = { name = "Seer's Pumps +1" , enl = "seer's pumps +1", model = 20611} +models.feet[15317] = { name = "Eisenschuhs" , enl = "eisenschuhs", model = 20618} +models.feet[15318] = { name = "Rubious Pumps" , enl = "rubious pumps", model = 20610} +models.feet[15319] = { name = "Shade Leggings +1" , enl = "shade leggings +1", model = 20609} +models.feet[15320] = { name = "Powder Boots" , enl = "powder boots", model = 20481} +models.feet[15321] = { name = "Kampfschuhs" , enl = "kampfschuhs", model = 20618} +models.feet[15322] = { name = "Herald's Gaiters" , enl = "herald's gaiters", model = 20501} +models.feet[15323] = { name = "Cure Clogs" , enl = "cure clogs", model = 20500} +models.feet[15324] = { name = "Caitiff's Socks" , enl = "caitiff's socks", model = 20503} +models.feet[15325] = { name = "Evoker's Boots" , enl = "evoker's boots", model = 20585} +models.feet[15326] = { name = "Gargoyle Boots" , enl = "gargoyle boots", model = 20582} +models.feet[15327] = { name = "Fuma Sune-Ate" , enl = "fuma sune-ate", model = 20539} +models.feet[15328] = { name = "Root Sabots" , enl = "root sabots", model = 20595} +models.feet[15329] = { name = "Blessed Pumps" , enl = "blessed pumps", model = 20622} +models.feet[15330] = { name = "Hmn. Sune-Ate" , enl = "hachiman sune-ate", model = 20621} +models.feet[15331] = { name = "Blessed Pumps +1" , enl = "blessed pumps +1", model = 20622} +models.feet[15332] = { name = "Hmn. Sune-Ate +1" , enl = "hachiman sune-ate +1", model = 20621} +models.feet[15333] = { name = "Lord's Sabatons" , enl = "lord's sabatons", model = 20619} +models.feet[15334] = { name = "Wise Pigaches" , enl = "wise pigaches", model = 20623} +models.feet[15335] = { name = "Wise Pigaches +1" , enl = "wise pigaches +1", model = 20623} +models.feet[15336] = { name = "Ysh. Sune-Ate +1" , enl = "yasha sune-ate +1", model = 20578} +models.feet[15337] = { name = "King's Sabatons" , enl = "king's sabatons", model = 20619} +models.feet[15338] = { name = "Vampire Boots" , enl = "vampire boots", model = 20583} +models.feet[15339] = { name = "Black Sollerets" , enl = "black sollerets", model = 20631} +models.feet[15340] = { name = "Onyx Sollerets" , enl = "onyx sollerets", model = 20631} +models.feet[15341] = { name = "Alumine Solerets" , enl = "alumine solerets", model = 20630} +models.feet[15342] = { name = "Luisant Solerets" , enl = "luisant solerets", model = 20630} +models.feet[15343] = { name = "Trader's Pigaches" , enl = "trader's pigaches", model = 20629} +models.feet[15344] = { name = "Baron's Pigaches" , enl = "baron's pigaches", model = 20629} +models.feet[15345] = { name = "Unicorn Leggings" , enl = "unicorn leggings", model = 20628} +models.feet[15346] = { name = "Ucn. Leggings +1" , enl = "unicorn leggings +1", model = 20628} +models.feet[15347] = { name = "Volans Greaves" , enl = "volans greaves", model = 20508} +models.feet[15348] = { name = "Mountain Gaiters" , enl = "mountain gaiters", model = 20501} +models.feet[15349] = { name = "Rutter Sabatons" , enl = "rutter sabatons", model = 20502} +models.feet[15350] = { name = "Rostrum Pumps" , enl = "rostrum pumps", model = 20611} +models.feet[15351] = { name = "Bounding Boots" , enl = "bounding boots", model = 20486} +models.feet[15352] = { name = "Ftr. Calligae +1" , enl = "fighter's calligae +1", model = 20544} +models.feet[15353] = { name = "Tpl. Gaiters +1" , enl = "temple gaiters +1", model = 20546} +models.feet[15354] = { name = "Hlr. Duckbills +1" , enl = "healer's duckbills +1", model = 20548} +models.feet[15355] = { name = "Wzd. Sabots +1" , enl = "wizard's sabots +1", model = 20550} +models.feet[15356] = { name = "Wlk. Boots +1" , enl = "warlock's boots +1", model = 20552} +models.feet[15357] = { name = "Rog. Poulaines +1" , enl = "rogue's poulaines +1", model = 20554} +models.feet[15358] = { name = "Glt. Leggings +1" , enl = "gallant leggings +1", model = 20556} +models.feet[15359] = { name = "Chs. Sollerets +1" , enl = "chaos sollerets +1", model = 20568} +models.feet[15360] = { name = "Bst. Gaiters +1" , enl = "beast gaiters +1", model = 20560} +models.feet[15361] = { name = "Chl. Slippers +1" , enl = "choral slippers +1", model = 20562} +models.feet[15362] = { name = "Htr. Socks +1" , enl = "hunter's socks +1", model = 20564} +models.feet[15363] = { name = "Myn. Sune-Ate +1" , enl = "myochin sune-ate +1", model = 20566} +models.feet[15364] = { name = "Nin. Kyahan +1" , enl = "ninja kyahan +1", model = 20568} +models.feet[15365] = { name = "Drn. Greaves +1" , enl = "drachen greaves +1", model = 20570} +models.feet[15366] = { name = "Evk. Pigaches +1" , enl = "evoker's pigaches +1", model = 20572} +models.feet[15661] = { name = "Homam Gambieras" , enl = "homam gambieras", model = 20639} +models.feet[15662] = { name = "Nashira Crackows" , enl = "nashira crackows", model = 20640} +models.feet[15663] = { name = "Crow Gaiters" , enl = "crow gaiters", model = 20642} +models.feet[15664] = { name = "Raven Gaiters" , enl = "raven gaiters", model = 20642} +models.feet[15665] = { name = "War. Calligae +1" , enl = "warrior's calligae +1", model = 20545} +models.feet[15666] = { name = "Mel. Gaiters +1" , enl = "melee gaiters +1", model = 20547} +models.feet[15667] = { name = "Clr. Duckbills +1" , enl = "cleric's duckbills +1", model = 20549} +models.feet[15668] = { name = "Src. Sabots +1" , enl = "sorcerer's sabots +1", model = 20551} +models.feet[15669] = { name = "Dls. Boots +1" , enl = "duelist's boots +1", model = 20553} +models.feet[15670] = { name = "Asn. Poulaines +1" , enl = "assassin's poulaines +1", model = 20555} +models.feet[15671] = { name = "Vlr. Leggings +1" , enl = "valor leggings +1", model = 20557} +models.feet[15672] = { name = "Abs. Sollerets +1" , enl = "abyss sollerets +1", model = 20559} +models.feet[15673] = { name = "Mst. Gaiters +1" , enl = "monster gaiters +1", model = 20561} +models.feet[15674] = { name = "Brd. Slippers +1" , enl = "bard's slippers +1", model = 20563} +models.feet[15675] = { name = "Sct. Socks +1" , enl = "scout's socks +1", model = 20565} +models.feet[15676] = { name = "Sao. Sune-Ate +1" , enl = "saotome sune-ate +1", model = 20567} +models.feet[15677] = { name = "Kog. Kyahan +1" , enl = "koga kyahan +1", model = 20569} +models.feet[15678] = { name = "Wym. Greaves +1" , enl = "wyrm greaves +1", model = 20571} +models.feet[15679] = { name = "Smn. Pigaches +1" , enl = "summoner's pigaches +1", model = 20573} +models.feet[15680] = { name = "Hydra Gaiters" , enl = "hydra gaiters", model = 20612} +models.feet[15681] = { name = "Hydra Spats" , enl = "hydra spats", model = 20608} +models.feet[15682] = { name = "Hydra Sollerets" , enl = "hydra sollerets", model = 20644} +models.feet[15683] = { name = "Hydra Boots" , enl = "hydra boots", model = 20643} +models.feet[15684] = { name = "Magus Charuqs" , enl = "magus charuqs", model = 20645} +models.feet[15685] = { name = "Corsair's Bottes" , enl = "corsair's bottes", model = 20647} +models.feet[15686] = { name = "Pup. Babouches" , enl = "puppetry babouches", model = 20649} +models.feet[15687] = { name = "Sipahi Boots" , enl = "sipahi boots", model = 20651} +models.feet[15688] = { name = "Amir Boots" , enl = "amir boots", model = 20654} +models.feet[15689] = { name = "Jaridah Nails" , enl = "jaridah nails", model = 20652} +models.feet[15690] = { name = "Yigit Crackows" , enl = "yigit crackows", model = 20656} +models.feet[15691] = { name = "Storm Gambieras" , enl = "storm gambieras", model = 20639} +models.feet[15692] = { name = "Storm Crackows" , enl = "storm crackows", model = 20640} +models.feet[15693] = { name = "Abtal Boots" , enl = "abtal boots", model = 20651} +models.feet[15694] = { name = "Akinji Nails" , enl = "akinji nails", model = 20652} +models.feet[15695] = { name = "Pln. Crackows" , enl = "pahluwan crackows", model = 20655} +models.feet[15696] = { name = "Marid Leggings" , enl = "marid leggings", model = 20496} +models.feet[15697] = { name = "Marid Leggings +1" , enl = "marid leggings +1", model = 20496} +models.feet[15698] = { name = "Sneaking Boots" , enl = "sneaking boots", model = 20576} +models.feet[15699] = { name = "Templar Sabatons" , enl = "templar sabatons", model = 20619} +models.feet[15700] = { name = "Skanda Boots" , enl = "skanda boots", model = 20654} +models.feet[15701] = { name = "Arborist Nails" , enl = "arborist nails", model = 20652} +models.feet[15702] = { name = "Spagyric Nails" , enl = "spagyric nails", model = 20652} +models.feet[15703] = { name = "Hydra Greaves" , enl = "hydra greaves", model = 20507} +models.feet[15704] = { name = "Hydra Greaves +1" , enl = "hydra greaves +1", model = 20507} +models.feet[15705] = { name = "Ataractic Solea" , enl = "ataractic solea", model = 20483} +models.feet[15706] = { name = "Silken Pigaches" , enl = "silken pigaches", model = 20499} +models.feet[15707] = { name = "Magi Pigaches" , enl = "magi pigaches", model = 20499} +models.feet[15708] = { name = "Earth Greaves" , enl = "earth greaves", model = 20540} +models.feet[15709] = { name = "Mercenary's Boots" , enl = "mercenary's boots", model = 20651} +models.feet[15710] = { name = "Volunteer's Nails" , enl = "volunteer's nails", model = 20652} +models.feet[15711] = { name = "Ares' Sollerets" , enl = "ares' sollerets", model = 20662} +models.feet[15712] = { name = "Enyo's Leggings" , enl = "enyo's leggings", model = 20482} +models.feet[15713] = { name = "Phobos's Sabat." , enl = "phobos's sabatons", model = 20502} +models.feet[15714] = { name = "Deimos's Leggings" , enl = "deimos's leggings", model = 20509} +models.feet[15715] = { name = "Skadi's Jambeaux" , enl = "skadi's jambeaux", model = 20663} +models.feet[15716] = { name = "Njord's Ledelsens" , enl = "njord's ledelsens", model = 20530} +models.feet[15717] = { name = "Freyr's Ledelsens" , enl = "freyr's ledelsens", model = 20589} +models.feet[15718] = { name = "Freya's Ledelsens" , enl = "freya's ledelsens", model = 20534} +models.feet[15719] = { name = "Usukane Sune-Ate" , enl = "usukane sune-ate", model = 20664} +models.feet[15720] = { name = "H.kazu Kyahan" , enl = "hoshikazu kyahan", model = 20484} +models.feet[15721] = { name = "T.kazu Sune-Ate" , enl = "tsukikazu sune-ate", model = 20578} +models.feet[15722] = { name = "Hikazu Sune-Ate" , enl = "hikazu sune-ate", model = 20489} +models.feet[15723] = { name = "Marduk's Crackows" , enl = "marduk's crackows", model = 20665} +models.feet[15724] = { name = "Anu's Gaiters" , enl = "anu's gaiters", model = 20501} +models.feet[15725] = { name = "Ea's Crackows" , enl = "ea's crackows", model = 20656} +models.feet[15726] = { name = "Enlil's Crackows" , enl = "enlil's crackows", model = 20639} +models.feet[15727] = { name = "Morrigan's Pgch." , enl = "morrigan's pigaches", model = 20666} +models.feet[15728] = { name = "Nemain's Sabots" , enl = "nemain's sabots", model = 20498} +models.feet[15729] = { name = "Bodb's Pigaches" , enl = "bodb's pigaches", model = 20629} +models.feet[15730] = { name = "Macha's Pigaches" , enl = "macha's pigaches", model = 20581} +models.feet[15731] = { name = "Khimaira Gamash." , enl = "khimaira gamashes", model = 20614} +models.feet[15732] = { name = "Stout Gamashes" , enl = "stout gamashes", model = 20614} +models.feet[15733] = { name = "Askar Gambieras" , enl = "askar gambieras", model = 20675} +models.feet[15734] = { name = "Denali Gamashes" , enl = "denali gamashes", model = 20676} +models.feet[15735] = { name = "Goliard Clogs" , enl = "goliard clogs", model = 20677} +models.feet[15736] = { name = "Trotter Boots" , enl = "trotter boots", model = 20491} +models.feet[15737] = { name = "Sarutobi Kyahan" , enl = "sarutobi kyahan", model = 20484} +models.feet[15738] = { name = "Tabin Boots" , enl = "tabin boots", model = 20504} +models.feet[15739] = { name = "Tabin Boots +1" , enl = "tabin boots +1", model = 20504} +models.feet[15740] = { name = "Shadow Sabatons" , enl = "shadow sabatons", model = 20680} +models.feet[15741] = { name = "Valk. Sabatons" , enl = "valkyrie's sabatons", model = 20680} +models.feet[15742] = { name = "Shadow Clogs" , enl = "shadow clogs", model = 20681} +models.feet[15743] = { name = "Valkyrie's Clogs" , enl = "valkyrie's clogs", model = 20681} +models.feet[15744] = { name = "Bowman's Led." , enl = "bowman's ledelsens", model = 20574} +models.feet[15745] = { name = "Vampiric Boots" , enl = "vampiric boots", model = 20576} +models.feet[15746] = { name = "Dancer's Shoes" , enl = "dancer's toe shoes", model = 20690} +models.feet[15747] = { name = "Dancer's Shoes" , enl = "dancer's toe shoes", model = 20691} +models.feet[15748] = { name = "Scholar's Loafers" , enl = "scholar's loafers", model = 20694} +models.feet[15749] = { name = "I.R. Sollerets" , enl = "iron ram sollerets", model = 20493} +models.feet[15750] = { name = "Fourth Sabatons" , enl = "fourth division sabatons", model = 20505} +models.feet[15751] = { name = "Cobra Pigaches" , enl = "cobra unit pigaches", model = 20499} +models.feet[15752] = { name = "Dream Boots" , enl = "dream boots", model = 20620} +models.feet[15753] = { name = "Dream Boots +1" , enl = "dream boots +1", model = 20620} +models.feet[15754] = { name = "Sprinter's Shoes" , enl = "sprinter's shoes", model = 20543} +models.feet[15755] = { name = "Iron Ram Greaves" , enl = "iron ram greaves", model = 20701} +models.feet[15756] = { name = "Fourth Schuhs" , enl = "fourth division schuhs", model = 20702} +models.feet[15757] = { name = "Cobra Leggings" , enl = "cobra unit leggings", model = 20699} +models.feet[15758] = { name = "Cobra Crackows" , enl = "cobra unit crackows", model = 20700} +models.feet[15759] = { name = "Mahant Sandals" , enl = "mahant sandals", model = 20483} +models.feet[15760] = { name = "Marabout Sandals" , enl = "marabout sandals", model = 20483} +models.feet[28208] = { name = "Ares' Sollerets +1" , enl = "ares' sollerets +1", model = 20662} +models.feet[28209] = { name = "Skd. Jambeaux +1" , enl = "skadi's jambeaux +1", model = 20663} +models.feet[28210] = { name = "Usk. Sune-Ate +1" , enl = "usukane sune-ate +1", model = 20664} +models.feet[28211] = { name = "Mdk. Crackows +1" , enl = "marduk's crackows +1", model = 20665} +models.feet[28212] = { name = "Morrigan's Pgch. +1" , enl = "morrigan's pigaches +1", model = 20666} +models.feet[28213] = { name = "Ker's Sollerets" , enl = "ker's sollerets", model = 20662} +models.feet[28214] = { name = "Sigyn's Jambeaux" , enl = "sigyn's jambeaux", model = 20663} +models.feet[28215] = { name = "Omo. Sune-Ate" , enl = "omodaka sune-ate", model = 20664} +models.feet[28216] = { name = "Nabu's Crackows" , enl = "nabu's crackows", model = 20665} +models.feet[28217] = { name = "Fea's Pigaches" , enl = "fea's pigaches", model = 20666} +models.feet[28218] = { name = "Ate's Sollerets" , enl = "ate's sollerets", model = 20509} +models.feet[28219] = { name = "Idi's Ledelsens" , enl = "idi's ledelsens", model = 20534} +models.feet[28220] = { name = "Genta Sune-Ate" , enl = "genta sune-ate", model = 20489} +models.feet[28221] = { name = "Namru's Crackows" , enl = "namru's crackows", model = 20639} +models.feet[28222] = { name = "Neit's Pigaches" , enl = "neit's pigaches", model = 20581} +models.feet[28223] = { name = "Pumm. Calligae" , enl = "pummeler's calligae" , model = 20544 } +models.feet[28224] = { name = "Anch. Gaiters" , enl = "anchorite's gaiters" , model = 20546 } +models.feet[28225] = { name = "Theo. Duckbills" , enl = "theophany duckbills" , model = 20548 } +models.feet[28226] = { name = "Spaekona's Sabots" , enl = "spaekona's sabots" , model = 20550 } +models.feet[28227] = { name = "Atrophy Boots" , enl = "atrophy boots" , model = 20552 } +models.feet[28228] = { name = "Pillager's Poulaines" , enl = "pillager's poulaines" , model = 20554 } +models.feet[28229] = { name = "Rev. Leggings" , enl = "reverence leggings" , model = 20556 } +models.feet[28230] = { name = "Ignom. Sollerets" , enl = "ignominy sollerets" , model = 20558 } +models.feet[28231] = { name = "Totemic Gaiters" , enl = "totemic gaiters" , model = 20560 } +models.feet[28232] = { name = "Brioso Slippers" , enl = "brioso slippers" , model = 20562 } +models.feet[28233] = { name = "Orion Socks" , enl = "orion socks" , model = 20564 } +models.feet[28234] = { name = "Wakido Sune-Ate" , enl = "wakido sune-ate" , model = 20566 } +models.feet[28235] = { name = "Hachiya Kyahan" , enl = "hachiya kyahan" , model = 20568 } +models.feet[28236] = { name = "Vishap Greaves" , enl = "vishap greaves" , model = 20570 } +models.feet[28237] = { name = "Conv. Pigaches" , enl = "convoker's pigaches" , model = 20572 } +models.feet[28238] = { name = "Assim. Charuqs" , enl = "assimilator's charuqs" , model = 20645 } +models.feet[28239] = { name = "Lak. Bottes" , enl = "laksamana's bottes" , model = 20647 } +models.feet[28240] = { name = "Foire Babouches" , enl = "foire babouches" , model = 20649 } +models.feet[28241] = { name = "Maxixi Toeshoes" , enl = "maxixi toeshoes" , model = 20690 } +models.feet[28242] = { name = "Maxixi Toeshoes" , enl = "maxixi toeshoes" , model = 20691 } +models.feet[28243] = { name = "Acad. Loafers" , enl = "academic's loafers" , model = 20694 } +models.feet[28244] = { name = "Pumm. Calligae +1" , enl = "pummeler's calligae +1" , model = 20544 } +models.feet[28245] = { name = "Anch. Gaiters +1" , enl = "anchorite's gaiters +1" , model = 20546 } +models.feet[28246] = { name = "Theo. Duckbills +1" , enl = "theophany duckbills +1" , model = 20548 } +models.feet[28247] = { name = "Spae. Sabots +1" , enl = "spaekona's sabots +1" , model = 20550 } +models.feet[28248] = { name = "Atrophy Boots +1" , enl = "atrophy boots +1" , model = 20552 } +models.feet[28249] = { name = "Pill. Poulaines +1" , enl = "pillager's poulaines +1" , model = 20554 } +models.feet[28250] = { name = "Rev. Leggings +1" , enl = "reverence leggings +1" , model = 20556 } +models.feet[28251] = { name = "Igno. Sollerets +1" , enl = "ignominy sollerets +1" , model = 20558 } +models.feet[28252] = { name = "Tot. Gaiters +1" , enl = "totemic gaiters +1" , model = 20560 } +models.feet[28253] = { name = "Brioso Slippers +1" , enl = "brioso slippers +1" , model = 20562 } +models.feet[28254] = { name = "Orion Socks +1" , enl = "orion socks +1" , model = 20564 } +models.feet[28255] = { name = "Waki. Sune-Ate +1" , enl = "wakido sune-ate +1" , model = 20566 } +models.feet[28256] = { name = "Hachi. Kyahan +1" , enl = "hachiya kyahan +1" , model = 20568 } +models.feet[28257] = { name = "Vishap Greaves +1" , enl = "vishap greaves +1" , model = 20570 } +models.feet[28258] = { name = "Con. Pigaches +1" , enl = "convoker's pigaches +1" , model = 20572 } +models.feet[28259] = { name = "Assim. Charuqs +1" , enl = "assimilator's charuqs +1" , model = 20645 } +models.feet[28260] = { name = "Lak. Bottes +1" , enl = "laksamana's bottes +1" , model = 20647 } +models.feet[28261] = { name = "Foire Bab. +1" , enl = "foire babouches +1" , model = 20649 } +models.feet[28262] = { name = "Maxixi Toe. +1" , enl = "maxixi toeshoes +1" , model = 20690 } +models.feet[28263] = { name = "Maxixi Toe. +1" , enl = "maxixi toeshoes +1" , model = 20691 } +models.feet[28264] = { name = "Acad. Loafers +1" , enl = "academic's loafers +1" , model = 20694 } +models.feet[28265] = { name = "Geo. Sandals +1" , enl = "geomancy sandals +1" , model = 20788 } +models.feet[28266] = { name = "Runeist Bottes +1" , enl = "runeist bottes +1" , model = 20818 } +models.feet[28302] = { name = "Thatch Boots" , enl = "thatch boots" , model = 20846 } +models.feet[28304] = { name = "Litany Clogs" , enl = "litany clogs" , model = 20500 } +models.feet[28305] = { name = "Ejekamal Boots" , enl = "ejekamal boots" , model = 20808 } +models.feet[28306] = { name = "Outrider Greaves" , enl = "outrider greaves" , model = 20485 } +models.feet[28307] = { name = "Espial Socks" , enl = "espial socks" , model = 20503 } +models.feet[28308] = { name = "Wayfarer Clogs" , enl = "wayfarer clogs" , model = 20500 } +models.feet[28309] = { name = "Temachtiani Boots" , enl = "temachtiani boots" , model = 20582 } +models.feet[28311] = { name = "Manabyss Pigaches" , enl = "manabyss pigaches" , model = 20581 } +models.feet[28312] = { name = "Scamp's Sollerets" , enl = "scamp's sollerets" , model = 20493 } +models.feet[28313] = { name = "Daihanshi Habaki" , enl = "daihanshi habaki" , model = 20497 } +models.feet[28320] = { name = "Kar. Sollerets +1" , enl = "Karieyh sollerets +1" , model = 20822 } +models.feet[28321] = { name = "Thur. Boots +1" , enl = "Thurandaut boots +1" , model = 20823 } +models.feet[28322] = { name = "Orvail Souliers +1" , enl = "Orvail souliers +1" , model = 20824 } +models.feet[28324] = { name = "Alliance Boots" , enl = "alliance boots" , model = ' ' } +models.feet[28325] = { name = "Morass Boots" , enl = "morass boots", model = 20791} +models.feet[28326] = { name = "Woodland Boots" , enl = "woodland boots", model = 20791} +models.feet[28327] = { name = "Gorney Sollerets" , enl = "gorney sollerets", model = 20834} +models.feet[28328] = { name = "Shneddick Boots" , enl = "shneddick boots", model = 20835} +models.feet[28329] = { name = "Weather. Souliers" , enl = "weatherspoon souliers", model = 20836} +models.feet[28331] = { name = "Uk'uxkaj Boots" , enl = "uk'uxkaj boots", model = 20810} +models.feet[28332] = { name = "Cizin Greaves" , enl = "cizin greaves", model = 20754} +models.feet[28333] = { name = "Otronif Boots" , enl = "otronif boots", model = 20727} +models.feet[28334] = { name = "Iuitl Gaiters" , enl = "iuitl gaiters", model = 20763} +models.feet[28335] = { name = "Gende. Galoshes" , enl = "gendewitha galoshes", model = 20745} +models.feet[28336] = { name = "Hagondes Sabots" , enl = "hagondes sabots", model = 20742} +models.feet[28337] = { name = "Whirlpool Greaves" , enl = "whirlpool greaves", model = 20808} +models.feet[28338] = { name = "Mikinaak Greaves" , enl = "mikinaak greaves", model = 20829} +models.feet[28339] = { name = "Manibozho boots" , enl = "manibozho boots", model = 20830} +models.feet[28340] = { name = "Bokwus boots" , enl = "bokwus boots", model = 20831} +models.feet[28341] = { name = "Agrarian Boots" , enl = "agrarian boots", model = 20585} +models.feet[28342] = { name = "Orvail Souliers" , enl = "orvail souliers", model = 20824} +models.feet[28343] = { name = "Chocaliztli Boots" , enl = "chocaliztli boots", model = 20808} +models.feet[28344] = { name = "Thurandaut Boots" , enl = "thurandaut boots", model = 20823} +models.feet[28345] = { name = "Karieyh Sollerets" , enl = "karieyh sollerets", model = 20822} +models.feet[28346] = { name = "Geomancy Sandals" , enl = "geomancy sandals", model = 20788} +models.feet[28347] = { name = "Runeist Bottes" , enl = "runeist bottes", model = 20818} + +-- Most blanks are currently in "unused" +models.feet["Blanks"] = { 20833 } + +models.feet["Unknown"] = { 20592, 20593, 20698 } +models.feet["Unused"] = { 20512, 20513, 20514, 20515, 20516, 20517, 20518, 20519, 20520, 20521, + 20522, 20523, 20524, 20525, 20526, 20527, 20528, 20529, 20531, 20577, + 20588, 20591, 20594, 20597, 20598, 20599, 20600, 20601, 20603, 20604, + 20605, 20606, 20607, 20624, 20625, 20626, 20627, 20632, 20633, 20634, + 20635, 20636, 20657, 20658, 20660, 20661, 20667, 20668, 20669, 20670, + 20671, 20672, 20673, 20674, 20678, 20683, 20684, 20685, 20686, 20692, + 20693, 20697, 20704, 20708, 20709, 20713, 20715, 20717, 20718, 20723, + 20724, 20725, 20787, 20780, 20781, 20782, 20783, 20787, 20789, 20792, + 20793, 20794, 20795, 20796, 20797, 20798, 20799, 20809, 20811, 20812, + 20813, 20814, 20815, 20825, 20826 } diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/hands.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/hands.lua new file mode 100644 index 0000000..138ef41 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/hands.lua @@ -0,0 +1,904 @@ +-- Copyright © 2013, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +models.hands = {} + +models.hands["None"] = { name = "empty", model = 12288 } + +models.hands[10300] = { name = "Myrmex Mittens" , enl = "myrmex mittens", model = 12344} +models.hands[10301] = { name = "Arciten Gauntlets" , enl = "arciten gauntlets", model = 12490} +models.hands[10302] = { name = "Punishing Gloves" , enl = "punishing gloves", model = 12342} +models.hands[10303] = { name = "Hexed Gauntlets" , enl = "hexed gauntlets", model = 12293} +models.hands[10304] = { name = "Hexed Tekko" , enl = "hexed tekko", model = 12297} +models.hands[10305] = { name = "Hexed Wristbands" , enl = "hexed wristbands", model = 12459} +models.hands[10306] = { name = "Hexed Gages" , enl = "hexed gages", model = 12308} +models.hands[10307] = { name = "Hexed Cuffs" , enl = "hexed cuffs", model = 12306} +models.hands[10308] = { name = "Hexed Gantl. -1" , enl = "hexed gauntlets -1", model = 12293} +models.hands[10309] = { name = "Hexed Tekko -1" , enl = "hexed tekko -1", model = 12297} +models.hands[10310] = { name = "Hexed Wrist. -1" , enl = "hexed wristbands -1", model = 12459} +models.hands[10311] = { name = "Hexed Gages -1" , enl = "hexed gages -1", model = 12309} +models.hands[10312] = { name = "Hexed Cuffs -1" , enl = "hexed cuffs -1", model = 12306} +models.hands[10313] = { name = "Paalaka Gloves" , enl = "paalaka gloves", model = 12399} +models.hands[10314] = { name = "Aneirin's Gloves" , enl = "aneirin's gloves", model = 12400} +models.hands[10315] = { name = "Alcedo Gauntlets" , enl = "alcedo gauntlets", model = 12401} +models.hands[10316] = { name = "Dux Fng. Gnt." , enl = "dux finger gauntlets", model = 12624} +models.hands[10317] = { name = "Dux Fng. Gnt. +1" , enl = "dux finger gauntlets +1", model = 12624} +models.hands[10318] = { name = "Chelona Gloves" , enl = "chelona gloves", model = 12625} +models.hands[10319] = { name = "Chl. Gloves +1" , enl = "chelona gloves +1", model = 12625} +models.hands[10320] = { name = "Enif Manopolas" , enl = "enif manopolas", model = 12447} +models.hands[10321] = { name = "Adhara Gages" , enl = "adhara gages", model = 12448} +models.hands[10322] = { name = "Murzim Manopolas" , enl = "murzim manopolas", model = 12447} +models.hands[10323] = { name = "Shedir Gages" , enl = "shedir gages", model = 12448} +models.hands[10324] = { name = "Leoht Gloves" , enl = "leoht gloves", model = 12311} +models.hands[10325] = { name = "Hieros Mittens" , enl = "hieros mittens", model = 12430} +models.hands[10382] = { name = "Dream Mittens" , enl = "dream mittens", model = 12410} +models.hands[10383] = { name = "Dream Mittens +1" , enl = "dream mittens +1", model = 12410} +models.hands[10500] = { name = "Ogier's Gauntlets" , enl = "ogier's gauntlets", model = 12613} +models.hands[10501] = { name = "Athos's Gloves" , enl = "athos's gloves", model = 12614} +models.hands[10502] = { name = "Rubeus Gloves" , enl = "rubeus gloves", model = 12615} +models.hands[10503] = { name = "Brego Gloves" , enl = "brego gloves", model = 12408} +models.hands[10504] = { name = "Iuvenalis Mittens" , enl = "iuvenalis mittens", model = 12345} +models.hands[10505] = { name = "Nomkahpa Mittens" , enl = "nomkahpa mittens", model = 12417} +models.hands[10506] = { name = "Avant Gauntlets" , enl = "avant gauntlets", model = 12317} +models.hands[10507] = { name = "Avant Gauntlets +1" , enl = "avant gauntlets +1", model = 12317} +models.hands[10508] = { name = "Kacura Mittens" , enl = "kacura mittens", model = 12398} +models.hands[10509] = { name = "Kacura Mittens +1" , enl = "kacura mittens +1", model = 12398} +models.hands[10510] = { name = "Sweven Mitts" , enl = "sweven mitts", model = 12419} +models.hands[10511] = { name = "Sweven Mitts +1" , enl = "sweven mitts +1", model = 12419} +models.hands[10512] = { name = "Calma Gauntlets" , enl = "calma gauntlets", model = 12569} +models.hands[10513] = { name = "Mustela Gloves" , enl = "mustela gloves", model = 12536} +models.hands[10514] = { name = "Magavan Mitts" , enl = "magavan mitts", model = 12557} +models.hands[10515] = { name = "Miodio Gloves" , enl = "miodio gloves", model = 12397} +models.hands[10516] = { name = "Antias Mitts" , enl = "antias mitts", model = 12540} +models.hands[10517] = { name = "Repartie Gloves" , enl = "repartie gloves", model = 12342} +models.hands[10518] = { name = "Ayao's Gages" , enl = "ayao's gages", model = 12448} +models.hands[10519] = { name = "Rheic Mitts" , enl = "rheic mitts", model = 12483} +models.hands[10520] = { name = "Rheic Mitts +1" , enl = "rheic mitts +1", model = 12483} +models.hands[10521] = { name = "Rheic Mitts +2" , enl = "rheic mitts +2", model = 12483} +models.hands[10522] = { name = "Rheic Mitts +3" , enl = "rheic mitts +3", model = 12483} +models.hands[10523] = { name = "Phorcys Mitts" , enl = "phorcys mitts", model = 12483} +models.hands[10524] = { name = "Euxine Gloves" , enl = "euxine gloves", model = 12484} +models.hands[10525] = { name = "Euxine Gloves +1" , enl = "euxine gloves +1", model = 12484} +models.hands[10526] = { name = "Euxine Gloves +2" , enl = "euxine gloves +2", model = 12484} +models.hands[10527] = { name = "Euxine Gloves +3" , enl = "euxine gloves +3", model = 12484} +models.hands[10528] = { name = "Thaumas Gloves" , enl = "thaumas gloves", model = 12484} +models.hands[10529] = { name = "Tethyan Cuffs" , enl = "tethyan cuffs", model = 12485} +models.hands[10530] = { name = "Tethyan Cuffs +1" , enl = "tethyan cuffs +1", model = 12485} +models.hands[10531] = { name = "Tethyan Cuffs +2" , enl = "tethyan cuffs +2", model = 12485} +models.hands[10532] = { name = "Tethyan Cuffs +3" , enl = "tethyan cuffs +3", model = 12485} +models.hands[10533] = { name = "Nares Cuffs" , enl = "nares cuffs", model = 12485} +models.hands[10534] = { name = "Hrafn Gauntlets" , enl = "hrafn gauntlets", model = 12611} +models.hands[10535] = { name = "Tenryu Tekko" , enl = "tenryu tekko", model = 12612} +models.hands[10536] = { name = "Kheper Wristbands" , enl = "kheper wristbands", model = 12608} +models.hands[10537] = { name = "Auspex Gages" , enl = "auspex gages", model = 12610} +models.hands[10538] = { name = "Paean Cuffs" , enl = "paean cuffs", model = 12609} +models.hands[10539] = { name = "Huginn Gauntlets" , enl = "huginn gauntlets", model = 12611} +models.hands[10540] = { name = "Tenryu Tekko +1" , enl = "tenryu tekko +1", model = 12612} +models.hands[10541] = { name = "Khepri Wristbands" , enl = "khepri wristbands", model = 12608} +models.hands[10542] = { name = "Spurrina Gages" , enl = "spurrina gages", model = 12610} +models.hands[10543] = { name = "Iaso Cuffs" , enl = "iaso cuffs", model = 12609} +models.hands[10544] = { name = "Ugol Moufles" , enl = "ugol moufles", model = 12438} +models.hands[10545] = { name = "Mavros Moufles" , enl = "mavros moufles", model = 12438} +models.hands[10546] = { name = "Urja Gloves" , enl = "urja gloves", model = 12338} +models.hands[10547] = { name = "Sthira Gloves" , enl = "sthira gloves", model = 12338} +models.hands[10548] = { name = "Spolia Cuffs" , enl = "spolia cuffs", model = 12437} +models.hands[10549] = { name = "Opima Cuffs" , enl = "opima cuffs", model = 12437} +models.hands[10690] = { name = "War. Mufflers +2" , enl = "warrior's mufflers +2", model = 12353} +models.hands[10691] = { name = "Mel. Gloves +2" , enl = "melee gloves +2", model = 12355} +models.hands[10692] = { name = "Clr. Mitts +2" , enl = "cleric's mitts +2", model = 12357} +models.hands[10693] = { name = "Src. Gloves +2" , enl = "sorcerer's gloves +2", model = 12359} +models.hands[10694] = { name = "Dls. Gloves +2" , enl = "duelist's gloves +2", model = 12361} +models.hands[10695] = { name = "Asn. Armlets +2" , enl = "assassin's armlets +2", model = 12363} +models.hands[10696] = { name = "Vlr. Gauntlets +2" , enl = "valor gauntlets +2", model = 12365} +models.hands[10697] = { name = "Abs. Gauntlets +2" , enl = "abyss gauntlets +2", model = 12367} +models.hands[10698] = { name = "Mst. Gloves +2" , enl = "monster gloves +2", model = 12369} +models.hands[10699] = { name = "Brd. Cuffs +2" , enl = "bard's cuffs +2", model = 12371} +models.hands[10700] = { name = "Sct. Bracers +2" , enl = "scout's bracers +2", model = 12373} +models.hands[10701] = { name = "Sao. Kote +2" , enl = "saotome kote +2", model = 12375} +models.hands[10702] = { name = "Kog. Tekko +2" , enl = "koga tekko +2", model = 12377} +models.hands[10703] = { name = "Wym. Fng. Gnt. +2" , enl = "wyrm finger gauntlets +2", model = 12379} +models.hands[10704] = { name = "Smn. Bracers +2" , enl = "summoner's bracers +2", model = 12381} +models.hands[10705] = { name = "Mrg. Bazubands +2" , enl = "mirage bazubands +2", model = 12454} +models.hands[10706] = { name = "Comm. Gants +2" , enl = "commodore gants +2", model = 12456} +models.hands[10707] = { name = "Pantin Dastanas +2" , enl = "pantin dastanas +2", model = 12458} +models.hands[10708] = { name = "Etoile Bangles +2" , enl = "etoile bangles +2", model = 12592} +models.hands[10709] = { name = "Argute Bracers +2" , enl = "argute bracers +2", model = 12503} +models.hands[11104] = { name = "Rvg. Mufflers +2" , enl = "ravager's mufflers +2", model = 12570} +models.hands[11105] = { name = "Tantra Gloves +2" , enl = "tantra gloves +2", model = 12571} +models.hands[11106] = { name = "Orison Mitts +2" , enl = "orison mitts +2", model = 12572} +models.hands[11107] = { name = "Goetia Gloves +2" , enl = "goetia gloves +2", model = 12573} +models.hands[11108] = { name = "Estq. Ganthrt. +2" , enl = "estoqueur's gantherots +2", model = 12574} +models.hands[11109] = { name = "Raid. Armlets +2" , enl = "raider's armlets +2", model = 12575} +models.hands[11110] = { name = "Crd. Gauntlets +2" , enl = "creed gauntlets +2", model = 12576} +models.hands[11111] = { name = "Bale Gauntlets +2" , enl = "bale gauntlets +2", model = 12577} +models.hands[11112] = { name = "Frn. Manoplas +2" , enl = "ferine manoplas +2", model = 12578} +models.hands[11113] = { name = "Ad. Mnchtte. +2" , enl = "aoidos' manchettes +2", model = 12579} +models.hands[11114] = { name = "Syl. Glvltte. +2" , enl = "sylvan glovelettes +2", model = 12580} +models.hands[11115] = { name = "Unkai Kote +2" , enl = "unkai kote +2", model = 12581} +models.hands[11116] = { name = "Iga Tekko +2" , enl = "iga tekko +2", model = 12582} +models.hands[11117] = { name = "Lncr. Vmbrc. +2" , enl = "lancer's vambraces +2", model = 12583} +models.hands[11118] = { name = "Call. Bracers +2" , enl = "caller's bracers +2", model = 12584} +models.hands[11119] = { name = "Mv. Bazubands +2" , enl = "mavi bazubands +2", model = 12585} +models.hands[11120] = { name = "Nvrch. Gants +2" , enl = "navarch's gants +2", model = 12586} +models.hands[11121] = { name = "Cirque Guanti +2" , enl = "cirque guanti +2", model = 12587} +models.hands[11122] = { name = "Charis Bangles +2" , enl = "charis bangles +2", model = 12593} +models.hands[11123] = { name = "Svnt. Bracers +2" , enl = "savant's bracers +2", model = 12594} +models.hands[11204] = { name = "Rvg. Mufflers +1" , enl = "ravager's mufflers +1", model = 12570} +models.hands[11205] = { name = "Tantra Gloves +1" , enl = "tantra gloves +1", model = 12571} +models.hands[11206] = { name = "Orison Mitts +1" , enl = "orison mitts +1", model = 12572} +models.hands[11207] = { name = "Goetia Gloves +1" , enl = "goetia gloves +1", model = 12573} +models.hands[11208] = { name = "Estq. Ganthrt. +1" , enl = "estoqueur's gantherots +1", model = 12574} +models.hands[11209] = { name = "Raid. Armlets +1" , enl = "raider's armlets +1", model = 12575} +models.hands[11210] = { name = "Crd. Gauntlets +1" , enl = "creed gauntlets +1", model = 12576} +models.hands[11211] = { name = "Bale Gauntlets +1" , enl = "bale gauntlets +1", model = 12577} +models.hands[11212] = { name = "Frn. Manoplas +1" , enl = "ferine manoplas +1", model = 12578} +models.hands[11213] = { name = "Ad. Mnchtte. +1" , enl = "aoidos' manchettes +1", model = 12579} +models.hands[11214] = { name = "Syl. Glvltte. +1" , enl = "sylvan glovelettes +1", model = 12580} +models.hands[11215] = { name = "Unkai Kote +1" , enl = "unkai kote +1", model = 12581} +models.hands[11216] = { name = "Iga Tekko +1" , enl = "iga tekko +1", model = 12582} +models.hands[11217] = { name = "Lncr. Vmbrc. +1" , enl = "lancer's vambraces +1", model = 12583} +models.hands[11218] = { name = "Caller's Bracers +1" , enl = "caller's bracers +1", model = 12584} +models.hands[11219] = { name = "Mv. Bazubands +1" , enl = "mavi bazubands +1", model = 12585} +models.hands[11220] = { name = "Nvrch. Gants +1" , enl = "navarch's gants +1", model = 12586} +models.hands[11221] = { name = "Cirque Guanti +1" , enl = "cirque guanti +1", model = 12587} +models.hands[11222] = { name = "Charis Bangles +1" , enl = "charis bangles +1", model = 12593} +models.hands[11223] = { name = "Svnt. Bracers +1" , enl = "savant's bracers +1", model = 12594} +models.hands[11878] = { name = "Gules Mittens" , enl = "gules mittens", model = 12344} +models.hands[11879] = { name = "Gules Mittens +1" , enl = "gules mittens +1", model = 12344} +models.hands[11880] = { name = "Versa Mufflers" , enl = "versa mufflers", model = 12346} +models.hands[11881] = { name = "Versa Mufflers +1" , enl = "versa mufflers +1", model = 12346} +models.hands[11882] = { name = "Lore Cuffs" , enl = "lore cuffs", model = 12403} +models.hands[11883] = { name = "Lore Cuffs +1" , enl = "lore cuffs +1", model = 12403} +models.hands[11884] = { name = "Eradico Mitts" , enl = "eradico mitts", model = 12418} +models.hands[11885] = { name = "Schellenband" , enl = "schellenband", model = 12288} +models.hands[11886] = { name = "Ample Gloves" , enl = "ample gloves", model = 12338} +models.hands[11887] = { name = "Bellicus Dastanas" , enl = "bellicus dastanas", model = 12558} +models.hands[11888] = { name = "Bestia Mufflers" , enl = "bestia mufflers", model = 12561} +models.hands[11889] = { name = "Paragon Moufles" , enl = "paragon moufles", model = 12564} +models.hands[11890] = { name = "Skopos Bracers" , enl = "skopos bracers", model = 12537} +models.hands[11891] = { name = "Kokugetsu Kote" , enl = "kokugetsu kote", model = 12540} +models.hands[11892] = { name = "Spry Wristbands" , enl = "spry wristbands", model = 12543} +models.hands[11893] = { name = "Mederi Gants" , enl = "mederi gants", model = 12546} +models.hands[11894] = { name = "Literae Cuffs" , enl = "literae cuffs", model = 12549} +models.hands[11895] = { name = "Facio Gages" , enl = "facio gages", model = 12552} +models.hands[11896] = { name = "Ace's Mufflers" , enl = "ace's mufflers", model = 12351} +models.hands[11897] = { name = "Schutzen Mittens" , enl = "schutzen mittens", model = 12388} +models.hands[11898] = { name = "Wave Gages" , enl = "wave gages", model = 12423} +models.hands[11899] = { name = "Weald Gages" , enl = "weald gages", model = 12423} +models.hands[11900] = { name = "Savanna Gages" , enl = "savanna gages", model = 12423} +models.hands[11901] = { name = "Tropic Gages" , enl = "tropic gages", model = 12423} +models.hands[11902] = { name = "Strand Gages" , enl = "strand gages", model = 12423} +models.hands[11903] = { name = "Tide Gages" , enl = "tide gages", model = 12448} +models.hands[11904] = { name = "Thicket Gages" , enl = "thicket gages", model = 12448} +models.hands[11905] = { name = "Brook Gages" , enl = "brook gages", model = 12448} +models.hands[11906] = { name = "Wild Gages" , enl = "wild gages", model = 12448} +models.hands[11907] = { name = "Torrid Gages" , enl = "torrid gages", model = 12448} +models.hands[11908] = { name = "Brute Gauntlets" , enl = "brute gauntlets", model = 12396} +models.hands[11909] = { name = "Trigger Gloves" , enl = "trigger gloves", model = 12294} +models.hands[11910] = { name = "Derobade Mittens" , enl = "derobade mittens", model = 12304} +models.hands[11911] = { name = "Alcide's Mitts" , enl = "alcide's mitts", model = 12398} +models.hands[11912] = { name = "Alcide's Mitts +1" , enl = "alcide's mitts +1", model = 12398} +models.hands[11913] = { name = "Nemus Bazubands" , enl = "nemus bazubands", model = 12460} +models.hands[11914] = { name = "Nms. Bazubands +1" , enl = "nemus bazubands +1", model = 12460} +models.hands[11915] = { name = "Nebula Cuffs" , enl = "nebula cuffs", model = 12389} +models.hands[11916] = { name = "Nebula Cuffs +1" , enl = "nebula cuffs +1", model = 12389} +models.hands[11917] = { name = "Carapacho Cuffs" , enl = "carapacho cuffs", model = 12308} +models.hands[11918] = { name = "Khthonios Gloves" , enl = "khthonios gloves", model = 12309} +models.hands[11919] = { name = "Avesta Bangles" , enl = "avesta bangles", model = 12288} +models.hands[11920] = { name = "Melaco Mittens" , enl = "melaco mittens", model = 12302} +models.hands[11921] = { name = "Magma Gauntlets" , enl = "magma gauntlets", model = 12402} +models.hands[11922] = { name = "Pavor Gauntlets" , enl = "pavor gauntlets", model = 12439} +models.hands[11923] = { name = "Tjukurrpa Gauntlets" , enl = "tjukurrpa gauntlets", model = 12343} +models.hands[11924] = { name = "Alucinor Mitts" , enl = "alucinor mitts", model = 12507} +models.hands[11925] = { name = "Alruna's Gloves" , enl = "alruna's gloves", model = 12384} +models.hands[12048] = { name = "Ravager's Mufflers" , enl = "ravager's mufflers", model = 12570} +models.hands[12049] = { name = "Tantra Gloves" , enl = "tantra gloves", model = 12571} +models.hands[12050] = { name = "Orison Mitts" , enl = "orison mitts", model = 12572} +models.hands[12051] = { name = "Goetia Gloves" , enl = "goetia gloves", model = 12573} +models.hands[12052] = { name = "Estq. Gantherots" , enl = "estoqueur's gantherots", model = 12574} +models.hands[12053] = { name = "Raider's Armlets" , enl = "raider's armlets", model = 12575} +models.hands[12054] = { name = "Creed Gauntlets" , enl = "creed gauntlets", model = 12576} +models.hands[12055] = { name = "Bale Gauntlets" , enl = "bale gauntlets", model = 12577} +models.hands[12056] = { name = "Ferine Manoplas" , enl = "ferine manoplas", model = 12578} +models.hands[12057] = { name = "Aoidos' Mnchtte." , enl = "aoidos' manchettes", model = 12579} +models.hands[12058] = { name = "Syl. Glovelettes" , enl = "sylvan glovelettes", model = 12580} +models.hands[12059] = { name = "Unkai Kote" , enl = "unkai kote", model = 12581} +models.hands[12060] = { name = "Iga Tekko" , enl = "iga tekko", model = 12582} +models.hands[12061] = { name = "Lancer's Vambraces" , enl = "lancer's vambraces", model = 12583} +models.hands[12062] = { name = "Caller's Bracers" , enl = "caller's bracers", model = 12584} +models.hands[12063] = { name = "Mavi Bazubands" , enl = "mavi bazubands", model = 12585} +models.hands[12064] = { name = "Navarch's Gants" , enl = "navarch's gants", model = 12586} +models.hands[12065] = { name = "Cirque Guanti" , enl = "cirque guanti", model = 12587} +models.hands[12066] = { name = "Charis Bangles" , enl = "charis bangles", model = 12593} +models.hands[12067] = { name = "Savant's Bracers" , enl = "savant's bracers", model = 12594} +models.hands[12180] = { name = "Ebon Dastanas" , enl = "ebon dastanas", model = 12558} +models.hands[12181] = { name = "Furia Dastanas" , enl = "furia dastanas", model = 12559} +models.hands[12182] = { name = "Ebur Dastanas" , enl = "ebur dastanas", model = 12560} +models.hands[12183] = { name = "Ebon Mufflers" , enl = "ebon mufflers", model = 12561} +models.hands[12184] = { name = "Furia Mufflers" , enl = "furia mufflers", model = 12562} +models.hands[12185] = { name = "Ebur Mufflers" , enl = "ebur mufflers", model = 12563} +models.hands[12186] = { name = "Ebon Moufles" , enl = "ebon moufles", model = 12564} +models.hands[12187] = { name = "Furia Moufles" , enl = "furia moufles", model = 12565} +models.hands[12188] = { name = "Ebur Moufles" , enl = "ebur moufles", model = 12566} +models.hands[12189] = { name = "Ebon Gauntlets" , enl = "ebon gauntlets", model = 12567} +models.hands[12190] = { name = "Furia Gauntlets" , enl = "furia gauntlets", model = 12568} +models.hands[12191] = { name = "Ebur Gauntlets" , enl = "ebur gauntlets", model = 12569} +models.hands[12192] = { name = "Ebon Gloves" , enl = "ebon gloves", model = 12534} +models.hands[12193] = { name = "Furia Gloves" , enl = "furia gloves", model = 12535} +models.hands[12194] = { name = "Ebur Gloves" , enl = "ebur gloves", model = 12536} +models.hands[12195] = { name = "Ebon Bracers" , enl = "ebon bracers", model = 12537} +models.hands[12196] = { name = "Furia Bracers" , enl = "furia bracers", model = 12538} +models.hands[12197] = { name = "Ebur Bracers" , enl = "ebur bracers", model = 12539} +models.hands[12198] = { name = "Shikkoku Kote" , enl = "shikkoku kote", model = 12540} +models.hands[12199] = { name = "Shinku Kote" , enl = "shinku kote", model = 12541} +models.hands[12200] = { name = "Ginhaku Kote" , enl = "ginhaku kote", model = 12542} +models.hands[12201] = { name = "Ebon Wristbands" , enl = "ebon wristbands", model = 12543} +models.hands[12202] = { name = "Furia Wristbands" , enl = "furia wristbands", model = 12544} +models.hands[12203] = { name = "Ebur Wristbands" , enl = "ebur wristbands", model = 12545} +models.hands[12204] = { name = "Ebon Gants" , enl = "ebon gants", model = 12546} +models.hands[12205] = { name = "Furia Gants" , enl = "furia gants", model = 12547} +models.hands[12206] = { name = "Ebur Gants" , enl = "ebur gants", model = 12548} +models.hands[12207] = { name = "Ebon Cuffs" , enl = "ebon cuffs", model = 12549} +models.hands[12208] = { name = "Furia Cuffs" , enl = "furia cuffs", model = 12550} +models.hands[12209] = { name = "Ebur Cuffs" , enl = "ebur cuffs", model = 12551} +models.hands[12210] = { name = "Ebon Gages" , enl = "ebon gages", model = 12552} +models.hands[12211] = { name = "Furia Gages" , enl = "furia gages", model = 12553} +models.hands[12212] = { name = "Ebur Gages" , enl = "ebur gages", model = 12554} +models.hands[12213] = { name = "Ebon Mitts" , enl = "ebon mitts", model = 12555} +models.hands[12214] = { name = "Furia Mitts" , enl = "furia mitts", model = 12556} +models.hands[12215] = { name = "Ebur Mitts" , enl = "ebur mitts", model = 12557} +models.hands[12672] = { name = "Gauntlets" , enl = "gauntlets", model = 12290} +models.hands[12673] = { name = "Mythril Gauntlets" , enl = "mythril gauntlets", model = 12317} +models.hands[12674] = { name = "Gold Gauntlets" , enl = "gold gauntlets", model = 12313} +models.hands[12675] = { name = "Dst. Gauntlets" , enl = "darksteel gauntlets", model = 12310} +models.hands[12676] = { name = "Adaman Gauntlets" , enl = "adaman gauntlets", model = 12343} +models.hands[12677] = { name = "Kng. Handschuhs" , enl = "koenig handschuhs", model = 12383} +models.hands[12678] = { name = "Irn.Msk. Gauntlets" , enl = "iron musketeer's gauntlets", model = 12313} +models.hands[12679] = { name = "Judge's Gauntlets" , enl = "judge's gauntlets", model = 12318} +models.hands[12680] = { name = "Chain Mittens" , enl = "chain mittens", model = 12293} +models.hands[12681] = { name = "Silver Mittens" , enl = "silver mittens", model = 12293} +models.hands[12682] = { name = "Mufflers" , enl = "mufflers", model = 12300} +models.hands[12683] = { name = "Darksteel Mufflers" , enl = "darksteel mufflers", model = 12301} +models.hands[12684] = { name = "Thick Mufflers" , enl = "thick mufflers", model = 12346} +models.hands[12685] = { name = "Adaman Mufflers" , enl = "adaman mufflers", model = 12351} +models.hands[12686] = { name = "Ryl.Kgt. Mufflers" , enl = "royal knight's mufflers", model = 12301} +models.hands[12687] = { name = "Ryl.Sqr. Mufflers" , enl = "royal squire's mufflers", model = 12300} +models.hands[12688] = { name = "Scale Fng. Gnt." , enl = "scale finger gauntlets", model = 12316} +models.hands[12689] = { name = "Brass Fng. Gnt." , enl = "brass finger gauntlets", model = 12316} +models.hands[12690] = { name = "Seiryu's Kote" , enl = "seiryu's kote", model = 12387} +models.hands[12691] = { name = "Coral Fng. Gnt." , enl = "coral finger gauntlets", model = 12314} +models.hands[12692] = { name = "Dragon Fng. Gnt." , enl = "dragon finger gauntlets", model = 12348} +models.hands[12693] = { name = "Gavial Fng. Gnt." , enl = "gavial finger gauntlets", model = 12315} +models.hands[12694] = { name = "Ctr. F. Gauntlets" , enl = "centurion's finger gauntlets", model = 12314} +models.hands[12695] = { name = "Bronze Mittens +1" , enl = "bronze mittens +1", model = 12303} +models.hands[12696] = { name = "Leather Gloves" , enl = "leather gloves", model = 12289} +models.hands[12697] = { name = "Lizard Gloves" , enl = "lizard gloves", model = 12294} +models.hands[12698] = { name = "Studded Gloves" , enl = "studded gloves", model = 12289} +models.hands[12699] = { name = "Cuir Gloves" , enl = "cuir gloves", model = 12289} +models.hands[12700] = { name = "Raptor Gloves" , enl = "raptor gloves", model = 12295} +models.hands[12701] = { name = "Dusk Gloves" , enl = "dusk gloves", model = 12397} +models.hands[12702] = { name = "Tiger Gloves" , enl = "tiger gloves", model = 12338} +models.hands[12703] = { name = "Coeurl Gloves" , enl = "coeurl gloves", model = 12342} +models.hands[12704] = { name = "Bronze Mittens" , enl = "bronze mittens", model = 12303} +models.hands[12705] = { name = "Brass Mittens" , enl = "brass mittens", model = 12303} +models.hands[12706] = { name = "Iron Mittens" , enl = "iron mittens", model = 12302} +models.hands[12707] = { name = "Scorpion Mittens" , enl = "scorpion mittens", model = 12304} +models.hands[12708] = { name = "Darksteel Mittens" , enl = "darksteel mittens", model = 12302} +models.hands[12709] = { name = "Coral Mittens" , enl = "coral mittens", model = 12344} +models.hands[12710] = { name = "Bone Mittens" , enl = "bone mittens", model = 12304} +models.hands[12711] = { name = "Beetle Mittens" , enl = "beetle mittens", model = 12304} +models.hands[12712] = { name = "Tekko" , enl = "tekko", model = 12305} +models.hands[12713] = { name = "Cotton Tekko" , enl = "cotton tekko", model = 12305} +models.hands[12714] = { name = "Soil Tekko" , enl = "soil tekko", model = 12305} +models.hands[12715] = { name = "Kote" , enl = "kote", model = 12297} +models.hands[12716] = { name = "Shinobi Tekko" , enl = "shinobi tekko", model = 12292} +models.hands[12717] = { name = "Scp. Gnt. +1" , enl = "scorpion gauntlets +1", model = 12402} +models.hands[12718] = { name = "Iron Mittens +1" , enl = "iron mittens +1", model = 12302} +models.hands[12719] = { name = "Mrc. Tekko" , enl = "mercenary's tekko", model = 12305} +models.hands[12720] = { name = "Gloves" , enl = "gloves", model = 12309} +models.hands[12721] = { name = "Cotton Gloves" , enl = "cotton gloves", model = 12309} +models.hands[12722] = { name = "Bracers" , enl = "bracers", model = 12311} +models.hands[12723] = { name = "Wool Bracers" , enl = "wool bracers", model = 12311} +models.hands[12724] = { name = "Battle Bracers" , enl = "battle bracers", model = 12312} +models.hands[12725] = { name = "War Gloves" , enl = "war gloves", model = 12384} +models.hands[12726] = { name = "Mrc.Cpt. Gloves" , enl = "mercenary captain's gloves", model = 12309} +models.hands[12727] = { name = "Engineer's Gloves" , enl = "engineer's gloves", model = 12309} +models.hands[12728] = { name = "Cuffs" , enl = "cuffs", model = 12308} +models.hands[12729] = { name = "Linen Cuffs" , enl = "linen cuffs", model = 12308} +models.hands[12730] = { name = "Wool Cuffs" , enl = "wool cuffs", model = 12306} +models.hands[12731] = { name = "Velvet Cuffs" , enl = "velvet cuffs", model = 12306} +models.hands[12732] = { name = "Silk Cuffs" , enl = "silk cuffs", model = 12307} +models.hands[12733] = { name = "Noble's Mitts" , enl = "noble's mitts", model = 12345} +models.hands[12734] = { name = "Tct.Mgc. Cuffs" , enl = "tactician magician's cuffs", model = 12307} +models.hands[12736] = { name = "Mitts" , enl = "mitts", model = 12291} +models.hands[12737] = { name = "White Mitts" , enl = "white mitts", model = 12291} +models.hands[12738] = { name = "Linen Mitts" , enl = "linen mitts", model = 12298} +models.hands[12739] = { name = "Black Mitts" , enl = "black mitts", model = 12299} +models.hands[12740] = { name = "Silk Mitts" , enl = "silk mitts", model = 12299} +models.hands[12741] = { name = "Ludic Mitts" , enl = "ludic mitts", model = 12304} +models.hands[12742] = { name = "Rune Bangles" , enl = "rune bangles", model = 12288} +models.hands[12743] = { name = "Cmb.Cst. Mitts" , enl = "combat caster's mitts", model = 12298} +models.hands[12744] = { name = "Cuffs +1" , enl = "cuffs +1", model = 12308} +models.hands[12745] = { name = "Perle Moufles" , enl = "perle moufles", model = 12527} +models.hands[12746] = { name = "Aurore Gloves" , enl = "aurore gloves", model = 12529} +models.hands[12747] = { name = "Teal Cuffs" , enl = "teal cuffs", model = 12528} +models.hands[12748] = { name = "Thief's Kote" , enl = "thief's kote", model = 12292} +models.hands[12749] = { name = "Scentless Armlets" , enl = "scentless armlets", model = 12288} +models.hands[12750] = { name = "New Moon Armlets" , enl = "new moon armlets", model = 12288} +models.hands[12751] = { name = "Scp. Gauntlets" , enl = "scorpion gauntlets", model = 12402} +models.hands[12752] = { name = "Lgn. Mittens" , enl = "legionnaire's mittens", model = 12303} +models.hands[12753] = { name = "Ryl.Ftm. Gloves" , enl = "royal footman's gloves", model = 12289} +models.hands[12754] = { name = "Hume M Gloves" , enl = "hume m gloves", model = 12296} +models.hands[12755] = { name = "Elvaan Gloves" , enl = "elvaan gloves", model = 12296} +models.hands[12756] = { name = "Tarutaru Mitts" , enl = "tarutaru mitts", model = 12296} +models.hands[12757] = { name = "Mithran Gauntlets" , enl = "mithran gauntlets", model = 12296} +models.hands[12758] = { name = "Galkan Bracers" , enl = "galkan bracers", model = 12296} +models.hands[12759] = { name = "Elvaan Gauntlets" , enl = "elvaan gauntlets", model = 12296} +models.hands[12760] = { name = "Hume F Gloves" , enl = "hume f gloves", model = 12296} +models.hands[12761] = { name = "Custom M Gloves" , enl = "custom m gloves", model = 12319} +models.hands[12762] = { name = "Custom F Gloves" , enl = "custom f gloves", model = 12319} +models.hands[12763] = { name = "Magna Gauntlets" , enl = "magna gauntlets", model = 12319} +models.hands[12764] = { name = "Magna Gloves" , enl = "magna gloves", model = 12319} +models.hands[12765] = { name = "Wonder Mitts" , enl = "wonder mitts", model = 12319} +models.hands[12766] = { name = "Savage Gauntlets" , enl = "savage gauntlets", model = 12319} +models.hands[12767] = { name = "Elder's Bracers" , enl = "elder's bracers", model = 12319} +models.hands[12768] = { name = "Solid Fng. Gnt." , enl = "solid finger gauntlets", model = 12316} +models.hands[12769] = { name = "Chain Mittens +1" , enl = "chain mittens +1", model = 12293} +models.hands[12770] = { name = "Brass Mittens +1" , enl = "brass mittens +1", model = 12303} +models.hands[12771] = { name = "Brass Fng. Gnt. +1" , enl = "brass finger gauntlets +1", model = 12316} +models.hands[12772] = { name = "Silver Mittens +1" , enl = "silver mittens +1", model = 12293} +models.hands[12773] = { name = "Gloves +1" , enl = "gloves +1", model = 12309} +models.hands[12774] = { name = "Tekko +1" , enl = "tekko +1", model = 12305} +models.hands[12775] = { name = "Mitts +1" , enl = "mitts +1", model = 12291} +models.hands[12776] = { name = "Great Gloves" , enl = "great gloves", model = 12309} +models.hands[12777] = { name = "Cotton Tekko +1" , enl = "cotton tekko +1", model = 12305} +models.hands[12778] = { name = "Linen Cuffs +1" , enl = "linen cuffs +1", model = 12308} +models.hands[12779] = { name = "Bracers +1" , enl = "bracers +1", model = 12311 } +models.hands[12780] = { name = "Linen Mitts +1" , enl = "linen mitts +1", model = 12298} +models.hands[12781] = { name = "Soil Tekko +1" , enl = "soil tekko +1", model = 12305} +models.hands[12782] = { name = "Wool Cuffs +1" , enl = "wool cuffs +1", model = 12306} +models.hands[12783] = { name = "Wool Bracers +1" , enl = "wool bracers +1", model = 12311} +models.hands[12784] = { name = "Leather Gloves +1" , enl = "leather gloves +1", model = 12289} +models.hands[12785] = { name = "Fine Gloves" , enl = "fine gloves", model = 12294} +models.hands[12786] = { name = "Strong Gloves" , enl = "strong gloves", model = 12289} +models.hands[12787] = { name = "Cuir Gloves +1" , enl = "cuir gloves +1", model = 12289} +models.hands[12788] = { name = "Bone Mittens +1" , enl = "bone mittens +1", model = 12304} +models.hands[12789] = { name = "Beetle Mittens +1" , enl = "beetle mittens +1", model = 12304} +models.hands[12790] = { name = "Cpc. Mittens +1" , enl = "carapace mittens +1", model = 12304} +models.hands[12791] = { name = "Gauntlets +1" , enl = "gauntlets +1", model = 12290} +models.hands[12792] = { name = "Mufflers +1" , enl = "mufflers +1", model = 12300} +models.hands[12793] = { name = "Mage's Cuffs" , enl = "mage's cuffs", model = 12299} +models.hands[12794] = { name = "Mage's Mitts" , enl = "mage's mitts", model = 12299} +models.hands[12795] = { name = "Dino Gloves" , enl = "dino gloves", model = 12295} +models.hands[12796] = { name = "Asbestos Mitts" , enl = "asbestos mitts", model = 12291} +models.hands[12797] = { name = "Coarse Gauntlets" , enl = "coarse gauntlets", model = 12290} +models.hands[12798] = { name = "Zealot's Mitts" , enl = "zealot's mitts", model = 12291} +models.hands[12799] = { name = "Battle Gloves" , enl = "battle gloves", model = 12309} +models.hands[13700] = { name = "Beak Gloves" , enl = "beak gloves", model = 12294} +models.hands[13706] = { name = "Ogre Gloves" , enl = "ogre gloves", model = 12382} +models.hands[13713] = { name = "Carapace Mittens" , enl = "carapace mittens", model = 12304} +models.hands[13952] = { name = "Ochiudo's Kote" , enl = "ochiudo's kote", model = 12297} +models.hands[13953] = { name = "White Mitts +1" , enl = "white mitts +1", model = 12291} +models.hands[13954] = { name = "Silk Cuffs +1" , enl = "silk cuffs +1", model = 12307} +models.hands[13955] = { name = "Shinobi Tekko +1" , enl = "shinobi tekko +1", model = 12292} +models.hands[13956] = { name = "Scp. Mittens +1" , enl = "scorpion mittens +1", model = 12304} +models.hands[13957] = { name = "Yasha Tekko" , enl = "yasha tekko", model = 12386} +models.hands[13958] = { name = "Mythril Gnt. +1" , enl = "mythril gauntlets +1", model = 12317} +models.hands[13959] = { name = "Gilt Gauntlets" , enl = "gilt gauntlets", model = 12313} +models.hands[13960] = { name = "Beak Gloves +1" , enl = "beak gloves +1", model = 12294} +models.hands[13961] = { name = "Fighter's Mufflers" , enl = "fighter's mufflers", model = 12352} +models.hands[13962] = { name = "Temple Gloves" , enl = "temple gloves", model = 12354} +models.hands[13963] = { name = "Healer's Mitts" , enl = "healer's mitts", model = 12356} +models.hands[13964] = { name = "Wizard's Gloves" , enl = "wizard's gloves", model = 12358} +models.hands[13965] = { name = "Warlock's Gloves" , enl = "warlock's gloves", model = 12360} +models.hands[13966] = { name = "Rogue's Armlets" , enl = "rogue's armlets", model = 12362} +models.hands[13967] = { name = "Gallant Gauntlets" , enl = "gallant gauntlets", model = 12364} +models.hands[13968] = { name = "Chaos Gauntlets" , enl = "chaos gauntlets", model = 12366} +models.hands[13969] = { name = "Beast Gloves" , enl = "beast gloves", model = 12368} +models.hands[13970] = { name = "Choral Cuffs" , enl = "choral cuffs", model = 12370} +models.hands[13971] = { name = "Hunter's Bracers" , enl = "hunter's bracers", model = 12372} +models.hands[13972] = { name = "Myochin Kote" , enl = "myochin kote", model = 12374} +models.hands[13973] = { name = "Ninja Tekko" , enl = "ninja tekko", model = 12376} +models.hands[13974] = { name = "Drachen Fng. Gnt." , enl = "drachen finger gauntlets", model = 12378} +models.hands[13975] = { name = "Evoker's Bracers" , enl = "evoker's bracers", model = 12380} +models.hands[13976] = { name = "Dst. Mufflers +1" , enl = "darksteel mufflers +1", model = 12301} +models.hands[13977] = { name = "Light Gauntlets" , enl = "light gauntlets", model = 12290} +models.hands[13978] = { name = "Aiming Bracelets" , enl = "aiming bracelets", model = 12288} +models.hands[13979] = { name = "Silver Bangles" , enl = "silver bangles", model = 12288} +models.hands[13980] = { name = "Silver Bangles +1" , enl = "silver bangles +1", model = 12288} +models.hands[13981] = { name = "Turtle Bangles" , enl = "turtle bangles", model = 12288} +models.hands[13982] = { name = "Turtle Bangles +1" , enl = "turtle bangles +1", model = 12288} +models.hands[13983] = { name = "Gold Bangles" , enl = "gold bangles", model = 12288} +models.hands[13984] = { name = "Gold Bangles +1" , enl = "gold bangles +1", model = 12288} +models.hands[13985] = { name = "Platinum Bangles" , enl = "platinum bangles", model = 12288} +models.hands[13986] = { name = "Ptm. Bangles +1" , enl = "platinum bangles +1", model = 12288} +models.hands[13987] = { name = "Coral Bangles" , enl = "coral bangles", model = 12288} +models.hands[13988] = { name = "Merman's Bangles" , enl = "merman's bangles", model = 12288} +models.hands[13989] = { name = "Dst. Gauntlets +1" , enl = "darksteel gauntlets +1", model = 12310} +models.hands[13990] = { name = "Coral Fng. Gnt. +1" , enl = "coral finger gauntlets +1", model = 12314} +models.hands[13991] = { name = "Dragon F. Gnt. +1" , enl = "dragon finger gauntlets +1", model = 12348} +models.hands[13992] = { name = "Feral Gloves" , enl = "feral gloves", model = 12338} +models.hands[13993] = { name = "Torama Gloves" , enl = "torama gloves", model = 12342} +models.hands[13994] = { name = "Dst. Mittens +1" , enl = "darksteel mittens +1", model = 12302} +models.hands[13995] = { name = "Merman's Mittens" , enl = "merman's mittens", model = 12344} +models.hands[13996] = { name = "Kote +1" , enl = "kote +1", model = 12297} +models.hands[13997] = { name = "Battle Bracers +1" , enl = "battle bracers +1", model = 12312} +models.hands[13998] = { name = "War Gloves +1" , enl = "war gloves +1", model = 12384} +models.hands[13999] = { name = "Aristocrat's Mitts" , enl = "aristocrat's mitts", model = 12345} +models.hands[14000] = { name = "Silk Mitts +1" , enl = "silk mitts +1", model = 12299} +models.hands[14001] = { name = "Iron Fng. Gnt." , enl = "iron finger gauntlets", model = 12340} +models.hands[14002] = { name = "Iron Fng. Gnt. +1" , enl = "iron finger gauntlets +1", model = 12340} +models.hands[14003] = { name = "Steel Fng. Gnt." , enl = "steel finger gauntlets", model = 12341} +models.hands[14004] = { name = "Steel Fng. Gnt. +1" , enl = "steel finger gauntlets +1", model = 12341} +models.hands[14005] = { name = "Monsoon Tekko" , enl = "monsoon tekko", model = 12305} +models.hands[14006] = { name = "Zenith Mitts" , enl = "zenith mitts", model = 12395} +models.hands[14007] = { name = "Zenith Mitts +1" , enl = "zenith mitts +1", model = 12395} +models.hands[14008] = { name = "Cpc. Gauntlets" , enl = "carapace gauntlets", model = 12396} +models.hands[14009] = { name = "Cpc. Gauntlets +1" , enl = "carapace gauntlets +1", model = 12396} +models.hands[14010] = { name = "Black Gadlings" , enl = "black gadlings", model = 12439} +models.hands[14011] = { name = "Onyx Gadlings" , enl = "onyx gadlings", model = 12439} +models.hands[14012] = { name = "Thick Mufflers +1" , enl = "thick mufflers +1", model = 12346} +models.hands[14013] = { name = "Gnd.T.K. Gauntlets" , enl = "grand temple knight's gauntlets", model = 12317} +models.hands[14014] = { name = "Gnd.T.K. Bangles" , enl = "grand temple knight's bangles", model = 12288} +models.hands[14015] = { name = "Prf. Gloves" , enl = "praefectus's gloves", model = 12311} +models.hands[14016] = { name = "Mst.Cst. Mitts" , enl = "master caster's mitts", model = 12299} +models.hands[14017] = { name = "Mst.Cst. Bracelets" , enl = "master caster's bracelets", model = 12288} +models.hands[14018] = { name = "Gigas Bracelets" , enl = "gigas bracelets", model = 12288} +models.hands[14019] = { name = "Ogygos's Brc." , enl = "ogygos's bracelets", model = 12288} +models.hands[14020] = { name = "Enkelados's Brc." , enl = "enkelados's bracelets", model = 12288} +models.hands[14021] = { name = "Pallas's Bracelets" , enl = "pallas's bracelets", model = 12288} +models.hands[14022] = { name = "Alkyoneus's Brc." , enl = "alkyoneus's bracelets", model = 12288} +models.hands[14023] = { name = "Arhat's Tekko" , enl = "arhat's tekko", model = 12347} +models.hands[14024] = { name = "Devotee's Mitts" , enl = "devotee's mitts", model = 12291} +models.hands[14025] = { name = "Dvt. Mitts +1" , enl = "devotee's mitts +1", model = 12291} +models.hands[14026] = { name = "Hailstorm Tekko" , enl = "hailstorm tekko", model = 12305} +models.hands[14027] = { name = "Hailst. Tekko +1" , enl = "hailstorm tekko +1", model = 12305} +models.hands[14028] = { name = "Arhat's Tekko +1" , enl = "arhat's tekko +1", model = 12347} +models.hands[14029] = { name = "R.K. Mufflers +1" , enl = "royal knight's mufflers +1", model = 12301} +models.hands[14030] = { name = "R.K. Mufflers +2" , enl = "royal knight's mufflers +2", model = 12301} +models.hands[14031] = { name = "Bastokan Mittens" , enl = "bastokan mittens", model = 12303} +models.hands[14032] = { name = "Republic Mittens" , enl = "republic mittens", model = 12303} +models.hands[14033] = { name = "San. Mufflers" , enl = "san d'orian mufflers", model = 12300} +models.hands[14034] = { name = "Kingdom Mufflers" , enl = "kingdom mufflers", model = 12300} +models.hands[14035] = { name = "Irn.Msk. Gnt. +1" , enl = "iron musketeer's gauntlets +1", model = 12313} +models.hands[14036] = { name = "Irn.Msk. Gnt. +2" , enl = "iron musketeer's gauntlets +2", model = 12313} +models.hands[14037] = { name = "San. Gloves" , enl = "san d'orian gloves", model = 12289} +models.hands[14038] = { name = "Kingdom Gloves" , enl = "kingdom gloves", model = 12289} +models.hands[14039] = { name = "Bas. F. Gauntlets" , enl = "bastokan finger gauntlets", model = 12314} +models.hands[14040] = { name = "Rep. F. Gauntlets" , enl = "republic finger gauntlets", model = 12314} +models.hands[14041] = { name = "Susurrus Gauntlets" , enl = "susurrus gauntlets", model = 12522} +models.hands[14042] = { name = "Praeda Gauntlets" , enl = "praeda gauntlets", model = 12490} +models.hands[14043] = { name = "Windurstian Tekko" , enl = "windurstian tekko", model = 12305} +models.hands[14044] = { name = "Federation Tekko" , enl = "federation tekko", model = 12305} +models.hands[14045] = { name = "Win. Gloves" , enl = "windurstian gloves", model = 12309} +models.hands[14046] = { name = "Federation Gloves" , enl = "federation gloves", model = 12309} +models.hands[14047] = { name = "C.C. Mitts +1" , enl = "combat caster's mitts +1", model = 12298} +models.hands[14048] = { name = "C.C. Mitts +2" , enl = "combat caster's mitts +2", model = 12298} +models.hands[14049] = { name = "T.M. Cuffs +1" , enl = "tactician magician's cuffs +1", model = 12307} +models.hands[14050] = { name = "T.M. Cuffs +2" , enl = "tactician magician's cuffs +2", model = 12307} +models.hands[14051] = { name = "Alumine Moufles" , enl = "alumine moufles", model = 12438} +models.hands[14052] = { name = "Luisant Moufles" , enl = "luisant moufles", model = 12438} +models.hands[14053] = { name = "Trader's Cuffs" , enl = "trader's cuffs", model = 12437} +models.hands[14054] = { name = "Baron's Cuffs" , enl = "baron's cuffs", model = 12437} +models.hands[14055] = { name = "Unicorn Mittens" , enl = "unicorn mittens", model = 12436} +models.hands[14056] = { name = "Ucn. Mittens +1" , enl = "unicorn mittens +1", model = 12436} +models.hands[14057] = { name = "Ogre Gloves +1" , enl = "ogre gloves +1", model = 12382} +models.hands[14058] = { name = "Crimson Fng. Gnt." , enl = "crimson finger gauntlets", model = 12350} +models.hands[14059] = { name = "Blood Fng. Gnt." , enl = "blood finger gauntlets", model = 12350} +models.hands[14060] = { name = "Timarli Dastanas" , enl = "timarli dastanas", model = 12491} +models.hands[14061] = { name = "Kaiser Handschuhs" , enl = "kaiser handschuhs", model = 12383} +models.hands[14062] = { name = "Carbuncle Mitts" , enl = "carbuncle mitts", model = 12291} +models.hands[14063] = { name = "Prt. Bangles" , enl = "protecting bangles", model = 12288} +models.hands[14064] = { name = "Sand Gloves" , enl = "sand gloves", model = 12309} +models.hands[14065] = { name = "Garden Bangles" , enl = "garden bangles", model = 12288} +models.hands[14066] = { name = "Feronia's Bangles" , enl = "feronia's bangles", model = 12288} +models.hands[14067] = { name = "Tarasque Mitts" , enl = "tarasque mitts", model = 12299} +models.hands[14068] = { name = "Vgd. Gloves" , enl = "vagabond's gloves", model = 12392} +models.hands[14069] = { name = "Nomad's Gloves" , enl = "nomad's gloves", model = 12392} +models.hands[14070] = { name = "Fsh. Gloves" , enl = "fisherman's gloves", model = 12390} +models.hands[14071] = { name = "Angler's Gloves" , enl = "angler's gloves", model = 12390} +models.hands[14072] = { name = "Chocobo Gloves" , enl = "chocobo gloves", model = 12391} +models.hands[14073] = { name = "Rider's Gloves" , enl = "rider's gloves", model = 12391} +models.hands[14074] = { name = "Tarasque Mitts +1" , enl = "tarasque mitts +1", model = 12299} +models.hands[14075] = { name = "Andvaranauts" , enl = "andvaranauts", model = 12288} +models.hands[14076] = { name = "Hecatomb Mittens" , enl = "hecatomb mittens", model = 12388} +models.hands[14077] = { name = "Hct. Mittens +1" , enl = "hecatomb mittens +1", model = 12388} +models.hands[14078] = { name = "Errant Cuffs" , enl = "errant cuffs", model = 12389} +models.hands[14079] = { name = "Mahatma Cuffs" , enl = "mahatma cuffs", model = 12389} +models.hands[14816] = { name = "Armada Mufflers" , enl = "armada mufflers", model = 12351} +models.hands[14817] = { name = "Field Gloves" , enl = "field gloves", model = 12393} +models.hands[14818] = { name = "Worker Gloves" , enl = "worker gloves", model = 12393} +models.hands[14819] = { name = "Rasetsu Tekko" , enl = "rasetsu tekko", model = 12349} +models.hands[14820] = { name = "Rasetsu Tekko +1" , enl = "rasetsu tekko +1", model = 12349} +models.hands[14821] = { name = "Shura Kote" , enl = "shura kote", model = 12394} +models.hands[14822] = { name = "Shura Kote +1" , enl = "shura kote +1", model = 12394} +models.hands[14823] = { name = "Dragon Mittens" , enl = "dragon mittens", model = 12398} +models.hands[14824] = { name = "Dragon Mittens +1" , enl = "dragon mittens +1", model = 12398} +models.hands[14825] = { name = "Dusk Gloves +1" , enl = "dusk gloves +1", model = 12397} +models.hands[14826] = { name = "Austere Cuffs" , enl = "austere cuffs", model = 12403} +models.hands[14827] = { name = "Penance Cuffs" , enl = "penance cuffs", model = 12403} +models.hands[14828] = { name = "Gem Gauntlets" , enl = "gem gauntlets", model = 12343} +models.hands[14829] = { name = "Gavial Fng.Gnt. +1" , enl = "gavial finger gauntlets +1", model = 12315} +models.hands[14830] = { name = "Carpenter's Gloves" , enl = "carpenter's gloves", model = 12405} +models.hands[14831] = { name = "Smithy's Mitts" , enl = "smithy's mitts", model = 12406} +models.hands[14832] = { name = "Tanner's Gloves" , enl = "tanner's gloves", model = 12405} +models.hands[14833] = { name = "Marine M Gloves" , enl = "marine m gloves", model = 12296} +models.hands[14834] = { name = "Marine F Gloves" , enl = "marine f gloves", model = 12296} +models.hands[14835] = { name = "Wood Gauntlets" , enl = "wood gauntlets", model = 12296} +models.hands[14836] = { name = "Wood Gloves" , enl = "wood gloves", model = 12296} +models.hands[14837] = { name = "Creek M Mitts" , enl = "creek m mitts", model = 12296} +models.hands[14838] = { name = "Creek F Mitts" , enl = "creek f mitts", model = 12296} +models.hands[14839] = { name = "River Gauntlets" , enl = "river gauntlets", model = 12296} +models.hands[14840] = { name = "Dune Bracers" , enl = "dune bracers", model = 12296} +models.hands[14841] = { name = "Garrison Gloves" , enl = "garrison gloves", model = 12392} +models.hands[14842] = { name = "Ivory Mitts" , enl = "ivory mitts", model = 12291} +models.hands[14843] = { name = "Spiked Fng.Gnt." , enl = "spiked finger gauntlets", model = 12314} +models.hands[14844] = { name = "Rush Gloves" , enl = "rush gloves", model = 12289} +models.hands[14845] = { name = "Sly Gauntlets" , enl = "sly gauntlets", model = 12313} +models.hands[14846] = { name = "Sha'ir Gages" , enl = "sha'ir gages", model = 12423} +models.hands[14847] = { name = "Sheikh Gages" , enl = "sheikh gages", model = 12423} +models.hands[14848] = { name = "Barone Manopolas" , enl = "barone manopolas", model = 12424} +models.hands[14849] = { name = "Conte Manopolas" , enl = "conte manopolas", model = 12424} +models.hands[14850] = { name = "Bison Wristbands" , enl = "bison wristbands", model = 12422} +models.hands[14851] = { name = "Brv. Wristbands" , enl = "brave's wristbands", model = 12422} +models.hands[14852] = { name = "Igqira Manillas" , enl = "igqira manillas", model = 12425} +models.hands[14853] = { name = "Genie Manillas" , enl = "genie manillas", model = 12425} +models.hands[14854] = { name = "Noct Gloves" , enl = "noct gloves", model = 12421} +models.hands[14855] = { name = "Mist Mitts" , enl = "mist mitts", model = 12418} +models.hands[14856] = { name = "Seer's Mitts" , enl = "seer's mitts", model = 12419} +models.hands[14857] = { name = "Garish Mitts" , enl = "garish mitts", model = 12418} +models.hands[14858] = { name = "Shade Mittens" , enl = "shade mittens", model = 12417} +models.hands[14859] = { name = "Seer's Mitts +1" , enl = "seer's mitts +1", model = 12419} +models.hands[14860] = { name = "Eisenhentzes" , enl = "eisenhentzes", model = 12426} +models.hands[14861] = { name = "Rubious Mitts" , enl = "rubious mitts", model = 12418} +models.hands[14862] = { name = "Shade Mittens +1" , enl = "shade mittens +1", model = 12417} +models.hands[14863] = { name = "Kampfhentzes" , enl = "kampfhentzes", model = 12426} +models.hands[14864] = { name = "Palmer's Bangles" , enl = "palmer's bangles", model = 12288} +models.hands[14865] = { name = "Noct Gloves +1" , enl = "noct gloves +1", model = 12421} +models.hands[14866] = { name = "Concealing Cuffs" , enl = "concealing cuffs", model = 12308} +models.hands[14867] = { name = "Magical Mitts" , enl = "magical mitts", model = 12293} +models.hands[14868] = { name = "Arcane Cuffs" , enl = "arcane cuffs", model = 12437} +models.hands[14869] = { name = "Noritsune Kote" , enl = "noritsune kote", model = 12297} +models.hands[14870] = { name = "Trainer's Wrist." , enl = "trainer's wristbands", model = 12311} +models.hands[14871] = { name = "Trainer's Gloves" , enl = "trainer's gloves", model = 12289} +models.hands[14872] = { name = "Ostreger Mitts" , enl = "ostreger mitts", model = 12291} +models.hands[14873] = { name = "Bandomusha Kote" , enl = "bandomusha kote", model = 12297} +models.hands[14874] = { name = "Horomusha Kote" , enl = "horomusha kote", model = 12297} +models.hands[14875] = { name = "Blessed Mitts" , enl = "blessed mitts", model = 12430} +models.hands[14876] = { name = "Hachiman Kote" , enl = "hachiman kote", model = 12429} +models.hands[14877] = { name = "Blessed Mitts +1" , enl = "blessed mitts +1", model = 12430} +models.hands[14878] = { name = "Hachiman Kote +1" , enl = "hachiman kote +1", model = 12429} +models.hands[14879] = { name = "Lord's Gauntlets" , enl = "lord's gauntlets", model = 12427} +models.hands[14880] = { name = "Wise Gloves" , enl = "wise gloves", model = 12431} +models.hands[14881] = { name = "Wise Gloves +1" , enl = "wise gloves +1", model = 12431} +models.hands[14882] = { name = "Yasha Tekko +1" , enl = "yasha tekko +1", model = 12386} +models.hands[14883] = { name = "King's Gauntlets" , enl = "king's gauntlets", model = 12427} +models.hands[14884] = { name = "Mycophile Cuffs" , enl = "mycophile cuffs", model = 12403} +models.hands[14885] = { name = "Sennight Bangles" , enl = "sennight bangles", model = 12288} +models.hands[14886] = { name = "Heavy Gauntlets" , enl = "heavy gauntlets", model = 12310} +models.hands[14887] = { name = "Ocelot Gloves" , enl = "ocelot gloves", model = 12519} +models.hands[14888] = { name = "Augur's Gloves" , enl = "augur's gloves", model = 12511} +models.hands[14889] = { name = "Barbarian Mittens" , enl = "barbarian mittens", model = 12293} +models.hands[14890] = { name = "Ftr. Mufflers +1" , enl = "fighter's mufflers +1", model = 12352} +models.hands[14891] = { name = "Tpl. Gloves +1" , enl = "temple gloves +1", model = 12354} +models.hands[14892] = { name = "Hlr. Mitts +1" , enl = "healer's mitts +1", model = 12356} +models.hands[14893] = { name = "Wzd. Gloves +1" , enl = "wizard's gloves +1", model = 12358} +models.hands[14894] = { name = "Wlk. Gloves +1" , enl = "warlock's gloves +1", model = 12360} +models.hands[14895] = { name = "Rog. Armlets +1" , enl = "rogue's armlets +1", model = 12362} +models.hands[14896] = { name = "Glt. Gauntlets +1" , enl = "gallant gauntlets +1", model = 12364} +models.hands[14897] = { name = "Chs. Gauntlets +1" , enl = "chaos gauntlets +1", model = 12366} +models.hands[14898] = { name = "Bst. Gloves +1" , enl = "beast gloves +1", model = 12368} +models.hands[14899] = { name = "Chl. Cuffs +1" , enl = "choral cuffs +1", model = 12370} +models.hands[14900] = { name = "Htr. Bracers +1" , enl = "hunter's bracers +1", model = 12372} +models.hands[14901] = { name = "Myn. Kote +1" , enl = "myochin kote +1", model = 12374} +models.hands[14902] = { name = "Nin. Tekko +1" , enl = "ninja tekko +1", model = 12376} +models.hands[14903] = { name = "Drn. Fng. Gnt. +1" , enl = "drachen finger gauntlets +1", model = 12378} +models.hands[14904] = { name = "Evk. Bracers +1" , enl = "evoker's bracers +1", model = 12380} +models.hands[14905] = { name = "Homam Manopolas" , enl = "homam manopolas", model = 12447} +models.hands[14906] = { name = "Nashira Gages" , enl = "nashira gages", model = 12448} +models.hands[14907] = { name = "Crow Bracers" , enl = "crow bracers", model = 12450} +models.hands[14908] = { name = "Raven Bracers" , enl = "raven bracers", model = 12450} +models.hands[14909] = { name = "War. Mufflers +1" , enl = "warrior's mufflers +1", model = 12353} +models.hands[14910] = { name = "Mel. Gloves +1" , enl = "melee gloves +1", model = 12355} +models.hands[14911] = { name = "Clr. Mitts +1" , enl = "cleric's mitts +1", model = 12357} +models.hands[14912] = { name = "Src. Gloves +1" , enl = "sorcerer's gloves +1", model = 12359} +models.hands[14913] = { name = "Dls. Gloves +1" , enl = "duelist's gloves +1", model = 12361} +models.hands[14914] = { name = "Asn. Armlets +1" , enl = "assassin's armlets +1", model = 12363} +models.hands[14915] = { name = "Vlr. Gauntlets +1" , enl = "valor gauntlets +1", model = 12365} +models.hands[14916] = { name = "Abs. Gauntlets +1" , enl = "abyss gauntlets +1", model = 12367} +models.hands[14917] = { name = "Mst. Gloves +1" , enl = "monster gloves +1", model = 12369} +models.hands[14918] = { name = "Brd. Cuffs +1" , enl = "bard's cuffs +1", model = 12371} +models.hands[14919] = { name = "Sct. Bracers +1" , enl = "scout's bracers +1", model = 12373} +models.hands[14920] = { name = "Sao. Kote +1" , enl = "saotome kote +1", model = 12375} +models.hands[14921] = { name = "Kog. Tekko +1" , enl = "koga tekko +1", model = 12377} +models.hands[14922] = { name = "Wym. Fng. Gnt. +1" , enl = "wyrm finger gauntlets +1", model = 12379} +models.hands[14923] = { name = "Smn. Bracers +1" , enl = "summoner's bracers +1", model = 12381} +models.hands[14924] = { name = "Hydra Gloves" , enl = "hydra gloves", model = 12420} +models.hands[14925] = { name = "Hydra Mittens" , enl = "hydra mittens", model = 12416} +models.hands[14926] = { name = "Hydra Moufles" , enl = "hydra moufles", model = 12452} +models.hands[14927] = { name = "Hydra Bracers" , enl = "hydra bracers", model = 12451} +models.hands[14928] = { name = "Magus Bazubands" , enl = "magus bazubands", model = 12453} +models.hands[14929] = { name = "Corsair's Gants" , enl = "corsair's gants", model = 12455} +models.hands[14930] = { name = "Pup. Dastanas" , enl = "puppetry dastanas", model = 12457} +models.hands[14931] = { name = "Carbuncle's Cuffs" , enl = "carbuncle's cuffs", model = 12308} +models.hands[14932] = { name = "Sipahi Dastanas" , enl = "sipahi dastana", model = 12459} +models.hands[14933] = { name = "Amir Kolluks" , enl = "amir kolluks", model = 12462} +models.hands[14934] = { name = "Jaridah Bazubands" , enl = "jaridah bazubands", model = 12460} +models.hands[14935] = { name = "Yigit Gages" , enl = "yigit gages", model = 12464} +models.hands[14936] = { name = "Storm Manopolas" , enl = "storm manopolas", model = 12447} +models.hands[14937] = { name = "Storm Gages" , enl = "storm gages", model = 12448} +models.hands[14938] = { name = "Abtal Dastanas" , enl = "abtal dastanas", model = 12459} +models.hands[14939] = { name = "Akinji Bazubands" , enl = "akinji bazubands", model = 12460} +models.hands[14940] = { name = "Pln. Dastanas" , enl = "pahluwan dastanas", model = 12463} +models.hands[14941] = { name = "Marid Mittens" , enl = "marid mittens", model = 12304} +models.hands[14942] = { name = "Marid Mittens +1" , enl = "marid mittens +1", model = 12304} +models.hands[14943] = { name = "Barb. Moufles" , enl = "barbarossa's moufles", model = 12452} +models.hands[14944] = { name = "Deadeye Gloves" , enl = "deadeye gloves", model = 12384} +models.hands[14945] = { name = "Fencing Bracers" , enl = "fencing bracers", model = 12312} +models.hands[14946] = { name = "Nightmare Gloves" , enl = "nightmare gloves", model = 12421} +models.hands[14947] = { name = "Hanzo Tekko" , enl = "hanzo tekko", model = 12386} +models.hands[14948] = { name = "Genie Gages" , enl = "genie gages", model = 12464} +models.hands[14949] = { name = "Hydra Fng. Gnt." , enl = "hydra finger gauntlets", model = 12315} +models.hands[14950] = { name = "Hydra Fng. Gnt. +1" , enl = "hydra finger gauntlets +1", model = 12315} +models.hands[14951] = { name = "Dragon Kote" , enl = "dragon kote", model = 12348} +models.hands[14952] = { name = "Ice Gauntlets" , enl = "ice gauntlets", model = 12383} +models.hands[14953] = { name = "Sadhu Bracelets" , enl = "sadhu bracelets", model = 12288} +models.hands[14954] = { name = "Sadhu Cuffs" , enl = "sadhu cuffs", model = 12403} +models.hands[14955] = { name = "Silken Cuffs" , enl = "silken cuffs", model = 12307} +models.hands[14956] = { name = "Magi Cuffs" , enl = "magi cuffs", model = 12307} +models.hands[14957] = { name = "Aiming Gloves" , enl = "aiming gloves", model = 12289} +models.hands[14958] = { name = "Beast Bazubands" , enl = "beast bazubands", model = 12460} +models.hands[14959] = { name = "Mrc. Dastanas" , enl = "mercenary's dastanas", model = 12459} +models.hands[14960] = { name = "Evoker's Gages" , enl = "evoker's gages", model = 12464} +models.hands[14961] = { name = "Ares' Gauntlets" , enl = "ares' gauntlets", model = 12470} +models.hands[14962] = { name = "Enyo's Gauntlets" , enl = "enyo's gauntlets", model = 12290} +models.hands[14963] = { name = "Phobos's Gnt." , enl = "phobos's gauntlets", model = 12310} +models.hands[14964] = { name = "Deimos's Gnt." , enl = "deimos's gauntlets", model = 12317} +models.hands[14965] = { name = "Skadi's Bazubands" , enl = "skadi's bazubands", model = 12471} +models.hands[14966] = { name = "Njord's Gloves" , enl = "njord's gloves", model = 12338} +models.hands[14967] = { name = "Freyr's Gloves" , enl = "freyr's gloves", model = 12397} +models.hands[14968] = { name = "Freya's Gloves" , enl = "freya's gloves", model = 12342} +models.hands[14969] = { name = "Usukane Gote" , enl = "usukane gote", model = 12472} +models.hands[14970] = { name = "Hoshikazu Tekko" , enl = "hoshikazu tekko", model = 12347} +models.hands[14971] = { name = "Tsukikazu Gote" , enl = "tsukikazu gote", model = 12394} +models.hands[14972] = { name = "Hikazu Gote" , enl = "hikazu gote", model = 12429} +models.hands[14973] = { name = "Marduk's Dastanas" , enl = "marduk's dastanas", model = 12473} +models.hands[14974] = { name = "Anu's Gages" , enl = "anu's gages", model = 12464} +models.hands[14975] = { name = "Ea's Dastanas" , enl = "ea's dastanas", model = 12463} +models.hands[14976] = { name = "Enlil's Kolluks" , enl = "enlil's kolluks", model = 12462} +models.hands[14977] = { name = "Morrigan's Cuffs" , enl = "morrigan's cuffs", model = 12474} +models.hands[14978] = { name = "Nemain's Cuffs" , enl = "nemain's cuffs", model = 12306} +models.hands[14979] = { name = "Bodb's Cuffs" , enl = "bodb's cuffs", model = 12437} +models.hands[14980] = { name = "Macha's Cuffs" , enl = "macha's cuffs", model = 12389} +models.hands[14981] = { name = "Khimaira Wrist." , enl = "khimaira wristbands", model = 12422} +models.hands[14982] = { name = "Stout Wristbands" , enl = "stout wristbands", model = 12422} +models.hands[14983] = { name = "Askar Manopolas" , enl = "askar manopolas", model = 12483} +models.hands[14984] = { name = "Denali Wristbands" , enl = "denali wristbands", model = 12484} +models.hands[14985] = { name = "Goliard Cuffs" , enl = "goliard cuffs", model = 12485} +models.hands[14986] = { name = "Ochimusha Kote" , enl = "ochimusha kote", model = 12297} +models.hands[14987] = { name = "Thunder Mittens" , enl = "thunder mittens", model = 12304} +models.hands[14988] = { name = "Stone Bangles" , enl = "stone bangles", model = 12288} +models.hands[14989] = { name = "Aero Mufflers" , enl = "aero mufflers", model = 12300} +models.hands[14990] = { name = "Blizzard Gloves" , enl = "blizzard gloves", model = 12295} +models.hands[14991] = { name = "Fire Bracers" , enl = "fire bracers", model = 12311} +models.hands[14992] = { name = "Water Mitts" , enl = "water mitts", model = 12299} +models.hands[14993] = { name = "Tabin Bracers" , enl = "tabin bracers", model = 12312} +models.hands[14994] = { name = "Tabin Bracers +1" , enl = "tabin bracers +1", model = 12312} +models.hands[14995] = { name = "Shadow Gauntlets" , enl = "shadow gauntlets", model = 12488} +models.hands[14996] = { name = "Valk. Gauntlets" , enl = "valkyrie's gauntlets", model = 12488} +models.hands[14997] = { name = "Shadow Cuffs" , enl = "shadow cuffs", model = 12489} +models.hands[14998] = { name = "Valkyrie's Cuffs" , enl = "valkyrie's cuffs", model = 12489} +models.hands[14999] = { name = "Vampiric Mitts" , enl = "vampiric mitts", model = 12419} +models.hands[15000] = { name = "Caballero Gnt." , enl = "caballero gauntlets", model = 12427} +models.hands[15001] = { name = "Breeder Mufflers" , enl = "breeder mufflers", model = 12346} +models.hands[15002] = { name = "Dancer's Bangles" , enl = "dancer's bangles", model = 12498} +models.hands[15003] = { name = "Dancer's Bangles" , enl = "dancer's bangles", model = 12499} +models.hands[15004] = { name = "Scholar's Bracers" , enl = "scholar's bracers", model = 12502} +models.hands[15005] = { name = "Iron Ram Mufflers" , enl = "iron ram mufflers", model = 12300} +models.hands[15006] = { name = "Fourth Gauntlets" , enl = "fourth division gauntlets", model = 12313} +models.hands[15007] = { name = "Cobra Cuffs" , enl = "cobra unit cuffs", model = 12307} +models.hands[15008] = { name = "Trainee Gloves" , enl = "trainee gloves", model = 12393} +models.hands[15009] = { name = "I.R. Dastanas" , enl = "iron ram dastanas", model = 12509} +models.hands[15010] = { name = "Fourth Hentzes" , enl = "fourth division hentzes", model = 12510} +models.hands[15011] = { name = "Cobra Mittens" , enl = "cobra unit mittens", model = 12507} +models.hands[15012] = { name = "Cobra Gloves" , enl = "cobra unit gloves", model = 12508} +models.hands[15013] = { name = "Vicious Mufflers" , enl = "vicious mufflers", model = 12301} +models.hands[15014] = { name = "Patrician's Cuffs" , enl = "patrician's cuffs", model = 12306} +models.hands[15015] = { name = "Hachiryu Kote" , enl = "hachiryu kote", model = 12515} +models.hands[15016] = { name = "Orcish Gauntlets" , enl = "orcish gauntlets", model = 12504} +models.hands[15017] = { name = "Toad Mittens" , enl = "toad mittens", model = 12395} +models.hands[15018] = { name = "Ritterhentzes" , enl = "ritterhentzes", model = 12426} +models.hands[15019] = { name = "Serpentes Cuffs" , enl = "serpentes cuffs", model = 12467} +models.hands[15020] = { name = "Heafoc Mitts" , enl = "heafoc mitts", model = 12417} +models.hands[15021] = { name = "Aurum Gauntlets" , enl = "aurum gauntlets", model = 12427} +models.hands[15022] = { name = "Oracle's Gloves" , enl = "oracle's gloves", model = 12431} +models.hands[15023] = { name = "Enkidu's Mittens" , enl = "enkidu's mittens", model = 12344} +models.hands[15024] = { name = "Mag. Bazubands +1" , enl = "magus bazubands +1", model = 12453} +models.hands[15025] = { name = "Mirage Bazubands" , enl = "mirage bazubands", model = 12454} +models.hands[15026] = { name = "Mrg. Bazubands +1" , enl = "mirage bazubands +1", model = 12454} +models.hands[15027] = { name = "Corsair's Gants +1" , enl = "corsair's gants +1", model = 12455} +models.hands[15028] = { name = "Commodore Gants" , enl = "commodore gants", model = 12456} +models.hands[15029] = { name = "Comm. Gants +1" , enl = "commodore gants +1", model = 12456} +models.hands[15030] = { name = "Pup. Dastanas +1" , enl = "puppetry dastanas +1", model = 12457} +models.hands[15031] = { name = "Pantin Dastanas" , enl = "pantin dastanas", model = 12458} +models.hands[15032] = { name = "Pantin Dastanas +1" , enl = "pantin dastanas +1", model = 12458} +models.hands[15033] = { name = "Danzo Tekko" , enl = "danzo tekko", model = 12386} +models.hands[15034] = { name = "Stone Mufflers" , enl = "stone mufflers", model = 12351} +models.hands[15035] = { name = "Dnc. Bangles +1" , enl = "dancer's bangles +1", model = 12498} +models.hands[15036] = { name = "Dnc. Bangles +1" , enl = "dancer's bangles +1", model = 12499} +models.hands[15037] = { name = "Sch. Bracers +1" , enl = "scholar's bracers +1", model = 12502} +models.hands[15038] = { name = "Etoile Bangles" , enl = "etoile bangles", model = 12592} +models.hands[15039] = { name = "Etoile Bangles +1" , enl = "etoile bangles +1", model = 12592} +models.hands[15040] = { name = "Argute Bracers" , enl = "argute bracers", model = 12503} +models.hands[15041] = { name = "Argute Bracers +1" , enl = "argute bracers +1", model = 12503} +models.hands[15042] = { name = "Gothic Gauntlets" , enl = "gothic gauntlets", model = 12522} +models.hands[15043] = { name = "Carpenter's Cuffs" , enl = "carpenter's cuffs", model = 12487} +models.hands[15044] = { name = "Blksmith. Cuffs" , enl = "blacksmith's cuffs", model = 12487} +models.hands[15045] = { name = "Goldsmith's Cuffs" , enl = "goldsmith's cuffs", model = 12487} +models.hands[15046] = { name = "Weaver's Cuffs" , enl = "weaver's cuffs", model = 12487} +models.hands[15047] = { name = "Tanner's Cuffs" , enl = "tanner's cuffs", model = 12487} +models.hands[15048] = { name = "Bonewrk. Cuffs" , enl = "boneworker's cuffs", model = 12487} +models.hands[15049] = { name = "Alchemist's Cuffs" , enl = "alchemist's cuffs", model = 12487} +models.hands[15050] = { name = "Culinarian's Cuffs" , enl = "culinarian's cuffs", model = 12487} +models.hands[15051] = { name = "Fisherman's Cuffs" , enl = "fisherman's cuffs", model = 12487} +models.hands[15052] = { name = "Guerilla Gloves" , enl = "guerilla gloves", model = 12294} +models.hands[15053] = { name = "Combat Mittens" , enl = "combat mittens", model = 12298} +models.hands[15054] = { name = "Beacon Cuffs" , enl = "beacon cuffs", model = 12307} +models.hands[15055] = { name = "Finesse Gloves" , enl = "finesse gloves", model = 12294} +models.hands[15056] = { name = "Rover's Gloves" , enl = "rover's gloves", model = 12450} +models.hands[15057] = { name = "Bricta's Cuffs" , enl = "bricta's cuffs", model = 12403} +models.hands[15058] = { name = "Combat Mittens +1" , enl = "combat mittens +1", model = 12298} +models.hands[15059] = { name = "Finesse Gloves +1" , enl = "finesse gloves +1", model = 12294} +models.hands[15060] = { name = "Symbios Gloves" , enl = "symbios gloves", model = 12311} +models.hands[15061] = { name = "Sasuke Tekko" , enl = "sasuke tekko", model = 12349} +models.hands[15062] = { name = "Seraph Mittens" , enl = "seraph mittens", model = 12345} +models.hands[15063] = { name = "Thrift Gloves" , enl = "thrift gloves", model = 12342} +models.hands[15064] = { name = "Slither Gloves" , enl = "slither gloves", model = 12309} +models.hands[15065] = { name = "Medb's Gauntlets" , enl = "medb's gauntlets", model = 12343} +models.hands[15102] = { name = "Warrior's Mufflers" , enl = "warrior's mufflers", model = 12353} +models.hands[15103] = { name = "Melee Gloves" , enl = "melee gloves", model = 12355} +models.hands[15104] = { name = "Cleric's Mitts" , enl = "cleric's mitts", model = 12357} +models.hands[15105] = { name = "Sorcerer's Gloves" , enl = "sorcerer's gloves", model = 12359} +models.hands[15106] = { name = "Duelist's Gloves" , enl = "duelist's gloves", model = 12361} +models.hands[15107] = { name = "Assassin's Armlets" , enl = "assassin's armlets", model = 12363} +models.hands[15108] = { name = "Valor Gauntlets" , enl = "valor gauntlets", model = 12365} +models.hands[15109] = { name = "Abyss Gauntlets" , enl = "abyss gauntlets", model = 12367} +models.hands[15110] = { name = "Monster Gloves" , enl = "monster gloves", model = 12369} +models.hands[15111] = { name = "Bard's Cuffs" , enl = "bard's cuffs", model = 12371} +models.hands[15112] = { name = "Scout's Bracers" , enl = "scout's bracers", model = 12373} +models.hands[15113] = { name = "Saotome Kote" , enl = "saotome kote", model = 12375} +models.hands[15114] = { name = "Koga Tekko" , enl = "koga tekko", model = 12377} +models.hands[15115] = { name = "Wyrm Fng.Gnt." , enl = "wyrm finger gauntlets", model = 12379} +models.hands[15116] = { name = "Summoner's Brcr." , enl = "summoner's bracers", model = 12381} +models.hands[27928] = { name = "Ares' Gauntlets +1" , enl = "ares' gauntlets +1", model = 12470} +models.hands[27929] = { name = "Skd. Bazubands +1" , enl = "skadi's bazubands +1", model = 12471} +models.hands[27930] = { name = "Usk. Gote +1" , enl = "usukane gote +1", model = 12472} +models.hands[27931] = { name = "Mdk. Dastanas +1" , enl = "marduk's dastanas +1", model = 12473} +models.hands[27932] = { name = "Morrigan's Cuffs +1" , enl = "morrigan's cuffs +1", model = 12474} +models.hands[27933] = { name = "Ker's Gauntlets" , enl = "ker's gauntlets", model = 12470} +models.hands[27934] = { name = "Sigyn's Bazubands" , enl = "sigyn's bazubands", model = 12471} +models.hands[27935] = { name = "Omodaka Gote" , enl = "omodaka gote", model = 12472} +models.hands[27936] = { name = "Nabu's Dastanas" , enl = "nabu's dastanas", model = 12473} +models.hands[27937] = { name = "Fea's Cuffs" , enl = "fea's cuffs", model = 12474} +models.hands[27938] = { name = "Ate's Gauntlets" , enl = "ate's gauntlets", model = 12317} +models.hands[27939] = { name = "Idi's Gloves" , enl = "idi's gloves", model = 12342} +models.hands[27940] = { name = "Genta Gote" , enl = "genta gote", model = 12429} +models.hands[27941] = { name = "Namru's Dastanas" , enl = "namru's dastanas", model = 12462} +models.hands[27942] = { name = "Neit's Cuffs" , enl = "neit's cuffs", model = 12389} +models.hands[27943] = { name = "Pumm. Mufflers" , enl = "pummler's mufflers" , model = 12352 } +models.hands[27944] = { name = "Anchorite's Gloves" , enl = "anchorite's gloves" , model = 12354 } +models.hands[27945] = { name = "Theophany Mitts" , enl = "theophany mitts" , model = 12356 } +models.hands[27946] = { name = "Spaekona's Gloves" , enl = "spaekona's gloves" , model = 12358 } +models.hands[27947] = { name = "Atrophy Gloves" , enl = "atrophy gloves" , model = 12360 } +models.hands[27948] = { name = "Pillager's Armlets" , enl = "pillager's armlets" , model = 12362 } +models.hands[27949] = { name = "Rev. Gauntlets" , enl = "reverence gauntlets" , model = 12364 } +models.hands[27950] = { name = "Igno. Gauntlets" , enl = "ignominy gauntlets" , model = 12366 } +models.hands[27951] = { name = "Totemic Gloves" , enl = "totemic gloves" , model = 12368 } +models.hands[27952] = { name = "Brioso Cuffs" , enl = "brioso cuffs" , model = 12370 } +models.hands[27953] = { name = "Orion Bracers" , enl = "orion bracers" , model = 12372 } +models.hands[27954] = { name = "Wakido Kote" , enl = "wakido kote" , model = 12374 } +models.hands[27955] = { name = "Hachiya Tekko" , enl = "hachiya tekko" , model = 12376 } +models.hands[27956] = { name = "Vish. Fin. Gaunt" , enl = "vishap finger gauntlets" , model = 12378 } +models.hands[27957] = { name = "Con. Bracers" , enl = "convoker's bracers" , model = 12380 } +models.hands[27958] = { name = "Assim. Bazu." , enl = "assimilator's bazubands" , model = 12453 } +models.hands[27959] = { name = "Lak. Gants" , enl = "laksamana's gants" , model = 12455 } +models.hands[27960] = { name = "Foire Dastanas" , enl = "foire dastanas" , model = 12457 } +models.hands[27961] = { name = "Maxixi Bangles" , enl = "maxixi bangles" , model = 12498 } +models.hands[27962] = { name = "Maxixi Bangles" , enl = "maxixi bangles" , model = 12499 } +models.hands[27963] = { name = "Acad. Bracers" , enl = "academic's bracers" , model = 12502 } +models.hands[27964] = { name = "Pumm. Mufflers +1" , enl = "pummeler's mufflers +1" , model = 12352 } +models.hands[27965] = { name = "Anch. Gloves +1" , enl = "anchorite's gloves +1" , model = 12354 } +models.hands[27966] = { name = "Theo. Mitts +1" , enl = "theophany mitts +1" , model = 12356 } +models.hands[27967] = { name = "Spae. Gloves +1" , enl = "spaekona's gloves +1" , model = 12358 } +models.hands[27968] = { name = "Atrophy Gloves +1" , enl = "atrophy gloves +1" , model = 12360 } +models.hands[27969] = { name = "Pill. Armlets +1" , enl = "pillager's armlets +1" , model = 12362 } +models.hands[27970] = { name = "Rev. Gauntlets +1" , enl = "reverence gauntlets +1" , model = 12364 } +models.hands[27971] = { name = "Igno. Gauntlets +1" , enl = "ignominy gauntlets +1" , model = 12366 } +models.hands[27972] = { name = "Tot. Gloves +1" , enl = "totemic gloves +1" , model = 12368 } +models.hands[27973] = { name = "Brioso Cuffs +1" , enl = "brioso cuffs +1" , model = 12370 } +models.hands[27974] = { name = "Orion Bracers +1" , enl = "orion bracers +1" , model = 12372 } +models.hands[27975] = { name = "Wakido Kote +1" , enl = "wakido kote +1" , model = 12374 } +models.hands[27976] = { name = "Hachiya Tekko +1" , enl = "hachiya tekko +1" , model = 12376 } +models.hands[27977] = { name = "Vishap F. G. +1" , enl = "vishap finger gauntlets +1" , model = 12378 } +models.hands[27978] = { name = "Con. Bracers +1" , enl = "convoker's bracers +1" , model = 12380 } +models.hands[27979] = { name = "Assim. Bazu. +1" , enl = "assimilator's bazubands +1" , model = 12453 } +models.hands[27980] = { name = "Lak. Gants +1" , enl = "Laksamana's gants +1" , model = 12455 } +models.hands[27981] = { name = "Foire Dastanas +1" , enl = "foire dastanas +1" , model = 12457 } +models.hands[27982] = { name = "Maxixi Bangles +1" , enl = "maxixi bangles +1" , model = 12498 } +models.hands[27983] = { name = "Maxixi Bangles +1 (Level 99)" , enl = "maxixi bangles +1" , model = 12499 } +models.hands[27984] = { name = "Acad. Bracers +1" , enl = "academic's bracers +1" , model = 12502 } +models.hands[27985] = { name = "Geo. Mitaines +1" , enl = "geomancy mitaines +1" , model = 12596 } +models.hands[27986] = { name = "Runeist Mitons +1" , enl = "runeist mitons +1" , model = 12626 } +models.hands[28023] = { name = "Work Gloves" , enl = "work gloves" , model = 12654 } +models.hands[28025] = { name = "Regimen Mittens" , enl = "regimen mittens" , model = 12304 } +models.hands[28026] = { name = "Aiwon Gauntlets" , enl = "aiwon gauntlets" , model = 12310 } +models.hands[28027] = { name = "Boor Bracelets" , enl = "boor bracelets" , model = 12288 } +models.hands[28028] = { name = "Otomi Gloves" , enl = "otomi gloves" , model = 12616 } +models.hands[28029] = { name = "Outrider Mittens" , enl = "outrider mittens" , model = 12293 } +models.hands[28030] = { name = "Espial Bracers" , enl = "espial bracers" , model = 12311 } +models.hands[28031] = { name = "Wayfarer Cuffs" , enl = "wayfarer cuffs" , model = 12308 } +models.hands[28032] = { name = "Temachtiani Gloves" , enl = "Temachtiani gloves" , model = 12390 } +models.hands[28034] = { name = "Dynasty Mitts" , enl = "dynasty mitts" , model = 12345 } +models.hands[28035] = { name = "Lurid Mitts" , enl = "lurid mitts" , model = 12418 } +models.hands[28042] = { name = "Karieyh Moufles +1" , enl = "Karieyh moufles +1" , model = 12630 } +models.hands[28043] = { name = "Thur. Gloves +1" , enl = "Thurandaut gloves +1" , model = 12631 } +models.hands[28044] = { name = "Orvail Cuffs +1" , enl = "Orvail cuffs +1" , model = 12632 } +models.hands[28046] = { name = "Gorney Moufles" , enl = "gorney moufles", model = 12642} +models.hands[28047] = { name = "Shneddick Gloves" , enl = "shneddick gloves", model = 12643} +models.hands[28048] = { name = "Weather. Cuffs" , enl = "weatherspoon cuffs", model = 12644} +models.hands[28050] = { name = "Buremte Gloves" , enl = "buremte gloves", model = 12640} +models.hands[28051] = { name = "Cizin Mufflers" , enl = "cizin mufflers", model = 12562} +models.hands[28052] = { name = "Otronif Gloves" , enl = "otronif gloves", model = 12532} +models.hands[28053] = { name = "Iuitl Wristbands" , enl = "iuitl wristbands", model = 12571} +models.hands[28054] = { name = "Gendewitha Gages" , enl = "gendewitha gages", model = 12553} +models.hands[28055] = { name = "Hagondes Cuffs" , enl = "hagondes cuffs", model = 12550} +models.hands[28056] = { name = "Yaoyotl Gloves" , enl = "yaoyotl gloves", model = 12616} +models.hands[28057] = { name = "Miki. Gauntlets" , enl = "mikinaak gauntlets", model = 12637} +models.hands[28058] = { name = "Manibozho Gloves" , enl = "manibozho gloves", model = 12638} +models.hands[28059] = { name = "Bokwus Gloves" , enl = "bokwus gloves", model = 12639} +models.hands[28060] = { name = "Treefeller Gloves" , enl = "treefeller gloves", model = 12393} +models.hands[28061] = { name = "Orvail Cuffs" , enl = "orvail cuffs", model = 12632} +models.hands[28062] = { name = "Quauhpilli Gloves" , enl = "quauhpilli gloves", model = 12616} +models.hands[28064] = { name = "Thurandaut Gloves" , enl = "thurandaut gloves", model = 12631} +models.hands[28065] = { name = "Karieyh Moufles" , enl = "karieyh moufles", model = 12630} +models.hands[28066] = { name = "Geomancy Mitaines" , enl = "geomancy mitaines", model = 12596} +models.hands[28067] = { name = "Runeist Mitons" , enl = "runeist mitons", model = 12626} + +models.hands["Unknown"] = { 12408, 12633 } + +models.hands["Blanks"] = { 12518, 12523, 12595, 12619, 12622, 12623, 12641 +} + +models.hands["Unused"] = { 12320, 12321, 12322, 12323, 12324, 12325, 12326, 12327, 12328, 12329, + 12330, 12331, 12332, 12333, 12334, 12335, 12336, 12337, 12339, 12385, + 12407, 12409, 12411, 12412, 12413, 12414, 12415, 12428, 12432, 12433, + 12434, 12435, 12440, 12441, 12442, 12443, 12444, 12465, 12466, 12468, + 12469, 12475, 12476, 12477, 12478, 12479, 12480, 12481, 12482, 12486, + 12492, 12493, 12494, 12495, 12496, 12497, 12500, 12505, 12512, 12516, + 12517, 12521, 12534, 12525, 12526, 12531, 12532, 12533, 12588, 12589, + 12590, 12591, 12597, 12599, 12600, 12601, 12602, 12603, 12604, 12605, + 12606, 12607, 12617, 12620, 12621, 12628, 12629, 12634, 12635, 12636 +} diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/head.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/head.lua new file mode 100644 index 0000000..385e86f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/head.lua @@ -0,0 +1,977 @@ +-- Copyright © 2013-2014, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +models.head = {} + +models.head["None"] = { name = "empty" , model = 4096 } + +models.head[10400] = { name = "Hrafn Coronet" , enl = "hrafn coronet", model = 4419} +models.head[10401] = { name = "Tenryu Somen" , enl = "tenryu somen", model = 4420} +models.head[10402] = { name = "Kheper Bonnet" , enl = "kheper bonnet", model = 4416} +models.head[10403] = { name = "Auspex Coif" , enl = "auspex coif", model = 4418} +models.head[10404] = { name = "Paean Mitra" , enl = "paean mitra", model = 4417} +models.head[10405] = { name = "Huginn Coronet" , enl = "huginn coronet", model = 4419} +models.head[10406] = { name = "Tenryu Somen +1" , enl = "tenryu somen +1", model = 4420} +models.head[10407] = { name = "Khepri Bonnet" , enl = "khepri bonnet", model = 4416} +models.head[10408] = { name = "Spurrina Coif" , enl = "spurrina coif", model = 4418} +models.head[10409] = { name = "Iaso Mitra" , enl = "iaso mitra", model = 4417} +models.head[10410] = { name = "Ugol Salade" , enl = "ugol salade", model = 4246} +models.head[10411] = { name = "Mavros Salade" , enl = "mavros salade", model = 4246} +models.head[10412] = { name = "Urja Helm" , enl = "urja helm", model = 4103} +models.head[10413] = { name = "Sthira Helm" , enl = "sthira helm", model = 4103} +models.head[10414] = { name = "Spolia Chapeau" , enl = "spolia chapeau", model = 4245} +models.head[10415] = { name = "Opima Chapeau" , enl = "opima chapeau", model = 4245} +models.head[10416] = { name = "Canute's Helm" , enl = "canute's helm", model = 4236} +models.head[10417] = { name = "Manea's Armet" , enl = "manea's armet", model = 4235} +models.head[10418] = { name = "Seiokona Beret" , enl = "seiokona beret", model = 4120} +models.head[10419] = { name = "Hexed Coronet" , enl = "hexed coronet", model = 4101} +models.head[10420] = { name = "Hexed Somen" , enl = "hexed somen", model = 4015} +models.head[10421] = { name = "Hexed Bonnet" , enl = "hexed bonnet", model = 4267} +models.head[10422] = { name = "Hexed Coif" , enl = "hexed coif", model = 4116} +models.head[10423] = { name = "Hexed Mitra" , enl = "hexed mitra", model = 4114} +models.head[10424] = { name = "Hexed Coronet -1" , enl = "hexed coronet -1", model = 4101} +models.head[10425] = { name = "Hexed Somen -1" , enl = "hexed somen -1", model = 4015} +models.head[10426] = { name = "Hexed Bonnet -1" , enl = "hexed bonnet -1", model = 4267} +models.head[10427] = { name = "Hexed Coif -1" , enl = "hexed coif -1", model = 4116} +models.head[10428] = { name = "Hexed Mitra -1" , enl = "hexed mitra -1", model = 4114} +models.head[10429] = { name = "Moogle Masque" , enl = "moogle masque", model = 4403} +models.head[10430] = { name = "Decennial Crown" , enl = "decennial crown", model = 4427} +models.head[10431] = { name = "Decennial Tiara" , enl = "decennial tiara", model = 4428} +models.head[10432] = { name = "Decennial Crown +1" , enl = "decennial crown +1", model = 4427} +models.head[10433] = { name = "Decennial Tiara +1" , enl = "decennial tiara +1", model = 4430} +models.head[10434] = { name = "Dux Visor" , enl = "dux visor", model = 4432} +models.head[10435] = { name = "Dux Visor +1" , enl = "dux visor +1", model = 4432} +models.head[10436] = { name = "Chelona Hat" , enl = "chelona hat", model = 4433} +models.head[10437] = { name = "Chelona Hat +1" , enl = "chelona hat +1", model = 4433} +models.head[10438] = { name = "Enif Zucchetto" , enl = "enif zucchetto", model = 4255} +models.head[10439] = { name = "Adhara Turban" , enl = "adhara turban", model = 4256} +models.head[10440] = { name = "Murzim Zucchetto" , enl = "murzim zucchetto", model = 4255} +models.head[10441] = { name = "Shedir Turban" , enl = "shedir turban", model = 4256} +models.head[10442] = { name = "Laeradr Helm" , enl = "laeradr helm", model = 4295} +models.head[10443] = { name = "Bendis's Hairpin" , enl = "bendis's hairpin", model = 4096} +models.head[10444] = { name = "Zouave's Beret" , enl = "zouave's beret", model = 4192} +models.head[10445] = { name = "Nemetona Cap" , enl = "nemetona cap", model = 4152} +models.head[10446] = { name = "Ahriman Cap" , enl = "ahriman cap", model = 4424} +models.head[10447] = { name = "Pyracmon Cap" , enl = "pyracmon cap", model = 4424} +models.head[10448] = { name = "Kahin Turban" , enl = "kahin turban", model = 4231} +models.head[10650] = { name = "War. Mask +2" , enl = "warrior's mask +2", model = 4161} +models.head[10651] = { name = "Mel. Crown +2" , enl = "melee crown +2", model = 4163} +models.head[10652] = { name = "Clr. Cap +2" , enl = "cleric's cap +2", model = 4165} +models.head[10653] = { name = "Src. Petasos +2" , enl = "sorcerer's petasos +2", model = 4167} +models.head[10654] = { name = "Dls. Chapeau +2" , enl = "duelist's chapeau +2", model = 4169} +models.head[10655] = { name = "Asn. Bonnet +2" , enl = "assassin's bonnet +2", model = 4171} +models.head[10656] = { name = "Vlr. Coronet +2" , enl = "valor coronet +2", model = 4173} +models.head[10657] = { name = "Abs. Burgeonet +2" , enl = "abyss burgeonet +2", model = 4175} +models.head[10658] = { name = "Mst. Helm +2" , enl = "monster helm +2", model = 4177} +models.head[10659] = { name = "Brd. Roundlet +2" , enl = "bard's roundlet +2", model = 4179} +models.head[10660] = { name = "Sct. Beret +2" , enl = "scout's beret +2", model = 4181} +models.head[10661] = { name = "Sao. Kabuto +2" , enl = "saotome kabuto +2", model = 4183} +models.head[10662] = { name = "Kog. Hatsuburi +2" , enl = "koga hatsuburi +2", model = 4185} +models.head[10663] = { name = "Wym. Armet +2" , enl = "wyrm armet +2", model = 4187} +models.head[10664] = { name = "Smn. Horn +2" , enl = "summoner's horn +2", model = 4189} +models.head[10665] = { name = "Mirage Keffiyeh +2" , enl = "mirage keffiyeh +2", model = 4262} +models.head[10666] = { name = "Comm. Tricorne +2" , enl = "commodore's tricorne +2", model = 4264} +models.head[10667] = { name = "Pantin Taj +2" , enl = "pantin taj +2", model = 4266} +models.head[10668] = { name = "Etoile Tiara +2" , enl = "etoile tiara +2", model = 4400} +models.head[10669] = { name = "Argute M.board +2" , enl = "argute mortarboard +2", model = 4311} +models.head[10864] = { name = "Ocelo. Headpiece" , enl = "ocelomeh headpiece", model = 4396} +models.head[10865] = { name = "Nefer Khat" , enl = "nefer khat", model = 4397} +models.head[10866] = { name = "Mekira-oto" , enl = "mekira-oto", model = 4398} +models.head[10867] = { name = "Oce. Headpiece +1" , enl = "ocelomeh headpiece +1", model = 4396} +models.head[10868] = { name = "Nefer Khat +1" , enl = "nefer khat +1", model = 4397} +models.head[10869] = { name = "Mekira-oto +1" , enl = "mekira-oto +1", model = 4398} +models.head[10870] = { name = "Sceamol Band" , enl = "sceamol band", model = 4096} +models.head[10871] = { name = "Pandinus Beret" , enl = "pandinus beret", model = 4120} +models.head[10872] = { name = "Ruach Crown" , enl = "ruach crown", model = 4226} +models.head[10873] = { name = "Theia's Hairpin" , enl = "theia's hairpin", model = 4096} +models.head[10874] = { name = "Chersos Helm" , enl = "chersos helm", model = 4191} +models.head[10875] = { name = "Snowman Cap" , enl = "snowman cap", model = 4218} +models.head[10876] = { name = "Ogier's Helm" , enl = "ogier's helm", model = 4421} +models.head[10877] = { name = "Athos's Chapeau" , enl = "athos's chapeau", model = 4422} +models.head[10878] = { name = "Rubeus Bandeau" , enl = "rubeus bandeau", model = 4423} +models.head[10879] = { name = "Hreysti Helm" , enl = "hreysti helm", model = 4236} +models.head[10880] = { name = "Praeco Beret" , enl = "praeco beret", model = 4425} +models.head[10881] = { name = "Brego Helm" , enl = "brego helm", model = 4216} +models.head[10882] = { name = "Smilodon Mask" , enl = "smilodon mask", model = 4213} +models.head[10883] = { name = "Gallian Helm" , enl = "gallian helm", model = 4214} +models.head[10884] = { name = "Avant Helm" , enl = "avant helm", model = 4125} +models.head[10885] = { name = "Avant Helm +1" , enl = "avant helm +1", model = 4125} +models.head[10886] = { name = "Kacura Cap" , enl = "kacura cap", model = 4206} +models.head[10887] = { name = "Kacura Cap +1" , enl = "kacura cap +1", model = 4206} +models.head[10888] = { name = "Sweven Corona" , enl = "sweven corona", model = 4227} +models.head[10889] = { name = "Sweven Corona +1" , enl = "sweven corona +1", model = 4227} +models.head[10890] = { name = "Calma Armet" , enl = "calma armet", model = 4377} +models.head[10891] = { name = "Mustela Mask" , enl = "mustela mask", model = 4353} +models.head[10892] = { name = "Magavan Beret" , enl = "magavan beret", model = 4365} +models.head[10893] = { name = "Prodigy's Circlet" , enl = "prodigy's circlet", model = 4116} +models.head[10894] = { name = "Striga Crown" , enl = "striga crown", model = 4203} +models.head[10895] = { name = "Palnatoke's Cap" , enl = "palnatoke's cap", model = 4192} +models.head[10896] = { name = "A'as Circlet" , enl = "a'as circlet", model = 4116} +models.head[10897] = { name = "Rheic Salade" , enl = "rheic salade", model = 4291} +models.head[10898] = { name = "Rheic Salade +1" , enl = "rheic salade +1", model = 4291} +models.head[10899] = { name = "Rheic Salade +2" , enl = "rheic salade +2", model = 4291} +models.head[10900] = { name = "Rheic Salade +3" , enl = "rheic salade +3", model = 4291} +models.head[10901] = { name = "Phorcys Salade" , enl = "phorcys salade", model = 4291} +models.head[10902] = { name = "Euxine Hat" , enl = "euxine hat", model = 4292} +models.head[10903] = { name = "Euxine Hat +1" , enl = "euxine hat +1", model = 4292} +models.head[10904] = { name = "Euxine Hat +2" , enl = "euxine hat +2", model = 4292} +models.head[10905] = { name = "Euxine Hat +3" , enl = "euxine hat +3", model = 4292} +models.head[10906] = { name = "Thaumas Hat" , enl = "thaumas hat", model = 4292} +models.head[10907] = { name = "Tethyan Cap" , enl = "tethyan cap", model = 4293} +models.head[10908] = { name = "Tethyan Cap +1" , enl = "tethyan cap +1", model = 4293} +models.head[10909] = { name = "Tethyan Cap +2" , enl = "tethyan cap +2", model = 4293} +models.head[10910] = { name = "Tethyan Cap +3" , enl = "tethyan cap +3", model = 4293} +models.head[10911] = { name = "Nares Cap" , enl = "nares cap", model = 4293} +models.head[10912] = { name = "Drachenhorn" , enl = "drachenhorn", model = 4222} +models.head[10913] = { name = "Hyaline Hat" , enl = "hyaline hat", model = 4223} +models.head[11064] = { name = "Ravager's Mask +2" , enl = "ravager's mask +2", model = 4378} +models.head[11065] = { name = "Tantra Crown +2" , enl = "tantra crown +2", model = 4379} +models.head[11066] = { name = "Orison Cap +2" , enl = "orison cap +2", model = 4380} +models.head[11067] = { name = "Goetia Petasos +2" , enl = "goetia petasos +2", model = 4381} +models.head[11068] = { name = "Estq. Chappel +2" , enl = "estoqueur's chappel +2", model = 4382} +models.head[11069] = { name = "Raid. Bonnet +2" , enl = "raider's bonnet +2", model = 4383} +models.head[11070] = { name = "Creed Armet +2" , enl = "creed armet +2", model = 4384} +models.head[11071] = { name = "Bale Burgeonet +2" , enl = "bale burgeonet +2", model = 4385} +models.head[11072] = { name = "Ferine Cabasset +2" , enl = "ferine cabasset +2", model = 4386} +models.head[11073] = { name = "Aoidos' Calot +2" , enl = "aoidos' calot +2", model = 4387} +models.head[11074] = { name = "Sylvan Gapette +2" , enl = "sylvan gapette +2", model = 4388} +models.head[11075] = { name = "Unkai Kabuto +2" , enl = "unkai kabuto +2", model = 4389} +models.head[11076] = { name = "Iga Zukin +2" , enl = "iga zukin +2", model = 4390} +models.head[11077] = { name = "Lancer's Mezail +2" , enl = "lancer's mezail +2", model = 4391} +models.head[11078] = { name = "Caller's Horn +2" , enl = "caller's horn +2", model = 4392} +models.head[11079] = { name = "Mavi Kavuk +2" , enl = "mavi kavuk +2", model = 4393} +models.head[11080] = { name = "Nvrch. Tricorne +2" , enl = "navarch's tricorne +2", model = 4394} +models.head[11081] = { name = "Cirque Cappello +2" , enl = "cirque cappello +2", model = 4395} +models.head[11082] = { name = "Charis Tiara +2" , enl = "charis tiara +2", model = 4401} +models.head[11083] = { name = "Svnt. Bonnet +2" , enl = "savant's bonnet +2", model = 4402} +models.head[11164] = { name = "Ravager's Mask +1" , enl = "ravager's mask +1", model = 4378} +models.head[11165] = { name = "Tantra Crown +1" , enl = "tantra crown +1", model = 4379} +models.head[11166] = { name = "Orison Cap +1" , enl = "orison cap +1", model = 4380} +models.head[11167] = { name = "Goetia Petasos +1" , enl = "goetia petasos +1", model = 4381} +models.head[11168] = { name = "Estq. Chappel +1" , enl = "estoqueur's chappel +1", model = 4382} +models.head[11169] = { name = "Raid. Bonnet +1" , enl = "raider's bonnet +1", model = 4383} +models.head[11170] = { name = "Creed Armet +1" , enl = "creed armet +1", model = 4384} +models.head[11171] = { name = "Bale Burgeonet +1" , enl = "bale burgeonet +1", model = 4385} +models.head[11172] = { name = "Ferine Cabasset +1" , enl = "ferine cabasset +1", model = 4386} +models.head[11173] = { name = "Aoidos' Calot +1" , enl = "aoidos' calot +1", model = 4387} +models.head[11174] = { name = "Sylvan Gapette +1" , enl = "sylvan gapette +1", model = 4388} +models.head[11175] = { name = "Unkai Kabuto +1" , enl = "unkai kabuto +1", model = 4389} +models.head[11176] = { name = "Iga Zukin +1" , enl = "iga zukin +1", model = 4390} +models.head[11177] = { name = "Lancer's Mezail +1" , enl = "lancer's mezail +1", model = 4391} +models.head[11178] = { name = "Caller's Horn +1" , enl = "caller's horn +1", model = 4392} +models.head[11179] = { name = "Mavi Kavuk +1" , enl = "mavi kavuk +1", model = 4393} +models.head[11180] = { name = "Nvrch. Tricorne +1" , enl = "navarch's tricorne +1", model = 4394} +models.head[11181] = { name = "Cirque Cappello +1" , enl = "cirque cappello +1", model = 4395} +models.head[11182] = { name = "Charis Tiara +1" , enl = "charis tiara +1", model = 4401} +models.head[11183] = { name = "Svnt. Bonnet +1" , enl = "savant's bonnet +1", model = 4402} +models.head[11464] = { name = "Magus Keffiyeh +1" , enl = "magus keffiyeh +1", model = 4261} +models.head[11465] = { name = "Mirage Keffiyeh" , enl = "mirage keffiyeh", model = 4262} +models.head[11466] = { name = "Mirage Keffiyeh +1" , enl = "mirage keffiyeh +1", model = 4262} +models.head[11467] = { name = "Cor. Tricorne +1" , enl = "corsair's tricorne +1", model = 4263} +models.head[11468] = { name = "Comm. Tricorne" , enl = "commodore tricorne", model = 4264} +models.head[11469] = { name = "Comm. Tricorne +1" , enl = "commodore tricorne +1", model = 4264} +models.head[11470] = { name = "Puppetry Taj +1" , enl = "puppetry taj +1", model = 4265} +models.head[11471] = { name = "Pantin Taj" , enl = "pantin taj", model = 4266} +models.head[11472] = { name = "Pantin Taj +1" , enl = "pantin taj +1", model = 4266} +models.head[11473] = { name = "Mirror Tiara" , enl = "mirror tiara", model = 4224} +models.head[11474] = { name = "Louhi's Mask" , enl = "louhi's mask", model = 4158} +models.head[11475] = { name = "Dancer's Tiara +1" , enl = "dancer's tiara +1", model = 4306} +models.head[11476] = { name = "Dancer's Tiara +1" , enl = "dancer's tiara +1", model = 4307} +models.head[11477] = { name = "Sch. M.board +1" , enl = "scholar's mortarboard +1", model = 4310} +models.head[11478] = { name = "Etoile Tiara" , enl = "etoile tiara", model = 4400} +models.head[11479] = { name = "Etoile Tiara +1" , enl = "etoile tiara +1", model = 4400} +models.head[11480] = { name = "Argute M.board" , enl = "argute mortarboard", model = 4311} +models.head[11481] = { name = "Argute M.board +1" , enl = "argute mortarboard +1", model = 4311} +models.head[11482] = { name = "Eyepatch" , enl = "eyepatch", model = 4329} +models.head[11483] = { name = "Gnole Crown" , enl = "gnole crown", model = 4203} +models.head[11484] = { name = "Antica Band" , enl = "antica band", model = 4096} +models.head[11485] = { name = "Spelunker's Helm" , enl = "spelunker's helm", model = 4331} +models.head[11486] = { name = "Diana Corona" , enl = "diana corona", model = 4116} +models.head[11487] = { name = "Champion's Galea" , enl = "champion's galea", model = 4332} +models.head[11488] = { name = "Anwig Salade" , enl = "anwig salade", model = 4333} +models.head[11489] = { name = "Selenian Cap" , enl = "selenian cap", model = 4334} +models.head[11490] = { name = "Snow Bunny Hat" , enl = "snow bunny hat", model = 4238} +models.head[11491] = { name = "S. Bunny Hat +1" , enl = "snow bunny hat +1", model = 4238} +models.head[11492] = { name = "Risky Patch" , enl = "risky patch", model = 4329} +models.head[11493] = { name = "Precision Bandana" , enl = "precision bandana", model = 4097} +models.head[11494] = { name = "Circe's Hat" , enl = "circe's hat", model = 4114} +models.head[11495] = { name = "Zeal Cap" , enl = "zeal cap", model = 4152} +models.head[11496] = { name = "Fenrir's Crown" , enl = "fenrir's crown", model = 4153} +models.head[11497] = { name = "Spurrer Beret" , enl = "spurrer beret", model = 4229} +models.head[11498] = { name = "Zeal Cap +1" , enl = "zeal cap +1", model = 4152} +models.head[11499] = { name = "Trainee's Specs." , enl = "trainee's spectacles", model = 4221} +models.head[11500] = { name = "Chocobo Beret" , enl = "chocobo beret", model = 4198} +models.head[11501] = { name = "Nocturnus Helm" , enl = "nocturnus helm", model = 4327} +models.head[11502] = { name = "Acubens Helm" , enl = "acubens helm", model = 4204} +models.head[11503] = { name = "Perle Salade" , enl = "perle salade", model = 4335} +models.head[11504] = { name = "Aurore Beret" , enl = "aurore beret", model = 4337} +models.head[11505] = { name = "Teal Chapeau" , enl = "teal chapeau", model = 4336} +models.head[11506] = { name = "Varangian Helm" , enl = "varangian helm", model = 4326} +models.head[11507] = { name = "Hero's Galea" , enl = "hero's galea", model = 4275} +models.head[11508] = { name = "Tokon Hachimaki" , enl = "tokon hachimaki", model = 4312} +models.head[11509] = { name = "Issen Hachimaki" , enl = "issen hachimaki", model = 4312} +models.head[11510] = { name = "Kensho Hachimaki" , enl = "kensho hachimaki", model = 4312} +models.head[11511] = { name = "Hako Hachimaki" , enl = "hako hachimaki", model = 4312} +models.head[11512] = { name = "Ryoshi Hachimaki" , enl = "ryoshi hachimaki", model = 4312} +models.head[11513] = { name = "Senshin Hachimaki" , enl = "senshin hachimaki", model = 4312} +models.head[11514] = { name = "Rekka Hachimaki" , enl = "rekka hachimaki", model = 4312} +models.head[11515] = { name = "Shitotsu Hachimaki" , enl = "shitotsu hachimaki", model = 4312} +models.head[11516] = { name = "Kanja Hachimaki" , enl = "kanja hachimaki", model = 4312} +models.head[11517] = { name = "Kengo Hachimaki" , enl = "kengo hachimaki", model = 4312} +models.head[11518] = { name = "Rokugo Hachimaki" , enl = "rokugo hachimaki", model = 4312} +models.head[11519] = { name = "Hakke Hachimaki" , enl = "hakke hachimaki", model = 4312} +models.head[11520] = { name = "Shunten Hachimaki" , enl = "shunten hachimaki", model = 4312} +models.head[11521] = { name = "Saika Hachimaki" , enl = "saika hachimaki", model = 4312} +models.head[11522] = { name = "Lightsome Cap" , enl = "lightsome cap", model = 4110} +models.head[11523] = { name = "Brisk Mask" , enl = "brisk mask", model = 4190} +models.head[11524] = { name = "Noesis Helm" , enl = "noesis helm", model = 4210} +models.head[11525] = { name = "Maestria Mask" , enl = "maestria mask", model = 4112} +models.head[11526] = { name = "Bersail Cap" , enl = "bersail cap", model = 4110} +models.head[11527] = { name = "Hecate's Crown" , enl = "hecate's crown", model = 4227} +models.head[11778] = { name = "Gules Cap" , enl = "gules cap", model = 4152} +models.head[11779] = { name = "Gules Cap +1" , enl = "gules cap +1", model = 4152} +models.head[11780] = { name = "Versa Celata" , enl = "versa celata", model = 4154} +models.head[11781] = { name = "Versa Celata +1" , enl = "versa celata +1", model = 4154} +models.head[11782] = { name = "Lore Hat" , enl = "lore hat", model = 4211} +models.head[11783] = { name = "Lore Hat +1" , enl = "lore hat +1", model = 4211} +models.head[11784] = { name = "Aias Bonnet" , enl = "aias bonnet", model = 4230} +models.head[11785] = { name = "Veloce Zuchetto" , enl = "veloce zuchetto", model = 4232} +models.head[11786] = { name = "Laurel Wreath" , enl = "laurel wreath", model = 4203} +models.head[11787] = { name = "Hikmet Hat" , enl = "hikmet hat", model = 4197} +models.head[11788] = { name = "Jester's Hat" , enl = "jester's hat", model = 4341} +models.head[11789] = { name = "Bellicus Celata" , enl = "bellicus celata", model = 4366} +models.head[11790] = { name = "Bestia Helm" , enl = "bestia helm", model = 4369} +models.head[11791] = { name = "Paragon Galea" , enl = "paragon galea", model = 4372} +models.head[11792] = { name = "Skopos Visor" , enl = "skopos visor", model = 4345} +models.head[11793] = { name = "Koku. Hatsuburi" , enl = "kokugetsu hatsuburi", model = 4348} +models.head[11794] = { name = "Spry Headgear" , enl = "spry headgear", model = 4342} +models.head[11795] = { name = "Mederi Tam" , enl = "mederi tam", model = 4354} +models.head[11796] = { name = "Literae Hat" , enl = "literae hat", model = 4357} +models.head[11797] = { name = "Facio Caubeen" , enl = "facio caubeen", model = 4360} +models.head[11798] = { name = "Twilight Helm" , enl = "twilight helm", model = 4199} +models.head[11799] = { name = "Zelus Tiara" , enl = "zelus tiara", model = 4233} +models.head[11800] = { name = "Cenobite's Coiffe" , enl = "cenobite's coiffe", model = 4114} +models.head[11801] = { name = "Deceit Mask" , enl = "deceit mask", model = 4112} +models.head[11802] = { name = "Alcide's Cap" , enl = "alcide's cap", model = 4206} +models.head[11803] = { name = "Alcide's Cap +1" , enl = "alcide's cap +1", model = 4206} +models.head[11804] = { name = "Nemus Khud" , enl = "nemus khud", model = 4269} +models.head[11805] = { name = "Nemus Khud +1" , enl = "nemus khud +1", model = 4269} +models.head[11806] = { name = "Nebula Hat" , enl = "nebula hat", model = 4197} +models.head[11807] = { name = "Nebula Hat +1" , enl = "nebula hat +1", model = 4197} +models.head[11808] = { name = "Fazheluo Helm" , enl = "fazheluo helm", model = 4207} +models.head[11809] = { name = "Cuauh. Headpiece" , enl = "cuauhtli headpiece", model = 4193} +models.head[11810] = { name = "Hyksos Khat" , enl = "hyksos khat", model = 4200} +models.head[11811] = { name = "Destrier Beret" , enl = "destrier beret", model = 4208} +models.head[11812] = { name = "Charity Cap" , enl = "charity cap", model = 4209} +models.head[11813] = { name = "Chimera Hairpin" , enl = "chimera hairpin", model = 4096} +models.head[11814] = { name = "Ganesha's Mask" , enl = "ganesha's mask", model = 4112} +models.head[11815] = { name = "Appetence Crown" , enl = "appetence crown", model = 4116} +models.head[11816] = { name = "Oneiros Helm" , enl = "oneiros helm", model = 4240} +models.head[11817] = { name = "Oneiros Barbut" , enl = "oneiros barbut", model = 4243} +models.head[11818] = { name = "Oneiros Headgear" , enl = "oneiros headgear", model = 4241} +models.head[11819] = { name = "Oneiros Coif" , enl = "oneiros coif", model = 4242} +models.head[11820] = { name = "Khthonios Mask" , enl = "khthonios mask", model = 4112} +models.head[11821] = { name = "Khthonios Helm" , enl = "khthonios helm", model = 4149} +models.head[11822] = { name = "Amanita Hairpin" , enl = "amanita hairpin", model = 4096} +models.head[11823] = { name = "Cocoon Band" , enl = "cocoon band", model = 4096} +models.head[11824] = { name = "Fazheluo Helm +1" , enl = "fazheluo helm +1", model = 4207} +models.head[11825] = { name = "Cuauh. Hdpce. +1" , enl = "cuauhtli headpiece +1", model = 4193} +models.head[11826] = { name = "Hyksos Khat +1" , enl = "hyksos khat +1", model = 4200} +models.head[11827] = { name = "Lenore's Hairpin" , enl = "lenore's hairpin", model = 4096} +models.head[12008] = { name = "Ravager's Mask" , enl = "ravager's mask", model = 4378} +models.head[12009] = { name = "Tantra Crown" , enl = "tantra crown", model = 4379} +models.head[12010] = { name = "Orison Cap" , enl = "orison cap", model = 4380} +models.head[12011] = { name = "Goetia Petasos" , enl = "goetia petasos", model = 4381} +models.head[12012] = { name = "Estq. Chappel" , enl = "estoqueur's chappel", model = 4382} +models.head[12013] = { name = "Raider's Bonnet" , enl = "raider's bonnet", model = 4383} +models.head[12014] = { name = "Creed Armet" , enl = "creed armet", model = 4384} +models.head[12015] = { name = "Bale Burgeonet" , enl = "bale burgeonet", model = 4385} +models.head[12016] = { name = "Ferine Cabasset" , enl = "ferine cabasset", model = 4386} +models.head[12017] = { name = "Aoidos' Calot" , enl = "aoidos' calot", model = 4387} +models.head[12018] = { name = "Sylvan Gapette" , enl = "sylvan gapette", model = 4388} +models.head[12019] = { name = "Unkai Kabuto" , enl = "unkai kabuto", model = 4389} +models.head[12020] = { name = "Iga Zukin" , enl = "iga zukin", model = 4390} +models.head[12021] = { name = "Lancer's Mezail" , enl = "lancer's mezail", model = 4391} +models.head[12022] = { name = "Caller's Horn" , enl = "caller's horn", model = 4392} +models.head[12023] = { name = "Mavi Kavuk" , enl = "mavi kavuk", model = 4393} +models.head[12024] = { name = "Navarch's Tricorne" , enl = "navarch's tricorne", model = 4394} +models.head[12025] = { name = "Cirque Cappello" , enl = "cirque cappello", model = 4395} +models.head[12026] = { name = "Charis Tiara" , enl = "charis tiara", model = 4401} +models.head[12027] = { name = "Savant's Bonnet" , enl = "savant's bonnet", model = 4402} +models.head[12108] = { name = "Ebon Celata" , enl = "ebon celata", model = 4366} +models.head[12109] = { name = "Furia Celata" , enl = "furia celata", model = 4367} +models.head[12110] = { name = "Ebur Celata" , enl = "ebur celata", model = 4368} +models.head[12111] = { name = "Ebon Helm" , enl = "ebon helm", model = 4369} +models.head[12112] = { name = "Furia Helm" , enl = "furia helm", model = 4370} +models.head[12113] = { name = "Ebur Helm" , enl = "ebur helm", model = 4371} +models.head[12114] = { name = "Ebon Galea" , enl = "ebon galea", model = 4372} +models.head[12115] = { name = "Furia Galea" , enl = "furia galea", model = 4373} +models.head[12116] = { name = "Ebur Galea" , enl = "ebur galea", model = 4374} +models.head[12117] = { name = "Ebon Armet" , enl = "ebon armet", model = 4375} +models.head[12118] = { name = "Furia Armet" , enl = "furia armet", model = 4376} +models.head[12119] = { name = "Ebur Armet" , enl = "ebur armet", model = 4377} +models.head[12120] = { name = "Ebon Mask" , enl = "ebon mask", model = 4351} +models.head[12121] = { name = "Furia Mask" , enl = "furia mask", model = 4352} +models.head[12122] = { name = "Ebur Mask" , enl = "ebur mask", model = 4353} +models.head[12123] = { name = "Ebon Visor" , enl = "ebon visor", model = 4345} +models.head[12124] = { name = "Furia Visor" , enl = "furia visor", model = 4346} +models.head[12125] = { name = "Ebur Visor" , enl = "ebur visor", model = 4347} +models.head[12126] = { name = "Shikkoku Hatsuburi" , enl = "shikkoku hatsuburi", model = 4348} +models.head[12127] = { name = "Shinku Hatsuburi" , enl = "shinku hatsuburi", model = 4349} +models.head[12128] = { name = "Ginhaku Hatsuburi" , enl = "ginhaku hatsuburi", model = 4350} +models.head[12129] = { name = "Ebon Headgear" , enl = "ebon headgear", model = 4342} +models.head[12130] = { name = "Furia Headgear" , enl = "furia headgear", model = 4343} +models.head[12131] = { name = "Ebur Headgear" , enl = "ebur headgear", model = 4344} +models.head[12132] = { name = "Ebon Tam" , enl = "ebon tam", model = 4354} +models.head[12133] = { name = "Furia Tam" , enl = "furia tam", model = 4355} +models.head[12134] = { name = "Ebur Tam" , enl = "ebur tam", model = 4356} +models.head[12135] = { name = "Ebon Hat" , enl = "ebon hat", model = 4357} +models.head[12136] = { name = "Furia Hat" , enl = "furia hat", model = 4358} +models.head[12137] = { name = "Ebur Hat" , enl = "ebur hat", model = 4359} +models.head[12138] = { name = "Ebon Caubeen" , enl = "ebon caubeen", model = 4360} +models.head[12139] = { name = "Furia Caubeen" , enl = "furia caubeen", model = 4361} +models.head[12140] = { name = "Ebur Caubeen" , enl = "ebur caubeen", model = 4362} +models.head[12141] = { name = "Ebon Beret" , enl = "ebon beret", model = 4363} +models.head[12142] = { name = "Furia Beret" , enl = "furia beret", model = 4364} +models.head[12143] = { name = "Ebur Beret" , enl = "ebur beret", model = 4365} +models.head[12416] = { name = "Sallet" , enl = "sallet", model = 4098} +models.head[12417] = { name = "Mythril Sallet" , enl = "mythril sallet", model = 4125} +models.head[12418] = { name = "Gold Armet" , enl = "gold armet", model = 4121} +models.head[12419] = { name = "Darksteel Armet" , enl = "darksteel armet", model = 4118} +models.head[12420] = { name = "Adaman Barbuta" , enl = "adaman barbuta", model = 4151} +models.head[12421] = { name = "Koenig Schaller" , enl = "koenig schaller", model = 4191} +models.head[12422] = { name = "Irn.Msk. Armet" , enl = "iron musketeer's armet", model = 4121} +models.head[12423] = { name = "Dst. Armet +1" , enl = "darksteel armet +1", model = 4118} +models.head[12424] = { name = "Iron Mask" , enl = "iron mask", model = 4101} +models.head[12425] = { name = "Silver Mask" , enl = "silver mask", model = 4101} +models.head[12426] = { name = "Banded Helm" , enl = "banded helm", model = 4108} +models.head[12427] = { name = "Bascinet" , enl = "bascinet", model = 4109} +models.head[12428] = { name = "Celata" , enl = "celata", model = 4154} +models.head[12429] = { name = "Adaman Celata" , enl = "adaman celata", model = 4159} +models.head[12430] = { name = "Ryl.Kgt. Bascinet" , enl = "royal knight's bascinet", model = 4109} +models.head[12431] = { name = "Ryl. Squire's Helm" , enl = "royal squire's helm", model = 4108} +models.head[12432] = { name = "Faceguard" , enl = "faceguard", model = 4124} +models.head[12433] = { name = "Brass Mask" , enl = "brass mask", model = 4124} +models.head[12434] = { name = "Genbu's Kabuto" , enl = "genbu's kabuto", model = 4195} +models.head[12435] = { name = "Coral Visor" , enl = "coral visor", model = 4122} +models.head[12436] = { name = "Dragon Mask" , enl = "dragon mask", model = 4156} +models.head[12437] = { name = "Gavial Mask" , enl = "gavial mask", model = 4123} +models.head[12438] = { name = "Centurion's Visor" , enl = "centurion's visor", model = 4122} +models.head[12439] = { name = "Bascinet +1" , enl = "bascinet +1", model = 4109} +models.head[12440] = { name = "Leather Bandana" , enl = "leather bandana", model = 4097} +models.head[12441] = { name = "Lizard Helm" , enl = "lizard helm", model = 4102} +models.head[12442] = { name = "Studded Bandana" , enl = "studded bandana", model = 4097} +models.head[12443] = { name = "Cuir Bandana" , enl = "cuir bandana", model = 4097} +models.head[12444] = { name = "Raptor Helm" , enl = "raptor helm", model = 4103} +models.head[12445] = { name = "Dusk Mask" , enl = "dusk mask", model = 4205} +models.head[12446] = { name = "Tiger Helm" , enl = "tiger helm", model = 4103} +models.head[12447] = { name = "Coeurl Mask" , enl = "coeurl mask", model = 4150} +models.head[12448] = { name = "Bronze Cap" , enl = "bronze cap", model = 4111} +models.head[12449] = { name = "Brass Cap" , enl = "brass cap", model = 4111} +models.head[12450] = { name = "Padded Cap" , enl = "padded cap", model = 4110} +models.head[12451] = { name = "Scorpion Mask" , enl = "scorpion mask", model = 4112} +models.head[12452] = { name = "Darksteel Cap" , enl = "darksteel cap", model = 4110} +models.head[12453] = { name = "Coral Cap" , enl = "coral cap", model = 4152} +models.head[12454] = { name = "Bone Mask" , enl = "bone mask", model = 4112} +models.head[12455] = { name = "Beetle Mask" , enl = "beetle mask", model = 4112} +models.head[12456] = { name = "Hachimaki" , enl = "hachimaki", model = 4113} +models.head[12457] = { name = "Cotton Hachimaki" , enl = "cotton hachimaki", model = 4113} +models.head[12458] = { name = "Soil Hachimaki" , enl = "soil hachimaki", model = 4113} +models.head[12459] = { name = "Zunari Kabuto" , enl = "zunari kabuto", model = 4015} +models.head[12460] = { name = "Shinobi Hachigane" , enl = "shinobi hachigane", model = 4100} +models.head[12461] = { name = "Scorpion Helm +1" , enl = "scorpion helm +1", model = 4210} +models.head[12463] = { name = "Bronze Cap +1" , enl = "bronze cap +1", model = 4111} +models.head[12464] = { name = "Headgear" , enl = "headgear", model = 4117} +models.head[12465] = { name = "Cotton Headgear" , enl = "cotton headgear", model = 4117} +models.head[12466] = { name = "Red Cap" , enl = "red cap", model = 4119} +models.head[12467] = { name = "Wool Cap" , enl = "wool cap", model = 4119} +models.head[12468] = { name = "Green Beret" , enl = "green beret", model = 4120} +models.head[12469] = { name = "War Beret" , enl = "war beret", model = 4192} +models.head[12470] = { name = "Mrc.Cpt. Headgear" , enl = "mercenary captain's headgear", model = 4117} +models.head[12471] = { name = "Headgear +1" , enl = "headgear +1", model = 4117} +models.head[12472] = { name = "Circlet" , enl = "circlet", model = 4116} +models.head[12473] = { name = "Poet's Circlet" , enl = "poet's circlet", model = 4116} +models.head[12474] = { name = "Wool Hat" , enl = "wool hat", model = 4114} +models.head[12475] = { name = "Velvet Hat" , enl = "velvet hat", model = 4114} +models.head[12476] = { name = "Silk Hat" , enl = "silk hat", model = 4115} +models.head[12477] = { name = "Noble's Crown" , enl = "noble's crown", model = 4153} +models.head[12478] = { name = "Tct.Mgc. Hat" , enl = "tactician magician's hat", model = 4115} +models.head[12479] = { name = "Wool Hat +1" , enl = "wool hat +1", model = 4114} +models.head[12480] = { name = "Lizard Helm +1" , enl = "lizard helm +1", model = 4102} +models.head[12481] = { name = "Cuir Bandana +1" , enl = "cuir bandana +1", model = 4097} +models.head[12482] = { name = "Scorpion Mask +1" , enl = "scorpion mask +1", model = 4112} +models.head[12483] = { name = "Aristocrat's Crown" , enl = "aristocrat's crown", model = 4153} +models.head[12484] = { name = "Mrc. Hachimaki" , enl = "mercenary's hachimaki", model = 4113} +models.head[12485] = { name = "Fungus Hat" , enl = "fungus hat", model = 4115} +models.head[12486] = { name = "Emperor Hairpin" , enl = "emperor hairpin", model = 4096} +models.head[12487] = { name = "Faceguard +1" , enl = "faceguard +1", model = 4124} +models.head[12488] = { name = "Ost Cerveliere" , enl = "ost cerveliere", model = 4123} +models.head[12489] = { name = "Pixie Hairpin" , enl = "pixie hairpin", model = 4096} +models.head[12490] = { name = "Yasha Jinpachi" , enl = "yasha jinpachi", model = 4194} +models.head[12492] = { name = "Fugacity Beret" , enl = "fugacity beret", model = 4229} +models.head[12493] = { name = "Accord Hat" , enl = "accord hat", model = 4197} +models.head[12494] = { name = "Gold Hairpin" , enl = "gold hairpin", model = 4096} +models.head[12495] = { name = "Silver Hairpin" , enl = "silver hairpin", model = 4096} +models.head[12496] = { name = "Copper Hairpin" , enl = "copper hairpin", model = 4096} +models.head[12497] = { name = "Brass Hairpin" , enl = "brass hairpin", model = 4096} +models.head[12498] = { name = "Cotton Headband" , enl = "cotton headband", model = 4096} +models.head[12499] = { name = "Flax Headband" , enl = "flax headband", model = 4096} +models.head[12500] = { name = "Traveler's Hat" , enl = "traveler's hat", model = 4115} +models.head[12501] = { name = "Monk's Headgear" , enl = "monk's headgear", model = 4117} +models.head[12502] = { name = "Cmp. Eye Circlet" , enl = "compound eye circlet", model = 4116} +models.head[12503] = { name = "Silk Headband" , enl = "silk headband", model = 4096} +models.head[12504] = { name = "Rainbow Headband" , enl = "rainbow headband", model = 4096} +models.head[12505] = { name = "Bone Hairpin" , enl = "bone hairpin", model = 4096} +models.head[12506] = { name = "Shell Hairpin" , enl = "shell hairpin", model = 4096} +models.head[12507] = { name = "Horn Hairpin" , enl = "horn hairpin", model = 4096} +models.head[12508] = { name = "Coral Hairpin" , enl = "coral hairpin", model = 4096} +models.head[12509] = { name = "Legionnaire's Cap" , enl = "legionnaire's cap", model = 4111} +models.head[12510] = { name = "Ryl.Ftm. Bandana" , enl = "royal footman's bandana", model = 4097} +models.head[12511] = { name = "Fighter's Mask" , enl = "fighter's mask", model = 4160} +models.head[12512] = { name = "Temple Crown" , enl = "temple crown", model = 4162} +models.head[12513] = { name = "Warlock's Chapeau" , enl = "warlock's chapeau", model = 4168} +models.head[12514] = { name = "Rogue's Bonnet" , enl = "rogue's bonnet", model = 4170} +models.head[12515] = { name = "Gallant Coronet" , enl = "gallant coronet", model = 4172} +models.head[12516] = { name = "Chaos Burgeonet" , enl = "chaos burgeonet", model = 4174} +models.head[12517] = { name = "Beast Helm" , enl = "beast helm", model = 4176} +models.head[12518] = { name = "Hunter's Beret" , enl = "hunter's beret", model = 4180} +models.head[12519] = { name = "Drachen Armet" , enl = "drachen armet", model = 4186} +models.head[12520] = { name = "Evoker's Horn" , enl = "evoker's horn", model = 4188} +models.head[12521] = { name = "Blue Ribbon" , enl = "blue ribbon", model = 4128} +models.head[12522] = { name = "Rusty Cap" , enl = "rusty cap", model = 4111} +models.head[12523] = { name = "Judge's Helm" , enl = "judge's helm", model = 4126} +models.head[12524] = { name = "Iron Mask +1" , enl = "iron mask +1", model = 4101} +models.head[12525] = { name = "Strong Cap" , enl = "strong cap", model = 4110} +models.head[12526] = { name = "Copper Hairpin +1" , enl = "copper hairpin +1", model = 4096} +models.head[12527] = { name = "Circlet +1" , enl = "circlet +1", model = 4116} +models.head[12528] = { name = "Brass Cap +1" , enl = "brass cap +1", model = 4111} +models.head[12529] = { name = "Brass Hairpin +1" , enl = "brass hairpin +1", model = 4096} +models.head[12530] = { name = "Sage's Circlet" , enl = "sage's circlet", model = 4116} +models.head[12531] = { name = "Silver Hairpin +1" , enl = "silver hairpin +1", model = 4096} +models.head[12532] = { name = "Brass Mask +1" , enl = "brass mask +1", model = 4124} +models.head[12533] = { name = "Silver Mask +1" , enl = "silver mask +1", model = 4101} +models.head[12534] = { name = "Hachimaki +1" , enl = "hachimaki +1", model = 4113} +models.head[12535] = { name = "Great Headgear" , enl = "great headgear", model = 4117} +models.head[12536] = { name = "Erd. Headband" , enl = "erudite's headband", model = 4096} +models.head[12537] = { name = "Ctn. Hachimaki +1" , enl = "cotton hachimaki +1", model = 4113} +models.head[12538] = { name = "Red Cap +1" , enl = "red cap +1", model = 4119} +models.head[12539] = { name = "Soil Hachimaki +1" , enl = "soil hachimaki +1", model = 4113} +models.head[12540] = { name = "Alluring Headband" , enl = "alluring headband", model = 4096} +models.head[12541] = { name = "Wool Cap +1" , enl = "wool cap +1", model = 4119} +models.head[12542] = { name = "Lth. Bandana +1" , enl = "leather bandana +1", model = 4097} +models.head[12543] = { name = "Windshear Hat" , enl = "windshear hat", model = 4114} +models.head[13568] = { name = "Scarlet Ribbon" , enl = "scarlet ribbon", model = 4132} +models.head[13569] = { name = "Purple Ribbon" , enl = "purple ribbon", model = 4129} +models.head[13574] = { name = "Black Ribbon" , enl = "black ribbon", model = 4131} +models.head[13590] = { name = "Green Ribbon" , enl = "green ribbon", model = 4130} +models.head[13698] = { name = "Beak Helm" , enl = "beak helm", model = 4102} +models.head[13701] = { name = "Beak Helm +1" , enl = "beak helm +1", model = 4102} +models.head[13704] = { name = "Ogre Mask" , enl = "ogre mask", model = 4190} +models.head[13711] = { name = "Carapace Mask" , enl = "carapace mask", model = 4112} +models.head[13824] = { name = "Strong Bandana" , enl = "strong bandana", model = 4097} +models.head[13825] = { name = "Bone Hairpin +1" , enl = "bone hairpin +1", model = 4096} +models.head[13826] = { name = "Bone Mask +1" , enl = "bone mask +1", model = 4112} +models.head[13827] = { name = "Beetle Mask +1" , enl = "beetle mask +1", model = 4112} +models.head[13828] = { name = "Horn Hairpin +1" , enl = "horn hairpin +1", model = 4096} +models.head[13829] = { name = "Carapace Mask +1" , enl = "carapace mask +1", model = 4112} +models.head[13830] = { name = "Lgn. Circlet" , enl = "legionnaire's circlet", model = 4116} +models.head[13831] = { name = "Sallet +1" , enl = "sallet +1", model = 4098} +models.head[13832] = { name = "Banded Helm +1" , enl = "banded helm +1", model = 4108} +models.head[13833] = { name = "Noble's Ribbon" , enl = "noble's ribbon", model = 4132} +models.head[13834] = { name = "Mage's Hat" , enl = "mage's hat", model = 4114} +models.head[13835] = { name = "Dino Helm" , enl = "dino helm", model = 4103} +models.head[13836] = { name = "Shell Hairpin +1" , enl = "shell hairpin +1", model = 4096} +models.head[13837] = { name = "Bonze's Circlet" , enl = "bonze's circlet", model = 4116} +models.head[13838] = { name = "Dodge Headband" , enl = "dodge headband", model = 4096} +models.head[13839] = { name = "Marine Hat" , enl = "marine hat", model = 4114} +models.head[13840] = { name = "Electrum Hairpin" , enl = "electrum hairpin", model = 4096} +models.head[13841] = { name = "Valkyrie's Mask" , enl = "valkyrie's mask", model = 4123} +models.head[13842] = { name = "Tavnazian Mask" , enl = "tavnazian mask", model = 4124} +models.head[13843] = { name = "Silk Hat +1" , enl = "silk hat +1", model = 4115} +models.head[13844] = { name = "Shn. Hachigane +1" , enl = "shinobi hachigane +1", model = 4100} +models.head[13845] = { name = "Celata +1" , enl = "celata +1", model = 4154} +models.head[13846] = { name = "Scorpion Helm" , enl = "scorpion helm", model = 4210} +models.head[13847] = { name = "Mythril Sallet +1" , enl = "mythril sallet +1", model = 4125} +models.head[13848] = { name = "Gilt Armet" , enl = "gilt armet", model = 4121} +models.head[13849] = { name = "Gold Hairpin +1" , enl = "gold hairpin +1", model = 4096} +models.head[13850] = { name = "Merman's Hairpin" , enl = "merman's hairpin", model = 4096} +models.head[13851] = { name = "Silk Headband +1" , enl = "silk headband +1", model = 4096} +models.head[13852] = { name = "Suijin Kabuto" , enl = "suijin kabuto", model = 4195} +models.head[13853] = { name = "Ferox Vizor" , enl = "ferox vizor", model = 4122} +models.head[13854] = { name = "Green Ribbon +1" , enl = "green ribbon +1", model = 4130} +models.head[13855] = { name = "Healer's Cap" , enl = "healer's cap", model = 4164} +models.head[13856] = { name = "Wizard's Petasos" , enl = "wizard's petasos", model = 4166} +models.head[13857] = { name = "Choral Roundlet" , enl = "choral roundlet", model = 4178} +models.head[13858] = { name = "Prism Headband" , enl = "prism headband", model = 4096} +models.head[13859] = { name = "Coral Visor +1" , enl = "coral visor +1", model = 4122} +models.head[13860] = { name = "Dragon Mask +1" , enl = "dragon mask +1", model = 4156} +models.head[13861] = { name = "Feral Helm" , enl = "feral helm", model = 4103} +models.head[13862] = { name = "Torama Mask" , enl = "torama mask", model = 4150} +models.head[13863] = { name = "Darksteel Cap +1" , enl = "darksteel cap +1", model = 4110} +models.head[13864] = { name = "Merman's Cap" , enl = "merman's cap", model = 4152} +models.head[13865] = { name = "Zunari Kabuto +1" , enl = "zunari kabuto +1", model = 4105} +models.head[13866] = { name = "Green Beret +1" , enl = "green beret +1", model = 4120} +models.head[13867] = { name = "War Beret +1" , enl = "war beret +1", model = 4192} +models.head[13868] = { name = "Myochin Kabuto" , enl = "myochin kabuto", model = 4182} +models.head[13869] = { name = "Ninja Hatsuburi" , enl = "ninja hatsuburi", model = 4184} +models.head[13870] = { name = "Opo-opo Crown" , enl = "opo-opo crown", model = 4201} +models.head[13871] = { name = "Iron Visor" , enl = "iron visor", model = 4122} +models.head[13872] = { name = "Iron Visor +1" , enl = "iron visor +1", model = 4122} +models.head[13873] = { name = "Steel Visor" , enl = "steel visor", model = 4122} +models.head[13874] = { name = "Steel Visor +1" , enl = "steel visor +1", model = 4122} +models.head[13875] = { name = "Msn. Jinpachi" , enl = "monsoon jinpachi", model = 4113} +models.head[13876] = { name = "Zenith Crown" , enl = "zenith crown", model = 4203} +models.head[13877] = { name = "Zenith Crown +1" , enl = "zenith crown +1", model = 4203} +models.head[13878] = { name = "Carapace Helm" , enl = "carapace helm", model = 4204} +models.head[13879] = { name = "Carapace Helm +1" , enl = "carapace helm +1", model = 4204} +models.head[13880] = { name = "President. Hairpin" , enl = "presidential hairpin", model = 4096} +models.head[13881] = { name = "Arhat's Jinpachi" , enl = "arhat's jinpachi", model = 4155} +models.head[13882] = { name = "Corsair's Hat" , enl = "corsair's hat", model = 4114} +models.head[13883] = { name = "Corsair's Hat +1" , enl = "corsair's hat +1", model = 4114} +models.head[13884] = { name = "Jester's Headband" , enl = "jester's headband", model = 4096} +models.head[13885] = { name = "Jgl. Headband" , enl = "juggler's headband", model = 4096} +models.head[13886] = { name = "Arh. Jinpachi +1" , enl = "arhat's jinpachi +1", model = 4155} +models.head[13887] = { name = "Black Sallet" , enl = "black sallet", model = 4247} +models.head[13888] = { name = "Onyx Sallet" , enl = "onyx sallet", model = 4247} +models.head[13889] = { name = "Bastokan Cap" , enl = "bastokan cap", model = 4111} +models.head[13890] = { name = "Republic Cap" , enl = "republic cap", model = 4111} +models.head[13891] = { name = "San d'Orian Helm" , enl = "san d'orian helm", model = 4108} +models.head[13892] = { name = "Kingdom Helm" , enl = "kingdom helm", model = 4109} +models.head[13893] = { name = "Irn.Msk. Armet +1" , enl = "iron musketeer's armet +1", model = 4121} +models.head[13894] = { name = "Irn.Msk. Armet +2" , enl = "iron musketeer's armet +2", model = 4121} +models.head[13895] = { name = "San. Bandana" , enl = "san d'orian bandana", model = 4097} +models.head[13896] = { name = "Kingdom Bandana" , enl = "kingdom bandana", model = 4097} +models.head[13897] = { name = "Bastokan Visor" , enl = "bastokan visor", model = 4122} +models.head[13898] = { name = "Republic Visor" , enl = "republic visor", model = 4122} +models.head[13899] = { name = "Bastokan Circlet" , enl = "bastokan circlet", model = 4116} +models.head[13900] = { name = "Republic Circlet" , enl = "republic circlet", model = 4116} +models.head[13901] = { name = "Win. Hachimaki" , enl = "windurstian hachimaki", model = 4113} +models.head[13902] = { name = "Fed. Hachimaki" , enl = "federation hachimaki", model = 4113} +models.head[13903] = { name = "Win. Headgear" , enl = "windurstian headgear", model = 4117} +models.head[13904] = { name = "Fed. Headgear" , enl = "federation headgear", model = 4117} +models.head[13905] = { name = "T.M. Hat +1" , enl = "tactician magician's hat +1", model = 4115} +models.head[13906] = { name = "T.M. Hat +2" , enl = "tactician magician's hat +2", model = 4115} +models.head[13907] = { name = "Ogre Mask +1" , enl = "ogre mask +1", model = 4190} +models.head[13908] = { name = "Crimson Mask" , enl = "crimson mask", model = 4158} +models.head[13909] = { name = "Blood Mask" , enl = "blood mask", model = 4158} +models.head[13910] = { name = "Roshi Jinpachi" , enl = "roshi jinpachi", model = 4194} +models.head[13911] = { name = "Kaiser Schaller" , enl = "kaiser schaller", model = 4191} +models.head[13912] = { name = "Shadow Mask" , enl = "shadow mask", model = 4123} +models.head[13913] = { name = "Mushroom Helm" , enl = "mushroom helm", model = 4098} +models.head[13914] = { name = "Aegishjalmr" , enl = "aegishjalmr", model = 4151} +models.head[13915] = { name = "Optical Hat" , enl = "optical hat", model = 4115} +models.head[13916] = { name = "Pumpkin Head" , enl = "pumpkin head", model = 4146} +models.head[13917] = { name = "Horror Head" , enl = "horror head", model = 4146} +models.head[13918] = { name = "Tiger Mask" , enl = "tiger mask", model = 4147} +models.head[13919] = { name = "Feral Mask" , enl = "feral mask", model = 4147} +models.head[13920] = { name = "Wyvern Helm" , enl = "wyvern helm", model = 4148} +models.head[13921] = { name = "Wyvern Helm +1" , enl = "wyvern helm +1", model = 4148} +models.head[13922] = { name = "Demon Helm" , enl = "demon helm", model = 4149} +models.head[13923] = { name = "Demon Helm +1" , enl = "demon helm +1", model = 4149} +models.head[13924] = { name = "Armada Celata" , enl = "armada celata", model = 4159} +models.head[13925] = { name = "Rst. Jinpachi" , enl = "rasetsu jinpachi", model = 4157} +models.head[13926] = { name = "Rst. Jinpachi +1" , enl = "rasetsu jinpachi +1", model = 4157} +models.head[13927] = { name = "Hecatomb Cap" , enl = "hecatomb cap", model = 4196} +models.head[13928] = { name = "Hecatomb Cap +1" , enl = "hecatomb cap +1", model = 4196} +models.head[13929] = { name = "Errant Hat" , enl = "errant hat", model = 4197} +models.head[13930] = { name = "Mahatma Hat" , enl = "mahatma hat", model = 4197} +models.head[13931] = { name = "Lilac Corsage" , enl = "lilac corsage", model = 4212} +models.head[13932] = { name = "Gala Corsage" , enl = "gala corsage", model = 4212} +models.head[13933] = { name = "Bridal Corsage" , enl = "bridal corsage", model = 4212} +models.head[13934] = { name = "Shr.Znr.Kabuto" , enl = "shura zunari kabuto", model = 4202} +models.head[13935] = { name = "Shr.Znr.Kabuto +1" , enl = "shura zunari kabuto +1", model = 4202} +models.head[13936] = { name = "Dragon Cap" , enl = "dragon cap", model = 4206} +models.head[13937] = { name = "Dragon Cap +1" , enl = "dragon cap +1", model = 4206} +models.head[13938] = { name = "Dusk Mask +1" , enl = "dusk mask +1", model = 4205} +models.head[13939] = { name = "Austere Hat" , enl = "austere hat", model = 4211} +models.head[13940] = { name = "Penance Hat" , enl = "penance hat", model = 4211} +models.head[13941] = { name = "Gem Barbuta" , enl = "gem barbuta", model = 4151} +models.head[13942] = { name = "Panther Mask" , enl = "panther mask", model = 4145} +models.head[13943] = { name = "Panther Mask +1" , enl = "panther mask +1", model = 4145} +models.head[13944] = { name = "Gavial Mask +1" , enl = "gavial mask +1", model = 4123} +models.head[13945] = { name = "Shaded Specs." , enl = "shaded spectacles", model = 4221} +models.head[13946] = { name = "Magnifying Specs." , enl = "magnifying spectacles", model = 4215} +models.head[13947] = { name = "Protective Specs." , enl = "protective spectacles", model = 4217} +models.head[13948] = { name = "Chef's Hat" , enl = "chef's hat", model = 4220} +models.head[13949] = { name = "Roshi Jinpachi +1" , enl = "roshi jinpachi +1", model = 4194} +models.head[13950] = { name = "Elite Beret" , enl = "elite beret", model = 4192} +models.head[13951] = { name = "Elite Beret +1" , enl = "elite beret +1", model = 4192} +models.head[15072] = { name = "Warrior's Mask" , enl = "warrior's mask", model = 4161} +models.head[15073] = { name = "Melee Crown" , enl = "melee crown", model = 4163} +models.head[15074] = { name = "Cleric's Cap" , enl = "cleric's cap", model = 4165} +models.head[15075] = { name = "Sorcerer's Petas." , enl = "sorcerer's petasos", model = 4167} +models.head[15076] = { name = "Duelist's Chapeau" , enl = "duelist's chapeau", model = 4169} +models.head[15077] = { name = "Assassin's Bonnet" , enl = "assassin's bonnet", model = 4171} +models.head[15078] = { name = "Valor Coronet" , enl = "valor coronet", model = 4173} +models.head[15079] = { name = "Abyss Burgeonet" , enl = "abyss burgeonet", model = 4175} +models.head[15080] = { name = "Monster Helm" , enl = "monster helm", model = 4177} +models.head[15081] = { name = "Bard's Roundlet" , enl = "bard's roundlet", model = 4179} +models.head[15082] = { name = "Scout's Beret" , enl = "scout's beret", model = 4181} +models.head[15083] = { name = "Saotome Kabuto" , enl = "saotome kabuto", model = 4183} +models.head[15084] = { name = "Koga Hatsuburi" , enl = "koga hatsuburi", model = 4185} +models.head[15085] = { name = "Wyrm Armet" , enl = "wyrm armet", model = 4187} +models.head[15086] = { name = "Summoner's Horn" , enl = "summoner's horn", model = 4189} +models.head[15147] = { name = "Garrison Sallet" , enl = "garrison sallet", model = 4098} +models.head[15148] = { name = "Mana Circlet" , enl = "mana circlet", model = 4116} +models.head[15149] = { name = "Rival Ribbon" , enl = "rival ribbon", model = 4128} +models.head[15150] = { name = "Shock Mask" , enl = "shock mask", model = 4112} +models.head[15151] = { name = "Super Ribbon" , enl = "super ribbon", model = 4132} +models.head[15152] = { name = "Cactuar Ribbon" , enl = "cactuar ribbon", model = 4130} +models.head[15153] = { name = "Sha'ir Turban" , enl = "sha'ir turban", model = 4231} +models.head[15154] = { name = "Sheikh Turban" , enl = "sheikh turban", model = 4231} +models.head[15155] = { name = "Barone Zucchetto" , enl = "barone zucchetto", model = 4232} +models.head[15156] = { name = "Conte Zucchetto" , enl = "conte zucchetto", model = 4232} +models.head[15157] = { name = "Bison Warbonnet" , enl = "bison warbonnet", model = 4230} +models.head[15158] = { name = "Brv. Warbonnet" , enl = "brave's warbonnet", model = 4230} +models.head[15159] = { name = "Igqira Tiara" , enl = "igqira tiara", model = 4233} +models.head[15160] = { name = "Genie Tiara" , enl = "genie tiara", model = 4233} +models.head[15161] = { name = "Noct Beret" , enl = "noct beret", model = 4229} +models.head[15162] = { name = "Mist Crown" , enl = "mist crown", model = 4226} +models.head[15163] = { name = "Seer's Crown" , enl = "seer's crown", model = 4227} +models.head[15164] = { name = "Garish Crown" , enl = "garish crown", model = 4226} +models.head[15165] = { name = "Shade Tiara" , enl = "shade tiara", model = 4225} +models.head[15166] = { name = "Seer's Crown +1" , enl = "seer's crown +1", model = 4227} +models.head[15167] = { name = "Eisenschaller" , enl = "eisenschaller", model = 4234} +models.head[15168] = { name = "Rubious Crown" , enl = "rubious crown", model = 4226} +models.head[15169] = { name = "Shade Tiara +1" , enl = "shade tiara +1", model = 4225} +models.head[15170] = { name = "Blink Band" , enl = "blink band", model = 4096} +models.head[15171] = { name = "Kampfschaller" , enl = "kampfschaller", model = 4234} +models.head[15172] = { name = "Noct Beret +1" , enl = "noct beret +1", model = 4229} +models.head[15173] = { name = "Kosshin" , enl = "kosshin", model = 4096} +models.head[15174] = { name = "Frenzy Sallet" , enl = "frenzy sallet", model = 4125} +models.head[15175] = { name = "Reviler's Helm" , enl = "reviler's helm", model = 4149} +models.head[15176] = { name = "Pumpkin Head II" , enl = "pumpkin head ii", model = 4146} +models.head[15177] = { name = "Horror Head II" , enl = "horror head ii", model = 4146} +models.head[15178] = { name = "Dream Hat" , enl = "dream hat", model = 4236} +models.head[15179] = { name = "Dream Hat +1" , enl = "dream hat +1", model = 4236} +models.head[15180] = { name = "Cache-nez" , enl = "cache-nez", model = 4101} +models.head[15181] = { name = "Pineal Hat" , enl = "pineal hat", model = 4115} +models.head[15182] = { name = "Zoolater Hat" , enl = "zoolater hat", model = 4211} +models.head[15183] = { name = "Dobson Bandana" , enl = "dobson bandana", model = 4097} +models.head[15184] = { name = "Voyager Sallet" , enl = "voyager sallet", model = 4125} +models.head[15185] = { name = "Walkure Mask" , enl = "walkure mask", model = 4123} +models.head[15186] = { name = "Trump Crown" , enl = "trump crown", model = 4203} +models.head[15187] = { name = "Hmn. Jinpachi +1" , enl = "hachiman jinpachi +1", model = 4237} +models.head[15188] = { name = "Hachiman Jinpachi" , enl = "hachiman jinpachi", model = 4237} +models.head[15189] = { name = "Lord's Armet" , enl = "lord's armet", model = 4235} +models.head[15190] = { name = "Wise Cap" , enl = "wise cap", model = 4239} +models.head[15191] = { name = "Wise Cap +1" , enl = "wise cap +1", model = 4239} +models.head[15192] = { name = "Yasha Jinpachi +1" , enl = "yasha jinpachi +1", model = 4194} +models.head[15193] = { name = "King's Armet" , enl = "king's armet", model = 4235} +models.head[15194] = { name = "Maat's Cap" , enl = "maat's cap", model = 4248} +models.head[15195] = { name = "Faerie Hairpin" , enl = "faerie hairpin", model = 4096} +models.head[15196] = { name = "Elegant Ribbon" , enl = "elegant ribbon", model = 4129} +models.head[15197] = { name = "Vampire Mask" , enl = "vampire mask", model = 4156} +models.head[15198] = { name = "Sprout Beret" , enl = "sprout beret", model = 4249} +models.head[15199] = { name = "Guide Beret" , enl = "guide beret", model = 4250} +models.head[15200] = { name = "Orc Helm" , enl = "orc helm", model = 4240} +models.head[15201] = { name = "Quadav Barbut" , enl = "quadav barbut", model = 4243} +models.head[15202] = { name = "Yagudo Headgear" , enl = "yagudo headgear", model = 4241} +models.head[15203] = { name = "Goblin Coif" , enl = "goblin coif", model = 4242} +models.head[15204] = { name = "Mandragora Beret" , enl = "mandragora beret", model = 4249} +models.head[15205] = { name = "Alumine Salade" , enl = "alumine salade", model = 4246} +models.head[15206] = { name = "Luisant Salade" , enl = "luisant salade", model = 4246} +models.head[15207] = { name = "Trader's Chapeau" , enl = "trader's chapeau", model = 4245} +models.head[15208] = { name = "Baron's Chapeau" , enl = "baron's chapeau", model = 4245} +models.head[15209] = { name = "Unicorn Cap" , enl = "unicorn cap", model = 4244} +models.head[15210] = { name = "Unicorn Cap +1" , enl = "unicorn cap +1", model = 4244} +models.head[15211] = { name = "Reraise Hairpin" , enl = "reraise hairpin", model = 4096} +models.head[15212] = { name = "Stars Cap" , enl = "stars cap", model = 4251} +models.head[15213] = { name = "Laurel Crown" , enl = "laurel crown", model = 4252} +models.head[15214] = { name = "Gadzradd's Helm" , enl = "gadzradd's helm", model = 4240} +models.head[15215] = { name = "Da'Vhu's Barbut" , enl = "da'vhu's barbut", model = 4243} +models.head[15216] = { name = "Tsoo's Headgear" , enl = "tsoo haja's headgear", model = 4241} +models.head[15217] = { name = "Choplix's Coif" , enl = "choplix's coif", model = 4242} +models.head[15218] = { name = "Entrancing Ribbon" , enl = "entrancing ribbon", model = 4132} +models.head[15219] = { name = "Sinister Mask" , enl = "sinister mask", model = 4101} +models.head[15220] = { name = "Rain Hat" , enl = "rain hat", model = 4211} +models.head[15221] = { name = "Patroclus's Helm" , enl = "patroclus's helm", model = 4204} +models.head[15222] = { name = "Spelunker's Hat" , enl = "spelunker's hat", model = 4115} +models.head[15223] = { name = "Ace's Helm" , enl = "ace's helm", model = 4210} +models.head[15224] = { name = "Empress Hairpin" , enl = "empress hairpin", model = 4096} +models.head[15225] = { name = "Ftr. Mask +1" , enl = "fighter's mask +1", model = 4160} +models.head[15226] = { name = "Tpl. Crown +1" , enl = "temple crown +1", model = 4162} +models.head[15227] = { name = "Hlr. Cap +1" , enl = "healer's cap +1", model = 4164} +models.head[15228] = { name = "Wzd. Petasos +1" , enl = "wizard's petasos +1", model = 4166} +models.head[15229] = { name = "Wlk. Chapeau +1" , enl = "warlock's chapeau +1", model = 4168} +models.head[15230] = { name = "Rog. Bonnet +1" , enl = "rogue's bonnet +1", model = 4170} +models.head[15231] = { name = "Glt. Coronet +1" , enl = "gallant coronet +1", model = 4172} +models.head[15232] = { name = "Chs. Burgeonet +1" , enl = "chaos burgeonet +1", model = 4174} +models.head[15233] = { name = "Bst. Helm +1" , enl = "beast helm +1", model = 4176} +models.head[15234] = { name = "Chl. Roundlet +1" , enl = "choral roundlet +1", model = 4178} +models.head[15235] = { name = "Htr. Beret +1" , enl = "hunter's beret +1", model = 4180} +models.head[15236] = { name = "Myn. Kabuto +1" , enl = "myochin kabuto +1", model = 4182} +models.head[15237] = { name = "Nin. Hatsuburi +1" , enl = "ninja hatsuburi +1", model = 4184} +models.head[15238] = { name = "Drn. Armet +1" , enl = "drachen armet +1", model = 4186} +models.head[15239] = { name = "Evk. Horn +1" , enl = "evoker's horn +1", model = 4188} +models.head[15240] = { name = "Homam Zucchetto" , enl = "homam zucchetto", model = 4255} +models.head[15241] = { name = "Nashira Turban" , enl = "nashira turban", model = 4256} +models.head[15242] = { name = "Crow Beret" , enl = "crow beret", model = 4258} +models.head[15243] = { name = "Raven Beret" , enl = "raven beret", model = 4258} +models.head[15244] = { name = "Flawless Ribbon" , enl = "flawless ribbon", model = 4130} +models.head[15245] = { name = "War. Mask +1" , enl = "warrior's mask +1", model = 4161} +models.head[15246] = { name = "Mel. Crown +1" , enl = "melee crown +1", model = 4163} +models.head[15247] = { name = "Clr. Cap +1" , enl = "cleric's cap +1", model = 4165} +models.head[15248] = { name = "Src. Petasos +1" , enl = "sorcerer's petasos +1", model = 4167} +models.head[15249] = { name = "Dls. Chapeau +1" , enl = "duelist's chapeau +1", model = 4169} +models.head[15250] = { name = "Asn. Bonnet +1" , enl = "assassin's bonnet +1", model = 4171} +models.head[15251] = { name = "Vlr. Coronet +1" , enl = "valor coronet +1", model = 4173} +models.head[15252] = { name = "Abs. Burgeonet +1" , enl = "abyss burgeonet +1", model = 4175} +models.head[15253] = { name = "Mst. Helm +1" , enl = "monster helm +1", model = 4177} +models.head[15254] = { name = "Brd. Roundlet +1" , enl = "bard's roundlet +1", model = 4179} +models.head[15255] = { name = "Sct. Beret +1" , enl = "scout's beret +1", model = 4181} +models.head[15256] = { name = "Sao. Kabuto +1" , enl = "saotome kabuto +1", model = 4183} +models.head[15257] = { name = "Kog. Hatsuburi +1" , enl = "koga hatsuburi +1", model = 4185} +models.head[15258] = { name = "Wym. Armet +1" , enl = "wyrm armet +1", model = 4187} +models.head[15259] = { name = "Smn. Horn +1" , enl = "summoner's horn +1", model = 4189} +models.head[15260] = { name = "Hydra Beret" , enl = "hydra beret", model = 4259} +models.head[15261] = { name = "Hydra Tiara" , enl = "hydra tiara", model = 4224} +models.head[15262] = { name = "Hydra Salade" , enl = "hydra salade", model = 4260} +models.head[15263] = { name = "Hydra Cap" , enl = "hydra cap", model = 4228} +models.head[15264] = { name = "Bahamut's Mask" , enl = "bahamut's mask", model = 4158} +models.head[15265] = { name = "Magus Keffiyeh" , enl = "magus keffiyeh", model = 4261} +models.head[15266] = { name = "Corsair's Tricorne" , enl = "corsair's tricorne", model = 4263} +models.head[15267] = { name = "Pup. Taj" , enl = "puppetry taj", model = 4265} +models.head[15268] = { name = "Eld. Bone Hairpin" , enl = "eldritch bone hairpin", model = 4096} +models.head[15269] = { name = "Eld. Horn Hairpin" , enl = "eldritch horn hairpin", model = 4096} +models.head[15270] = { name = "Walahra Turban" , enl = "walahra turban", model = 4231} +models.head[16061] = { name = "Sipahi Turban" , enl = "sipahi turban", model = 4267} +models.head[16062] = { name = "Amir Puggaree" , enl = "amir puggaree", model = 4270} +models.head[16063] = { name = "Jaridah Khud" , enl = "jaridah khud", model = 4268} +models.head[16064] = { name = "Yigit Turban" , enl = "yigit turban", model = 4272} +models.head[16065] = { name = "Storm Zucchetto" , enl = "storm zucchetto", model = 4255} +models.head[16066] = { name = "Storm Turban" , enl = "storm turban", model = 4256} +models.head[16067] = { name = "Abtal Turban" , enl = "abtal turban", model = 4267} +models.head[16068] = { name = "Akinji Khud" , enl = "akinji khud", model = 4268} +models.head[16069] = { name = "Pln. Qalansuwa" , enl = "pahluwan qalansuwa", model = 4271} +models.head[16070] = { name = "Glory Crown" , enl = "glory crown", model = 4298} +models.head[16071] = { name = "Kawahori Kabuto" , enl = "kawahori kabuto", model = 4202} +models.head[16072] = { name = "Brigand's Mask" , enl = "brigand's mask", model = 4150} +models.head[16073] = { name = "Hydra Mask" , enl = "hydra mask", model = 4123} +models.head[16074] = { name = "Hydra Mask +1" , enl = "hydra mask +1", model = 4123} +models.head[16075] = { name = "Witch Hat" , enl = "witch hat", model = 4277} +models.head[16076] = { name = "Coven Hat" , enl = "coven hat", model = 4277} +models.head[16077] = { name = "Megrim Crown" , enl = "megrim crown", model = 4153} +models.head[16078] = { name = "Blissful Chapeau" , enl = "blissful chapeau", model = 4245} +models.head[16079] = { name = "Silken Hat" , enl = "silken hat", model = 4115} +models.head[16080] = { name = "Magi Hat" , enl = "magi hat", model = 4115} +models.head[16081] = { name = "Curate's Hat" , enl = "curate's hat", model = 4239} +models.head[16082] = { name = "Volunteer's Khud" , enl = "volunteer's khud", model = 4269} +models.head[16083] = { name = "Mrc. Turban" , enl = "mercenary's turban", model = 4267} +models.head[16084] = { name = "Ares' Mask" , enl = "ares' mask", model = 4278} +models.head[16085] = { name = "Enyo's Mask" , enl = "enyo's mask", model = 4123} +models.head[16086] = { name = "Phobos's Mask" , enl = "phobos's mask", model = 4156} +models.head[16087] = { name = "Deimos's Mask" , enl = "deimos's mask", model = 4158} +models.head[16088] = { name = "Skadi's Visor" , enl = "skadi's visor", model = 4279} +models.head[16089] = { name = "Njord's Mask" , enl = "njord's mask", model = 4124} +models.head[16090] = { name = "Freyr's Mask" , enl = "freyr's mask", model = 4205} +models.head[16091] = { name = "Freya's Mask" , enl = "freya's mask", model = 4150} +models.head[16092] = { name = "Usukane Somen" , enl = "usukane somen", model = 4280} +models.head[16093] = { name = "H.kazu Hachimaki" , enl = "hoshikazu hachimaki", model = 4113} +models.head[16094] = { name = "T.kazu Jinpachi" , enl = "tsukikazu jinpachi", model = 4194} +models.head[16095] = { name = "Hikazu Kabuto" , enl = "hikazu kabuto", model = 4015} +models.head[16096] = { name = "Marduk's Tiara" , enl = "marduk's tiara", model = 4281} +models.head[16097] = { name = "Anu's Tiara" , enl = "anu's tiara", model = 4225} +models.head[16098] = { name = "Ea's Tiara" , enl = "ea's tiara", model = 4225} +models.head[16099] = { name = "Enlil's Tiara" , enl = "enlil's tiara", model = 4224} +models.head[16100] = { name = "Morrigan's Coron." , enl = "morrigan's coronal", model = 4282} +models.head[16101] = { name = "Nemain's Crown" , enl = "nemain's crown", model = 4226} +models.head[16102] = { name = "Bodb's Crown" , enl = "bodb's crown", model = 4227} +models.head[16103] = { name = "Macha's Crown" , enl = "macha's crown", model = 4153} +models.head[16104] = { name = "Khimaira Bonnet" , enl = "khimaira bonnet", model = 4230} +models.head[16105] = { name = "Stout Bonnet" , enl = "stout bonnet", model = 4230} +models.head[16106] = { name = "Askar Zucchetto" , enl = "askar zucchetto", model = 4291} +models.head[16107] = { name = "Denali Bonnet" , enl = "denali bonnet", model = 4292} +models.head[16108] = { name = "Goliard Chapeau" , enl = "goliard chapeau", model = 4293} +models.head[16109] = { name = "Egg Helm" , enl = "egg helm", model = 4294} +models.head[16110] = { name = "Plain Cap" , enl = "plain cap", model = 4110} +models.head[16111] = { name = "Tabin Beret" , enl = "tabin beret", model = 4120} +models.head[16112] = { name = "Tabin Beret +1" , enl = "tabin beret +1", model = 4120} +models.head[16113] = { name = "Shadow Helm" , enl = "shadow helm", model = 4296} +models.head[16114] = { name = "Valkyrie's Helm" , enl = "valkyrie's helm", model = 4296} +models.head[16115] = { name = "Shadow Hat" , enl = "shadow hat", model = 4297} +models.head[16116] = { name = "Valkyrie's Hat" , enl = "valkyrie's hat", model = 4297} +models.head[16117] = { name = "Valhalla Helm" , enl = "valhalla helm", model = 4295} +models.head[16118] = { name = "Moogle Cap" , enl = "moogle cap", model = 4299} +models.head[16119] = { name = "Nomad Cap" , enl = "nomad cap", model = 4300} +models.head[16120] = { name = "Redeyes" , enl = "redeyes", model = 4301} +models.head[16121] = { name = "Mamool Ja Helm" , enl = "mamool ja helm", model = 4302} +models.head[16122] = { name = "Troll Coif" , enl = "troll coif", model = 4304} +models.head[16123] = { name = "Lamia Garland" , enl = "lamia garland", model = 4303} +models.head[16124] = { name = "Qiqirn Hood" , enl = "qiqirn hood", model = 4305} +models.head[16125] = { name = "Breeder Mask" , enl = "breeder mask", model = 4177} +models.head[16126] = { name = "Bowman's Mask" , enl = "bowman's mask", model = 4190} +models.head[16127] = { name = "Carline Ribbon" , enl = "carline ribbon", model = 4128} +models.head[16128] = { name = "Wivre Hairpin" , enl = "wivre hairpin", model = 4096} +models.head[16129] = { name = "Wivre Hairpin +1" , enl = "wivre hairpin +1", model = 4096} +models.head[16130] = { name = "Wivre Mask" , enl = "wivre mask", model = 4156} +models.head[16131] = { name = "Wivre Mask +1" , enl = "wivre mask +1", model = 4156} +models.head[16132] = { name = "Dandy Spectacles" , enl = "dandy spectacles", model = 4301} +models.head[16133] = { name = "Fancy Spectacles" , enl = "fancy spectacles", model = 4301} +models.head[16134] = { name = "Zoraal Ja's Helm" , enl = "zoraal ja's helm", model = 4302} +models.head[16135] = { name = "Dartorgor's Coif" , enl = "dartorgor's coif", model = 4304} +models.head[16136] = { name = "No.3's Garland" , enl = "lamia no.3's garland", model = 4303} +models.head[16137] = { name = "Cacaroon's Hood" , enl = "cacaroon's hood", model = 4305} +models.head[16138] = { name = "Dancer's Tiara" , enl = "dancer's tiara", model = 4306} +models.head[16139] = { name = "Dancer's Tiara" , enl = "dancer's tiara", model = 4307} +models.head[16140] = { name = "Scholar's M.board" , enl = "scholar's mortarboard", model = 4310} +models.head[16141] = { name = "Iron Ram Helm" , enl = "iron ram helm", model = 4108} +models.head[16142] = { name = "Fourth Armet" , enl = "fourth division armet", model = 4121} +models.head[16143] = { name = "Cobra Hat" , enl = "cobra unit hat", model = 4115} +models.head[16144] = { name = "Sol Cap" , enl = "sol cap", model = 4319} +models.head[16145] = { name = "Lunar Cap" , enl = "lunar cap", model = 4320} +models.head[16146] = { name = "Iron Ram Sallet" , enl = "iron ram sallet", model = 4317} +models.head[16147] = { name = "Fourth Haube" , enl = "fourth division haube", model = 4318} +models.head[16148] = { name = "Cobra Cap" , enl = "cobra unit cap", model = 4315} +models.head[16149] = { name = "Cobra Cloche" , enl = "cobra unit cloche", model = 4316} +models.head[16150] = { name = "Saurian Helm" , enl = "saurian helm", model = 4102} +models.head[16151] = { name = "Leonine Mask" , enl = "leonine mask", model = 4147} +models.head[16152] = { name = "Hissho Hachimaki" , enl = "hissho hachimaki", model = 4237} +models.head[16153] = { name = "Reikyo Hairpin" , enl = "reikyo hairpin", model = 4096} +models.head[16154] = { name = "Karura Hachigane" , enl = "karura hachigane", model = 4100} +models.head[16155] = { name = "Aurum Armet" , enl = "aurum armet", model = 4235} +models.head[16156] = { name = "Oracle's Cap" , enl = "oracle's cap", model = 4239} +models.head[16157] = { name = "Enkidu's Cap" , enl = "enkidu's cap", model = 4152} +models.head[16158] = { name = "Gnadbhod's Helm" , enl = "gnadbhod's helm", model = 4240} +models.head[16159] = { name = "Zha'Go's Barbut" , enl = "zha'go's barbut", model = 4243} +models.head[16160] = { name = "Ree's Headgear" , enl = "ree habalo's headgear", model = 4241} +models.head[27648] = { name = "Ares' Mask +1" , enl = "ares' mask +1", model = 4278} +models.head[27649] = { name = "Skadi's Visor +1" , enl = "skadi's visor +1", model = 4279} +models.head[27650] = { name = "Usk. Somen +1" , enl = "usukane somen +1", model = 4280} +models.head[27651] = { name = "Marduk's Tiara +1" , enl = "marduk's tiara +1", model = 4281} +models.head[27652] = { name = "Mor. Coronal +1" , enl = "morrigan's coronal +1", model = 4282} +models.head[27653] = { name = "Ker's Mask" , enl = "ker's mask", model = 4278} +models.head[27654] = { name = "Sigyn's Visor" , enl = "sigyn's visor", model = 4279} +models.head[27655] = { name = "Omodaka Somen" , enl = "omodaka somen", model = 4280} +models.head[27656] = { name = "Nabu's Tiara" , enl = "nabu's tiara", model = 4281} +models.head[27657] = { name = "Fea's Coronal" , enl = "fea's coronal", model = 4282} +models.head[27658] = { name = "Ate's Mask" , enl = "ate's mask", model = 4158} +models.head[27659] = { name = "Idi's Mask" , enl = "idi's mask", model = 4150} +models.head[27660] = { name = "Genta Kabuto" , enl = "genta kabuto", model = 4015} +models.head[27661] = { name = "Namru's Tiara" , enl = "namru's tiara", model = 4224} +models.head[27662] = { name = "Neit's Crown" , enl = "neit's crown", model = 4153} +models.head[27663] = { name = "Pummeler's Mask" , enl = "pummeler's mask" , model = 4160 } +models.head[27664] = { name = "Anchorite's Crown" , enl = "anchorite's crown" , model = 4162 } +models.head[27665] = { name = "Theophany Cap" , enl = "theophany cap" , model = 4164 } +models.head[27666] = { name = "Spae. Petasos" , enl = "spaekona's petasos" , model = 4168 } +models.head[27667] = { name = "Atrophy Chapeau" , enl = "atrophy chapeau" , model = 4168 } +models.head[27668] = { name = "Pillager's Bonnet" , enl = "pillager's bonnet" , model = 4170 } +models.head[27669] = { name = "Rev. Coronet" , enl = "reverence coronet" , model = 4172 } +models.head[27670] = { name = "Ignominy Burgeonet" , enl = "ignominy burgeonet" , model = 4174 } +models.head[27671] = { name = "Totemic Helm" , enl = "totemic helm" , model = 4176 } +models.head[27672] = { name = "Brioso Roundlet" , enl = "brioso roundlet" , model = 4178 } +models.head[27673] = { name = "Orion Beret" , enl = "orion beret" , model = 4180 } +models.head[27674] = { name = "Wakido Kabuto" , enl = "wakido kabuto" , model = 4182 } +models.head[27675] = { name = "Hachiya Hatsuburi" , enl = "hachiya hatsuburi" , model = 4184 } +models.head[27676] = { name = "Vishap Armet" , enl = "vishap armet" , model = 4186 } +models.head[27677] = { name = "Convoker's Horn" , enl = "convoker's horn" , model = 4188 } +models.head[27678] = { name = "Assim. Keffiyeh" , enl = "assimilator's keffiyeh" , model = 4261 } +models.head[27679] = { name = "Laksamana's Hat" , enl = "laksamana's hat" , model = 4263 } +models.head[27680] = { name = "Foire Taj" , enl = "foire taj" , model = 4265 } +models.head[27681] = { name = "Maxixi Tiara" , enl = "maxixi tiara" , model = 4306 } +models.head[27682] = { name = "Maxixi Tiara" , enl = "maxixi tiara" , model = 4307 } +models.head[27683] = { name = "Acad. Mortarboard" , enl = "academic's mortarboard" , model = 4310 } +models.head[27684] = { name = "Pumm. Mask +1" , enl = "pummeler's mask +1" , model = 4160 } +models.head[27685] = { name = "Anchor. Crown +1" , enl = "anchorite's crown +1" , model = 4162 } +models.head[27686] = { name = "Theo. Cap +1" , enl = "theophany cap +1" , model = 4164 } +models.head[27687] = { name = "Spae. Petasos +1" , enl = "spaekona's petasos +1" , model = 4166 } +models.head[27688] = { name = "Atro. Chapeau +1" , enl = "atrophy chapeau +1" , model = 4168 } +models.head[27689] = { name = "Pill. Bonnet +1" , enl = "pillager's bonnet +1" , model = 4170 } +models.head[27690] = { name = "Rev. Coronet +1" , enl = "reverence coronet +1" , model = 4172 } +models.head[27691] = { name = "Igno. Burgeonet +1" , enl = "ignominy burgeonet +1" , model = 4174 } +models.head[27692] = { name = "Totemic Helm +1" , enl = "totemic helm +1" , model = 4176 } +models.head[27693] = { name = "Brioso Roundlet +1" , enl = "brioso roundlet +1" , model = 4178 } +models.head[27694] = { name = "Orion Beret +1" , enl = "orion beret +1" , model = 4180 } +models.head[27695] = { name = "Wakido Kabuto +1" , enl = "wakido kabuto +1" , model = 4182 } +models.head[27696] = { name = "Hachi. Hatsu. +1" , enl = "hachiya hatsuburi +1" , model = 4184 } +models.head[27697] = { name = "Vishap Armet +1" , enl = "vishap armet +1" , model = 4186 } +models.head[27698] = { name = "Con. Horn +1" , enl = "convoker's horn +1" , model = 4188 } +models.head[27699] = { name = "Assim. Keffiyeh +1" , enl = "assimilator's keffiyeh +1" , model = 4261 } +models.head[27700] = { name = "Lak. Hat +1" , enl = "laksamana's hat +1" , model = 4263 } +models.head[27701] = { name = "Foire Taj +1" , enl = "foire taj +1" , model = 4265 } +models.head[27702] = { name = "Maxixi Tiara +1" , enl = "maxixi tiara +1" , model = 4306 } +models.head[27703] = { name = "Maxixi Tiara +1" , enl = "maxixi tiara +1" , model = 4307 } +models.head[27704] = { name = "Acad. Mortar. +1" , enl = "academic's mortarboard +1" , model = 4310 } +models.head[27705] = { name = "Geo. Galero +1" , enl = "geomancy galero +1" , model = 4404 } +models.head[27706] = { name = "Rune. Bandeau +1" , enl = "runeist bandeau +1" , model = 4434 } +models.head[27733] = { name = "Straw Hat" , enl = "straw hat" , model = 4462 } +models.head[27735] = { name = "Enedron Glasses" , enl = "enedron glasses" , model = 4221 } +models.head[27736] = { name = "Quiahuiz Helm" , enl = "quiahuiz helm" , model = 4429 } +models.head[27737] = { name = "Kaabanax Hat" , enl = "kaabanax hat" , model = 4442 } +models.head[27738] = { name = "Ejekamal Mask" , enl = "ejekamal mask" , model = 4443 } +models.head[27739] = { name = "Otomi Helm" , enl = "otomi helm" , model = 4444 } +models.head[27740] = { name = "Outrider Mask" , enl = "outrider mask" , model = 4101 } +models.head[27741] = { name = "Espial Cap" , enl = "espial cap" , model = 4119 } +models.head[27742] = { name = "Wayfarer Circlet" , enl = "wayfarer circlet" , model = 4116 } +models.head[27743] = { name = "Tema. Headband" , enl = "temachtiani headband" , model = 4312 } +models.head[27744] = { name = "Lithelimb Cap" , enl = "lithelimb cap" , model = 4110 } +models.head[27745] = { name = "Felistris Mask" , enl = "felistris mask" , model = 4147 } +models.head[27752] = { name = "Karieyh Morion +1" , enl = "Karieyh morion +1" , model = 4438} +models.head[27753] = { name = "Thur. Chapeau +1" , enl = "Thurandaut chapeau +1" , model = 4439 } +models.head[27754] = { name = "Orvail Corona +1" , enl = "Orvail corona +1" , model = 4440 } +models.head[27756] = { name = "Slime Cap" , enl = "slime cap" , model = 4454 } +models.head[27757] = { name = "Bomb Masque" , enl = "bomb masque" , model = 4455 } +models.head[27758] = { name = "Bomb Masque +1" , enl = "bomb masque +1" , model = 4455 } +models.head[27759] = { name = "Korrigan Beret" , enl = "korrigan beret", model = 4250} +models.head[27760] = { name = "Chocobo Masque +1" , enl = "chocobo masque +1", model = 4449} +models.head[27761] = { name = "Gorney Morion" , enl = "gorney morion", model = 4450} +models.head[27762] = { name = "Shneddick Chapeau" , enl = "shneddick chapeau", model = 4451} +models.head[27763] = { name = "Weather. Corona" , enl = "weatherspoon corona", model = 4452} +models.head[27765] = { name = "Chocobo Masque" , enl = "chocobo masque", model = 4449} +models.head[27766] = { name = "Uk'uxkaj Cap" , enl = "uk'uxkaj cap", model = 4426} +models.head[27767] = { name = "Buremte Hat" , enl = "buremte hat", model = 4448} +models.head[27768] = { name = "Cizin Helm" , enl = "cizin helm", model = 4370} +models.head[27769] = { name = "Otronif Mask" , enl = "otronif mask", model = 4343} +models.head[27770] = { name = "Iuitl Headgear" , enl = "iuitl headgear", model = 4352} +models.head[27771] = { name = "Gende. Caubeen" , enl = "gendewitha caubeen", model = 4361} +models.head[27772] = { name = "Hagondes Hat" , enl = "hagondes hat", model = 4358} +models.head[27773] = { name = "Yaoyotl Helm" , enl = "yaoyotl helm", model = 4444} +models.head[27774] = { name = "Whirlpool Mask" , enl = "whirlpool mask", model = 4443} +models.head[27775] = { name = "Nahtirah Hat" , enl = "nahtirah hat", model = 4442} +models.head[27776] = { name = "Mikinaak Helm" , enl = "mikinaak helm", model = 4445} +models.head[27777] = { name = "Manibozho Beret" , enl = "manibozho beret", model = 4446} +models.head[27778] = { name = "Bokwus Circlet" , enl = "bokwus circlet", model = 4447} +models.head[27779] = { name = "Quauhpilli Helm" , enl = "quauhpilli helm", model = 4444} +models.head[27780] = { name = "Chocaliztli Mask" , enl = "chocaliztli mask", model = 4443} +models.head[27781] = { name = "Xux Hat" , enl = "xux hat", model = 4442} +models.head[27782] = { name = "Orvail Corona" , enl = "orvail corona", model = 4440} +models.head[27784] = { name = "Thurandaut Chapeau" , enl = "thurandaut chapeau", model = 4439} +models.head[27785] = { name = "Karieyh Morion" , enl = "karieyh morion", model = 4438} +models.head[27786] = { name = "Geomancy Galero" , enl = "geomancy galero", model = 4404} +models.head[27787] = { name = "Runeist Bandeau" , enl = "runeist bandeau", model = 4434} + +models.head[40001] = { name = "??? Chapeau" , model = 4257 } +models.head[40002] = { name = "Haudrale's Chapeau" , model = 4338 } +models.head[40003] = { name = "Geomancer Relic" , model = 4406 } +models.head[40004] = { name = "Rune Fencer Relic" , model = 4435 } + +models.head["Unknown"] = { 4314, 4441 } + +models.head["Unused"] = { 4099, 4104, 4106, 4107, 4127, 4133, 4134, 4135, 4136, 4137, 4138, 4139, + 4140, 4141, 4142, 4143, 4144, 4219, 4253, 4254, 4273, 4274, 4283, 4284, + 4285, 4286, 4287, 4288, 4289, 4290, 4308, 4309, 4313, 4321, 4322, 4322, + 4323, 4324, 4325, 4328, 4330, 4339, 4340, 4399, 4405, 4407, 4408, 4409, + 4410, 4411, 4412, 4413, 4414, 4415, 4431, 4436, 4437 } diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/helper_functions.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/helper_functions.lua new file mode 100644 index 0000000..7d84543 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/helper_functions.lua @@ -0,0 +1,131 @@ +-- Copyright © 2013-2015, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +function get_item_id(str,slot) + local item_result = false + + if str == "none" then + return "None" + else + + for k,v in pairs(models[slot]) do + if v['enl'] == str or v['name'] == str then + item_result = k + end + end + if item_result then + return tonumber(item_result) + else + return false + end + end +end + +function update_model(index) + packets.inject(packets.new('outgoing', 0x016, { ['Target Index'] = index })) +end + +function load_profile(name) + + if settings.profiles[windower.ffxi.get_player().name:lower() ..'_'.. name:lower()] then + settings[windower.ffxi.get_player().name:lower()]:update(settings.profiles[windower.ffxi.get_player().name:lower() ..'_'.. name:lower()]) + return true + elseif settings.profiles[name:lower()] then + settings[windower.ffxi.get_player().name:lower()]:update(settings.profiles[name:lower()]) + return true + end + + return false +end + +function save_profile(name) + if not name or name:len() == 0 then + error('No profile name was entered.') + end + + if not settings.profiles[name:lower()] then settings.profiles[name:lower()] = T{} end + settings.profiles[name:lower()]:update(settings[windower.ffxi.get_player().name:lower()]) + notice('Saved your current settings to the profile: ' .. name) +end + +function blink_logic(blink_type,character_index,player) + if settings.blinking["all"]["always"] then + return true + elseif settings.blinking[blink_type]["always"] then + return true + end + + if settings.blinking["all"]["combat"] and player.in_combat then + return true + elseif settings.blinking[blink_type]["combat"] and player.in_combat then + return true + end + + if settings.blinking["all"]["target"] and player.target_index == character_index then + return true + elseif settings.blinking[blink_type]["target"] and player.target_index == character_index then + return true + end + + return false +end + +function print_blink_settings(option) + print('DressUp (v'.._addon.version..') Blink Prevention Settings') + if option == "global" or option == "all" then + print(('All: '):text_color(255,255,255)..table.concat(map(settings.blinking["all"],formatting)," ")) + end + if option == "global" or option == "self" then + print(('Self: '):text_color(255,255,255)..table.concat(map(settings.blinking["self"],formatting)," ")) + end + if option == "global" or option == "others" then + print(('Others: '):text_color(255,255,255)..table.concat(map(settings.blinking["others"],formatting)," ")) + end + if option == "global" or option == "party" then + print(('Party: '):text_color(255,255,255)..table.concat(map(settings.blinking["party"],formatting)," ")) + end + if option == "global" or option == "follow" then + print(('Follow: '):text_color(255,255,255)..table.concat(map(settings.blinking["follow"],formatting)," ")) + end +end + +function map(t, func) + local out = {} + for k,v in pairs(t) do + out[k] = func(k, v) + end + return out +end + +function formatting(k, v) + v = tostring(v):gsub("^%l", string.upper) + if windower.wc_match(v,"True") then + v = ('T'):text_color(0, 255, 0) + else + v = 'F' + end + return k:gsub("^%l", string.upper) ..': ['..v..']' +end diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/legs.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/legs.lua new file mode 100644 index 0000000..84fc9bc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/legs.lua @@ -0,0 +1,903 @@ +-- Copyright © 2013, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + + +models.legs = {} + +models.legs["None"] = { name = "empty" , enl = "empty", model = 16384 } + +models.legs[10326] = { name = "Enif Cosciales" , enl = "enif cosciales", model = 16543} +models.legs[10327] = { name = "Adhara Seraweels" , enl = "adhara seraweels", model = 16544} +models.legs[10328] = { name = "Murzim Cosciales" , enl = "murzim cosciales", model = 16543} +models.legs[10329] = { name = "Shedir Seraweels" , enl = "shedir seraweels", model = 16544} +models.legs[10330] = { name = "Marine Boxers" , enl = "marine boxers", model = 16718} +models.legs[10331] = { name = "Marine Shorts" , enl = "marine shorts", model = 16718} +models.legs[10332] = { name = "Woodsy Boxers" , enl = "woodsy boxers", model = 16718} +models.legs[10333] = { name = "Woodsy Shorts" , enl = "woodsy shorts", model = 16719} +models.legs[10334] = { name = "Creek Boxers" , enl = "creek boxers", model = 16718} +models.legs[10335] = { name = "Creek Shorts" , enl = "creek shorts", model = 16719} +models.legs[10336] = { name = "River Shorts" , enl = "river shorts", model = 16718} +models.legs[10337] = { name = "Dune Boxers" , enl = "dune boxers", model = 16718} +models.legs[10338] = { name = "Marine Boxers +1" , enl = "marine boxers +1", model = 16718} +models.legs[10339] = { name = "Marine Shorts +1" , enl = "marine shorts +1", model = 16718} +models.legs[10340] = { name = "Woodsy Boxers +1" , enl = "woodsy boxers +1", model = 16718} +models.legs[10341] = { name = "Woodsy Shorts +1" , enl = "woodsy shorts +1", model = 16719} +models.legs[10342] = { name = "Creek Boxers +1" , enl = "creek boxers +1", model = 16718} +models.legs[10343] = { name = "Creek Shorts +1" , enl = "creek shorts +1", model = 16719} +models.legs[10344] = { name = "River Shorts +1" , enl = "river shorts +1", model = 16718} +models.legs[10345] = { name = "Dune Boxers +1" , enl = "dune boxers +1", model = 16718} +models.legs[10346] = { name = "Dux Cuisses" , enl = "dux cuisses", model = 16720} +models.legs[10347] = { name = "Dux Cuisses +1" , enl = "dux cuisses +1", model = 16720} +models.legs[10348] = { name = "Chelona Trousers" , enl = "chelona trousers", model = 16721} +models.legs[10349] = { name = "Chl. Trousers +1" , enl = "chelona trousers +1", model = 16721} +models.legs[10550] = { name = "Rheic Dirs" , enl = "rheic dirs", model = 16579} +models.legs[10551] = { name = "Rheic Dirs +1" , enl = "rheic dirs +1", model = 16579} +models.legs[10552] = { name = "Rheic Dirs +2" , enl = "rheic dirs +2", model = 16579} +models.legs[10553] = { name = "Rheic Dirs +3" , enl = "rheic dirs +3", model = 16579} +models.legs[10554] = { name = "Phorcys Dirs" , enl = "phorcys dirs", model = 16579} +models.legs[10555] = { name = "Euxine Kecks" , enl = "euxine kecks", model = 16580} +models.legs[10556] = { name = "Euxine Kecks +1" , enl = "euxine kecks +1", model = 16580} +models.legs[10557] = { name = "Euxine Kecks +2" , enl = "euxine kecks +2", model = 16580} +models.legs[10558] = { name = "Euxine Kecks +3" , enl = "euxine kecks +3", model = 16580} +models.legs[10559] = { name = "Thaumas Kecks" , enl = "thaumas kecks", model = 16580} +models.legs[10560] = { name = "Tethyan Trews" , enl = "tethyan trews", model = 16581} +models.legs[10561] = { name = "Tethyan Trews +1" , enl = "tethyan trews +1", model = 16581} +models.legs[10562] = { name = "Tethyan Trews +2" , enl = "tethyan trews +2", model = 16581} +models.legs[10563] = { name = "Tethyan Trews +3" , enl = "tethyan trews +3", model = 16581} +models.legs[10564] = { name = "Nares Trews" , enl = "nares trews", model = 16581} +models.legs[10565] = { name = "Hrafn Hose" , enl = "hrafn hose", model = 16707} +models.legs[10566] = { name = "Tenryu Hakama" , enl = "tenryu hakama", model = 16708} +models.legs[10567] = { name = "Kheper Kecks" , enl = "kheper kecks", model = 16704} +models.legs[10568] = { name = "Auspex Slops" , enl = "auspex slops", model = 16706} +models.legs[10569] = { name = "Paean Tights" , enl = "paean tights", model = 16705} +models.legs[10570] = { name = "Huginn Hose" , enl = "huginn hose", model = 16707} +models.legs[10571] = { name = "Tenryu Hakama +1" , enl = "tenryu hakama +1", model = 16708} +models.legs[10572] = { name = "Khepri Kecks" , enl = "khepri kecks", model = 16704} +models.legs[10573] = { name = "Spurrina Slops" , enl = "spurrina slops", model = 16706} +models.legs[10574] = { name = "Iaso Tights" , enl = "iaso tights", model = 16705} +models.legs[10575] = { name = "Ugol Brayettes" , enl = "ugol brayettes", model = 16534} +models.legs[10576] = { name = "Mavros Brayettes" , enl = "mavros brayettes", model = 16534} +models.legs[10577] = { name = "Urja Trousers" , enl = "urja trousers", model = 16434} +models.legs[10578] = { name = "Sthira Trousers" , enl = "sthira trousers", model = 16434} +models.legs[10579] = { name = "Spolia Trews" , enl = "spolia trews", model = 16533} +models.legs[10580] = { name = "Opima Trews" , enl = "opima trews", model = 16533} +models.legs[10581] = { name = "Lefay Brais" , enl = "lefay brais", model = 16497} +models.legs[10582] = { name = "Gardyloo Trousers" , enl = "gardyloo trousers", model = 16478} +models.legs[10583] = { name = "Hexed Hose" , enl = "hexed hose", model = 16389} +models.legs[10584] = { name = "Hexed Hakama" , enl = "hexed hakama", model = 16393} +models.legs[10585] = { name = "Hexed Kecks" , enl = "hexed kecks", model = 16555} +models.legs[10586] = { name = "Hexed Slops" , enl = "hexed slops", model = 16404} +models.legs[10587] = { name = "Hexed Tights" , enl = "hexed tights", model = 16402} +models.legs[10588] = { name = "Hexed Hose -1" , enl = "hexed hose -1", model = 16389} +models.legs[10589] = { name = "Hexed Hakama -1" , enl = "hexed hakama -1", model = 16393} +models.legs[10590] = { name = "Hexed Kecks -1" , enl = "hexed kecks -1", model = 16555} +models.legs[10591] = { name = "Hexed Slops -1" , enl = "hexed slops -1", model = 16404} +models.legs[10592] = { name = "Hexed Tights -1" , enl = "hexed tights -1", model = 16402} +models.legs[10593] = { name = "Decennial Tights" , enl = "decennial tights", model = 16715} +models.legs[10594] = { name = "Decennial Hose" , enl = "decennial hose", model = 16716} +models.legs[10595] = { name = "Decennial Tights +1" , enl = "decennial tights +1", model = 16715} +models.legs[10596] = { name = "Decennial Hose +1" , enl = "decennial hose +1", model = 16716} +models.legs[10597] = { name = "Akasha Chaps" , enl = "akasha chaps", model = 16495} +models.legs[10598] = { name = "Alcedo Cuisses" , enl = "alcedo cuisses", model = 16496} +models.legs[10710] = { name = "War. Cuisses +2" , enl = "warrior's cuisses +2", model = 16449} +models.legs[10711] = { name = "Mel. Hose +2" , enl = "melee hose +2", model = 16451} +models.legs[10712] = { name = "Clr. Pantaln. +2" , enl = "cleric's pantaloons +2", model = 16453} +models.legs[10713] = { name = "Src. Tonban +2" , enl = "sorcerer's tonban +2", model = 16455} +models.legs[10714] = { name = "Dls. Tights +2" , enl = "duelist's tights +2", model = 16457} +models.legs[10715] = { name = "Asn. Culottes +2" , enl = "assassin's culottes +2", model = 16459} +models.legs[10716] = { name = "Vlr. Breeches +2" , enl = "valor breeches +2", model = 16461} +models.legs[10717] = { name = "Abs. Flanchard +2" , enl = "abyss flanchard +2", model = 16463} +models.legs[10718] = { name = "Mst. Trousers +2" , enl = "monster trousers +2", model = 16465} +models.legs[10719] = { name = "Brd. Cannions +2" , enl = "bard's cannions +2", model = 16467} +models.legs[10720] = { name = "Sct. Braccae +2" , enl = "scout's braccae +2", model = 16469} +models.legs[10721] = { name = "Sao. Haidate +2" , enl = "saotome haidate +2", model = 16471} +models.legs[10722] = { name = "Kog. Hakama +2" , enl = "koga hakama +2", model = 16473} +models.legs[10723] = { name = "Wym. Brais +2" , enl = "wyrm brais +2", model = 16475} +models.legs[10724] = { name = "Smn. Spats +2" , enl = "summoner's spats +2", model = 16477} +models.legs[10725] = { name = "Mirage Shalwar +2" , enl = "mirage shalwar +2", model = 16550} +models.legs[10726] = { name = "Comm. Culottes +2" , enl = "commodore culottes +2", model = 16552} +models.legs[10727] = { name = "Ptn. Churidars +2" , enl = "pantin churidars +2", model = 16554} +models.legs[10728] = { name = "Etoile Tights +2" , enl = "etoile tights +2", model = 16688} +models.legs[10729] = { name = "Argute Pants +2" , enl = "argute pants +2", model = 16599} +models.legs[11124] = { name = "Rvg. Cuisses +2" , enl = "ravager's cuisses +2", model = 16666} +models.legs[11125] = { name = "Tantra Hose +2" , enl = "tantra hose +2", model = 16667} +models.legs[11126] = { name = "Orsn. Pantaln. +2" , enl = "orison pantaloons +2", model = 16668} +models.legs[11127] = { name = "Goet. Chausses +2" , enl = "goetia chausses +2", model = 16669} +models.legs[11128] = { name = "Estqr. Fuseau +2" , enl = "estoqueur's fuseau +2", model = 16670} +models.legs[11129] = { name = "Raid. Culottes +2" , enl = "raider's culottes +2", model = 16671} +models.legs[11130] = { name = "Creed Cuisses +2" , enl = "creed cuisses +2", model = 16672} +models.legs[11131] = { name = "Bale Flanchard +2" , enl = "bale flanchard +2", model = 16673} +models.legs[11132] = { name = "Ferine Quijotes +2" , enl = "ferine quijotes +2", model = 16674} +models.legs[11133] = { name = "Aoidos' Rhing. +2" , enl = "aoidos' rhingrave +2", model = 16675} +models.legs[11134] = { name = "Sylvan Bragues +2" , enl = "sylvan bragues +2", model = 16676} +models.legs[11135] = { name = "Unkai Haidate +2" , enl = "unkai haidate +2", model = 16677} +models.legs[11136] = { name = "Iga Hakama +2" , enl = "iga hakama +2", model = 16678} +models.legs[11137] = { name = "Lncr. Cuissots +2" , enl = "lancer's cuissots +2", model = 16679} +models.legs[11138] = { name = "Caller's Spats +2" , enl = "caller's spats +2", model = 16680} +models.legs[11139] = { name = "Mavi Tayt +2" , enl = "mavi tayt +2", model = 16681} +models.legs[11140] = { name = "Nvrch. Culottes +2" , enl = "navarch's culottes +2", model = 16682} +models.legs[11141] = { name = "Cirq. Pantaloni +2" , enl = "cirque pantaloni +2", model = 16683} +models.legs[11142] = { name = "Charis Tights +2" , enl = "charis tights +2", model = 16689} +models.legs[11143] = { name = "Savant's Pants +2" , enl = "savant's pants +2", model = 16690} +models.legs[11224] = { name = "Rvg. Cuisses +1" , enl = "ravager's cuisses +1", model = 16666} +models.legs[11225] = { name = "Tantra Hose +1" , enl = "tantra hose +1", model = 16667} +models.legs[11226] = { name = "Orsn. Pantaln. +1" , enl = "orison pantaloons +1", model = 16668} +models.legs[11227] = { name = "Goet. Chausses +1" , enl = "goetia chausses +1", model = 16669} +models.legs[11228] = { name = "Estqr. Fuseau +1" , enl = "estoqueur's fuseau +1", model = 16670} +models.legs[11229] = { name = "Raid. Culottes +1" , enl = "raider's culottes +1", model = 16671} +models.legs[11230] = { name = "Creed Cuisses +1" , enl = "creed cuisses +1", model = 16672} +models.legs[11231] = { name = "Bale Flanchard +1" , enl = "bale flanchard +1", model = 16673} +models.legs[11232] = { name = "Ferine Quijotes +1" , enl = "ferine quijotes +1", model = 16674} +models.legs[11233] = { name = "Aoidos' Rhing. +1" , enl = "aoidos' rhingrave +1", model = 16675} +models.legs[11234] = { name = "Sylvan Bragues +1" , enl = "sylvan bragues +1", model = 16676} +models.legs[11235] = { name = "Unkai Haidate +1" , enl = "unkai haidate +1", model = 16677} +models.legs[11236] = { name = "Iga Hakama +1" , enl = "iga hakama +1", model = 16678} +models.legs[11237] = { name = "Lncr. Cuissots +1" , enl = "lancer's cuissots +1", model = 16679} +models.legs[11238] = { name = "Caller's Spats +1" , enl = "caller's spats +1", model = 16680} +models.legs[11239] = { name = "Mavi Tayt +1" , enl = "mavi tayt +1", model = 16681} +models.legs[11240] = { name = "Nvrch. Culottes +1" , enl = "navarch's culottes +1", model = 16682} +models.legs[11241] = { name = "Cirq. Pantaloni +1" , enl = "cirque pantaloni +1", model = 16683} +models.legs[11242] = { name = "Charis Tights +1" , enl = "charis tights +1", model = 16689} +models.legs[11243] = { name = "Savant's Pants +1" , enl = "savant's pants +1", model = 16690} +models.legs[11928] = { name = "Gules Subligar" , enl = "gules subligar", model = 16440} +models.legs[11929] = { name = "Gules Subligar +1" , enl = "gules subligar +1", model = 16440} +models.legs[11930] = { name = "Versa Breeches" , enl = "versa breeches", model = 16442} +models.legs[11931] = { name = "Versa Breeches +1" , enl = "versa breeches +1", model = 16442} +models.legs[11932] = { name = "Lore Slops" , enl = "lore slops", model = 16499} +models.legs[11933] = { name = "Lore Slops +1" , enl = "lore slops +1", model = 16499} +models.legs[11934] = { name = "Cybele Pants" , enl = "cybele pants", model = 16527} +models.legs[11935] = { name = "Ambusher's Hose" , enl = "ambusher's hose", model = 16478} +models.legs[11936] = { name = "Bustle Dirs" , enl = "bustle dirs", model = 16558} +models.legs[11937] = { name = "Sagacity Lappas" , enl = "sagacity lappas", model = 16521} +models.legs[11938] = { name = "Bellicus Cuisses" , enl = "bellicus cuisses", model = 16654} +models.legs[11939] = { name = "Bestia Breeches" , enl = "bestia breeches", model = 16657} +models.legs[11940] = { name = "Paragon Brayettes" , enl = "paragon brayettes", model = 16660} +models.legs[11941] = { name = "Skopos Braccae" , enl = "skopos braccae", model = 16633} +models.legs[11942] = { name = "Kokugetsu Haidate" , enl = "kokugetsu haidate", model = 16636} +models.legs[11943] = { name = "Spry Tights" , enl = "spry tights", model = 16639} +models.legs[11944] = { name = "Mederi Slacks" , enl = "mederi slacks", model = 16642} +models.legs[11945] = { name = "Literae Pants" , enl = "literae pants", model = 16645} +models.legs[11946] = { name = "Facio Spats" , enl = "facio spats", model = 16648} +models.legs[11947] = { name = "Nisse Slacks" , enl = "nisse slacks", model = 16651} +models.legs[11948] = { name = "Stanch Cuisses" , enl = "stanch cuisses", model = 16439} +models.legs[11949] = { name = "Haven Hose" , enl = "haven hose", model = 16546} +models.legs[11950] = { name = "Alcide's Subligar" , enl = "alcide's subligar", model = 16494} +models.legs[11951] = { name = "Alcd. Subligar +1" , enl = "alcide's subligar +1", model = 16494} +models.legs[11952] = { name = "Nemus Salvars" , enl = "nemus salvars", model = 16556} +models.legs[11953] = { name = "Nemus Salvars +1" , enl = "nemus salvars +1", model = 16556} +models.legs[11954] = { name = "Nebula Slops" , enl = "nebula slops", model = 16485} +models.legs[11955] = { name = "Nebula Slops +1" , enl = "nebula slops +1", model = 16485} +models.legs[11956] = { name = "Novennial Hose" , enl = "novennial hose", model = 16619} +models.legs[11957] = { name = "Novennial Boots" , enl = "novennial thigh boots", model = 16629} +models.legs[11958] = { name = "Calmecac Trousers" , enl = "calmecac trousers", model = 16590} +models.legs[11959] = { name = "Calmecac Subligar" , enl = "calmecac subligar", model = 16591} +models.legs[11960] = { name = "Jingang Hose" , enl = "jingang hose", model = 16592} +models.legs[11961] = { name = "Menhit Slacks" , enl = "menhit slacks", model = 16593} +models.legs[11962] = { name = "Galvanic Slops" , enl = "galvanic slops", model = 16402} +models.legs[11963] = { name = "Affronter Cuisses" , enl = "affronter cuisses", model = 16409} +models.legs[11964] = { name = "Whirlwind Dirs" , enl = "whirlwind dirs", model = 16543} +models.legs[11965] = { name = "Dream Trousers" , enl = "dream trousers", model = 16627} +models.legs[11966] = { name = "Dream Trousers +1" , enl = "dream trousers +1", model = 16627} +models.legs[11967] = { name = "Dream Pants" , enl = "dream pants", model = 16628} +models.legs[11968] = { name = "Dream Pants +1" , enl = "dream pants +1", model = 16628} +models.legs[11969] = { name = "Ogier's Breeches" , enl = "ogier's breeches", model = 16709} +models.legs[11970] = { name = "Athos's Tights" , enl = "athos's tights", model = 16710} +models.legs[11971] = { name = "Rubeus Spats" , enl = "rubeus spats", model = 16711} +models.legs[11972] = { name = "Praeco Slacks" , enl = "praeco slacks", model = 16713} +models.legs[11973] = { name = "Hidalgo Slops" , enl = "hidalgo slops", model = 16533} +models.legs[11974] = { name = "Avant Cuisses" , enl = "avant cuisses", model = 16413} +models.legs[11975] = { name = "Avant Cuisses +1" , enl = "avant cuisses +1", model = 16413} +models.legs[11976] = { name = "Kacura Subligar" , enl = "kacura subligar", model = 16494} +models.legs[11977] = { name = "Kacura Subligar +1" , enl = "kacura subligar +1", model = 16494} +models.legs[11978] = { name = "Sweven Slacks" , enl = "sweven slacks", model = 16515} +models.legs[11979] = { name = "Sweven Slacks +1" , enl = "sweven slacks +1", model = 16515} +models.legs[11980] = { name = "Calma Hose" , enl = "calma hose", model = 16665} +models.legs[11981] = { name = "Mustela Brais" , enl = "mustela brais", model = 16632} +models.legs[11982] = { name = "Magavan Slops" , enl = "magavan slops", model = 16653} +models.legs[11983] = { name = "Triplus Subligar" , enl = "triplus subligar", model = 16588} +models.legs[11984] = { name = "Induro Cuisses" , enl = "induro cuisses", model = 16656} +models.legs[11985] = { name = "Tussle Breeches" , enl = "tussle breeches", model = 16659} +models.legs[11986] = { name = "Mystagog Slacks" , enl = "mystagog slacks", model = 16644} +models.legs[11987] = { name = "Ngen Seraweels" , enl = "ngen seraweels", model = 16544} +models.legs[12068] = { name = "Ravager's Cuisses" , enl = "ravager's cuisses", model = 16666} +models.legs[12069] = { name = "Tantra Hose" , enl = "tantra hose", model = 16667} +models.legs[12070] = { name = "Orison Pantaloons" , enl = "orison pantaloons", model = 16668} +models.legs[12071] = { name = "Goetia Chausses" , enl = "goetia chausses", model = 16669} +models.legs[12072] = { name = "Estq. Fuseau" , enl = "estoqueur's fuseau", model = 16670} +models.legs[12073] = { name = "Raider's Culottes" , enl = "raider's culottes", model = 16671} +models.legs[12074] = { name = "Creed Cuisses" , enl = "creed cuisses", model = 16672} +models.legs[12075] = { name = "Bale Flanchard" , enl = "bale flanchard", model = 16673} +models.legs[12076] = { name = "Ferine Quijotes" , enl = "ferine quijotes", model = 16674} +models.legs[12077] = { name = "Aoidos' Rhingrave" , enl = "aoidos' rhingrave", model = 16675} +models.legs[12078] = { name = "Sylvan Bragues" , enl = "sylvan bragues", model = 16676} +models.legs[12079] = { name = "Unkai Haidate" , enl = "unkai haidate", model = 16677} +models.legs[12080] = { name = "Iga Hakama" , enl = "iga hakama", model = 16678} +models.legs[12081] = { name = "Lancer's Cuissots" , enl = "lancer's cuissots", model = 16679} +models.legs[12082] = { name = "Caller's Spats" , enl = "caller's spats", model = 16680} +models.legs[12083] = { name = "Mavi Tayt" , enl = "mavi tayt", model = 16681} +models.legs[12084] = { name = "Navarch's Culottes" , enl = "navarch's culottes", model = 16682} +models.legs[12085] = { name = "Cirque Pantaloni" , enl = "cirque pantaloni", model = 16683} +models.legs[12086] = { name = "Charis Tights" , enl = "charis tights", model = 16689} +models.legs[12087] = { name = "Savant's Pants" , enl = "savant's pants", model = 16690} +models.legs[12216] = { name = "Ebon Cuisses" , enl = "ebon cuisses", model = 16654} +models.legs[12217] = { name = "Furia Cuisses" , enl = "furia cuisses", model = 16655} +models.legs[12218] = { name = "Ebur Cuisses" , enl = "ebur cuisses", model = 16656} +models.legs[12219] = { name = "Ebon Breeches" , enl = "ebon breeches", model = 16657} +models.legs[12220] = { name = "Furia Breeches" , enl = "furia breeches", model = 16658} +models.legs[12221] = { name = "Ebur Breeches" , enl = "ebur breeches", model = 16659} +models.legs[12222] = { name = "Ebon Brayettes" , enl = "ebon brayettes", model = 16660} +models.legs[12223] = { name = "Furia Brayettes" , enl = "furia brayettes", model = 16661} +models.legs[12224] = { name = "Ebur Brayettes" , enl = "ebur brayettes", model = 16662} +models.legs[12225] = { name = "Ebon Hose" , enl = "ebon hose", model = 16663} +models.legs[12226] = { name = "Furia Hose" , enl = "furia hose", model = 16664} +models.legs[12227] = { name = "Ebur Hose" , enl = "ebur hose", model = 16665} +models.legs[12228] = { name = "Ebon Brais" , enl = "ebon brais", model = 16630} +models.legs[12229] = { name = "Furia Brais" , enl = "furia brais", model = 16631} +models.legs[12230] = { name = "Ebur Brais" , enl = "ebur brais", model = 16632} +models.legs[12231] = { name = "Ebon Braccae" , enl = "ebon braccae", model = 16633} +models.legs[12232] = { name = "Furia Braccae" , enl = "furia braccae", model = 16634} +models.legs[12233] = { name = "Ebur Braccae" , enl = "ebur braccae", model = 16635} +models.legs[12234] = { name = "Shikkoku Haidate" , enl = "shikkoku haidate", model = 16636} +models.legs[12235] = { name = "Shinku Haidate" , enl = "shinku haidate", model = 16637} +models.legs[12236] = { name = "Ginhaku Haidate" , enl = "ginhaku haidate", model = 16638} +models.legs[12237] = { name = "Ebon Tights" , enl = "ebon tights", model = 16639} +models.legs[12238] = { name = "Furia Tights" , enl = "furia tights", model = 16640} +models.legs[12239] = { name = "Ebur Tights" , enl = "ebur tights", model = 16641} +models.legs[12240] = { name = "Ebon Slacks" , enl = "ebon slacks", model = 16642} +models.legs[12241] = { name = "Furia Slacks" , enl = "furia slacks", model = 16643} +models.legs[12242] = { name = "Ebur Slacks" , enl = "ebur slacks", model = 16644} +models.legs[12243] = { name = "Ebon Pants" , enl = "ebon pants", model = 16645} +models.legs[12244] = { name = "Furia Pants" , enl = "furia pants", model = 16646} +models.legs[12245] = { name = "Ebur Pants" , enl = "ebur pants", model = 16647} +models.legs[12246] = { name = "Ebon Spats" , enl = "ebon spats", model = 16648} +models.legs[12247] = { name = "Furia Spats" , enl = "furia spats", model = 16649} +models.legs[12248] = { name = "Ebur Spats" , enl = "ebur spats", model = 16650} +models.legs[12249] = { name = "Ebon Slops" , enl = "ebon slops", model = 16651} +models.legs[12250] = { name = "Furia Slops" , enl = "furia slops", model = 16652} +models.legs[12251] = { name = "Ebur Slops" , enl = "ebur slops", model = 16653} +models.legs[12800] = { name = "Cuisses" , enl = "cuisses", model = 16368} +models.legs[12801] = { name = "Mythril Cuisses" , enl = "mythril cuisses", model = 16413} +models.legs[12802] = { name = "Gold Cuisses" , enl = "gold cuisses", model = 16409} +models.legs[12803] = { name = "Darksteel Cuisses" , enl = "darksteel cuisses", model = 16406} +models.legs[12804] = { name = "Adaman Cuisses" , enl = "adaman cuisses", model = 16439} +models.legs[12805] = { name = "Koenig Diechlings" , enl = "koenig diechlings", model = 16479} +models.legs[12806] = { name = "Irn.Msk. Cuisses" , enl = "iron musketeer's cuisses", model = 16409} +models.legs[12807] = { name = "Judge's Cuisses" , enl = "judge's cuisses", model = 16414} +models.legs[12808] = { name = "Chain Hose" , enl = "chain hose", model = 16389} +models.legs[12809] = { name = "Silver Hose" , enl = "silver hose", model = 16389} +models.legs[12810] = { name = "Breeches" , enl = "breeches", model = 16396} +models.legs[12811] = { name = "Dst. Breeches" , enl = "darksteel breeches", model = 16397} +models.legs[12812] = { name = "Thick Breeches" , enl = "thick breeches", model = 16442} +models.legs[12813] = { name = "Adaman Breeches" , enl = "adaman breeches", model = 16447} +models.legs[12814] = { name = "Ryl.Kgt. Breeches" , enl = "royal knight's breeches", model = 16397} +models.legs[12815] = { name = "Ryl.Sqr. Breeches" , enl = "royal squire's breeches", model = 16396} +models.legs[12816] = { name = "Scale Cuisses" , enl = "scale cuisses", model = 16412} +models.legs[12817] = { name = "Brass Cuisses" , enl = "brass cuisses", model = 16412} +models.legs[12818] = { name = "Byakko's Haidate" , enl = "byakko's haidate", model = 16483} +models.legs[12819] = { name = "Coral Cuisses" , enl = "coral cuisses", model = 16410} +models.legs[12820] = { name = "Dragon Cuisses" , enl = "dragon cuisses", model = 16444} +models.legs[12821] = { name = "Gavial Cuisses" , enl = "gavial cuisses", model = 16411} +models.legs[12822] = { name = "Ctr. Cuisses" , enl = "centurion's cuisses", model = 16410} +models.legs[12823] = { name = "Brz. Subligar +1" , enl = "bronze subligar +1", model = 16399} +models.legs[12824] = { name = "Leather Trousers" , enl = "leather trousers", model = 16385} +models.legs[12825] = { name = "Lizard Trousers" , enl = "lizard trousers", model = 16390} +models.legs[12826] = { name = "Studded Trousers" , enl = "studded trousers", model = 16385} +models.legs[12827] = { name = "Cuir Trousers" , enl = "cuir trousers", model = 16385} +models.legs[12828] = { name = "Raptor Trousers" , enl = "raptor trousers", model = 16391} +models.legs[12829] = { name = "Beak Trousers" , enl = "beak trousers", model = 16390} +models.legs[12830] = { name = "Tiger Trousers" , enl = "tiger trousers", model = 16434} +models.legs[12831] = { name = "Coeurl Trousers" , enl = "coeurl trousers", model = 16438} +models.legs[12832] = { name = "Bronze Subligar" , enl = "bronze subligar", model = 16399} +models.legs[12833] = { name = "Brass Subligar" , enl = "brass subligar", model = 16399} +models.legs[12834] = { name = "Bone Subligar" , enl = "bone subligar", model = 16400} +models.legs[12835] = { name = "Beetle Subligar" , enl = "beetle subligar", model = 16400} +models.legs[12836] = { name = "Iron Subligar" , enl = "iron subligar", model = 16398} +models.legs[12837] = { name = "Carapace Subligar" , enl = "carapace subligar", model = 16400} +models.legs[12838] = { name = "Scorpion Subligar" , enl = "scorpion subligar", model = 16400} +models.legs[12839] = { name = "Darksteel Subligar" , enl = "darksteel subligar", model = 16398} +models.legs[12840] = { name = "Sitabaki" , enl = "sitabaki", model = 16401} +models.legs[12841] = { name = "Cotton Sitabaki" , enl = "cotton sitabaki", model = 16401} +models.legs[12842] = { name = "Soil Sitabaki" , enl = "soil sitabaki", model = 16401} +models.legs[12843] = { name = "Haidate" , enl = "haidate", model = 16393} +models.legs[12844] = { name = "Shinobi Hakama" , enl = "shinobi hakama", model = 16388} +models.legs[12846] = { name = "Wukong's Hakama" , enl = "wukong's hakama", model = 16388} +models.legs[12847] = { name = "Yasha Hakama" , enl = "yasha hakama", model = 16482} +models.legs[12848] = { name = "Brais" , enl = "brais", model = 16405} +models.legs[12849] = { name = "Cotton Brais" , enl = "cotton brais", model = 16405} +models.legs[12850] = { name = "Hose" , enl = "hose", model = 16407} +models.legs[12851] = { name = "Wool Hose" , enl = "wool hose", model = 16407} +models.legs[12852] = { name = "Battle Hose" , enl = "battle hose", model = 16408} +models.legs[12853] = { name = "War Brais" , enl = "war brais", model = 16480} +models.legs[12854] = { name = "Mrc.Cpt. Hose" , enl = "mercenary captain's hose", model = 16405} +models.legs[12855] = { name = "Mrc. Sitabaki" , enl = "mercenary's sitabaki", model = 16401} +models.legs[12856] = { name = "Slops" , enl = "slops", model = 16404} +models.legs[12857] = { name = "Linen Slops" , enl = "linen slops", model = 16404} +models.legs[12858] = { name = "Wool Slops" , enl = "wool slops", model = 16402} +models.legs[12859] = { name = "Velvet Slops" , enl = "velvet slops", model = 16402} +models.legs[12860] = { name = "Silk Slops" , enl = "silk slops", model = 16403} +models.legs[12861] = { name = "Noble's Slacks" , enl = "noble's slacks", model = 16441} +models.legs[12862] = { name = "Tct.Mgc. Slops" , enl = "tactician magician's slops", model = 16403} +models.legs[12863] = { name = "Solid Cuisses" , enl = "solid cuisses", model = 16412} +models.legs[12864] = { name = "Slacks" , enl = "slacks", model = 16387} +models.legs[12865] = { name = "Black Slacks" , enl = "black slacks", model = 16387} +models.legs[12866] = { name = "Linen Slacks" , enl = "linen slacks", model = 16394} +models.legs[12867] = { name = "White Slacks" , enl = "white slacks", model = 16395} +models.legs[12868] = { name = "Silk Slacks" , enl = "silk slacks", model = 16395} +models.legs[12870] = { name = "Cmb.Cst. Slacks" , enl = "combat caster's slacks", model = 16394} +models.legs[12871] = { name = "Custom Slacks" , enl = "custom slacks", model = 16415} +models.legs[12872] = { name = "Custom Pants" , enl = "custom pants", model = 16415} +models.legs[12873] = { name = "Magna M Chausses" , enl = "magna m chausses", model = 16415} +models.legs[12874] = { name = "Magna F Chausses" , enl = "magna f chausses", model = 16415} +models.legs[12875] = { name = "Wonder Braccae" , enl = "wonder braccae", model = 16415} +models.legs[12876] = { name = "Savage Loincloth" , enl = "savage loincloth", model = 16415} +models.legs[12877] = { name = "Elder's Braguette" , enl = "elder's braguette", model = 16415} +models.legs[12878] = { name = "Coral Subligar" , enl = "coral subligar", model = 16440} +models.legs[12879] = { name = "Dusk Trousers" , enl = "dusk trousers", model = 16493} +models.legs[12880] = { name = "Ogre Trousers" , enl = "ogre trousers", model = 16478} +models.legs[12881] = { name = "Lgn. Subligar" , enl = "legionnaire's subligar", model = 16399} +models.legs[12882] = { name = "Ryl.Ftm. Trousers" , enl = "royal footman's trousers", model = 16385} +models.legs[12883] = { name = "Hume Slacks" , enl = "hume slacks", model = 16392} +models.legs[12884] = { name = "Hume Pants" , enl = "hume pants", model = 16392} +models.legs[12885] = { name = "Elv. M Chausses" , enl = "elvaan m chausses", model = 16392} +models.legs[12886] = { name = "Tarutaru Braccae" , enl = "tarutaru braccae", model = 16392} +models.legs[12887] = { name = "Mithran Loincloth" , enl = "mithran loincloth", model = 16392} +models.legs[12888] = { name = "Galkan Braguette" , enl = "galkan braguette", model = 16392} +models.legs[12889] = { name = "Elvaan F Chausses" , enl = "elvaan f chausses", model = 16392} +models.legs[12890] = { name = "Chain Hose +1" , enl = "chain hose +1", model = 16389} +models.legs[12891] = { name = "Iron Subligar +1" , enl = "iron subligar +1", model = 16398} +models.legs[12892] = { name = "Brass Subligar +1" , enl = "brass subligar +1", model = 16399} +models.legs[12893] = { name = "Brass Cuisses +1" , enl = "brass cuisses +1", model = 16412} +models.legs[12894] = { name = "Silver Hose +1" , enl = "silver hose +1", model = 16389} +models.legs[12895] = { name = "Breeches +1" , enl = "breeches +1", model = 16396} +models.legs[12896] = { name = "Brais +1" , enl = "brais +1", model = 16405} +models.legs[12897] = { name = "Slops +1" , enl = "slops +1", model = 16404} +models.legs[12898] = { name = "Slacks +1" , enl = "slacks +1", model = 16387} +models.legs[12899] = { name = "Sitabaki +1" , enl = "sitabaki +1", model = 16401} +models.legs[12900] = { name = "Great Brais" , enl = "great brais", model = 16405} +models.legs[12901] = { name = "Linen Slops +1" , enl = "linen slops +1", model = 16404} +models.legs[12902] = { name = "Ctn. Sitabaki +1" , enl = "cotton sitabaki +1", model = 16401} +models.legs[12903] = { name = "Hose +1" , enl = "hose +1", model = 16407} +models.legs[12904] = { name = "Linen Slacks +1" , enl = "linen slacks +1", model = 16394} +models.legs[12905] = { name = "Soil Sitabaki +1" , enl = "soil sitabaki +1", model = 16401} +models.legs[12906] = { name = "Wool Slops +1" , enl = "wool slops +1", model = 16402} +models.legs[12907] = { name = "Wool Hose +1" , enl = "wool hose +1", model = 16407} +models.legs[12908] = { name = "Lth. Trousers +1" , enl = "leather trousers +1", model = 16385} +models.legs[12909] = { name = "Fine Trousers" , enl = "fine trousers", model = 16390} +models.legs[12910] = { name = "Strong Trousers" , enl = "strong trousers", model = 16385} +models.legs[12911] = { name = "Cuir Trousers +1" , enl = "cuir trousers +1", model = 16385} +models.legs[12912] = { name = "Bone Subligar +1" , enl = "bone subligar +1", model = 16400} +models.legs[12913] = { name = "Beetle Subligar +1" , enl = "beetle subligar +1", model = 16400} +models.legs[12914] = { name = "Cpc. Subligar +1" , enl = "carapace subligar +1", model = 16400} +models.legs[12915] = { name = "Freesword's Slops" , enl = "freesword's slops", model = 16404} +models.legs[12916] = { name = "Cuisses +1" , enl = "cuisses +1", model = 16386} +models.legs[12917] = { name = "Mage's Slacks" , enl = "mage's slacks", model = 16387} +models.legs[12918] = { name = "Mage's Slops" , enl = "mage's slops", model = 16402} +models.legs[12919] = { name = "Dino Trousers" , enl = "dino trousers", model = 16391} +models.legs[12920] = { name = "Matre Bragezenn" , enl = "matre bragezenn", model = 16385} +models.legs[12921] = { name = "Ace's Hose" , enl = "ace's hose", model = 16605} +models.legs[12922] = { name = "Martial Slacks" , enl = "martial slacks", model = 16395} +models.legs[12923] = { name = "Jujitsu Sitabaki" , enl = "jujitsu sitabaki", model = 16401} +models.legs[12924] = { name = "Magic Cuisses" , enl = "magic cuisses", model = 16412} +models.legs[12925] = { name = "Shn. Hakama +1" , enl = "shinobi hakama +1", model = 16388} +models.legs[12926] = { name = "White Slacks +1" , enl = "white slacks +1", model = 16395} +models.legs[12927] = { name = "Silk Slops +1" , enl = "silk slops +1", model = 16403} +models.legs[14208] = { name = "Scp. Subligar +1" , enl = "scorpion subligar +1", model = 16400} +models.legs[14209] = { name = "Dst. Breeches +1" , enl = "darksteel breeches +1", model = 16397} +models.legs[14210] = { name = "Perle Brayettes" , enl = "perle brayettes", model = 16623} +models.legs[14211] = { name = "Mythril Cuisses +1" , enl = "mythril cuisses +1", model = 16413} +models.legs[14212] = { name = "Gilt Cuisses" , enl = "gilt cuisses", model = 16409} +models.legs[14213] = { name = "Beak Trousers +1" , enl = "beak trousers +1", model = 16390} +models.legs[14214] = { name = "Fighter's Cuisses" , enl = "fighter's cuisses", model = 16448} +models.legs[14215] = { name = "Temple Hose" , enl = "temple hose", model = 16450} +models.legs[14216] = { name = "Healer's Pantaln." , enl = "healer's pantaloons", model = 16452} +models.legs[14217] = { name = "Wizard's Tonban" , enl = "wizard's tonban", model = 16454} +models.legs[14218] = { name = "Warlock's Tights" , enl = "warlock's tights", model = 16456} +models.legs[14219] = { name = "Rogue's Culottes" , enl = "rogue's culottes", model = 16458} +models.legs[14220] = { name = "Gallant Breeches" , enl = "gallant breeches", model = 16460} +models.legs[14221] = { name = "Chaos Flanchard" , enl = "chaos flanchard", model = 16462} +models.legs[14222] = { name = "Beast Trousers" , enl = "beast trousers", model = 16464} +models.legs[14223] = { name = "Choral Cannions" , enl = "choral cannions", model = 16466} +models.legs[14224] = { name = "Hunter's Braccae" , enl = "hunter's braccae", model = 16468} +models.legs[14225] = { name = "Myochin Haidate" , enl = "myochin haidate", model = 16470} +models.legs[14226] = { name = "Ninja Hakama" , enl = "ninja hakama", model = 16472} +models.legs[14227] = { name = "Drachen Brais" , enl = "drachen brais", model = 16474} +models.legs[14228] = { name = "Evoker's Spats" , enl = "evoker's spats", model = 16476} +models.legs[14229] = { name = "Dst. Cuisses +1" , enl = "darksteel cuisses +1", model = 16406} +models.legs[14230] = { name = "Coral Cuisses +1" , enl = "coral cuisses +1", model = 16410} +models.legs[14231] = { name = "Dragon Cuisses +1" , enl = "dragon cuisses +1", model = 16444} +models.legs[14232] = { name = "Feral Trousers" , enl = "feral trousers", model = 16434} +models.legs[14233] = { name = "Torama Trousers" , enl = "torama trousers", model = 16438} +models.legs[14234] = { name = "Dst. Subligar +1" , enl = "darksteel subligar +1", model = 16398} +models.legs[14235] = { name = "Merman's Subligar" , enl = "merman's subligar", model = 16440} +models.legs[14236] = { name = "Haidate +1" , enl = "haidate +1", model = 16393} +models.legs[14237] = { name = "Battle Hose +1" , enl = "battle hose +1", model = 16408} +models.legs[14238] = { name = "War Brais +1" , enl = "war brais +1", model = 16480} +models.legs[14239] = { name = "Aristo. Slacks" , enl = "aristocrat's slacks", model = 16441} +models.legs[14240] = { name = "Silk Slacks +1" , enl = "silk slacks +1", model = 16395} +models.legs[14241] = { name = "Portent Pants" , enl = "portent pants", model = 16491} +models.legs[14242] = { name = "Rusty Subligar" , enl = "rusty subligar", model = 16399} +models.legs[14243] = { name = "Iron Cuisses" , enl = "iron cuisses", model = 16436} +models.legs[14244] = { name = "Iron Cuisses +1" , enl = "iron cuisses +1", model = 16436} +models.legs[14245] = { name = "Steel Cuisses" , enl = "steel cuisses", model = 16437} +models.legs[14246] = { name = "Steel Cuisses +1" , enl = "steel cuisses +1", model = 16437} +models.legs[14247] = { name = "Zenith Slacks" , enl = "zenith slacks", model = 16491} +models.legs[14248] = { name = "Zenith Slacks +1" , enl = "zenith slacks +1", model = 16491} +models.legs[14249] = { name = "Opaline Hose" , enl = "opaline hose", model = 16500} +models.legs[14250] = { name = "Ceremonial Hose" , enl = "ceremonial hose", model = 16500} +models.legs[14251] = { name = "Wedding Hose" , enl = "wedding hose", model = 16500} +models.legs[14252] = { name = "Thick Breeches +1" , enl = "thick breeches +1", model = 16442} +models.legs[14253] = { name = "Arhat's Hakama" , enl = "arhat's hakama", model = 16443} +models.legs[14254] = { name = "Master's Sitabaki" , enl = "master's sitabaki", model = 16401} +models.legs[14255] = { name = "Mst. Sitabaki +1" , enl = "master's sitabaki +1", model = 16401} +models.legs[14256] = { name = "Arhat's Hakama +1" , enl = "arhat's hakama +1", model = 16443} +models.legs[14257] = { name = "Aurore Brais" , enl = "aurore brais", model = 16625} +models.legs[14258] = { name = "Teal Slops" , enl = "teal slops", model = 16624} +models.legs[14259] = { name = "Bastokan Subligar" , enl = "bastokan subligar", model = 16399} +models.legs[14260] = { name = "Republic Subligar" , enl = "republic subligar", model = 16399} +models.legs[14261] = { name = "Ryl.Sqr. Brch. +1" , enl = "royal squire's breeches +1", model = 16396} +models.legs[14262] = { name = "Ryl.Sqr. Brch. +2" , enl = "royal squire's breeches +2", model = 16396} +models.legs[14263] = { name = "I.M. Cuisses +1" , enl = "iron musketeer's cuisses +1", model = 16409} +models.legs[14264] = { name = "I.M. Cuisses +2" , enl = "iron musketeer's cuisses +2", model = 16409} +models.legs[14265] = { name = "San. Trousers" , enl = "san d'orian trousers", model = 16385} +models.legs[14266] = { name = "Kingdom Trousers" , enl = "kingdom trousers", model = 16385} +models.legs[14267] = { name = "Bastokan Cuisses" , enl = "bastokan cuisses", model = 16410} +models.legs[14268] = { name = "Republic Cuisses" , enl = "republic cuisses", model = 16410} +models.legs[14269] = { name = "Win. Sitabaki" , enl = "windurstian sitabaki", model = 16401} +models.legs[14270] = { name = "Fed. Sitabaki" , enl = "federation sitabaki", model = 16401} +models.legs[14271] = { name = "Windurstian Brais" , enl = "windurstian brais", model = 16405} +models.legs[14272] = { name = "Federation Brais" , enl = "federation brais", model = 16405} +models.legs[14273] = { name = "Windurstian Slops" , enl = "windurstian slops", model = 16404} +models.legs[14274] = { name = "Federation Slops" , enl = "federation slops", model = 16404} +models.legs[14275] = { name = "C.C. Slacks +1" , enl = "combat caster's slacks +1", model = 16394} +models.legs[14276] = { name = "C.C. Slacks +2" , enl = "combat caster's slacks +2", model = 16394} +models.legs[14277] = { name = "T.M. Slops +1" , enl = "tactician magician's slops +1", model = 16403} +models.legs[14278] = { name = "T.M. Slops +2" , enl = "tactician magician's slops +2", model = 16403} +models.legs[14279] = { name = "Ogre Trousers +1" , enl = "ogre trousers +1", model = 16478} +models.legs[14280] = { name = "Crimson Cuisses" , enl = "crimson cuisses", model = 16446} +models.legs[14281] = { name = "Blood Cuisses" , enl = "blood cuisses", model = 16446} +models.legs[14282] = { name = "Arcane Slops" , enl = "arcane slops", model = 16533} +models.legs[14283] = { name = "Kaiser Diechlings" , enl = "kaiser diechlings", model = 16479} +models.legs[14286] = { name = "Frog Trousers" , enl = "frog trousers", model = 16391} +models.legs[14287] = { name = "Luna Subligar" , enl = "luna subligar", model = 16400} +models.legs[14288] = { name = "Clown's Subligar" , enl = "clown's subligar", model = 16440} +models.legs[14289] = { name = "Cln. Subligar +1" , enl = "clown's subligar +1", model = 16440} +models.legs[14290] = { name = "Vagabond's Hose" , enl = "vagabond's hose", model = 16488} +models.legs[14291] = { name = "Nomad's Hose" , enl = "nomad's hose", model = 16488} +models.legs[14292] = { name = "Fisherman's Hose" , enl = "fisherman's hose", model = 16486} +models.legs[14293] = { name = "Angler's Hose" , enl = "angler's hose", model = 16486} +models.legs[14294] = { name = "Chocobo Hose" , enl = "chocobo hose", model = 16487} +models.legs[14295] = { name = "Rider's Hose" , enl = "rider's hose", model = 16487} +models.legs[14296] = { name = "Armada Breeches" , enl = "armada breeches", model = 16447} +models.legs[14297] = { name = "Field Hose" , enl = "field hose", model = 16489} +models.legs[14298] = { name = "Worker Hose" , enl = "worker hose", model = 16489} +models.legs[14299] = { name = "Rst. Hakama" , enl = "rasetsu hakama", model = 16445} +models.legs[14300] = { name = "Rst. Hakama +1" , enl = "rasetsu hakama +1", model = 16445} +models.legs[14301] = { name = "Errant Slops" , enl = "errant slops", model = 16485} +models.legs[14302] = { name = "Mahatma Slops" , enl = "mahatma slops", model = 16485} +models.legs[14303] = { name = "Shura Haidate" , enl = "shura haidate", model = 16490} +models.legs[14304] = { name = "Shura Haidate +1" , enl = "shura haidate +1", model = 16490} +models.legs[14305] = { name = "Dragon Subligar" , enl = "dragon subligar", model = 16494} +models.legs[14306] = { name = "Drn. Subligar +1" , enl = "dragon subligar +1", model = 16494} +models.legs[14307] = { name = "Dusk Trousers +1" , enl = "dusk trousers +1", model = 16493} +models.legs[14308] = { name = "Hecatomb Subligar" , enl = "hecatomb subligar", model = 16484} +models.legs[14309] = { name = "Hct. Subligar +1" , enl = "hecatomb subligar +1", model = 16484} +models.legs[14310] = { name = "Austere Slops" , enl = "austere slops", model = 16499} +models.legs[14311] = { name = "Penance Slops" , enl = "penance slops", model = 16499} +models.legs[14312] = { name = "Gem Cuisses" , enl = "gem cuisses", model = 16439} +models.legs[14313] = { name = "Gavial Cuisses +1" , enl = "gavial cuisses +1", model = 16411} +models.legs[14314] = { name = "Garrison Hose" , enl = "garrison hose", model = 16488} +models.legs[14315] = { name = "Sha'ir Seraweels" , enl = "sha'ir seraweels", model = 16519} +models.legs[14316] = { name = "Sheikh Seraweels" , enl = "sheikh seraweels", model = 16519} +models.legs[14317] = { name = "Barone Cosciales" , enl = "barone cosciales", model = 16520} +models.legs[14318] = { name = "Conte Cosciales" , enl = "conte cosciales", model = 16520} +models.legs[14319] = { name = "Bison Kecks" , enl = "bison kecks", model = 16518} +models.legs[14320] = { name = "Brave's Kecks" , enl = "brave's kecks", model = 16518} +models.legs[14321] = { name = "Igqira Lappas" , enl = "igqira lappas", model = 16521} +models.legs[14322] = { name = "Genie Lappas" , enl = "genie lappas", model = 16521} +models.legs[14323] = { name = "Noct Brais" , enl = "noct brais", model = 16517} +models.legs[14324] = { name = "Mist Slacks" , enl = "mist slacks", model = 16514} +models.legs[14325] = { name = "Seer's Slacks" , enl = "seer's slacks", model = 16515} +models.legs[14326] = { name = "Garish Slacks" , enl = "garish slacks", model = 16514} +models.legs[14327] = { name = "Shade Tights" , enl = "shade tights", model = 16513} +models.legs[14328] = { name = "Seer's Slacks +1" , enl = "seer's slacks +1", model = 16515} +models.legs[14329] = { name = "Eisendiechlings" , enl = "eisendiechlings", model = 16522} +models.legs[14330] = { name = "Rubious Slacks" , enl = "rubious slacks", model = 16514} +models.legs[14331] = { name = "Shade Tights +1" , enl = "shade tights +1", model = 16513} +models.legs[14332] = { name = "Kampfdiechlings" , enl = "kampfdiechlings", model = 16522} +models.legs[14333] = { name = "Noct Brais +1" , enl = "noct brais +1", model = 16517} +models.legs[14334] = { name = "Shm. Haidate" , enl = "shinimusha haidate", model = 16393} +models.legs[14335] = { name = "Nokizaru Hakama" , enl = "nokizaru hakama", model = 16388} +models.legs[15117] = { name = "Warrior's Cuisses" , enl = "warrior's cuisses", model = 16449} +models.legs[15118] = { name = "Melee Hose" , enl = "melee hose", model = 16451} +models.legs[15119] = { name = "Cleric's Pantaln." , enl = "cleric's pantaloons", model = 16453} +models.legs[15120] = { name = "Sorcerer's Tonban" , enl = "sorcerer's tonban", model = 16455} +models.legs[15121] = { name = "Duelist's Tights" , enl = "duelist's tights", model = 16457} +models.legs[15122] = { name = "Assassin's Culottes" , enl = "assassin's culottes", model = 16459} +models.legs[15123] = { name = "Valor Breeches" , enl = "valor breeches", model = 16461} +models.legs[15124] = { name = "Abyss Flanchard" , enl = "abyss flanchard", model = 16463} +models.legs[15125] = { name = "Monster Trousers" , enl = "monster trousers", model = 16465} +models.legs[15126] = { name = "Bard's Cannions" , enl = "bard's cannions", model = 16467} +models.legs[15127] = { name = "Scout's Braccae" , enl = "scout's braccae", model = 16469} +models.legs[15128] = { name = "Saotome Haidate" , enl = "saotome haidate", model = 16471} +models.legs[15129] = { name = "Koga Hakama" , enl = "koga hakama", model = 16473} +models.legs[15130] = { name = "Wyrm Brais" , enl = "wyrm brais", model = 16475} +models.legs[15131] = { name = "Summoner's Spats" , enl = "summoner's spats", model = 16477} +models.legs[15367] = { name = "Falconer's Hose" , enl = "falconer's hose", model = 16412} +models.legs[15368] = { name = "War Hose" , enl = "war hose", model = 16408} +models.legs[15369] = { name = "Aikido Koshita" , enl = "aikido koshita", model = 16395} +models.legs[15370] = { name = "Sable Cuisses" , enl = "sable cuisses", model = 16406} +models.legs[15371] = { name = "Dst. Codpiece" , enl = "darksteel codpiece", model = 16398} +models.legs[15372] = { name = "Magic Slacks" , enl = "magic slacks", model = 16387} +models.legs[15373] = { name = "Bravo's Subligar" , enl = "bravo's subligar", model = 16400} +models.legs[15374] = { name = "Druid's Slops" , enl = "druid's slops", model = 16404} +models.legs[15375] = { name = "Aries Subligar" , enl = "aries subligar", model = 16416} +models.legs[15376] = { name = "Taurus Subligar" , enl = "taurus subligar", model = 16416} +models.legs[15377] = { name = "Gemini Subligar" , enl = "gemini subligar", model = 16416} +models.legs[15378] = { name = "Cancer Subligar" , enl = "cancer subligar", model = 16416} +models.legs[15379] = { name = "Leo Subligar" , enl = "leo subligar", model = 16416} +models.legs[15380] = { name = "Virgo Subligar" , enl = "virgo subligar", model = 16416} +models.legs[15381] = { name = "Libra Subligar" , enl = "libra subligar", model = 16416} +models.legs[15382] = { name = "Scorpius Subligar" , enl = "scorpius subligar", model = 16416} +models.legs[15383] = { name = "Sagit. Subligar" , enl = "sagittarius subligar", model = 16416} +models.legs[15384] = { name = "Capri. Subligar" , enl = "capricornus subligar", model = 16416} +models.legs[15385] = { name = "Aquarius Subligar" , enl = "aquarius subligar", model = 16416} +models.legs[15386] = { name = "Pisces Subligar" , enl = "pisces subligar", model = 16416} +models.legs[15387] = { name = "Tracker's Kecks" , enl = "tracker's kecks", model = 16518} +models.legs[15388] = { name = "Ophiu. Subligar" , enl = "ophiuchus subligar", model = 16416} +models.legs[15389] = { name = "Vir Subligar" , enl = "vir subligar", model = 16417} +models.legs[15390] = { name = "Femina Subligar" , enl = "femina subligar", model = 16417} +models.legs[15391] = { name = "Blessed Trousers" , enl = "blessed trousers", model = 16526} +models.legs[15392] = { name = "Hachiman Hakama" , enl = "hachiman hakama", model = 16525} +models.legs[15393] = { name = "Bls. Trousers +1" , enl = "blessed trousers +1", model = 16526} +models.legs[15394] = { name = "Hmn. Hakama +1" , enl = "hachiman hakama +1", model = 16525} +models.legs[15395] = { name = "Lord's Cuisses" , enl = "lord's cuisses", model = 16523} +models.legs[15396] = { name = "Wise Braconi" , enl = "wise braconi", model = 16527} +models.legs[15397] = { name = "Wise Braconi +1" , enl = "wise braconi +1", model = 16527} +models.legs[15398] = { name = "Yasha Hakama +1" , enl = "yasha hakama +1", model = 16482} +models.legs[15399] = { name = "King's Cuisses" , enl = "king's cuisses", model = 16523} +models.legs[15400] = { name = "Black Cuisses" , enl = "black cuisses", model = 16535} +models.legs[15401] = { name = "Onyx Cuisses" , enl = "onyx cuisses", model = 16535} +models.legs[15402] = { name = "Alumine Brayettes" , enl = "alumine brayettes", model = 16534} +models.legs[15403] = { name = "Luisant Brayettes" , enl = "luisant brayettes", model = 16534} +models.legs[15404] = { name = "Trader's Slops" , enl = "trader's slops", model = 16533} +models.legs[15405] = { name = "Baron's Slops" , enl = "baron's slops", model = 16533} +models.legs[15406] = { name = "Unicorn Subligar" , enl = "unicorn subligar", model = 16532} +models.legs[15407] = { name = "Ucn. Subligar +1" , enl = "unicorn subligar +1", model = 16532} +models.legs[15408] = { name = "Hume Trunks" , enl = "hume trunks", model = 16541} +models.legs[15409] = { name = "Hume Shorts" , enl = "hume shorts", model = 16541} +models.legs[15410] = { name = "Elvaan Trunks" , enl = "elvaan trunks", model = 16541} +models.legs[15411] = { name = "Elvaan Shorts" , enl = "elvaan shorts", model = 16541} +models.legs[15412] = { name = "Tarutaru Trunks" , enl = "tarutaru trunks", model = 16541} +models.legs[15413] = { name = "Mithra Shorts" , enl = "mithra shorts", model = 16541} +models.legs[15414] = { name = "Galka Trunks" , enl = "galka trunks", model = 16541} +models.legs[15415] = { name = "Hume Trunks +1" , enl = "hume trunks +1", model = 16541} +models.legs[15416] = { name = "Hume Shorts +1" , enl = "hume shorts +1", model = 16541} +models.legs[15417] = { name = "Elvaan Trunks +1" , enl = "elvaan trunks +1", model = 16541} +models.legs[15418] = { name = "Elvaan Shorts +1" , enl = "elvaan shorts +1", model = 16541} +models.legs[15419] = { name = "Taru. Trunks +1" , enl = "tarutaru trunks +1", model = 16541} +models.legs[15420] = { name = "Mithra Shorts +1" , enl = "mithra shorts +1", model = 16541} +models.legs[15421] = { name = "Galka Trunks +1" , enl = "galka trunks +1", model = 16541} +models.legs[15422] = { name = "Black Hose" , enl = "black hose", model = 16486} +models.legs[15423] = { name = "Tarutaru Shorts" , enl = "tarutaru shorts", model = 16542} +models.legs[15424] = { name = "Taru. Shorts +1" , enl = "tarutaru shorts +1", model = 16542} +models.legs[15425] = { name = "Galliard Trousers" , enl = "galliard trousers", model = 16515} +models.legs[15426] = { name = "Torrent Subligar" , enl = "torrent subligar", model = 16586} +models.legs[15427] = { name = "Teutates Subligar" , enl = "teutates subligar", model = 16563} +models.legs[15428] = { name = "Ocelot Trousers" , enl = "ocelot trousers", model = 16615} +models.legs[15429] = { name = "Wicca Subligar" , enl = "wicca subligar", model = 16600} +models.legs[15430] = { name = "Augur's Brais" , enl = "augur's brais", model = 16497} +models.legs[15561] = { name = "Ftr. Cuisses +1" , enl = "fighter's cuisses +1", model = 16448} +models.legs[15562] = { name = "Tpl. Hose +1" , enl = "temple hose +1", model = 16450} +models.legs[15563] = { name = "Hlr. Pantaln. +1" , enl = "healer's pantaloons +1", model = 16452} +models.legs[15564] = { name = "Wzd. Tonban +1" , enl = "wizard's tonban +1", model = 16454} +models.legs[15565] = { name = "Wlk. Tights +1" , enl = "warlock's tights +1", model = 16456} +models.legs[15566] = { name = "Rog. Culottes +1" , enl = "rogue's culottes +1", model = 16458} +models.legs[15567] = { name = "Glt. Breeches +1" , enl = "gallant breeches +1", model = 16460} +models.legs[15568] = { name = "Chs. Flanchard +1" , enl = "chaos flanchard +1", model = 16462} +models.legs[15569] = { name = "Bst. Trousers +1" , enl = "beast trousers +1", model = 16464} +models.legs[15570] = { name = "Chl. Cannions +1" , enl = "choral cannions +1", model = 16466} +models.legs[15571] = { name = "Htr. Braccae +1" , enl = "hunter's braccae +1", model = 16468} +models.legs[15572] = { name = "Myn. Haidate +1" , enl = "myochin haidate +1", model = 16470} +models.legs[15573] = { name = "Nin. Hakama +1" , enl = "ninja hakama +1", model = 16472} +models.legs[15574] = { name = "Drn. Brais +1" , enl = "drachen brais +1", model = 16474} +models.legs[15575] = { name = "Evk. Spats +1" , enl = "evoker's spats +1", model = 16476} +models.legs[15576] = { name = "Homam Cosciales" , enl = "homam cosciales", model = 16543} +models.legs[15577] = { name = "Nashira Seraweels" , enl = "nashira seraweels", model = 16544} +models.legs[15578] = { name = "Crow Hose" , enl = "crow hose", model = 16546} +models.legs[15579] = { name = "Raven Hose" , enl = "raven hose", model = 16546} +models.legs[15580] = { name = "War. Cuisses +1" , enl = "warrior's cuisses +1", model = 16449} +models.legs[15581] = { name = "Mel. Hose +1" , enl = "melee hose +1", model = 16451} +models.legs[15582] = { name = "Clr. Pantaln. +1" , enl = "cleric's pantaloons +1", model = 16453} +models.legs[15583] = { name = "Src. Tonban +1" , enl = "sorcerer's tonban +1", model = 16455} +models.legs[15584] = { name = "Dls. Tights +1" , enl = "duelist's tights +1", model = 16457} +models.legs[15585] = { name = "Asn. Culottes +1" , enl = "assassin's culottes +1", model = 16459} +models.legs[15586] = { name = "Vlr. Breeches +1" , enl = "valor breeches +1", model = 16461} +models.legs[15587] = { name = "Abs. Flanchard +1" , enl = "abyss flanchard +1", model = 16463} +models.legs[15588] = { name = "Mst. Trousers +1" , enl = "monster trousers +1", model = 16465} +models.legs[15589] = { name = "Brd. Cannions +1" , enl = "bard's cannions +1", model = 16467} +models.legs[15590] = { name = "Sct. Braccae +1" , enl = "scout's braccae +1", model = 16469} +models.legs[15591] = { name = "Sao. Haidate +1" , enl = "saotome haidate +1", model = 16471} +models.legs[15592] = { name = "Kog. Hakama +1" , enl = "koga hakama +1", model = 16473} +models.legs[15593] = { name = "Wym. Brais +1" , enl = "wyrm brais +1", model = 16475} +models.legs[15594] = { name = "Smn. Spats +1" , enl = "summoner's spats +1", model = 16477} +models.legs[15595] = { name = "Hydra Brais" , enl = "hydra brais", model = 16516} +models.legs[15596] = { name = "Hydra Tights" , enl = "hydra tights", model = 16512} +models.legs[15597] = { name = "Hydra Brayettes" , enl = "hydra brayettes", model = 16548} +models.legs[15598] = { name = "Hydra Hose" , enl = "hydra hose", model = 16547} +models.legs[15599] = { name = "Bahamut's Hose" , enl = "bahamut's hose", model = 16407} +models.legs[15600] = { name = "Magus Shalwar" , enl = "magus shalwar", model = 16549} +models.legs[15601] = { name = "Corsair's Culottes" , enl = "corsair's culottes", model = 16551} +models.legs[15602] = { name = "Pup. Churidars" , enl = "puppetry churidars", model = 16553} +models.legs[15603] = { name = "Sipahi Zerehs" , enl = "sipahi zerehs", model = 16555} +models.legs[15604] = { name = "Amir Dirs" , enl = "amir dirs", model = 16558} +models.legs[15605] = { name = "Jaridah Salvars" , enl = "jaridah salvars", model = 16556} +models.legs[15606] = { name = "Yigit Seraweels" , enl = "yigit seraweels", model = 16560} +models.legs[15607] = { name = "Abtal Zerehs" , enl = "abtal zerehs", model = 16555} +models.legs[15608] = { name = "Akinji Salvars" , enl = "akinji salvars", model = 16556} +models.legs[15609] = { name = "Pln. Seraweels" , enl = "pahluwan seraweels", model = 16559} +models.legs[15610] = { name = "Sturdy Trousers" , enl = "sturdy trousers", model = 16385} +models.legs[15611] = { name = "Sturdy Slacks" , enl = "sturdy slacks", model = 16387} +models.legs[15612] = { name = "Strike Subligar" , enl = "strike subligar", model = 16440} +models.legs[15613] = { name = "Jet Seraweels" , enl = "jet seraweels", model = 16560} +models.legs[15614] = { name = "Exorcist Hose" , enl = "exorcist hose", model = 16389} +models.legs[15615] = { name = "Hydra Cuisses" , enl = "hydra cuisses", model = 16411} +models.legs[15616] = { name = "Hydra Cuisses +1" , enl = "hydra cuisses +1", model = 16411} +models.legs[15617] = { name = "Barb. Zerehs" , enl = "barbarossa's zerehs", model = 16555} +models.legs[15618] = { name = "Vendor's Slops" , enl = "vendor's slops", model = 16533} +models.legs[15619] = { name = "Prince's Slops" , enl = "prince's slops", model = 16533} +models.legs[15620] = { name = "Silken Slops" , enl = "silken slops", model = 16403} +models.legs[15621] = { name = "Magi Slops" , enl = "magi slops", model = 16403} +models.legs[15622] = { name = "Mrc. Trousers" , enl = "mercenary's trousers", model = 16441} +models.legs[15623] = { name = "Volunteer's Brais" , enl = "volunteer's brais", model = 16517} +models.legs[15624] = { name = "Mrc. Subligar" , enl = "mercenary's subligar", model = 16398} +models.legs[15625] = { name = "Ares' Flanchard" , enl = "ares' flanchard", model = 16566} +models.legs[15626] = { name = "Enyo's Cuisses" , enl = "enyo's cuisses", model = 16368} +models.legs[15627] = { name = "Phobos's Cuisses" , enl = "phobos's cuisses", model = 16406} +models.legs[15628] = { name = "Deimos's Cuisses" , enl = "deimos's cuisses", model = 16413} +models.legs[15629] = { name = "Skadi's Chausses" , enl = "skadi's chausses", model = 16567} +models.legs[15630] = { name = "Njord's Trousers" , enl = "njord's trousers", model = 16434} +models.legs[15631] = { name = "Freyr's Trousers" , enl = "freyr's trousers", model = 16493} +models.legs[15632] = { name = "Freya's Trousers" , enl = "freya's trousers", model = 16438} +models.legs[15633] = { name = "Usukane Hizayoroi" , enl = "usukane hizayoroi", model = 16568} +models.legs[15634] = { name = "Hoshikazu Hakama" , enl = "hoshikazu hakama", model = 16388} +models.legs[15635] = { name = "Tsukikazu Haidate" , enl = "tsukikazu haidate", model = 16393} +models.legs[15636] = { name = "Hikazu Hakama" , enl = "hikazu hakama", model = 16525} +models.legs[15637] = { name = "Marduk's Shalwar" , enl = "marduk's shalwar", model = 16569} +models.legs[15638] = { name = "Anu's Brais" , enl = "anu's brais", model = 16405} +models.legs[15639] = { name = "Ea's Brais" , enl = "ea's brais", model = 16517} +models.legs[15640] = { name = "Enlil's Brayettes" , enl = "enlil's brayettes", model = 16548} +models.legs[15641] = { name = "Morrigan's Slops" , enl = "morrigan's slops", model = 16570} +models.legs[15642] = { name = "Nemain's Slops" , enl = "nemain's slops", model = 16404} +models.legs[15643] = { name = "Bodb's Slops" , enl = "bodb's slops", model = 16533} +models.legs[15644] = { name = "Macha's Slops" , enl = "macha's slops", model = 16485} +models.legs[15645] = { name = "Khimaira Kecks" , enl = "khimaira kecks", model = 16518} +models.legs[15646] = { name = "Stout Kecks" , enl = "stout kecks", model = 16518} +models.legs[15647] = { name = "Askar Dirs" , enl = "askar dirs", model = 16579} +models.legs[15648] = { name = "Denali Kecks" , enl = "denali kecks", model = 16580} +models.legs[15649] = { name = "Goliard Trews" , enl = "goliard trews", model = 16581} +models.legs[15650] = { name = "Shock Subligar" , enl = "shock subligar", model = 16400} +models.legs[15651] = { name = "Ice Trousers" , enl = "ice trousers", model = 16391} +models.legs[15652] = { name = "Blaze Hose" , enl = "blaze hose", model = 16407} +models.legs[15653] = { name = "Tabin Hose" , enl = "tabin hose", model = 16408} +models.legs[15654] = { name = "Tabin Hose +1" , enl = "tabin hose +1", model = 16408} +models.legs[15655] = { name = "Shadow Cuishes" , enl = "shadow cuishes", model = 16584} +models.legs[15656] = { name = "Valkyrie's Cuishes" , enl = "valkyrie's cuishes", model = 16584} +models.legs[15657] = { name = "Shadow Trews" , enl = "shadow trews", model = 16585} +models.legs[15658] = { name = "Valkyrie's Trews" , enl = "valkyrie's trews", model = 16585} +models.legs[15659] = { name = "Dancer's Tights" , enl = "dancer's tights", model = 16594} +models.legs[15660] = { name = "Dancer's Tights" , enl = "dancer's tights", model = 16595} +models.legs[16311] = { name = "Scholar's Pants" , enl = "scholar's pants", model = 16598} +models.legs[16312] = { name = "I.R. Breeches" , enl = "iron ram breeches", model = 16396} +models.legs[16313] = { name = "Fourth Cuisses" , enl = "fourth division cuisses", model = 16409} +models.legs[16314] = { name = "Cobra Slops" , enl = "cobra unit slops", model = 16403} +models.legs[16315] = { name = "Iron Ram Hose" , enl = "iron ram hose", model = 16605} +models.legs[16316] = { name = "Fourth Schoss" , enl = "fourth division schoss", model = 16606} +models.legs[16317] = { name = "Cobra Subligar" , enl = "cobra unit subligar", model = 16603} +models.legs[16318] = { name = "Cobra Trews" , enl = "cobra unit trews", model = 16604} +models.legs[16319] = { name = "Sangoma Lappa" , enl = "sangoma lappas", model = 16521} +models.legs[16320] = { name = "Kensei Sitabaki" , enl = "kensei sitabaki", model = 16401} +models.legs[16321] = { name = "Custom Trunks" , enl = "custom trunks", model = 16609} +models.legs[16322] = { name = "Custom Shorts" , enl = "custom shorts", model = 16609} +models.legs[16323] = { name = "Magna Trunks" , enl = "magna trunks", model = 16609} +models.legs[16324] = { name = "Magna Shorts" , enl = "magna shorts", model = 16609} +models.legs[16325] = { name = "Wonder Trunks" , enl = "wonder trunks", model = 16610} +models.legs[16326] = { name = "Wonder Shorts" , enl = "wonder shorts", model = 16609} +models.legs[16327] = { name = "Savage Shorts" , enl = "savage shorts", model = 16609} +models.legs[16328] = { name = "Elder Trunks" , enl = "elder trunks", model = 16609} +models.legs[16329] = { name = "Custom Trunks +1" , enl = "custom trunks +1", model = 16609} +models.legs[16330] = { name = "Custom Shorts +1" , enl = "custom shorts +1", model = 16609} +models.legs[16331] = { name = "Magna Trunks +1" , enl = "magna trunks +1", model = 16609} +models.legs[16332] = { name = "Magna Shorts +1" , enl = "magna shorts +1", model = 16609} +models.legs[16333] = { name = "Wonder Trunks +1" , enl = "wonder trunks +1", model = 16610} +models.legs[16334] = { name = "Wonder Shorts +1" , enl = "wonder shorts +1", model = 16609} +models.legs[16335] = { name = "Savage Shorts +1" , enl = "savage shorts +1", model = 16609} +models.legs[16336] = { name = "Elder Trunks +1" , enl = "elder trunks +1", model = 16609} +models.legs[16337] = { name = "Hachiryu Haidate" , enl = "hachiryu haidate", model = 16611} +models.legs[16338] = { name = "Ruby Seraweels" , enl = "ruby seraweels", model = 16544} +models.legs[16339] = { name = "Paddock Trousers" , enl = "paddock trousers", model = 16390} +models.legs[16340] = { name = "Armadillo Cuisses" , enl = "armadillo cuisses", model = 16436} +models.legs[16341] = { name = "Aurum Cuisses" , enl = "aurum cuisses", model = 16523} +models.legs[16342] = { name = "Oracle's Braconi" , enl = "oracle's braconi", model = 16527} +models.legs[16343] = { name = "Enkidu's Subligar" , enl = "enkidu's subligar", model = 16440} +models.legs[16344] = { name = "Oily Trousers" , enl = "oily trousers", model = 16390} +models.legs[16345] = { name = "Magus Shalwar +1" , enl = "magus shalwar +1", model = 16549} +models.legs[16346] = { name = "Mirage Shalwar" , enl = "mirage shalwar", model = 16550} +models.legs[16347] = { name = "Mirage Shalwar +1" , enl = "mirage shalwar +1", model = 16550} +models.legs[16348] = { name = "Cor. Culottes +1" , enl = "corsair's culottes +1", model = 16551} +models.legs[16349] = { name = "Comm. Culottes" , enl = "commodore culottes", model = 16552} +models.legs[16350] = { name = "Comm. Culottes +1" , enl = "commodore culottes +1", model = 16552} +models.legs[16351] = { name = "Pup. Churidars +1" , enl = "puppetry churidars +1", model = 16553} +models.legs[16352] = { name = "Pantin Churidars" , enl = "pantin churidars", model = 16554} +models.legs[16353] = { name = "Ptn. Churidars +1" , enl = "pantin churidars +1", model = 16554} +models.legs[16354] = { name = "Malagigi's Trousers" , enl = "malagigi's trousers", model = 16390} +models.legs[16355] = { name = "Bedivere's Hose" , enl = "bedivere's hose", model = 16407} +models.legs[16356] = { name = "Nimue's Tights" , enl = "nimue's tights", model = 16513} +models.legs[16357] = { name = "Dancer's Tights +1" , enl = "dancer's tights +1", model = 16594} +models.legs[16358] = { name = "Dancer's Tights +1" , enl = "dancer's tights +1", model = 16595} +models.legs[16359] = { name = "Scholar's Pants +1" , enl = "scholar's pants +1", model = 16598} +models.legs[16360] = { name = "Etoile Tights" , enl = "etoile tights", model = 16688} +models.legs[16361] = { name = "Etoile Tights +1" , enl = "etoile tights +1", model = 16688} +models.legs[16362] = { name = "Argute Pants" , enl = "argute pants", model = 16599} +models.legs[16363] = { name = "Argute Pants +1" , enl = "argute pants +1", model = 16599} +models.legs[16364] = { name = "Benedight Hose" , enl = "benedight hose", model = 16616} +models.legs[16365] = { name = "Argent Hose" , enl = "argent hose", model = 16616} +models.legs[16366] = { name = "Platino Hose" , enl = "platino hose", model = 16616} +models.legs[16367] = { name = "Phl. Trousers" , enl = "phlegethon's trousers", model = 16385} +models.legs[16368] = { name = "Herder's Subligar" , enl = "herder's subligar", model = 16400} +models.legs[16369] = { name = "Blitzer Poleyn" , enl = "blitzer poleyn", model = 16620} +models.legs[16370] = { name = "Desultor Tassets" , enl = "desultor tassets", model = 16621} +models.legs[16371] = { name = "Tatsu. Sitagoromo" , enl = "tatsumaki sitagoromo", model = 16622} +models.legs[16372] = { name = "Stearc Subligar" , enl = "stearc subligar", model = 16416} +models.legs[16373] = { name = "Kyoshu Sitabaki" , enl = "kyoshu sitabaki", model = 16401} +models.legs[16374] = { name = "Layqa Seraweels" , enl = "layqa seraweels", model = 16519} +models.legs[16375] = { name = "Surge Subligar" , enl = "surge subligar", model = 16494} +models.legs[16376] = { name = "Bahram Cuisses" , enl = "bahram cuisses", model = 16444} +models.legs[16377] = { name = "Kyo. Sitabaki +1" , enl = "kyoshu sitabaki +1", model = 16401} +models.legs[16378] = { name = "Dinner Hose" , enl = "dinner hose", model = 16614} +models.legs[16379] = { name = "Inmicus Cuisses" , enl = "inmicus cuisses", model = 16439} +models.legs[16380] = { name = "Entois trousers" , enl = "entois trousers", model = 16391} +models.legs[16381] = { name = "Tumbler Trunks" , enl = "tumbler trunks", model = 16434} +models.legs[16382] = { name = "Adapa's Slacks" , enl = "adapa's slacks", model = 16387} +models.legs[16383] = { name = "Mirador Trousers" , enl = "mirador trousers", model = 16391} +models.legs[28068] = { name = "Abatteur Subligar" , enl = "abatteur subligar", model = 16532} +models.legs[28069] = { name = "Punga's Slops" , enl = "punga's slops", model = 16499} +models.legs[28070] = { name = "Briscard Cuisses" , enl = "briscard cuisses", model = 16523} +models.legs[28071] = { name = "Ares' Flanchard +1" , enl = "ares' flanchard +1", model = 16566} +models.legs[28072] = { name = "Skd. Chausses +1" , enl = "skadi's chausses +1", model = 16567} +models.legs[28073] = { name = "Usk. Hizayoroi +1" , enl = "usukane hizayoroi +1", model = 16568} +models.legs[28074] = { name = "Mdk. Shalwar +1" , enl = "marduk's shalwar +1", model = 16569} +models.legs[28075] = { name = "Morrigan's Slops +1" , enl = "morrigan's slops +1", model = 16570} +models.legs[28076] = { name = "Ker's Flanchard" , enl = "ker's flanchard", model = 16566} +models.legs[28077] = { name = "Sigyn's Chausses" , enl = "sigyn's chausses", model = 16567} +models.legs[28078] = { name = "Omo. Hizayoroi" , enl = "omodaka hizayoroi", model = 16568} +models.legs[28079] = { name = "Nabu's Shalwar" , enl = "nabu's shalwar", model = 16569} +models.legs[28080] = { name = "Fea's Slops" , enl = "fea's slops", model = 16570} +models.legs[28081] = { name = "Ate's Flanchard" , enl = "ate's flanchard", model = 16413} +models.legs[28082] = { name = "Idi's Trousers" , enl = "idi's trousers", model = 16478} +models.legs[28083] = { name = "Genta-no-Hakama" , enl = "genta-no-hakama", model = 16525} +models.legs[28084] = { name = "Namru's Shalwar" , enl = "namru's shalwar", model = 16548} +models.legs[28085] = { name = "Neit's Slops" , enl = "neit's slops", model = 16485} +models.legs[28086] = { name = "Rustic Trunks" , enl = "rustic trunks", model = 16742} +models.legs[28087] = { name = "Shoal Trunks" , enl = "shoal trunks", model = 16743} +models.legs[28088] = { name = "Rustic Trunks +1" , enl = "rustic trunks +1", model = 16742} +models.legs[28089] = { name = "Shoal Trunks +1" , enl = "shoal trunks +1", model = 16743} +models.legs[28090] = { name = "Pumm. Cuisses" , enl = "pummeler's cuisses" , model = 16448 } +models.legs[28091] = { name = "Anchorite's Hose" , enl = "anchorite's hose" , model = 16450 } +models.legs[28092] = { name = "Theo. Pantaloons" , enl = "theophany pantaloons" , model = 16452 } +models.legs[28093] = { name = "Spaekona's Tonban" , enl = "spaekona's tonban" , model = 16454 } +models.legs[28094] = { name = "Atrophy Tights" , enl = "atrophy tights" , model = 16456 } +models.legs[28095] = { name = "Pillager's Culottes" , enl = "pillager's culottes" , model = 16458 } +models.legs[28096] = { name = "Reverence Breeches" , enl = "reverence breeches" , model = 16460 } +models.legs[28097] = { name = "Ignominy Flanchard" , enl = "ignominy flanchard" , model = 16462 } +models.legs[28098] = { name = "Totemic Trousers" , enl = "totemic trousers" , model = 16464 } +models.legs[28099] = { name = "Brioso Cannions" , enl = "brioso cannions" , model = 16466 } +models.legs[28100] = { name = "Orion Braccae" , enl = "orion braccae" , model = 16468 } +models.legs[28101] = { name = "Wakido Haidate" , enl = "wakido haidate" , model = 16470 } +models.legs[28102] = { name = "Hachiya Hakama" , enl = "hachiya hakama" , model = 16472 } +models.legs[28103] = { name = "Vishap Brais" , enl = "vishap brais" , model = 16474 } +models.legs[28104] = { name = "Convoker's Spats" , enl = "convoker's spats" , model = 16476 } +models.legs[28105] = { name = "Assim. Shalwar" , enl = "assimilator's shalwar" , model = 16549 } +models.legs[28106] = { name = "Lak. Culottes" , enl = "laksamana's culottes" , model = 16551 } +models.legs[28107] = { name = "Foire Churidars" , enl = "foire churidars" , model = 16553 } +models.legs[28108] = { name = "Maxixi Tights" , enl = "maxixi tights" , model = 16594 } +models.legs[28109] = { name = "Maxixi Tights" , enl = "maxixi tights" , model = 16595 } +models.legs[28110] = { name = "Academic's Pants" , enl = "academic's pants" , model = 16598 } +models.legs[28111] = { name = "Pumm. Cuisses +1" , enl = "pummeler's cuisses +1" , model = 16448 } +models.legs[28112] = { name = "Anch. Hose +1" , enl = "anchorite's hose +1" , model = 16450 } +models.legs[28113] = { name = "Theo. Pant. +1" , enl = "theophany pantaloons +1" , model = 16452 } +models.legs[28114] = { name = "Spae. Tonban +1" , enl = "spaekona's tonban +1" , model = 16454 } +models.legs[28115] = { name = "Atrophy Tights +1" , enl = "atrophy tights +1" , model = 16456 } +models.legs[28116] = { name = "Pill. Culottes +1" , enl = "pillager's culottes +1" , model = 16458 } +models.legs[28117] = { name = "Rev. Breeches +1" , enl = "reverence breeches +1" , model = 16460 } +models.legs[28118] = { name = "Igno. Flan. +1" , enl = "ignominy flanchard +1" , model = 16462 } +models.legs[28119] = { name = "Tot. Trousers +1" , enl = "totemic trousers +1" , model = 16464 } +models.legs[28120] = { name = "Brioso Cann. +1" , enl = "brioso cannions +1" , model = 16466 } +models.legs[28121] = { name = "Orion Braccae +1" , enl = "orion braccae +1" , model = 16468 } +models.legs[28122] = { name = "Wakido Haidate +1" , enl = "wakido haidate +1" , model = 16470 } +models.legs[28123] = { name = "Hachi. Hakama +1" , enl = "hachiya hakama +1" , model = 16472 } +models.legs[28124] = { name = "Vishap Brais +1" , enl = "vishap brais +1" , model = 16474 } +models.legs[28125] = { name = "Con. Spats +1" , enl = "convoker's spats +1" , model = 16476 } +models.legs[28126] = { name = "Assim. Shalwar +1" , enl = "assimilator's shalwar +1" , model = 16549 } +models.legs[28127] = { name = "Lak. Trews +1" , enl = "laksamana's trews +1" , model = 16551 } +models.legs[28128] = { name = "Foire Churidars +1" , enl = "foire churidars +1" , model = 16553 } +models.legs[28129] = { name = "Maxixi Tights +1" , enl = "maxixi tights +1" , model = 16594 } +models.legs[28130] = { name = "Maxixi Tights +1" , enl = "maxixi tights +1" , model = 16595 } +models.legs[28131] = { name = "Acad. Pants +1" , enl = "academic's pants +1" , model = 16598 } +models.legs[28132] = { name = "Geo. Pants +1" , enl = "geomancy pants +1" , model = 16723 } +models.legs[28133] = { name = "Rune. Trousers +1" , enl = "runeist trousers +1" , model = 16722 } +models.legs[28165] = { name = "Laktisma Leggings" , enl = "laktisma leggings" , model = 16401 } +models.legs[28166] = { name = "Quiahuiz Leggings" , enl = "quiahuiz leggings" , model = 16717 } +models.legs[28167] = { name = "Kaabnax Trousers" , enl = "kaabnax trousers" , model = 16730 } +models.legs[28168] = { name = "Outrider Hose" , enl = "outrider hose" , model = 16389 } +models.legs[28169] = { name = "Espial Hose" , enl = "espial hose" , model = 16407 } +models.legs[28170] = { name = "Wayfarer Slops" , enl = "wayfarer slops" , model = 16404 } +models.legs[28171] = { name = "Temachtiani Pants" , enl = "Temachtiani pants" , model = 16486 } +models.legs[28173] = { name = "Osmium Cuisses" , enl = "osmium cuisses" , model = 16437 } +models.legs[28174] = { name = "Theurgist's Slacks" , enl = "theurgist's slacks" , model = 16515 } +models.legs[28182] = { name = "Kari. Brayettes +1" , enl = "Karieyh brayettes +1" , model = 16727 } +models.legs[28183] = { name = "Thur. Tights +1" , enl = "Thurandaut tights +1" , model = 16728 } +models.legs[28184] = { name = "Orvail Pants +1" , enl = "Orvail pants +1" , model = 16729 } +models.legs[28185] = { name = "Alliance Pants" , enl = "alliance pants" , model = ' ' } +models.legs[28186] = { name = "Morass Pants" , enl = "morass pants", model = 16731} +models.legs[28187] = { name = "Woodland Pants" , enl = "woodland pants", model = 16732} +models.legs[28188] = { name = "Gorney Brayettes" , enl = "gorney brayettes", model = 16738} +models.legs[28189] = { name = "Shneddick Tights" , enl = "shneddick tights", model = 16739} +models.legs[28190] = { name = "Weatherspoon Pants" , enl = "weatherspoon pants", model = 16740} +models.legs[28192] = { name = "Cizin Breeches" , enl = "cizin breeches", model = 16658} +models.legs[28193] = { name = "Otronif Brais" , enl = "otronif brais", model = 16631} +models.legs[28194] = { name = "Iuitl Tights" , enl = "iuitl tights", model = 16640} +models.legs[28195] = { name = "Gendewitha Spats" , enl = "gendewitha spats", model = 16649} +models.legs[28196] = { name = "Hagondes Pants" , enl = "hagondes pants", model = 16646} +models.legs[28197] = { name = "Nahtirah Trousers" , enl = "nahtirah trousers", model = 16730} +models.legs[28198] = { name = "Miki. Cuisses" , enl = "mikinaak cuisses", model = 16733} +models.legs[28199] = { name = "Manibozho Brais" , enl = "manibozho brais", model = 16734} +models.legs[28200] = { name = "Bokwus Slops" , enl = "bokwus slops", model = 16735} +models.legs[28201] = { name = "Xux Trousers" , enl = "xux trousers", model = 16730} +models.legs[28202] = { name = "Dredger Hose" , enl = "dredger hose", model = 16489} +models.legs[28203] = { name = "Orvail Pants" , enl = "orvail pants", model = 16728} +models.legs[28204] = { name = "Thurandaut Tights" , enl = "thurandaut tights", model = 16727} +models.legs[28205] = { name = "Karieyh Brayettes" , enl = "karieyh brayettes", model = 16726} +models.legs[28206] = { name = "Geomancy Pants" , enl = "geomancy pants", model = 16723} +models.legs[28207] = { name = "Runeist Trousers" , enl = "runeist trousers", model = 16722} + +models.legs[70001] = { name = "??? Slops" , model = 16545 } +models.legs[70002] = { name = "Special Volunteer's" , model = 16557 } + +--16696 is Etoile for Taru M +--16715 Male, 16716 Female +--16718 Swimsuit (?), 16719 Taru F Elvaan F +models.legs["Unknown"] = { 16602, 16607, 16626, 16692, 16694, 16695, 16712 } + +models.legs["Unused"] = { 16418, 16418, 16419, 16420, 16421, 16422, 16423, 16424, 16425, + 16427, 16428, 16429, 16430, 16431, 16432, 16433, 16435, 16481, + 16492, 16498, 16501, 16502, 16503, 16504, 16505, 16506, 16507, + 16508, 16509, 16510, 16511, 16524, 16528, 16529, 16530, 16531, + 16536, 16537, 16538, 16539, 16540, 16561, 16562, 16564, 16565, + 16571, 16572, 16573, 16574, 16575, 16576, 16577, 16578, 16582, + 16583, 16588, 16589, 16596, 16597, 16601, 16608, 16612, 16613, + 16617, 16618, 16629, 16684, 16685, 16686, 16687, 16691, 16693, + 16697, 16698, 16699, 16700, 16701, 16702, 16703, 16714, 16724, + 16725, 16729, 16736, 16737, 16741 } diff --git a/Data/DefaultContent/Libraries/addons/addons/DressUp/static_variables.lua b/Data/DefaultContent/Libraries/addons/addons/DressUp/static_variables.lua new file mode 100644 index 0000000..0359eea --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/DressUp/static_variables.lua @@ -0,0 +1,98 @@ +-- Copyright © 2013-2014, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of DressUp nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +helptext = [[DressUp - Command List: +1. help - Displays this help menu. +2a. self/others [race/face/<item slot>] [<item name>/<race name>/<face>] +2b. player <player name> [race/face/item slot] [item name/name/face] + - Assigns models to yourself, others, or an individual player as specified. + - Supports IDs as well as names. Specify male or female if necessary. +3. clear [self/others/player] <player name> [race/face/<item slot>] + - Clears settings for the selection. Player name specific to player option. +4. replacements [race/face/<item slot>] <selection1> <selection2> + - Handles 1:1 replacement, similar to .DAT swapping. +5. blinking [self/others/party/follow/all] [always/target/combat/all] [on/off] + - Changes blinking settings. Toggles if nothing is specified. + - Also accepts "bmn" and "blinkmenot" as command prefix. +6. autoupdate - Updates your model as you send the commands to do so. + - This uses outgoing packets. + ]] + +-- Initializes default settings table +defaults = {} +defaults.autoupdate = false +defaults.profiles = {} +defaults["others"] = {} +defaults.replacements = { face = {}, race = {}, head = {}, body = {}, hands = {}, legs = {}, feet = {}, main = {}, sub = {}, ranged = {} } + +defaults.blinking = {} +defaults.blinking["party"] = { target = false, always = false, combat = false} +defaults.blinking["others"] = { target = false, always = false, combat = false} +defaults.blinking["all"] = { target = false, always = false, combat = false } +defaults.blinking["self"] = { target = false, always = false, combat = false } +defaults.blinking["follow"] = { target = false, always = false, combat = false } + +-- Array of races and various abbreviations accepted for race strings + +_races = {} +_races["hume"] = { ["m"] = 1, ["f"] = 2, ["male"] = 1, ["female"] = 2 } +_races["h"] = { ["m"] = 1, ["f"] = 2, ["male"] = 1, ["female"] = 2 } +_races["elvaan"] = { ["m"] = 3, ["f"] = 4, ["male"] = 3, ["female"] = 4 } +_races["elv"] = { ["m"] = 3, ["f"] = 4, ["male"] = 3, ["female"] = 4 } +_races["e"] = { ["m"] = 3, ["f"] = 4, ["male"] = 3, ["female"] = 4 } +_races["tarutaru"] = { ["m"] = 5, ["f"] = 6, ["male"] = 5, ["female"] = 6 } +_races["taru"] = { ["m"] = 5, ["f"] = 6, ["male"] = 5, ["female"] = 6 } +_races["t"] = { ["m"] = 5, ["f"] = 6, ["male"] = 5, ["female"] = 6 } +_races["mithra"] = 7 +_races["m"] = 7 +_races["galka"] = 8 +_races["g"] = 8 + +-- Maps commonly known face IDs to their actual IDs + +_faces = {} +_faces["1a"] = 0 +_faces["1b"] = 1 +_faces["2a"] = 2 +_faces["2b"] = 3 +_faces["3a"] = 4 +_faces["3b"] = 5 +_faces["4a"] = 6 +_faces["4b"] = 7 +_faces["5a"] = 8 +_faces["5b"] = 9 +_faces["6a"] = 10 +_faces["6b"] = 11 +_faces["7a"] = 12 +_faces["7b"] = 13 +_faces["8a"] = 14 +_faces["8b"] = 15 +_faces["Fomor"] = 29 +_faces["Mannequin"] = 30 + +-- PC Update Masks associated with model changes + +model_mask = L{16,17,20,21} diff --git a/Data/DefaultContent/Libraries/addons/addons/EasyNuke/EasyNuke.lua b/Data/DefaultContent/Libraries/addons/addons/EasyNuke/EasyNuke.lua new file mode 100644 index 0000000..055bcfe --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EasyNuke/EasyNuke.lua @@ -0,0 +1,253 @@ +--[[ +Copyright © 2018, Nyarlko +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of EasyNuke nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Nyarlko, or it's members, BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'EasyNuke' +_addon.author = 'Nyarlko' +_addon.version = '1.0.8' +_addon.command = "ez" + +require('sets') +require('tables') +require('strings') + +config = require('config') + +local defaults = T{} +defaults.current_element = "fire" +defaults.target_mode = "t" +settings = config.load(defaults) + +current_element = "fire" +target_mode = "t" + +elements = T{"fire","wind","thunder","light","ice","water","earth","dark"} +elements_dark = T{"ice","water","earth","dark"} +elements_light = T{"fire","wind","thunder","light"} +elements_index = 1 +other_modes = S{"drain","aspir","absorb","cure"} + +targets = T{"t","bt","stnpc",} +targets_index = 1 + +spell_tables = {} +spell_tables["fire"] = {"Fire","Fire II","Fire III","Fire IV","Fire V","Fire VI",} +spell_tables["fire"]["ga"] = {"Firaga","Firaga II","Firaga III","Firaja",} +spell_tables["fire"]["ra"] = {"Fira","Fira II","Fira III"} +spell_tables["fire"]["helix"] = {"Pyrohelix","Pyrohelix II"} +spell_tables["fire"]["am"] = {"Flare","Flare II"} +spell_tables["earth"] = {"Stone","Stone II","Stone III","Stone IV","Stone V","Stone VI",} +spell_tables["earth"]["ga"] = {"Stonega","Stonega II","Stonega III","Stoneja",} +spell_tables["earth"]["ra"] = {"Stonera","Stonera II","Stonera III"} +spell_tables["earth"]["helix"] = {"Geohelix","Geohelix II"} +spell_tables["earth"]["am"] = {"Quake","Quake II"} +spell_tables["wind"] = {"Aero","Aero II","Aero III","Aero IV","Aero V","Aero VI",} +spell_tables["wind"]["ga"] = {"Aeroga","Aeroga II","Aeroga III","Aeroja",} +spell_tables["wind"]["ra"] = {"Aerora","Aerora II","Aerora III"} +spell_tables["wind"]["helix"] = {"Anemohelix","Anemohelix II"} +spell_tables["wind"]["am"] = {"Tornado","Tornado II"} +spell_tables["water"] = {"Water","Water II","Water III","Water IV","Water V","Water VI",} +spell_tables["water"]["ga"] = {"Waterga","Waterga II","Waterga III","Waterja",} +spell_tables["water"]["ra"] = {"Watera","Watera II","Watera III"} +spell_tables["water"]["helix"] = {"Hydrohelix","Hydrohelix II"} +spell_tables["water"]["am"] = {"Flood","Flood II"} +spell_tables["ice"] = {"Blizzard","Blizzard II","Blizzard III","Blizzard IV","Blizzard V","Blizzard VI",} +spell_tables["ice"]["ga"] = {"Blizzaga","Blizzaga II","Blizzaga III","Blizzaja",} +spell_tables["ice"]["ra"] = {"Blizzara","Blizzara II","Blizzara III"} +spell_tables["ice"]["helix"] = {"Cryohelix","Cryohelix II"} +spell_tables["ice"]["am"] = {"Freeze","Freeze II"} +spell_tables["thunder"] = {"Thunder","Thunder II","Thunder III","Thunder IV","Thunder V","Thunder VI",} +spell_tables["thunder"]["ga"] = {"Thundaga","Thundaga II","Thundaga III","Thundaja",} +spell_tables["thunder"]["ra"] = {"Thundara","Thundara II","Thundara III"} +spell_tables["thunder"]["helix"] = {"Ionohelix","Ionohelix II"} +spell_tables["thunder"]["am"] = {"Burst","Burst II"} +spell_tables["light"] = {"Banish","Banish II","Holy","Banish III",} +spell_tables["light"]["ga"] = {"Banishga","Banishga II"} +spell_tables["light"]["helix"] = {"Luminohelix","Luminohelix II"} +spell_tables["dark"] = {"Impact"} +spell_tables["dark"]["ga"] = {"Comet"} +spell_tables["dark"]["helix"] = {"Noctohelix", "Noctohelix II"} +spell_tables["cure"] = {"Cure","Cure II","Cure III","Cure IV","Cure V","Cure VI"} +spell_tables["cure"]["ga"] = {"Curaga","Curaga II","Curaga III","Curaga IV","Curaga V",} +spell_tables["cure"]["ra"] = {"Cura","Cura II","Cura III"} +spell_tables["drain"] = {"Aspir","Aspir II","Aspir III","Drain","Drain II","Drain III"} +spell_tables["drain"]["ga"] = spell_tables["drain"] +spell_tables["drain"]["ra"] = spell_tables["drain"] +spell_tables["aspir"] = spell_tables["drain"] +spell_tables["aspir"]["ga"] = spell_tables["drain"] +spell_tables["aspir"]["ra"] = spell_tables["drain"] +spell_tables["absorb"] = {"Absorb-Acc","Absorb-TP","Absorb-Attri","Absorb-STR","Absorb-DEX","Absorb-VIT","Absorb-AGI","Absorb-INT","Absorb-MND","Absorb-CHR"} +spell_tables["absorb"]["ga"] = spell_tables["absorb"] +spell_tables["absorb"]["ra"] = spell_tables["absorb"] + +local indices = { + fire = 1, + wind = 2, + thunder = 3, + light = 4, + ice = 5, + water = 6, + earth = 7, + dark = 8, +} + +function execute_spell_cast(spell_type, arg) + local current_spell_table = nil + if spell_type == nil then + current_spell_table = spell_tables[current_element] + else + current_spell_table = spell_tables[current_element][spell_type] + end + if arg == nil then arg = 1 end + arg = tonumber(arg) + if current_spell_table == nil or arg > #current_spell_table then + windower.add_to_chat(206,"Invalid Spell.") return + end + windower.chat.input("/ma \""..current_spell_table[arg].."\" <"..target_mode..">") +end + +windower.register_event("unhandled command", function (command, arg) + if command == "boom" or command == "nuke" then + execute_spell_cast(nil, arg) + elseif command == "boomga" or command == "bga" then + execute_spell_cast("ga", arg) + elseif command == "boomra" or command == "bra" then + execute_spell_cast("ra", arg) + elseif command == "boomhelix" or command == "bhelix" then + execute_spell_cast("helix", arg) + elseif command == "boomam" or command == "bam" then + execute_spell_cast("am", arg) + end +end) + +windower.register_event('addon command', function (command, arg) + + if command == "boom" or command == "nuke" then + execute_spell_cast(nil, arg) + elseif command == "boomga" or command == "bga" then + execute_spell_cast("ga", arg) + elseif command == "boomra" or command == "bra" then + execute_spell_cast("ra", arg) + elseif command == "boomhelix" or command == "bhelix" then + execute_spell_cast("helix", arg) + elseif command == "boomam" or command == "bam" then + execute_spell_cast("am", arg) + + elseif command == "target" then + if arg then + arg = string.lower(arg) + target_mode = arg + else + targets_index = targets_index % #targets + 1 + target_mode = targets[targets_index] + end + windower.add_to_chat(206,"Target Mode is now: "..target_mode) + + elseif command == "element" or command == "mode" then + arg = string.lower(arg) + if elements:contains(arg) or other_modes:contains(arg) then + current_element = arg + windower.add_to_chat(206,"Element Mode is now: "..string.ucfirst(current_element)) + else + windower.add_to_chat(206,"Invalid element") + end + + elseif command == "cycle" then + if arg then + arg = string.lower(arg) + end + if arg == nil then + if not elements:contains(current_element) then + elements_index = 1 + else + elements_index = indices[current_element] + elements_index = elements_index % 8 + 1 + end + current_element = elements[elements_index] + elseif arg == "back" then + if not elements:contains(current_element) then + elements_index = 1 + else + elements_index = indices[current_element] + elements_index = elements_index - 1 + end + if elements_index < 1 then + elements_index = 8 + end + current_element = elements[elements_index] + elseif arg == "dark" then + if not elements_dark:contains(current_element) then + elements_index = 1 + else + elements_index = elements_index % 4 + 1 + end + current_element = elements_dark[elements_index] + elseif arg == "light" then + if not elements_light:contains(current_element) then + elements_index = 1 + else + elements_index = elements_index % 4 + 1 + end + current_element = elements_light[elements_index] + elseif arg == "fusion" or "fus" then + if current_element ~= "fire" and current_element ~= "light" then + current_element = "fire" + elseif current_element == "fire" then + current_element = "light" + elseif current_element == "light" then + current_element = "fire" + end + elseif arg == "distortion" or arg == "dist" then + if current_element ~= "ice" and current_element ~= "water" then + current_element = "ice" + elseif current_element == "ice" then + current_element = "water" + elseif current_element == "water" then + current_element = "ice" + end + elseif arg == "gravitation" or arg == "grav" then + if current_element ~= "earth" and current_element ~= "dark" then + current_element = "earth" + elseif current_element == "earth" then + current_element = "dark" + elseif current_element == "dark" then + current_element = "earth" + end + elseif arg == "fragmentation" or arg == "frag" then + if current_element ~= "thunder" and current_element ~= "wind" then + current_element = "thunder" + elseif current_element == "thunder" then + current_element = "wind" + elseif current_element == "wind" then + current_element = "thunder" + end + end + windower.add_to_chat(206, "Element Mode is now: "..string.ucfirst(current_element)) + elseif command == "show" or command == "current" or command == "showcurrent" then + windower.add_to_chat(206, "----- Element Mode: "..string.ucfirst(current_element).." --- Target Mode: < "..target_mode.." > -----") + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/EasyNuke/readme.md b/Data/DefaultContent/Libraries/addons/addons/EasyNuke/readme.md new file mode 100644 index 0000000..1d92b7b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EasyNuke/readme.md @@ -0,0 +1,81 @@ +EasyNuke provides universal commands for casting single target and area of effect BLM and GEO nukes, and WHM cures. + +Commands: + +#### element XXX +* Changes current element mode to XXX. + * EX: //ez element ice <<<< Sets mode to ice. + * Macro usage: /con ez element XXX + * Valid arguments: Fire, Wind, Thunder, Light, Ice, Water, Earth, Dark, Drain, Aspir, Absorb, Cure + * Cure Mode: Follows standard single/ga/ra pattern and usage + * Drain and Aspir Mode: Aspir, Aspir II, Aspir III, Drain, Drain II, Drain III + * Absorb Mode: Absorb-Acc, Absorb-TP, Absorb-Attri, Absorb-STR, Absorb-DEX, Absorb-VIT, Absorb-AGI, Absorb-INT, Absorb-MND, Absorb-CHR + * EX: //ez element absorb >>> //ez boom 4 <<<< Casts Absorb-STR + * Macro usage: /con ez element XXX + +#### cycle +* Cycles through element modes in the following left-to-right order: Fire, Wind, Thunder, Light, Ice, Water, Earth, Dark + * EX: //ez cycle <<<< If you were in Light mode, then you will change to Ice mode. + * Macro usage: /con ez cycle + +#### cycle back +* Same as "cycle", but goes in right-to-left order. + * EX: //ez cycle back <<<< If you were in Light mode, then you will change to Thunder mode. + * Macro usage: /con ez cycle back + +#### cycle dark +* Cycles through element modes in the following order: Ice, Water, Earth, Dark + * EX: //ez cycle dark <<<< If you were in Dark mode, then you will change to Ice mode. + * Macro usage: /con ez cycle dark + +#### cycle light +* Cycles through element modes in the following order: Fire, Wind, Thunder, Light + * EX: //ez cycle light <<<< If you were in Light mode, then you will change to Fire mode. + * Macro usage: /con ez cycle light + +#### cycle XXX +* Cycles between the two elements of a T2 skillchain. + * Valid commands: Elements included + * Fusion, Fus: Fire, Light + * Fragmentation, Frag: Thunder, Wind + * Distortion, Dist: Ice, Water + * Gravitation, Grav: Earth, Dark + * EX: //ez cycle dist <<<< If you were in Ice mode, will change you to Water mode. If you were in any other mode, will change you to Ice mode. + * Macro usage: /con ez cycle fragmentation + +#### target XXX +* Changes targeting mode to #. This sets what's between the < > brackets used for targeting in macros. + * EX: //ez target bt <<<< Spells will be cast using <bt>. + * There are no failsafes for this. Any given argument will be accepted, even if it does not function as a targeting argument. + * If no argument is given, then will cycle through target modes in the following order: t, bt, stnpc + * Macro usage: /con ez target # OR /con ez target + +#### showcurrent / show / current +* Echoes the current elemental and targeting modes in the chat log. + * EX: //ez showcurrent + * Macro usage: /con ez show + +#### boom XXX +* Casts a single target tierXXX nuke of the current element, and using the current targeting mode. + * EX: //ez boom 4 <<<< If Element Mode is Fire, and targeting mode is "t", you will cast Fire IV on your current target. + * EX2: //boom 6 <<<< If Element Mode is Ice, and targeting mode is "bt", you will cast Blizzard VI on the current battle target. + * Macro usage: /con ez boom # /OR/ /con boom # + +#### boomga XXX +* Casts an area of effect tierXXX nuke of the current element: BLM: -ga, -ga II, -ga III, -ja. (Also works for Curaga's while in Cure mode.) + * EX: //ez boomga 4 <<<< If Element Mode is Ice, and targeting mode is "t", will cast Blizzaja on your current target. + * EX2: //boomga 3 <<<< If Element Mode is Ice, and targeting mode is "bt", you will cast Blizzaga III on the current battle target. + * Macro usage: /con ez boomga # /OR/ /con boomga # + +#### boomra XXX +* Casts an area of effect nuke of tier XXX. GEO's -ra AOE nukes and WHM's Cura line + * EX: //ez boomra 3 <<<< If Element Mode is Ice, and mode is "bt", will cast Blizzara III on your current battle target. + * EX2: //boomra 2 <<<< If Element Mode is Cure, and mode is "me", you will cast Cura II on yourself. + * Macro usage: /con ez boomra # /OR/ /con boomra # + +#### boomhelix +* Casts the appropriate SCH Helix spell of tier XXX. + * EX: //ez boomhelix 2 <<<< If Element Mode is Ice, and target mode is "t", will cast Cryohelix II on your current target. + * EX2: //boomhelix <<<< If Element Mode is Fire, and target mode is "bt", will cast Pyrohelix on your current battle target. + * Macro usage: /con ez boomhelix # /OR/ /con boomhelix # +* Also supports a short version //bhelix # or //ez bhelix
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/EmpyPopTracker.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/EmpyPopTracker.lua new file mode 100644 index 0000000..a6ae2aa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/EmpyPopTracker.lua @@ -0,0 +1,383 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'Empy Pop Tracker' +_addon.author = 'Dean James (Xurion of Bismarck)' +_addon.commands = { 'ept', 'empypoptracker' } +_addon.version = '2.5.0' + +config = require('config') +res = require('resources') +nm_data = require('nms/index') + +active = false + +local EmpyPopTracker = {} + +local defaults = {} +defaults.text = {} +defaults.text.pos = {} +defaults.text.pos.x = 0 +defaults.text.pos.y = 0 +defaults.text.bg = {} +defaults.text.bg.alpha = 150 +defaults.text.bg.blue = 0 +defaults.text.bg.green = 0 +defaults.text.bg.red = 0 +defaults.text.bg.visible = true +defaults.text.padding = 8 +defaults.text.text = {} +defaults.text.text.font = 'Consolas' +defaults.text.text.size = 10 +defaults.tracking = 'briareus' +defaults.visible = true +defaults.add_to_chat_mode = 8 +defaults.colors = {} +defaults.colors.needed = {} +defaults.colors.needed.red = 255 +defaults.colors.needed.green = 50 +defaults.colors.needed.blue = 50 +defaults.colors.obtained = {} +defaults.colors.obtained.red = 100 +defaults.colors.obtained.green = 255 +defaults.colors.obtained.blue = 100 +defaults.colors.pool = {} +defaults.colors.pool.red = 255 +defaults.colors.pool.green = 170 +defaults.colors.pool.blue = 0 +defaults.colors.bg = {} +defaults.colors.bg.red = 0 +defaults.colors.bg.green = 0 +defaults.colors.bg.blue = 0 +defaults.colors.bgall = {} +defaults.colors.bgall.red = 0 +defaults.colors.bgall.green = 75 +defaults.colors.bgall.blue = 0 +defaults.collectables = true +defaults.expanded = true + +EmpyPopTracker.settings = config.load(defaults) +EmpyPopTracker.text = require('texts').new(EmpyPopTracker.settings.text, EmpyPopTracker.settings) + +function start_color(color) + return '\\cs(' .. EmpyPopTracker.settings.colors[color].red .. ',' .. EmpyPopTracker.settings.colors[color].green .. ',' .. EmpyPopTracker.settings.colors[color].blue .. ')' +end + +function owns_item(id, items) + for _, bag in pairs(items) do + if type(bag) == 'table' then + for _, item in ipairs(bag) do + if item.id == id then + return true + end + end + end + end + + return false +end + +function get_item_count(id, items) + local count = 0 + for _, bag in pairs(items) do + if type(bag) == 'table' then + for _, item in ipairs(bag) do + if item.id == id then + count = count + item.count + end + end + end + end + + return count +end + +function owns_key_item(id, items) + local owned = false + + for _, item_id in pairs(items) do + if item_id == id then + owned = true + break + end + end + + return owned +end + +function item_treasure_pool_count(id, treasure) + local count = 0 + + for _, item in pairs(treasure) do + if item.item_id == id then + count = count + 1 + end + end + + return count +end + +function ucwords(str) + local result = string.gsub(str, '(%a)([%w_\']*)', function(first, rest) + return first:upper() .. rest:lower() + end) + + return result +end + +function get_indent(depth) + return string.rep(' ', depth) +end + +function generate_text(data, key_items, items, depth) + local text = depth == 1 and data.name or '' + for _, pop in pairs(data.pops) do + local resource + local item_scope + local owns_pop + local in_pool_count = 0 + local item_identifier = '' + + if pop.type == 'key item' then + resource = res.key_items[pop.id] + owns_pop = owns_key_item(pop.id, key_items) + item_identifier = 'Ж ' + else + resource = res.items[pop.id] + owns_pop = owns_item(pop.id, items) + in_pool_count = item_treasure_pool_count(pop.id, items.treasure) + end + + local pop_name = 'Unknown pop' + if resource then + pop_name = ucwords(resource.name) + end + + if depth == 1 and EmpyPopTracker.settings.expanded then + text = text .. '\n' + end + + local item_colour = start_color(owns_pop and 'obtained' or 'needed') + local pool_notification = '' + if in_pool_count > 0 then + pool_notification = start_color('pool') .. ' [' .. in_pool_count .. ']' .. '\\cr' + end + + local name_color = '' + local name_color_end = '' + if not EmpyPopTracker.settings.expanded and owns_pop then + name_color = item_colour + name_color_end = '\\cr' + end + + text = text .. '\n' .. get_indent(depth) .. name_color .. pop.dropped_from.name .. name_color_end + + if EmpyPopTracker.settings.expanded then + text = text .. '\n' .. get_indent(depth) .. ' >> ' .. item_colour .. item_identifier .. pop_name .. '\\cr' .. pool_notification + end + + if pop.dropped_from.pops then + text = text .. generate_text(pop.dropped_from, key_items, items, depth + 1) + end + end + + if data.collectable and EmpyPopTracker.settings.collectables then + local count = get_item_count(data.collectable, items) + local start = '' + local finish = '' + if count >= data.collectable_target_count then + start = start_color('obtained') + finish = '\\cr' + end + + text = text .. '\n\n' .. start .. res.items[data.collectable].name .. ': ' .. count .. '/' .. data.collectable_target_count .. finish + end + + return text +end + +EmpyPopTracker.generate_info = function(nm, key_items, items) + return { + has_all_pops = not nm.pops or T(nm.pops):all(function(item) + return item.type == 'item' and owns_item(item.id, items) or owns_key_item(item.id, key_items) + end), + text = generate_text(nm, key_items, items, 1) + } +end + +function find_nms(pattern) + local matching_nms = {} + local lower_pattern = pattern:lower() + for _, nm in pairs(nm_data) do + local nm_name = nm.name:lower() + local result = windower.wc_match(nm_name, lower_pattern) + if result then + table.insert(matching_nms, nm_name) + end + end + + return matching_nms +end + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + + if commands[command] then + commands[command](...) + else + commands.help() + end +end) + +commands = {} + +commands.track = function(...) + local nm_search_pattern = table.concat({...}, ' ') + local matching_nm_names = find_nms(nm_search_pattern) + + if #matching_nm_names == 0 then + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, 'Unable to find a NM using: "' .. nm_search_pattern .. '"') + elseif #matching_nm_names > 1 then + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '"' .. nm_search_pattern .. '" matches ' .. #matching_nm_names .. ' NMs. Please be more explicit:') + for key, matching_file_name in pairs(matching_nm_names) do + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, ' Match ' .. key .. ': ' .. ucwords(matching_file_name)) + end + else + active = true + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, 'Now tracking: ' .. ucwords(matching_nm_names[1])) + EmpyPopTracker.settings.tracking = matching_nm_names[1] + EmpyPopTracker.update() + commands.show() + end +end +commands.t = commands.track + +commands.hide = function() + active = false + EmpyPopTracker.text:visible(false) + EmpyPopTracker.settings.visible = false + EmpyPopTracker.settings:save() +end + +commands.show = function() + active = true + EmpyPopTracker.text:visible(true) + EmpyPopTracker.settings.visible = true + EmpyPopTracker.settings:save() + EmpyPopTracker.update() +end + +commands.help = function() + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '---Empy Pop Tracker---') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, 'Available commands:') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept track briareus - tracks Briareus pops (search patterns such as apadem* work too!)') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept hide - hides the UI') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept show - shows the UI') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept list - lists all trackable NMs') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept mini - toggles mini/expanded mode') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept collectables - toggles the collectable item') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '//ept help - displays this help') +end + +commands.list = function() + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, '---Empy Pop Tracker---') + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, 'Trackable NMs:') + for _, nm in pairs(nm_data) do + windower.add_to_chat(EmpyPopTracker.settings.add_to_chat_mode, ucwords(nm.name)) + end +end + +commands.bg = function() + local tracking_nm = nm_data[EmpyPopTracker.settings.tracking] + local url = 'https://www.bg-wiki.com/bg/' .. tracking_nm.name + windower.open_url(url) +end + +commands.collectables = function() + EmpyPopTracker.settings.collectables = not EmpyPopTracker.settings.collectables + EmpyPopTracker.settings:save() + EmpyPopTracker.update() +end + +commands.mini = function() + EmpyPopTracker.settings.expanded = not EmpyPopTracker.settings.expanded + EmpyPopTracker.settings:save() + EmpyPopTracker.update() +end + +EmpyPopTracker.update = function() + local key_items = windower.ffxi.get_key_items() + local items = windower.ffxi.get_items() + local tracked_nm_data = nm_data[EmpyPopTracker.settings.tracking] + local generated_info = EmpyPopTracker.generate_info(tracked_nm_data, key_items, items) + EmpyPopTracker.text:text(generated_info.text) + if generated_info.has_all_pops then + EmpyPopTracker.text:bg_color(EmpyPopTracker.settings.colors.bgall.red, EmpyPopTracker.settings.colors.bgall.green, EmpyPopTracker.settings.colors.bgall.blue) + else + EmpyPopTracker.text:bg_color(EmpyPopTracker.settings.colors.bg.red, EmpyPopTracker.settings.colors.bg.green, EmpyPopTracker.settings.colors.bg.blue) + end + if EmpyPopTracker.settings.visible then + EmpyPopTracker.text:visible(true) + end +end + +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in and EmpyPopTracker.settings.visible then + active = true + EmpyPopTracker.update() + end +end) + +windower.register_event('add item', 'remove item', function() + if active then + EmpyPopTracker.update() + end +end) + +windower.register_event('incoming chunk', function(id) + --0x055: KI update + --0x0D2: Treasure pool addition + --0x0D3: Treasure pool lot/drop + if active and id == 0x055 or id == 0x0D2 or id == 0x0D3 then + EmpyPopTracker.update() + end +end) + +windower.register_event('login', function() + if EmpyPopTracker.settings.visible then + EmpyPopTracker.text:visible(true) + active = true + end +end) + +windower.register_event('logout', function() + EmpyPopTracker.text:visible(false) + active = false +end) + +return EmpyPopTracker diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/README.md b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/README.md new file mode 100644 index 0000000..42a99a5 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/README.md @@ -0,0 +1,69 @@ +# FFXI Empy Pop Tracker + +An FFXI Windower 4 addon that tracks items and key items for popping various NMs, such as Briareus, Apademak and Warder of Courage. + +  + +Originally developed to track Abyssea Empyrean weapon NMs, hence the name. Key items are identified by the Zhe (Ж) character. Treasure pool counts for pop items are listed in amber after the item in the format of [3] (assuming 3 of that item in the pool). + +All text colours are configurable via the auto-generated settings.xml file. + +## Installation + +Empy Pop Tracker is now available via the Windower 4 addons list. + +## Load + +`//lua load empypoptracker` + +Note: You won't have to do this if you obtained this addon via Windower. + +## Track an NM + +`//ept track glavoid` tracks Glavoid pop items/key items. + +You can also track an NM by using a wildcard pattern, because fuck having to remember how to spell Itzpapalotl: + +`//ept track itz*` + +For a full list of trackable NMs, see the nms directory or use the `list` command (see below). + +## Other Commands + +### List Trackable NMs + +`//ept list` + +### Open BG Wiki for NM + +`//ept bg` + +### Hide UI + +`//ept hide` + +### Show UI + +`//ept show` + +### Toggle Mini Mode + +`//ept mini` + +### Toggle Collectable Item Display + +`//ept collectables` + +### Display Help + +`//ept help` + +## Where is Fistule? + +Fistule is a unique NM when compared to the others. It does not require KIs that can be tracked, so it isn't included with the addon. + +## Contributing + +If there's an NM you want to have added, or if you notice something not quite right, please [raise an issue](https://github.com/xurion/ffxi-empy-pop-tracker/issues). + +Or better yet, [pull requests](https://github.com/xurion/ffxi-empy-pop-tracker/pulls) are welcome! diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/README.md b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/README.md new file mode 100644 index 0000000..38b7f51 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/README.md @@ -0,0 +1,70 @@ +# NM data + +The data structure for each trackable NM uses a series of nested NM entities. A standard NM entity contains the following data: + +| Key | Type | Required? | Description | +| ------------------------ | --------- | --------- | ----------------------------------- | +| name | String | Required | Name of the NM | +| collectable | Number | Optional | The ID of the collectable item | +| collectable_target_count | Number | Optional | The target no. of collectable items | +| pops | Table | Optional | The pop information for the NM | +| pops{}.id | Number | Required | The ID of the item/key item | +| pops{}.type | String | Required | Either "key item" or "item" | +| pops{}.dropped_from | NM Entity | Required | A nested set of NM information | + +A simple example of the above would be: + +```lua +{ + name = 'Azdaja', + collectable = 3292, --Azdaja's Horn + collectable_target_count = 75, + pops = { { + id = 1531, --Vacant Bugard Eye + type = 'key item', + dropped_from = { name = 'Deelgeed, Timed (F-9/F-10)' } + } } +} +``` + +A larger example with multiple nested entities: + +```lua +{ + name = 'Bukhis', + collectable = 2966, --Bukhis's Wing + collectable_target_count = 50, + pops = { { + id = 1508, --Ingrown Taurus Nail + type = 'key item', + dropped_from = { + name = 'Khalkotaur, Forced (F-4)', + pops = { { + id = 3098, --Gnarled Taurus Horn + type = 'item', + dropped_from = { name = 'Aestutaur (G-9/G-10)' } + } } + } + }, { + id = 1509, --Ossified Gargouille Hand + type = 'key item', + dropped_from = { + name = 'Quasimodo, Forced (F-4)', + pops = { { + id = 3099, --Gargouille Stone + type = 'item', + dropped_from = { + name = 'Gruesome Gargouille (F-10/G-10)' + } + } } + } + }, { + id = 1510, --Imbrued Vampyr Fang + type = 'key item', + dropped_from = { name = 'Lord Varney, Timed (G-10/H-10)' } + } } +} + +``` + +The main addon file requires the index.lua file which in turn is responsible for requiring and returning data for each nm. diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/alfard.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/alfard.lua new file mode 100644 index 0000000..a88f6c9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/alfard.lua @@ -0,0 +1,60 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Alfard', + collectable = 3291, --Alfard's Fang + collectable_target_count = 75, + pops = { { + id = 1530, --Venomous hydra fang + type = 'key item', + dropped_from = { + name = 'Ningishzida, Forced (I-7/I-8)', + pops = { { + id = 3262, --Jaculus Wing + type = 'item', + dropped_from = { name = 'Jaculus, Timed (I-8)' } + }, { + id = 3261, --Minaruja Skull + type = 'item', + dropped_from = { + name = 'Minaruja, Forced (I-10)', + pops = { { + id = 3267, --Pursuer's Wing + type = 'item', + dropped_from = { name = 'Faunus Wyvern (I-9)' } + } } + } + }, { + id = 3268, --High-Quality Wivre Hide + type = 'item', + dropped_from = { name = 'Glade Wivre (I-8)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/apademak.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/apademak.lua new file mode 100644 index 0000000..924545d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/apademak.lua @@ -0,0 +1,59 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Apademak', + collectable = 3289, --Apademak Horn + collectable_target_count = 75, + pops = { { + id = 1525, --Torn Khimaira Wing + type = 'key item', + dropped_from = { + name = 'Dhorme Khimaira, Forced (F-7)', + pops = { { + id = 3246, --Snow God Core + type = 'item', + dropped_from = { + name = 'Upas-Kamuy, Forced (G-5)', + pops = { { + id = 3252, --Gelid Arm + dropped_from = { name = 'Snowflake (G-5)' } + } } + } + }, { + id = 3247, --Sisyphus Fragment + type = 'item', + dropped_from = { name = 'Sisyphus, Timed (F-6/G-6)' } + }, { + id = 3253, --High-quality marid hide + type = 'item', + dropped_from = { name = 'Olyphant (F-6)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/arch dynamis lord.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/arch dynamis lord.lua new file mode 100644 index 0000000..3e693c7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/arch dynamis lord.lua @@ -0,0 +1,87 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Arch Dynamis Lord', + pops = { { + id = 3429, --Fiendish Tome (26) + type = 'item', + dropped_from = { + name = 'Dynamis Lord, Forced (E-8)', + pops = { { + id = 3358, --Shrouded Bijou + type = 'item', + dropped_from = { name = 'Various Demon lottery NMs' } + } } + } + }, { + id = 3430, --Fiendish Tome (27) + type = 'item', + dropped_from = { + name = 'Duke Haures, Forced (J-7)', + pops = { { + id = 3400, --Odious Skull + type = 'item', + dropped_from = { name = 'Kindred DRK, RDM & SAM' } + } } + } + }, { + id = 3431, --Fiendish Tome (28) + type = 'item', + dropped_from = { + name = 'Marquis Caim, Forced (J-6)', + pops = { { + id = 3401, --Odious Horn + type = 'item', + dropped_from = { name = 'Kindred BRD, NIN, SMN & WAR' } + } } + } + }, { + id = 3432, --Fiendish Tome (29) + type = 'item', + dropped_from = { + name = 'Baron Avnas, Forced (I-5)', + pops = { { + id = 3402, --Odious Blood + type = 'item', + dropped_from = { name = 'Kindred DRG, MNK, THF & WHM' } + } } + } + }, { + id = 3433, --Fiendish Tome (30) + type = 'item', + dropped_from = { + name = 'Count Haagenti, Forced (F-7)', + pops = { { + id = 3403, --Odious Pen + type = 'item', + dropped_from = { name = 'Kindred BLM, BST, PLD & RNG' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/azdaja.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/azdaja.lua new file mode 100644 index 0000000..774304f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/azdaja.lua @@ -0,0 +1,38 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Azdaja', + collectable = 3292, --Azdaja's Horn + collectable_target_count = 75, + pops = { { + id = 1531, --Vacant Bugard Eye + type = 'key item', + dropped_from = { name = 'Deelgeed, Timed (F-9/F-10)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/briareus.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/briareus.lua new file mode 100644 index 0000000..a7c8f9b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/briareus.lua @@ -0,0 +1,67 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Briareus', + collectable = 2929, --Helm of Briareus + collectable_target_count = 50, + pops = { { + id = 1482, --Dented Gigas Shield + type = 'key item', + dropped_from = { + name = 'Adamastor, Forced (C-4)', + pops = { { + id = 2894, --Trophy Shield + type = 'item', + dropped_from = { name = 'Bathyal Gigas (C-5/D-5)' } + } } + } + }, { + id = 1484, --Severed Gigas Collar + type = 'key item', + dropped_from = { + name = 'Grandgousier, Forced (F-10)', + pops = { { + id = 2896, --Massive Armband + type = 'item', + dropped_from = { name = 'Demersal Gigas (E-9/F-9)' } + } } + } + }, { + id = 1483, --Warped Gigas Armband + type = 'key item', + dropped_from = { + name = 'Pantagruel, Forced (F-7)', + pops = { { + id = 2895, --Oversized Sock + type = 'item', + dropped_from = { name = 'Hadal Gigas (F-6/F-7)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/brulo.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/brulo.lua new file mode 100644 index 0000000..35317fa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/brulo.lua @@ -0,0 +1,52 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Brulo', + collectable = 3294, --Colorless Soul + collectable_target_count = 75, + pops = { { + id = 1652, --Emerald demilune abyssite + type = 'key item', + dropped_from = { + name = 'Koios (Conflux #5)', + pops = { { + id = 1565, --Colorful demilune abyssite + type = 'key item', + dropped_from = { + name = 'Fire/Earth Elemental', + pops = { { + id = 1564, --Clear demilune abyssite + type = 'key item', + dropped_from = { name = 'Any Cruor Prospector' } + } } + } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/bukhis.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/bukhis.lua new file mode 100644 index 0000000..699b4c1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/bukhis.lua @@ -0,0 +1,62 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Bukhis', + collectable = 2966, --Bukhis's Wing + collectable_target_count = 50, + pops = { { + id = 1508, --Ingrown Taurus Nail + type = 'key item', + dropped_from = { + name = 'Khalkotaur, Forced (F-4)', + pops = { { + id = 3098, --Gnarled Taurus Horn + type = 'item', + dropped_from = { name = 'Aestutaur (G-9/G-10)' } + } } + } + }, { + id = 1509, --Ossified Gargouille Hand + type = 'key item', + dropped_from = { + name = 'Quasimodo, Forced (F-4)', + pops = { { + id = 3099, --Gargouille Stone + type = 'item', + dropped_from = { + name = 'Gruesome Gargouille (F-10/G-10)' + } + } } + } + }, { + id = 1510, --Imbrued Vampyr Fang + type = 'key item', + dropped_from = { name = 'Lord Varney, Timed (G-10/H-10)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/carabosse.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/carabosse.lua new file mode 100644 index 0000000..88bec48 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/carabosse.lua @@ -0,0 +1,56 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Carabosse', + collectable = 2930, --Carabosse's Gem + collectable_target_count = 50, + pops = { { + id = 1485, --Pellucid Fly Eye + type = 'key item', + dropped_from = { + name = 'La Theine Liege, Forced (I-7)', + pops = { { + id = 2897, --Transparent Insect Wing + type = 'item', + dropped_from = { name = 'Plateau Glider (H-7)' } + } } + } + }, { + id = 1486, --Shimmering Pixie Pinion + type = 'key item', + dropped_from = { + name = 'Baba Yaga, Forced (H-7)', + pops = { { + id = 2898, --Piceous Scale + type = 'item', + dropped_from = { name = 'Farfadet (H-7)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/chloris.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/chloris.lua new file mode 100644 index 0000000..ba64fc7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/chloris.lua @@ -0,0 +1,115 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Chloris', + collectable = 2928, --2Lf. Chloris Bud + collectable_target_count = 50, + pops = { { + id = 1470, --Gory Scorpion Claw + type = 'key item', + dropped_from = { + name = 'Hedetet, Forced (F-7)', + pops = { { + id = 2921, --Venomous Scorpion Stinger + type = 'item', + dropped_from = { name = 'Canyon Scorpion (F-7)' } + }, { + id = 2948, --Acidic Humus + type = 'item', + dropped_from = { + name = 'Gancanagh, Forced (H-8)', + pops = { { + id = 2920, --Alkaline Humus + type = 'item', + dropped_from = { name = 'Pachypodium (H-8)' } + } } + } + } } + } + }, { + id = 1469, --Torn Bat Wing + type = 'key item', + dropped_from = { + name = 'Treble Noctules, Forced (I-9)', + pops = { { + id = 2919, --Bloody Fang + type = 'item', + dropped_from = { name = 'Blood Bat (I-9)' } + }, { + id = 2947, --Exorcised Skull + type = 'item', + dropped_from = { + name = 'Cannered Noz, Forced (F-6)', + pops = { { + id = 2918, --Baleful Skull + type = 'item', + dropped_from = { name = 'Caoineag (F-6)' } + } } + } + } } + } + }, { + id = 1468, --Veinous Hecteyes Eyelid + type = 'key item', + dropped_from = { + name = 'Ophanim, Forced (G-9)', + pops = { { + id = 2917, --Bloodshot Hecteye + type = 'item', + dropped_from = { name = 'Beholder (G-9)' } + }, { + id = 2946, --Tarnished Pincer + type = 'item', + dropped_from = { + name = 'Vetehinen, Forced (H-10)', + pops = { { + id = 2916, --High-quality Limule Pincer + type = 'item', + dropped_from = { name = 'Gulch Limule (H-10)' } + } } + } + }, { + id = 2945, --Shriveled Wing + type = 'item', + dropped_from = { + name = 'Halimede, Forced (G-12)', + pops = { { + id = 2915, --High-quality Clionid Wing + type = 'item', + dropped_from = { name = 'Gully Clionid (G-12)' } + } } + } + } } + } + }, { + id = 1471, --Mossy Adamantoise Shell + type = 'key item', + dropped_from = { name = 'Chukwa, Timed (F-5)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/cirein-croin.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/cirein-croin.lua new file mode 100644 index 0000000..3f664e7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/cirein-croin.lua @@ -0,0 +1,49 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Cirein-croin', + collectable = 2965, --Cirein. Lantern + collectable_target_count = 50, + pops = { { + id = 1504, --Glistening Orobon Liver + type = 'key item', + dropped_from = { + name = 'Cep-Kamuy, Forced (F-4)', + pops = { { + id = 3089, --Orobon Cheekmeat + type = 'item', + dropped_from = { name = 'Ancient Orobon (F-5)' } + } } + } + }, { + id = 1505, --Doffed Poroggo Hat + type = 'key item', + dropped_from = { name = 'Heqet, Timed (I-6)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/dragua.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/dragua.lua new file mode 100644 index 0000000..0bb120c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/dragua.lua @@ -0,0 +1,38 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Dragua', + collectable = 3288, --Dragua's Scale + collectable_target_count = 75, + pops = { { + id = 1521, --Bloodied Dragon Ear + type = 'key item', + dropped_from = { name = 'Hazhdiha, Timed (H-10)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/glavoid.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/glavoid.lua new file mode 100644 index 0000000..cb604bd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/glavoid.lua @@ -0,0 +1,84 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Glavoid', + collectable = 2927, --Glavoid Shell + collectable_target_count = 50, + pops = { { + id = 1473, --Sodden Sandworm Husk + type = 'key item', + dropped_from = { name = 'Minhocao, Timed (I-6)' } + }, { + id = 1475, --Sticky Gnat Wing + type = 'key item', + dropped_from = { name = 'Adze, Timed (G-5)' } + }, { + id = 1472, --Fat-lined Cockatrice Skin + type = 'key item', + dropped_from = { + name = 'Alectryon (H-8)', + pops = { { + id = 2923, --Cockatrice Tailmeat + type = 'item', + dropped_from = { name = 'Cluckatrice (H-8)' } + }, { + id = 2949, --Quivering Eft Egg + type = 'item', + dropped_from = { + name = 'Abas, Forced (K-10)', + pops = { { + id = 2922, --Eft Egg + dropped_from = { name = 'Canyon Eft (J-10/J-11)' } + } } + } + } } + } + }, { + id = 1474, --Luxuriant manticore mane + type = 'key item', + dropped_from = { + name = 'Muscaliet, Forced (J-6)', + pops = { { + id = 2925, --Resilient Mane + type = 'item', + dropped_from = { name = 'Hieracosphinx (J-6)' } + }, { + id = 2950, --Smooth Whisker + type = 'item', + dropped_from = { + name = 'Tefenet, Forced (G-6)', + pops = { { + id = 2924, --Shocking Whisker + dropped_from = { name = 'Jaguarundi (G-6)' } + } } + } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/index.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/index.lua new file mode 100644 index 0000000..a2ab5b6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/index.lua @@ -0,0 +1,59 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +local nms = { + 'alfard', + 'apademak', + 'arch dynamis lord', + 'azdaja', + 'briareus', + 'brulo', + 'bukhis', + 'carabosse', + 'chloris', + 'cirein-croin', + 'dragua', + 'glavoid', + 'isgebind', + 'itzpapalotl', + 'kukulkan', + 'maere', + 'ogopogo', + 'orthrus', + 'sedna', + 'sobek', + 'ulhuadshi', + 'warder of courage' +} + +nm_data = {} +for _, nm in pairs(nms) do + nm_data[nm] = require('nms/' .. nm) +end + +return nm_data diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/isgebind.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/isgebind.lua new file mode 100644 index 0000000..76f5aa7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/isgebind.lua @@ -0,0 +1,38 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Isgebind', + collectable = 3290, --Isgebind's Heart + collectable_target_count = 75, + pops = { { + id = 1526, --Begrimed Dragon Hide + type = 'key item', + dropped_from = { name = 'Kur, Timed (I-5/J-5)' } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/itzpapalotl.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/itzpapalotl.lua new file mode 100644 index 0000000..47d078a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/itzpapalotl.lua @@ -0,0 +1,60 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Itzpapalotl', + collectable = 2962, --Itzpapa. Scale + collectable_target_count = 50, + pops = { { + id = 1488, --Venomous Wamoura Feeler + type = 'key item', + dropped_from = { + name = 'Granite Borer, Forced (K-10)', + pops = { { + id = 3072, --Withered Cocoon + type = 'item', + dropped_from = { name = 'Gullycampa (K-10)' } + } } + } + }, { + id = 1490, --Distended Chigoe Abdomen + type = 'key item', + dropped_from = { name = 'Tunga, Timed (K-10)' } + }, { + id = 1489, --Bulbous crawler cocoon + type = 'key item', + dropped_from = { + name = 'Blazing Eruca, Forced (J-10)', + pops = { { + id = 3073, --Eruca Egg + type = 'item', + dropped_from = { name = 'Ignis Eruca (J-10)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/kukulkan.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/kukulkan.lua new file mode 100644 index 0000000..4b1e896 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/kukulkan.lua @@ -0,0 +1,67 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Kukulkan', + collectable = 2932, --Kukulkan's Fang + collectable_target_count = 50, + pops = { { + id = 1466, --Mucid Ahriman Eyeball + type = 'key item', + dropped_from = { + name = 'Arimaspi, Forced (K-6)', + pops = { { + id = 2913, --Clouded Lens + type = 'item', + dropped_from = { name = 'Deep Eye (K-6/K-7)' } + } } + } + }, { + id = 1464, --Tattered Hippogryph Wing + type = 'key item', + dropped_from = { + name = 'Alkonost, Forced (H-6)', + pops = { { + id = 2912, --Giant Bugard Tusk + type = 'item', + dropped_from = { name = 'Ypotryll (I-7)' } + } } + } + }, { + id = 1465, --Cracked Wivre Horn + type = 'key item', + dropped_from = { + name = 'Keratyrannos, Forced (G-6)', + pops = { { + id = 2910, --Armored Dragonhorn + type = 'item', + dropped_from = { name = 'Mesa Wivre (G-6)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/maere.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/maere.lua new file mode 100644 index 0000000..3250818 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/maere.lua @@ -0,0 +1,52 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Maere', + collectable = 3294, --Colorless Soul + collectable_target_count = 75, + pops = { { + id = 1654, --Indigo demilune abyssite + type = 'key item', + dropped_from = { + name = 'Gamayun (Conflux #8)', + pops = { { + id = 1565, --Colorful demilune abyssite + type = 'key item', + dropped_from = { + name = 'Air/Dark Elemental', + pops = { { + id = 1564, --Clear demilune abyssite + type = 'key item', + dropped_from = { name = 'Any Cruor Prospector' } + } } + } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ogopogo.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ogopogo.lua new file mode 100644 index 0000000..d4ca13a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ogopogo.lua @@ -0,0 +1,52 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Ogopogo', + collectable = 3294, --Colorless Soul + collectable_target_count = 75, + pops = { { + id = 1653, --Vermillion demilune abyssite + type = 'key item', + dropped_from = { + name = 'Chione (Conflux #7)', + pops = { { + id = 1565, --Colorful demilune abyssite + type = 'key item', + dropped_from = { + name = 'Ice/Water Elemental', + pops = { { + id = 1564, --Clear demilune abyssite + type = 'key item', + dropped_from = { name = 'Any Cruor Prospector' } + } } + } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/orthrus.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/orthrus.lua new file mode 100644 index 0000000..59d13a8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/orthrus.lua @@ -0,0 +1,59 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Orthrus', + collectable = 3287, --Orthrus's Claw + collectable_target_count = 75, + pops = { { + id = 1520, --Steaming cerberus tongue + type = 'key item', + dropped_from = { + name = 'Amarok, Forced (E-6)', + pops = { { + id = 3231, --Sharabha Hide + type = 'item', + dropped_from = { + name = 'Sharabha, Forced (G-5)', + pops = { { + id = 3237, + dropped_from = { name = 'Dune Manticore (F-5/F-6)' } + } } + } + }, { + id = 3232, --Tiger King Hide + type = 'item', + dropped_from = { name = 'Ansherekh, Timed (F-8/G-8)' } + }, { + id = 3238, --H.Q. Dhalmel Hide + type = 'item', + dropped_from = { name = 'Camelopardalis (F-7/G-7)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sedna.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sedna.lua new file mode 100644 index 0000000..c00d641 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sedna.lua @@ -0,0 +1,49 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Sedna', + collectable = 2967, --Sedna's Tusk + collectable_target_count = 50, + pops = { { + id = 1512, --Shimmering Pugil Scale + type = 'key item', + dropped_from = { name = 'Hrosshvalur, Timed (J-6)' } + }, { + id = 1511, --Glossy Sea Monk Sucker + type = 'key item', + dropped_from = { + name = 'Iku-Turso, Forced (J-7)', + pops = { { + id = 3100, --Moonbeam Clam + type = 'item', + dropped_from = { name = 'Jasconius (I-7/J-7)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sobek.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sobek.lua new file mode 100644 index 0000000..4aa9a80 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/sobek.lua @@ -0,0 +1,60 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Sobek', + collectable = 2964, --Sobek's Skin + collectable_target_count = 50, + pops = { { + id = 1500, --Molted Peiste Skin + type = 'key item', + dropped_from = { name = 'Gukumatz, Timed (J-11)' } + }, { + id = 1498, --Bloodstained Bugard Fang + type = 'key item', + dropped_from = { + name = 'Minax Bugard, Forced (K-10)', + pops = { { + id = 3085, --Bewitching Tusk + type = 'item', + dropped_from = { name = 'Abyssobugard (J-10/K-11)' } + } } + } + }, { + id = 1499, --Gnarled Lizard Nail + type = 'key item', + dropped_from = { + name = 'Sirrush, Forced (I-11)', + pops = { { + id = 3086, --Molt Scraps + type = 'item', + dropped_from = { name = 'Dusk Lizard (J-11)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ulhuadshi.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ulhuadshi.lua new file mode 100644 index 0000000..2245791 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/ulhuadshi.lua @@ -0,0 +1,49 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = 'Ulhuadshi', + collectable = 2963, --Ulhuadshi's Fang + collectable_target_count = 50, + pops = { { + id = 1492, --Shriveled Hecteyes Stalk + type = 'key item', + dropped_from = { name = 'Amun, Timed (H-8/I-9)' } + }, { + id = 1491, --Mucid Worm Segment + type = 'key item', + dropped_from = { + name = 'Pallid Percy, Forced (J-7)', + pops = { { + id = 3074, --Blanched Silver + type = 'item', + dropped_from = { name = 'Entozoon (J-7)' } + } } + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/warder of courage.lua b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/warder of courage.lua new file mode 100644 index 0000000..6534c62 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/nms/warder of courage.lua @@ -0,0 +1,79 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Empy Pop Tracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) 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. +]] + +return { + name = "Warder of Courage", + pops = { { + id = 2986, --Primal Nazar + type = 'key item', + dropped_from = { + name = 'Dremi (NPC)', + pops = { { + id = 2976, --Primary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Temperance (Zdei, portal #1)' } + }, { + id = 2977, --Secondary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Fortitude (Ghrah, portal #3)' } + }, { + id = 2978, --Tertiary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Faith (Euvhi, portal #12)' } + }, { + id = 2979, --Quaternary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Justice (Xzomit, portal #6)' } + }, { + id = 2980, --Quinary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Hope (Phuabo, portal #1)' } + }, { + id = 2981, --Senary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Prudence (Hpemde, portal #9)' } + }, { + id = 2982, --Septenary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Love (Yovra)' } + }, { + id = 2983, --Octonary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Dignity (Limule, portal #4)' } + }, { + id = 2984, --Nonary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Loyalty (Clionid, portal #13)' } + }, { + id = 2985, --Denary Nazar + type = 'key item', + dropped_from = { name = 'Warder of Mercy (Murex, portal #7)' } + }} + } + } } +} diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo-full.png b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo-full.png Binary files differnew file mode 100644 index 0000000..02df409 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo-full.png diff --git a/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo.png b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo.png Binary files differnew file mode 100644 index 0000000..db24c67 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/EmpyPopTracker/readme/demo.png diff --git a/Data/DefaultContent/Libraries/addons/addons/FastCS/FastCS.lua b/Data/DefaultContent/Libraries/addons/addons/FastCS/FastCS.lua new file mode 100644 index 0000000..8999372 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/FastCS/FastCS.lua @@ -0,0 +1,140 @@ +_addon.name = "FastCS" +_addon.author = "Cairthenn" +_addon.version = "1.3" +_addon.commands = {"FastCS","FCS"} + +--Requires: + +require("luau") + +-- Settings: + +defaults = {} +defaults.frame_rate_divisor = 2 +defaults.exclusions = S{"home point #1", "home point #2", "home point #3", "home point #4", "home point #5", "igsli", "urbiolaine", "teldro-kesdrodo", "nunaarl bthtrogg", "survival guide", "waypoint"} +settings = config.load(defaults) + +-- Globals: +__Globals = { + enabled = false, -- Boolean that indicates whether the Config speed-up is currently enabled + zoning = false, -- Boolean that indicates whether the player is zoning with the config speed-up enabled +} + +-- Help text definition: + +helptext = [[FastCS - Command List: +1. help - Displays this help menu. +2a. fps [30|60|uncapped] +2b. frameratedivisor [2|1|0] + - Changes the default FPS after exiting a cutscene. + - The prefix can be used interchangeably. For example, "fastcs fps 2" will set the default to 30 FPS. +3. exclusion [add|remove] <name> + - Adds or removes a target from the exclusions list. Case insensitive. + ]] + +function disable() + + __Globals.enabled = false + + windower.send_command("config FrameRateDivisor ".. (settings.frame_rate_divisor or 2)) + +end + +function enable() + + __Globals.enabled = true + + windower.send_command("config FrameRateDivisor 0") + +end + +windower.register_event('unload',disable) +windower.register_event('logout',disable) +windower.register_event('outgoing chunk',function(id) + + if id == 0x00D and __Globals.enabled then -- Last packet sent when zoning out + disable() + __Globals.zoning = true + end + +end) + +windower.register_event('incoming chunk',function(id,o,m,is_inj) + + if id == 0x00A and not is_inj and __Globals.zoning then + enable() + __Globals.zoning = false + end + +end) + +windower.register_event('load',function() + local player = windower.ffxi.get_player() + + if player and player.status == 4 then + windower.send_command("config FrameRateDivisor 0") + else + disable() + end + +end) + +windower.register_event("status change", function(new,old) + + local target = windower.ffxi.get_mob_by_target('t') + + if not target or target and not settings.exclusions:contains(target.name:lower()) then + + if new == 4 then + enable() + elseif old == 4 then + disable() + end + + end + +end) + +windower.register_event("addon command", function (command,...) + command = command and command:lower() or "help" + local args = T{...}:map(string.lower) + + if command == "help" then + print(helptext) + elseif command == "fps" or command == "frameratedivisor" then + if #args == 0 then + settings.frame_rate_divisor = (settings.frame_rate_divisor + 1) % 3 + local help_message = (settings.frame_rate_divisor == 0) and "Uncapped" or (settings.frame_rate_divisor == 1) and "60 FPS" or (settings.frame_rate_divisor == 2) and "30 FPS" + notice("Default frame rate divisor is now: " .. settings.frame_rate_divisor .. " (" .. help_message .. ")" ) + elseif #args == 1 then + if args[1] == "60" or args[1] == "1" then + settings.frame_rate_divisor = 1 + elseif args[1] == "30" or args[1] == "2" then + settings.frame_rate_divisor = 2 + elseif args[1] == "uncapped" or args[1] == "0" then + settings.frame_rate_divisor = 0 + end + local help_message = (settings.frame_rate_divisor == 0) and "Uncapped" or (settings.frame_rate_divisor == 1) and "60 FPS" or (settings.frame_rate_divisor == 2) and "30 FPS" + notice("Default frame rate divisor is now: " .. settings.frame_rate_divisor .. " (" .. help_message .. ")" ) + else + error("The command syntax was invalid.") + end + settings:save() + elseif command == "exclusion" then + if #args == 2 then + if args[1] == "add" then + settings.exclusions:add(args[2]:lower()) + notice(args[2] .. " added to the exclusions list.") + elseif args[1] == "remove" then + settings.exclusions:remove(args[2]:lower()) + notice(args[2] .. " removed from the exclusions list.") + else + error("The command syntax was invalid.") + end + else + error("The command syntax was invalid.") + end + else + error("The command syntax was invalid.") + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/FastCS/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/FastCS/ReadMe.md new file mode 100644 index 0000000..46f8de8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/FastCS/ReadMe.md @@ -0,0 +1,10 @@ +**Author:** Cairthenn<br> +**Version:** 1.2<br> +**Date:** Jan. 31, 2016<br> + +# FastCS # + +* Uses the config plugin to automatically disable the frame rate cap during custcenes. +* Note that this affects regular NPC interaction menus. +* You must have the config plugin loaded. +* This may cause undesirable behavior during specific cutscenes. Unload the addon if this happens. diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/README.md b/Data/DefaultContent/Libraries/addons/addons/GearSwap/README.md new file mode 100644 index 0000000..650af85 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/README.md @@ -0,0 +1,40 @@ +Author: Byrth + +Version: 0.930 + +Date: 06/13/2017 + +GearSwap + +Abbreviation: gs + +Commands (<> indicates a field. You do not actually have to use <>s): +* gs c <string> : Passes the <string> to the self_command() user function. +* gs equip <string> : Attempts to interpret the <string> as an index of the sets table and equip that set. Will ignore "sets" if the string starts with it. +** gs equip naked : This equips the default set "naked," which is just a bunch of empty slots. If you remake sets (sets={}) in your get_sets(), this will not work. +* gs debugmode : Activates GearSwap's Debug Mode, which prints out why specific gear equipping attempts failed, shows you when you're entering events, and enables the eval command. +** gs eval <string> : This command evaluates the <string> as Lua code in the global gearswap environment (not the user environment, which is in the user_env table). It is only available when debugmode is on. +* gs showswaps : Shows when your gear successfully changes and what it changes to. +* gs load <string> : (or l <string>) Attempts to load the first version of <string> found, assuming it is a file path relative to 9 potential base directories, in this order: +** ..GearSwap/libs-dev/<string> +** ..GearSwap/libs/<string> +** GearSwap/data/<character_name>/<string> +** GearSwap/data/common/<string> +** GearSwap/data/<string> +** APPDATA/Windower/GearSwap/<character_name>/<string> +** APPDATA/Windower/GearSwap/common/<string> +** APPDATA/Windower/GearSwap/<string> +** ..Windower/addons/libs/<string> +* gs reload : Reloads the current user file. +* gs export <options> : Exports your currently equipped gear, inventory, or all the items in your current Lua files' sets into GearSwap .lua or spellcast .xml format. Takes options "inventory", "all", "wearable", "sets", and "xml." Defaults to currently equipped gear and lua otherwise. Also exports appropriate advanced set tables with augments for currently equipped gear and inventory. +* gs enable <slot> : Enables equip commands targeting a specified slot. "All" will allow all equip commands. Providing no slot argument will enable user GearSwap file execution, if it was disabled. +* gs disable <slot> : Disables equip commands targeting a given slot. "All" will prevent all equip commands. Providing no second argument will disable user GearSwap file execution, although registered events will still run. +* gs validate <sets|inv> <filter> : This command checks to see whether the equipment in the sets table also exists in your inventory (default), or (by passing "inv") whether the equipment in your inventory exists in your sets table. <filter> is an optional list of words that restricts the output to only those items that contain text from one of the filter's words. + +Purpose: To assist in the micromanaging of equipment! + +Settings Files: +There is no settings file for GearSwap. + +Additional Assistance: +The Windower/addons/GearSwap/beta_examples_and_information folder has a file in it named Variables.xlsx that gives more specific information. If that is insufficient, you can go to BlueGartr's FFXI section or FFXIAH and ask for more assistance. diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Advanced sets tables.txt b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Advanced sets tables.txt new file mode 100644 index 0000000..56a08e2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Advanced sets tables.txt @@ -0,0 +1,47 @@ +============== Advanced "sets" tables ============== + +-- Augments -- +There are a lot of different augments, many/all of which have been mapped in the extdata library. +This system will use the first item it finds in your inventory that matches the name/augment +combination that you provide. If you provide an ambiguous set of conditions, the resulting equipped +item will be ambiguous (could vary based on the pairs iterator). + +In order to specify an item by augment, you must write the augment exactly as it appears on your equipment: + sets.aftercast_Idle = {ring1={name="Dark Ring",augments={"Phys. Damage Taken -6%"}}, + ring2={name="Dark Ring",augments={"Breath Damage Taken -6%","{Phys. Damage Taken -5%"}}} + + +If it pleases you, you can store your augmented items in variables: + lefty = {name="Dark Ring",augments={"Phys. Damage Taken -6%"}} + righty = {name="Dark Ring",augments={"Breath Damage Taken -6%","Phys. Damage Taken -5%"}} + sets.aftercast_Idle = {ring1=lefty,ring2=righty} + +This will save you a little work when you are dealing with using the same augmented item in multiple sets. + + +If you are having trouble getting GearSwap to recognize your augment, equip the gear and use //gs export. +This will create a Lua file in ../windower/addons/gearswap/data/export that includes a properly formatted table. + + + + +-- Prioritizing gear changes -- +Currently equip swaps can be ordered by using advanced "sets" tables similar to the above: + sets.nohead = {head={name="empty",priority=1},lring={name="Angha ring",priority=2}} + +Gear is changed in descending order from highest to lowest, so the above set would equip Angha ring and then +unequip the head slot. All gear that is not defined is given a default priority of zero, and all positive and +negative rational numbers are acceptable priorities. If two pieces of gear have the same priority value, they +will be swapped in the default order (from 0 to 15 in ../windower/res/slots.lua). + +I do not recommend moving Sub in front of Main, Ammo in front of Range, etc. as these will cause silent equip +failures. + + + +-- Specifying a bag -- +With the introduction of Mog Wardrobe, it is now possible to equip items from either Inventory or Wardrobe: + sets.wardrobe_feet = {feet={name="Bokwus Boots",bag="wardrobe"}} + +There is really no reason to do this 99% of the time, but it has been included as a failsafe to let people +still specify at least two augments in case the augment system stops working in the future.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BLM.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BLM.lua new file mode 100644 index 0000000..d49bf4b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BLM.lua @@ -0,0 +1,622 @@ +include('organizer-lib') + +function get_sets() + mp_efficiency = 0 + macc_level = 0 + + sets.TH = { + hands={ name="Merlinic Dastanas", augments={'"Treasure Hunter"+2',},hp=9,mp=20}, + legs={ name="Merlinic Shalwar", augments={'Pet: Accuracy+16 Pet: Rng. Acc.+16','Pet: Haste+1','"Treasure Hunter"+1','Mag. Acc.+9 "Mag.Atk.Bns."+9',},hp=29,mp=44}, + waist="Chaac Belt", + } + + sets.precast = {} + + + sets.precast.FastCast = {} + + sets.precast.FastCast.Default = { + main="Marin Staff +1", + ammo="Impatiens", + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',},hp=22,mp=56}, + neck={name="Orunmila's Torque",mp=30}, + ear1="Enchanter Earring +1", + ear2={name="Loquacious Earring",mp=30}, + body={ name="Zendik Robe",hp=57,mp=61}, + hands={ name="Merlinic Dastanas", augments={'Mag. Acc.+29','"Fast Cast"+7','INT+1',},hp=9,mp=20}, + lring="Kishar Ring", + rring="Weather. Ring +1", + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + waist="Witful Belt", + legs={name="Psycloth Lappas",hp=43,mp=109}, + feet={ name="Merlinic Crackows", augments={'"Mag.Atk.Bns."+11','"Fast Cast"+7',},hp=4,mp=20}, + } + + sets.precast.FastCast.Death = set_combine(sets.precast.FastCast.Default,{ + ammo={name="Ghastly Tathlum +1",mp=35}, + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',},hp=22,mp=56}, + neck={name="Orunmila's Torque",mp=30}, + ear1={name="Etiolation Earring",hp=50,mp=50}, + ear2={name="Loquacious Earring",mp=30}, + body={ name="Zendik Robe",hp=57,mp=61}, + hands={ name="Merlinic Dastanas", augments={'Mag. Acc.+29','"Fast Cast"+7','INT+1',},hp=9,mp=20}, + lring="Kishar Ring", + rring={name="Sangoma Ring",mp=70}, + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + waist={name="Mujin Obi",mp=60}, + legs={name="Psycloth Lappas",hp=43,mp=109}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + }) + + sets.precast.FastCast['Elemental Magic'] = set_combine(sets.precast.FastCast.Default,{ + ear1={name="Barkarole Earring",mp=25}, + body={ name="Dalmatica +1", augments={'Occ. quickens spellcasting +3%','"Fast Cast"+6','Pet: "Mag.Def.Bns."+6',},hp=-55,mp=55}, + }) + + sets.precast.FastCast['Enhancing Magic'] = set_combine(sets.precast.FastCast.Default,{waist="Siegel Sash"}) + + sets.precast.Cure = set_combine(sets.precast.FastCast.Default,{body="Heka's Kalasiris", + back={name="Pahtli Cape",mp=50}, + legs={name="Doyen Pants",hp=43,mp=32}, + lear={name="Mendicant's Earring",mp=30}, + }) + sets.precast.Stoneskin = set_combine(sets.precast.FastCast['Enhancing Magic'],{ + legs={name="Doyen Pants",hp=43,mp=32}, + }) + + sets.precast.Manafont = {body={name="Archmage's Coat +1",hp=54,mp=59}} + + sets.Impact = {head=empty,body={name="Twilight Cloak",mp=75},legs={name="Perdition Slops",hp=73,mp=59}} + + sets.midcast = {} + + sets.midcast.magic_base = { + main="Laevateinn", + sub="Enki Strap", + ammo={name="Mana Ampulla",mp=20}, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',},hp=22,mp=56}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + legs={ name="Lengo Pants", augments={'INT+10','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=43,mp=29}, + feet={name="Herald's Gaiters",mp=12}, + neck="Incanter's Torque", + waist="Austerity Belt +1", + lear={name="Mendicant's Earring",mp=30}, + right_ear="Dignitary's Earring", + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + right_ring="Defending Ring", + back="Umbra Cape", + } + + sets.midcast.Stun = {main="Laevateinn", + sub="Enki strap", + ammo={name="Hydrocera",mp=20}, + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',},hp=22,mp=56}, + neck="Erra Pendant", + ear1="Enchanter Earring +1", + ear2="Dignitary's Earring", + body={name="Psycloth Vest",hp=54,mp=59}, + hands={ name="Merlinic Dastanas", augments={'Mag. Acc.+29','"Fast Cast"+7','INT+1',},hp=9,mp=20}, + lring={name="Sangoma Ring",mp=70}, + rring="Shiva Ring +1", + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + waist="Ninurta's Sash", + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',},hp=29,mp=44}, + feet={name="Artsieq Boots",hp=13,mp=44}, + } + + sets.midcast['Elemental Magic'] = { + [0] = {}, + [1] = {} + } + + sets.ElementalMagicMAB = { + Earth={neck={name="Quanpur Necklace", mp=10}}, + Dark={ + head={ name="Pixie Hairpin +1", hp=-35,mp=120}, + rring="Archon Ring", + } + } + + -- MAcc level 0 (Macc and Enmity irrelevant) + sets.midcast['Elemental Magic'][0][0] = { + main="Laevateinn", + sub="Enki Strap", + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',},hp=22,mp=56}, + neck="Baetyl Pendant", + ear1={name="Barkarole Earring",mp=25}, + ear2="Friomisi Earring", + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + ring1="Shiva Ring +1", + ring2="Shiva Ring +1", + back={ name="Taranus's Cape", augments={'INT+20','System: 1 ID: 80 Val: 19','"Mag.Atk.Bns."+10',}}, + waist={name="Yamabuki-no-Obi",mp=35}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + } + + sets.midcast.MB={ + head={ name="Merlinic Hood", augments={'Mag. Acc.+24 "Mag.Atk.Bns."+24','Magic burst dmg.+11%','INT+1','Mag. Acc.+8',},hp=22,mp=56}, + body={ name="Merlinic Jubbah", augments={'"Mag.Atk.Bns."+29','Magic burst dmg.+11%','INT+10',},hp=41,mp=67}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+26','Magic burst dmg.+11%','INT+10','Mag. Acc.+13',},hp=29,mp=44}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + neck="Mizu. Kubikazari", + right_ring="Mujin Band", + } + + --sets.midcast['Elemental Magic'][0][0] = set_combine(sets.midcast['Elemental Magic'][0][0],sets.midcast.MB) + + sets.midcast['Elemental Magic'][0][1] = set_combine(sets.midcast['Elemental Magic'][0][0],{ + body={name="Spaekona's Coat +2",hp=81,mp=88} + }) + + -- MAcc level 1 (MAcc and Enmity relevant) + sets.midcast['Elemental Magic'][1][0] = {main="Laevateinn", + sub="Enki Strap", + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',},hp=22,mp=56}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + legs={ name="Merlinic Shalwar", augments={'Mag. Acc.+25 "Mag.Atk.Bns."+25','MND+4','Mag. Acc.+15','"Mag.Atk.Bns."+12',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + neck={name="Sanctity Necklace",hp=35,mp=35}, + waist={name="Acuity Belt +1", mp=35}, + ear2="Novia Earring", + ear1={name="Barkarole Earring",mp=25}, + ring1="Shiva Ring +1", + ring2="Shiva Ring +1", + back={ name="Taranus's Cape", augments={'INT+20','System: 1 ID: 80 Val: 19','"Mag.Atk.Bns."+10',}}, + } + + sets.midcast['Elemental Magic'][1][1] = set_combine(sets.midcast['Elemental Magic'][1][0],{ + body={name="Spaekona's Coat +2",hp=81,mp=88} + }) + + sets.midcast.Death = { + main="Laevateinn", + sub={name="Niobid Strap",mp=20}, + ammo={name="Ghastly Tathlum +1",mp=35}, + head={ name="Pixie Hairpin +1", hp=-35,mp=120}, + body={ name="Merlinic Jubbah", augments={'"Mag.Atk.Bns."+29','Magic burst dmg.+11%','INT+10',},hp=41,mp=67}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+26','Magic burst dmg.+11%','INT+10','Mag. Acc.+13',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + neck="Mizu. Kubikazari", + waist={name="Mujin Obi",mp=60}, + ear2={name="Etiolation Earring",hp=50,mp=50}, + ear1={name="Barkarole Earring",mp=25}, + left_ring="Archon Ring", + right_ring="Mujin Band", + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + } + sets.midcast['Dia II'] = sets.TH + sets.midcast.Dia = sets.TH + sets.midcast.Diaga = sets.TH + sets.midcast.Burn = sets.TH + + + sets.midcast['Dark Magic'] = { + main={ name="Rubicundity", augments={'Mag. Acc.+10','"Mag.Atk.Bns."+10','Dark magic skill +10','"Conserve MP"+7',}}, + sub="Genmei Shield", + ammo="Hasty Pinion +1", + head={ name="Pixie Hairpin +1", hp=-35,mp=120}, + body={name="Psycloth Vest",hp=54,mp=59}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + legs={ name="Merlinic Shalwar", augments={'Mag. Acc.+25 "Mag.Atk.Bns."+25','MND+4','Mag. Acc.+15','"Mag.Atk.Bns."+12',},hp=29,mp=44}, + feet={ name="Merlinic Crackows", augments={'Mag. Acc.+21','"Drain" and "Aspir" potency +11','INT+1',},hp=4,mp=20}, + neck="Erra Pendant", + waist="Ninurta's Sash", + left_ear={name="Hirudinea Earring",hp=-5,mp=-5}, + right_ear={name="Loquac. Earring",mp=30}, + left_ring="Evanescence Ring", + right_ring="Archon Ring", + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + } + + sets.midcast['Enfeebling Magic'] = { + main="Laevateinn", + sub="Enki Strap", + ammo="Pemphredo Tathlum", + head={name="Amalric Coif +1",hp=27,mp=61}, + neck="Erra Pendant", + ear1="Enchanter Earring +1", + ear2="Dignitary's Earring", + body={name="Zendik Robe",hp=57,mp=61}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + lring={name="Sangoma Ring",mp=70}, + ring2="Weather. Ring +1", + back={ name="Taranus's Cape", augments={'INT+20','System: 1 ID: 80 Val: 19','"Mag.Atk.Bns."+10',}}, + waist={name="Luminary Sash",mp=45}, + legs={name="Psycloth Lappas",hp=43,mp=109}, + feet={name="Artsieq Boots",hp=13,mp=44}, + } + + sets.midcast.Vidohunir = { + ammo="Pemphredo Tathlum", + head={ name="Pixie Hairpin +1", hp=-35,mp=120}, + neck={name="Saevus Pendant +1",mp=20}, + ear1={name="Barkarole Earring",mp=25}, + ear2="Friomisi Earring", + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + lring="Shiva Ring +1", + rring="Archon Ring", + back={ name="Taranus's Cape", augments={'INT+20','System: 1 ID: 80 Val: 19','"Mag.Atk.Bns."+10',}}, + waist={name="Acuity Belt +1", mp=35}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + } + + sets.midcast.Myrkr = { + ammo={name="Ghastly Tathlum +1",mp=35}, + head={ name="Pixie Hairpin +1", hp=-35,mp=120}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + legs={ name="Psycloth Lappas", augments={'MP+80','Mag. Acc.+15','"Fast Cast"+7',},hp=43,mp=109}, + feet={name="Artsieq Boots",hp=13,mp=44}, + neck={name="Sanctity Necklace",hp=35,mp=35}, + waist={name="Mujin Obi",mp=60}, + ear1={name="Etiolation Earring",hp=50,mp=50}, + right_ear={name="Gifted Earring",mp=45}, + rring={name="Sangoma Ring",mp=70}, + right_ring={name="Lebeche Ring",mp=40}, + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + } + + sets.midcast.Cure = { + main="Vadose Rod", + sub="Genmei Shield", + ammo={name="Mana Ampulla",mp=20}, + head={name="Amalric Coif +1",hp=27,mp=61}, + neck="Phalaina Locket", + lear={name="Mendicant's Earring",mp=30}, + rear="Novia Earring", + body="Heka's Kalasiris", + hands={name="Revealer's Mitts +1",hp=22,mp=44}, + lring={name="Sangoma Ring",mp=70}, + right_ring={name="Lebeche Ring",mp=40}, + back={name="Pahtli Cape",mp=50}, + waist={name="Luminary Sash",mp=45}, + legs={ name="Merlinic Shalwar", augments={'Mag. Acc.+25 "Mag.Atk.Bns."+25','MND+4','Mag. Acc.+15','"Mag.Atk.Bns."+12',},hp=29,mp=44}, + feet={name="Wicce Sabots +1",hp=9,mp=20}, + } + + + sets.midcast.EnhancingDuration = { + main={ name="Gada", augments={'Enh. Mag. eff. dur. +6','"Mag.Atk.Bns."+9',}}, + sub={name="Ammurapi Shield",hp=22,mp=58}, + hands={ name="Telchine Gloves", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=52,mp=44}, + head={ name="Telchine Cap", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=36,mp=32,}, + body={ name="Telchine Chas.", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=54,mp=59}, + legs={ name="Telchine Braconi", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=43,mp=29}, + feet={ name="Telchine Pigaches", augments={'Song spellcasting time -7%','Enh. Mag. eff. dur. +10',},hp=13,mp=44}, + } + + sets.midcast.Stoneskin = set_combine(sets.midcast.EnhancingDuration,{ + neck={name="Nodens Gorget",hp=25,mp=25}, + waist="Siegel Sash", + legs="Shedir Seraweels" + }) + + sets.midcast.Aquaveil = set_combine(sets.midcast.EnhancingDuration,{ + main="Vadose Rod", + head={name="Amalric Coif +1",hp=27,mp=61}, + sub="Genmei Shield", + waist={name="Emphatikos Rope",mp=20}, + legs="Shedir Seraweels" + }) + + sets.midcast.Refresh = set_combine(sets.midcast.EnhancingDuration,{ + head={name="Amalric Coif +1",hp=27,mp=61}, + back="Grapevine Cape", + feet={name="Inspirited Boots",hp=9,mp=20}, + }) + + sets.midcast.Phalanx = set_combine(sets.midcast.EnhancingDuration,{}) + + sets.aftercast = {} + sets.aftercast.Idle = {} + sets.aftercast.Idle.keys = {[0]="Refresh",[1]="PDT"} + sets.aftercast.Idle.ind = 0 +--[[ sets.aftercast.Idle[0] = { + main="Laevateinn", + sub={name="Niobid Strap",mp=20}, + ammo={name="Ghastly Tathlum +1",mp=35}, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',},hp=22,mp=56}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Amalric Gages +1",hp=13,mp=106}, + legs={ name="Lengo Pants", augments={'INT+10','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=43,mp=29}, + feet={ name="Amalric Nails +1", hp=4,mp=106}, + neck="Loricate Torque +1", + waist={name="Mujin Obi",mp=60}, + ear1={name="Etiolation Earring",hp=50,mp=50}, + right_ear={name="Gifted Earring",mp=45}, + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + right_ring="Defending Ring", + back={ name="Bane Cape", augments={'Elem. magic skill +3','Dark magic skill +7','"Mag.Atk.Bns."+1','"Fast Cast"+5',}}, + }]] + sets.aftercast.Idle[0] = { + main="Mafic Cudgel", + sub="Genmei Shield", + ammo={name="Mana Ampulla",mp=20}, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',},hp=22,mp=56}, + neck="Loricate Torque +1", + ear1={name="Etiolation Earring",hp=50,mp=50}, + ear2="Sorcerer's Earring", + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={ name="Merlinic Dastanas", augments={'VIT+8','Attack+18','"Refresh"+1','Accuracy+17 Attack+17','Mag. Acc.+7 "Mag.Atk.Bns."+7',},hp=9,mp=20}, + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + ring2="Defending Ring", + back="Umbra Cape", + waist={name="Mujin Obi",mp=60}, + legs={ name="Lengo Pants", augments={'INT+10','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=43,mp=29}, + feet={name="Herald's Gaiters",mp=12}, + } + + sets.aftercast.Idle[1] = { + main="Mafic Cudgel", + sub="Genmei Shield", + ammo={name="Mana Ampulla",mp=20}, + head={ name="Hagondes Hat +1", augments={'Phys. dmg. taken -3%','Magic dmg. taken -2%','"Mag.Atk.Bns."+26',},hp=36,mp=32}, + neck="Loricate Torque +1", + ear1="Telos Earring", + ear1={name="Etiolation Earring",hp=50,mp=50}, + body={name="Hagondes Coat +1",hp=54,mp=59}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + ring2="Defending Ring", + back="Umbra Cape", + waist="Ninurta's Sash", + legs={ name="Hagondes Pants +1", augments={'Phys. dmg. taken -4%','Magic dmg. taken -4%','Magic burst dmg.+10%',},hp=43,mp=29}, + feet={name="Battlecast Gaiters",hp=13}, + } + + sets.aftercast.Idle['Mana Wall'] = { + back={ name="Taranus's Cape", augments={'Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',},mp=78}, + feet={name="Wicce Sabots +1",hp=9,mp=20}, + } + + sets.aftercast.Resting = { + main={name="Numen Staff",mp=45}, + sub="Oneiros Grip", + ammo={name="Mana Ampulla",mp=20}, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',},hp=22,mp=56}, + neck={name="Eidolon Pendant +1",mp=15}, + ear1="Relaxing Earring", + ear2={name="Antivenom Earring",mp=15}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Nares Cuffs",mp=48}, -- MP+3% + ring1={name="Celestial Ring",mp=20}, + ring2="Angha Ring", + back={name="Felicitas Cape +1",mp=15}, + waist="Austerity Belt +1", + legs={ name="Lengo Pants", augments={'INT+10','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=43,mp=29}, + feet={name="Chelona Boots +1",mp=40}, + } + + sets.aftercast.Engaged = { + main="Mafic Cudgel", + sub="Genmei Shield", + head={ name="Hagondes Hat +1", augments={'Phys. dmg. taken -3%','Magic dmg. taken -2%','"Mag.Atk.Bns."+26',},hp=36,mp=32}, + neck="Loricate Torque +1", + ear2="Brutal Earring", + ear1={name="Etiolation Earring",hp=50,mp=50}, + --body="Onca Suit",hands=empty, + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + ring2="Defending Ring", + --back="Umbra Cape",waist="Ninurta's Sash",legs=empty,feet=empty} + body={name="Hagondes Coat +1",hp=54,mp=59}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',},hp=-20,mp=20}, + ring2="Defending Ring", + back="Umbra Cape", + waist="Ninurta's Sash", + legs={ name="Hagondes Pants +1", augments={'Phys. dmg. taken -4%','Magic dmg. taken -4%','Magic burst dmg.+10%',},hp=43,mp=29}, + feet={name="Battlecast Gaiters",hp=13}, + } + + sets.Obis = {} + sets.Obis.Fire = {waist='Hachirin-no-Obi'} + sets.Obis.Earth = {waist='Hachirin-no-Obi'} + sets.Obis.Water = {waist='Hachirin-no-Obi'} + sets.Obis.Wind = {waist='Hachirin-no-Obi'} + sets.Obis.Ice = {waist='Hachirin-no-Obi'} + sets.Obis.Lightning = {waist='Hachirin-no-Obi'} + sets.Obis.Light = {waist='Hachirin-no-Obi'} + sets.Obis.Dark = {waist='Hachirin-no-Obi'} + sets.Zodiac = {lring={name="Zodiac Ring",mp=25}} + + sets.aftercast.empty = {neck="Loricate Torque +1"} + sets.aftercast.Chry = {neck={name="Chrysopoeia Torque",mp=30}} + tp_level = 'empty' + + stuntarg = 'Shantotto' + send_command('input /macro book 2;wait .1;input /macro set 1') +end + +windower.register_event('tp change',function(new,old) + if new > 2990 then + tp_level = 'Chry' + else + tp_level = 'empty' + end + if not midaction() then + if sets.aftercast[player.status] then + equip(sets.aftercast[player.status],sets.aftercast[tp_level]) + else + equip(sets.aftercast.Idle,sets.aftercast[tp_level]) + end + end + set_priorities('mp','hp') +end) + +function precast(spell) + if sets.precast[spell.english] then + equip(sets.precast[spell.english][macc_level] or sets.precast[spell.english]) + elseif string.find(spell.english,'Cur') and spell.english ~='Cursna' then + equip(sets.precast.Cure) + elseif spell.english == 'Impact' then + equip(sets.precast.FastCast['Elemental Magic'],sets.Impact) + if not buffactive['Elemental Seal'] then + add_to_chat(8,'--------- Elemental Seal is down ---------') + end + elseif spell.action_type == 'Magic' then + if spell.skill == 'Elemental Magic' then + equip(sets.precast.FastCast['Elemental Magic']) + elseif spell.skill == 'Enhancing Magic' then + equip(sets.precast.FastCast['Enhancing Magic']) + else + equip(sets.precast.FastCast.Default) + end + end + + if spell.english == 'Stun' and stuntarg ~= 'Shantotto' then + send_command('@input /t '..stuntarg..' ---- Byrth Stunned!!! ---- ') + end + set_priorities('mp','hp') +end + +function midcast(spell) + equip_idle_set() + if buffactive.manawell or spell.mppaftercast > 50 then + mp_efficiency = 0 + else + mp_efficiency = 1 + end + + if spell.action_type == 'Magic' then + equip(sets.midcast.magic_base) + end + + if string.find(spell.english,'Cur') and spell.english ~='Cursna' then + weathercheck(spell.element,sets.midcast.Cure) + elseif spell.english == 'Impact' then + weathercheck(spell.element,set_combine(sets.midcast['Elemental Magic'][macc_level][mp_efficiency],sets.Impact)) + elseif spell.english == 'Death' then + equip(sets.midcast.Death) + elseif sets.midcast[spell.name] then + weathercheck(spell.element,sets.midcast[spell.name]) + elseif spell.skill == 'Elemental Magic' then + weathercheck(spell.element,sets.midcast['Elemental Magic'][macc_level][mp_efficiency]) + zodiaccheck(spell.element) + if sets.ElementalMagicMAB[spell.element] then + equip(sets.ElementalMagicMAB[spell.element]) + end + elseif spell.skill == "Enhancing Magic" and not S{'Warp','Warp II','Retrace','Teleport-Holla','Teleport-Mea','Teleport-Dem','Teleport-Altep','Teleport-Vahzl','Teleport-Yhoat'}:contains(spell.english) then + equip(sets.midcast.EnhancingDuration) + elseif spell.skill then + equip(sets.aftercast.Idle,sets.aftercast[tp_level]) + weathercheck(spell.element,sets.midcast[spell.skill]) + end + + if spell.english == 'Sneak' and spell.target.name == player.name then + send_command('cancel 71;') + end + set_priorities('mp','hp') +end + +function aftercast(spell) + if player.status == 'Idle' then + equip_idle_set() + elseif sets.aftercast[player.status] then + equip(sets.aftercast[player.status],sets.aftercast[tp_level]) + else + equip(sets.aftercast.Idle,sets.aftercast[tp_level]) + end + if not spell.interrupted then + if spell.english == 'Sleep' or spell.english == 'Sleepga' then + send_command('@wait 55;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + elseif spell.english == 'Sleep II' or spell.english == 'Sleepga II' then + send_command('@wait 85;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + elseif spell.english == 'Break' or spell.english == 'Breakga' then + send_command('@wait 25;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + end + end + set_priorities('mp','hp') +end + +function status_change(new,old) + if new == 'Resting' then + equip(sets.aftercast.Resting) + elseif new == 'Engaged' then + if not midaction() then + equip(sets.aftercast.Engaged,sets.aftercast[tp_level]) + end + disable('main','sub') + else + equip_idle_set() + equip(sets.aftercast[tp_level]) + end + set_priorities('mp','hp') +end + +function buff_change(name,gol,tab) + if name == 'Mana Wall' and gol and not midaction() then + equip(sets.aftercast.Idle[sets.aftercast.Idle.ind],sets.aftercast.Idle['Mana Wall']) + end + set_priorities('mp','hp') +end + +function self_command(command) + if command:lower() == 'stuntarg' then + stuntarg = player.target.name + elseif command:lower() == 'macc' then + macc_level = (macc_level+1)%2 + equip(sets.midcast['Elemental Magic'][macc_level][mp_efficiency]) + if macc_level == 1 then windower.add_to_chat(8,'MMMMMMAcctivated!') + else windower.add_to_chat(8,'MDamaged') end + elseif command:lower() == 'idle' then + sets.aftercast.Idle.ind = (sets.aftercast.Idle.ind+1)%2 + windower.add_to_chat(8,'------------------------ '..sets.aftercast.Idle.keys[sets.aftercast.Idle.ind]..' Set is now the default Idle set -----------------------') + end + set_priorities('mp','hp') +end + +-- This function is user defined, but never called by GearSwap itself. It's just a user function that's only called from user functions. I wanted to check the weather and equip a weather-based set for some spells, so it made sense to make a function for it instead of replicating the conditional in multiple places. + +function weathercheck(spell_element,set) + if not set then return end + if spell_element == world.weather_element or spell_element == world.day_element then + equip(set,sets.Obis[spell_element]) + else + equip(set) + end + if set[spell_element] then equip(set[spell_element]) end +end + +function zodiaccheck(spell_element) + if spell_element == world.day_element and spell_element ~= 'Dark' and spell_element ~= 'Light' then + equip(sets.Zodiac) + end +end + +function equip_idle_set() + if buffactive['Mana Wall'] then + equip(sets.aftercast.Idle[sets.aftercast.Idle.ind],sets.aftercast.Idle['Mana Wall']) + else + equip(sets.aftercast.Idle[sets.aftercast.Idle.ind]) + end + if player.tp == 3000 then equip(sets.aftercast.Chry) end + set_priorities('mp','hp') +end + +function set_priorities(key1,key2) + local future,current = gearswap.equip_list,gearswap.equip_list_history + function get_val(piece,key) + if piece and type(piece)=='table' and piece[key] and type(piece[key])=='number' then + return piece[key] + end + return 0 + end + local diff = {} + for i,v in pairs(future) do + local priority = get_val(future[i],key1) - get_val(current[i],key1) + (get_val(future[i],key2) - get_val(current[i],key2)) + if type(v) == 'table' then + future[i].priority = priority + else + future[i] = {name=v,priority=priority} + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BRD.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BRD.lua new file mode 100644 index 0000000..6c5812f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_BRD.lua @@ -0,0 +1,384 @@ +include('organizer-lib') + +function get_sets() + sets.precast = {} + sets.precast.JA = {} + + sets.weapons = {sub="Twashtar"} + -- Precast Sets + sets.precast.JA.Nightingale = {feet="Bihu Slippers +1"} + + sets.precast.JA.Troubadour = {body="Bihu Justaucorps +1"} + + sets.precast.JA['Soul Voice'] = {legs="Bihu Cannions +1"} + + sets.precast.FC = {} + + sets.precast.FC.Song = { + main="Sangoma", + sub="Genmei Shield", + range={ name="Linos", augments={'Accuracy+11','Occ. quickens spellcasting +3%',}}, + ammo=empty, + head="Fili Calot +1", + body="Inyanga Jubbah +1", + hands={ name="Gende. Gages +1", augments={'Phys. dmg. taken -3%','Song spellcasting time -5%',}}, + legs="Querkening Brais", + feet={ name="Telchine Pigaches", augments={'Song spellcasting time -7%',}}, + neck="Orunmila's Torque", + waist="Flume Belt +1", + left_ear="Loquac. Earring", + right_ear="Enchntr. Earring +1", + left_ring="Defending Ring", + right_ring="Weather. Ring +1", + back="Perimede Cape", -- 80% FC, 10% quickens + } + sets.precast['Honor March'] = {range="Marsyas",ammo=empty} + + sets.precast.FC.Normal = { + main="Sangoma", + sub="Genmei Shield", + range={ name="Linos", augments={'Accuracy+11','Occ. quickens spellcasting +3%',}}, + ammo=empty, + head="Nahtirah Hat", + neck="Orunmila's Torque", + left_ear="Loquac. Earring", + right_ear="Enchntr. Earring +1", + body="Inyanga Jubbah +1", + hands="Gendewitha Gages +1", + ring1="Kishar Ring", + ring2="Weather. Ring +1", + back={ name="Intarabus's Cape", augments={'CHR+20','Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',}}, + waist="Witful Belt", + legs="Lengo Pants", + feet="Chelona Boots +1"} -- 71% FC, 10% Quickens + + sets.precast.Cure = { + main="Felibre's Dague", + sub="Genbu's Shield", + body="Heka's Kalasiris", + legs="Doyen Pants", + back="Pahtli Cape" + } + + sets.precast.EnhancingMagic = {waist="Siegel Sash"} + + sets.precast.WS = {} + sets.precast.WS['Mordant Rime'] = { + range={ name="Linos", augments={'Accuracy+15','"Dbl.Atk."+3','Quadruple Attack +3',}}, + head="Brioso Roundlet +2", + body={ name="Bihu Jstcorps +1", augments={'Enhances "Troubadour" effect',}}, + hands={ name="Leyline Gloves", augments={'Accuracy+15','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Fast Cast"+3',}}, + legs="Jokushu Haidate", + feet="Aya. Gambieras +1", + neck="Fotia Gorget", + waist="Grunfeld Rope", + left_ear="Ishvara Earring", + right_ear="Mache Earring +1", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back={ name="Intarabus's Cape", augments={'DEX+20','Accuracy+20 Attack+20','"Dual Wield"+10',}}, + } + --[[sets.precast.WS['Mordant Rime'] = { + range="Gjallarhorn", + ammo=empty, + head="Brioso Roundlet +2", + body={ name="Bihu Jstcorps +1", augments={'Enhances "Troubadour" effect',}}, + hands="Brioso Cuffs +2", + legs={ name="Bihu Cannions +1", augments={'Enhances "Soul Voice" effect',}}, + feet="Brioso Slippers +2", + neck="Fotia Gorget", + waist="Windbuffet Belt +1", + left_ear="Ishvara Earring", + right_ear="Mache Earring +1", + left_ring="Carb. Ring +1", + right_ring="Carb. Ring +1", + back={ name="Intarabus's Cape", augments={'CHR+20','Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',}}, + }]] + + sets.precast.WS['Evisceration'] = {range={ name="Linos", augments={'Accuracy+15','"Dbl.Atk."+3','Quadruple Attack +3',}},ammo=empty, + head="Lustratio Cap +1",neck="Fotia Gorget",ear1="Mache earring +1",ear2="Kuwunga Earring", + body="Bihu Justaucorps +1", + hands="Leyline Gloves", + ring1="Ramuh Ring +1", + ring2="Begrudging Ring", + back="Rancorous Mantle", + waist="Fotia Belt", + legs="Lustratio Subligar +1", + feet="Aya. Gambieras +1",} + + sets.precast.WS["Rudra's Storm"] = {range={ name="Linos", augments={'Accuracy+15','"Dbl.Atk."+3','Quadruple Attack +3',}},ammo=empty, + head="Lustratio Cap +1",neck="Caro Necklace",ear1="Moonshade Earring",ear2="Mache earring +1", + body="Bihu Justaucorps +1",hands="Leyline Gloves",ring1="Ramuh Ring +1",ring2="Ramuh Ring +1", + back="Letalis Mantle",waist="Grunfeld Rope",legs="Lustratio Subligar +1",feet="Lustratio Leggings +1"} + + sets.precast.WS['Aeolian Edge'] = { + head="Welkin Crown", + body={ name="Bihu Jstcorps +1", augments={'Enhances "Troubadour" effect',}}, + hands={ name="Chironic Gloves", augments={'Mag. Acc.+24 "Mag.Atk.Bns."+24','Enmity-3','INT+3','Mag. Acc.+10','"Mag.Atk.Bns."+10',}}, + legs="Gyve Trousers", + feet={ name="Lustra. Leggings +1", augments={'HP+65','STR+15','DEX+15',}}, + neck="Baetyl Pendant", + waist="Eschan Stone", + left_ear="Friomisi Earring", + right_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + left_ring="Shiva Ring +1", + right_ring="Shiva Ring +1", + } + + -- Midcast Sets + sets.midcast = {} + + sets.midcast.Haste = {main="Mafic Cudgel",sub="Genmei Shield", + head={name="Nahtirah Hat",priority=11},neck="Orunmila's Torque",ear1="Loquac. Earring",ear2={name="Gifted Earring",priority=10}, + body={name="Zendik Robe",priority=12},hands={name="Gendewitha Gages +1",priority=6},ring2={name="Kishar Ring",priority=7}, + back={name="Pahtli Cape",priority=9},waist="Ninurta's Sash",legs="Bihu Cannions +1",feet={name="Chelona Boots +1",priority=8}} + + sets.midcast.Debuff = { + main="Carnwenhan", + sub="Ammurapi Shield", + range="Gjallarhorn", + ammo=empty, + head="Brioso Roundlet +2", + body={ name="Chironic Doublet", augments={'Mag. Acc.+20 "Mag.Atk.Bns."+20','"Resist Silence"+5','CHR+7','Mag. Acc.+15',}}, + hands="Inyan. Dastanas +1", + legs="Inyanga Shalwar +1", + feet="Brioso Slippers +2", + neck="Canto Necklace +1", + waist="Luminary Sash", + left_ear="Dignitary's Earring", + right_ear="Enchntr. Earring +1", + left_ring="Carb. Ring +1", + right_ring="Carb. Ring +1", + back={ name="Intarabus's Cape", augments={'CHR+20','Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',}}, + } + + sets.midcast.Duration = { + main="Carnwenhan", + body="Fili Hongreline +1", + legs="Inyanga Shalwar +1", + feet="Brioso Slippers +2", + neck="Moonbow Whistle", + } + + sets.midcast.Buff = { + main="Carnwenhan", + sub="Genmei Shield", + head="Fili Calot +1", + neck="Moonbow Whistle", + body="Fili Hongreline +1", + hands="Fili Manchettes +1", + legs="Inyanga Shalwar +1", + feet="Brioso Slippers +2", + } + + sets.midcast.DBuff = {range="Daurdabla",ammo=empty} + + sets.midcast.GBuff = {range="Gjallarhorn",ammo=empty} + + + sets.midcast.Ballad = {legs="Fili Rhingrave +1"} + + sets.midcast.Madrigal = {back={ name="Intarabus's Cape", augments={'CHR+20','Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',}},} + + sets.midcast.Prelude = {back={ name="Intarabus's Cape", augments={'CHR+20','Mag. Acc+20 /Mag. Dmg.+20','"Fast Cast"+10',}},} + + sets.midcast.Scherzo = {feet="Fili Cothurnes +1"} + + sets.midcast.Paeon = {head="Brioso Roundlet +2"} + + sets.midcast.March = {hands="Fili Manchettes +1",} + + sets.midcast.Lullaby = {range="Daurdabla",hands="Brioso Cuffs +2"} + + sets.midcast['Honor March'] = { + range="Marsyas", + ammo=empty, + hands="Fili Manchettes +1", + } + + + sets.midcast.Waltz = {} + + sets.midcast.Cure = {main="Chatoyant Staff",head="Marduk's Tiara +1",neck="Phalaina Locket",ear2="Novia earring", + body="Heka's Kalasiris",hands="Revealer's Mitts +1",legs="Bihu Cannions +1",feet="Bihu Slippers +1"} + + sets.midcast.Stoneskin = {head="Marduk's Tiara +1",neck="Nodens Gorget",body="Inyanga Jubbah +1", + legs="Shedir Seraweels",feet="Bihu Slippers +1"} + + sets.midcast.Cursna={ + head={ name="Vanya Hood", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',}}, + body="Vanya Robe", + hands="Hieros Mittens", + legs={ name="Vanya Slops", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',}}, + left_ring="Haoma's Ring", + right_ring="Haoma's Ring", + back="Oretan. Cape +1", + feet="Vanya Clogs", + neck="Debilis Medallion", + } + + + --Aftercast Sets + sets.aftercast = {} + sets.aftercast.Regen = {main={name="Sangoma",priority=15},sub={name="Genmei Shield",priority=16},range={name="Oneiros Harp",priority=14},ammo={name=empty,priority=13}, + head="Bihu Roundlet +1",neck="Loricate Torque +1",ear1={name="Loquac. Earring",priority=7},ear2={name="Gifted Earring",priority=5}, + body="Ischemia Chasuble",hands={name="Umuthi Gloves",priority=9},ring1="Defending Ring",ring2={name="Dark Ring",priority=8}, + back="Umbra Cape",waist="Flume Belt +1",legs={name="Lengo Pants",priority=6},feet="Fili Cothurnes +1"} + + sets.aftercast.PDT = { + main="Sangoma", + sub="Genmei Shield", + range="Oneiros Harp", + ammo=empty, + head="Lithelimb Cap", + body="Emet Harness +1", + hands="Umuthi Gloves", + legs="Jokushu Haidate", + feet="Fili Cothurnes +1", + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Loquac. Earring", + right_ear="Gifted Earring", + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + back="Solemnity Cape", + } + + sets.aftercast.Engaged = { + range={ name="Linos", augments={'Accuracy+15','"Dbl.Atk."+3','Quadruple Attack +3',}}, + ammo=empty, + head="Aya. Zucchetto +1", + body="Ayanmo Corazza +1", + hands={ name="Leyline Gloves", augments={'Accuracy+15','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Fast Cast"+3',}}, + legs="Jokushu Haidate", + feet="Aya. Gambieras +1", + neck="Lissome Necklace", + waist="Windbuffet Belt +1", + left_ear="Telos Earring", + right_ear="Mache Earring +1", + left_ring="Ramuh Ring +1", + right_ring="Rajas Ring", + back={ name="Intarabus's Cape", augments={'DEX+20','Accuracy+20 Attack+20','"Dual Wield"+10',}}, + } + + sets.aftercast._tab = {'Regen','PDT'} + + sets.aftercast._index = 1 + + sets.aftercast.Idle = sets.aftercast[sets.aftercast._tab[sets.aftercast._index]] + + sets.midcast.Base = sets.aftercast.PDT -- sets.midcast.Haste + + DaurdSongs = T{'Water Carol','Water Carol II','Herb Pastoral','Goblin Gavotte'} + + send_command('input /macro book 3;wait .1;input /macro set 1') + timer_reg = {} + pianissimo_cycle = false +end + +function pretarget(spell) + if spell.type == 'BardSong' and spell.target.type and spell.target.type == 'PLAYER' and not buffactive.pianissimo and not spell.target.charmed and not pianissimo_cycle then + cancel_spell() + pianissimo_cycle = true + send_command('input /ja "Pianissimo" <me>;wait 1.5;input /ma "'..spell.name..'" '..spell.target.name..';') + return + end + if spell.name ~= 'Pianissimo' then + pianissimo_cycle = false + end +end + +function precast(spell) + if spell.type == 'BardSong' then + equip_song_gear(spell) + equip(sets.precast.FC.Song) + if spell.english == 'Honor March' then + equip(sets.precast['Honor March']) + end + elseif spell.action_type == 'Magic' then + equip(sets.precast.FC.Normal) + if string.find(spell.english,'Cur') and spell.name ~= 'Cursna' then + equip(sets.precast.Cure) + end + if spell.skill == 'Enhancing Magic' then + equip(sets.precast.EnhancingMagic) + end + elseif spell.prefix == '/weaponskill' and sets.precast.WS[spell.name] then + equip(sets.precast.WS[spell.name]) + end + + --if player.status == 'Engaged' then equip({range=nil}) end -- Why? +end + +function midcast(spell) + if spell.type == 'BardSong' then + equip_song_gear(spell) + elseif string.find(spell.english,'Waltz') and spell.english ~= 'Healing Waltz' then + equip(sets.midcast.Base,sets.midcast.Waltz) + elseif sets.midcast[spell.english] then + equip(sets.midcast.Base,sets.midcast[spell.english]) + elseif string.find(spell.english,'Cur') then + equip(sets.midcast.Base,sets.midcast.Cure) + elseif spell.prefix == '/weaponskill' and sets.precast.WS[spell.name] then + equip(sets.precast.WS[spell.name]) + else + equip(sets.midcast.Base) + end + + if sets.precast.JA[spell.english] then equip(sets.precast.JA[spell.english]) end +end + +function aftercast(spell) + if midaction() then return end + + if player.status == 'Engaged' then + equip(sets.aftercast.Engaged) + else + equip(sets.aftercast.Idle) + end +end + +function status_change(new,old) + if new == 'Engaged' then + equip(sets.aftercast.Engaged) + --disable('main','sub','ammo') + elseif T{'Idle','Resting'}:contains(new) then + equip(sets.aftercast.Idle) + end +end + +function self_command(cmd) + if cmd == 'unlock' then + enable('main','sub','ammo') + elseif cmd == 'midact' then + midaction(false) + elseif cmd == 'idle' then + sets.aftercast._index = sets.aftercast._index%(#sets.aftercast._tab) + 1 + windower.add_to_chat(8,'Aftercast Set: '..sets.aftercast._tab[sets.aftercast._index]) + sets.aftercast.Idle = sets.aftercast[sets.aftercast._tab[sets.aftercast._index]] + equip(sets.aftercast.Idle) + end +end + +function equip_song_gear(spell) + if DaurdSongs:contains(spell.english) then + equip(sets.midcast.Base,sets.midcast.DBuff) + else + if spell.target.type == 'MONSTER' then + equip(sets.midcast.Base,sets.midcast.Debuff,sets.midcast.GBuff) + if buffactive.troubadour or buffactive['elemental seal'] then + equip(sets.midcast.Duration,{range="Marsyas",ammo="empty"}) + end + if string.find(spell.english,'Lullaby') then equip(sets.midcast.Duration,sets.midcast.Lullaby) end + else + equip(sets.midcast.Base,sets.midcast.Buff,sets.midcast.GBuff) + if spell.english == 'Honor March' then equip(sets.midcast['Honor March']) + elseif string.find(spell.english,'Ballad') then equip(sets.midcast.Ballad) + elseif string.find(spell.english,'Scherzo') then equip(sets.midcast.Scherzo) + elseif string.find(spell.english,'Paeon') then equip(sets.midcast.Paeon) + elseif string.find(spell.english,'Prelude') then equip(sets.midcast.Prelude) + elseif string.find(spell.english,'Madrigal') then equip(sets.midcast.Madrigal) + end + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_DNC.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_DNC.lua new file mode 100644 index 0000000..787bf26 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_DNC.lua @@ -0,0 +1,729 @@ +include('organizer-lib') + +function get_sets() + + ta_hands = {name="Adhemar Wristbands +1"} + acc_hands = {name="Adhemar Wristbands +1"} + wsd_hands = {name="Maxixi Bangles +3",} + crit_hands = {name="Adhemar Wristbands +1"} + dt_hands = { name="Herculean Gloves", augments={'Accuracy+30','Damage taken-4%','STR+9','Attack+4',}} + waltz_hands = { name="Herculean Gloves", augments={'"Waltz" potency +10%','CHR+9','Attack+4',}} + + sets.subs = {sub="Airy Buckler"} + + ------------------- JA Sets ---------------------- + sets.JA = {} + sets.JA.Trance = {head="Horos Tiara +1"} + sets.JA.Precast_Waltz = {legs="Desultor Tassets"} + + waltz_mode = 0 + sets.JA.Waltz = {} + sets.JA.Waltz[0] = { + ammo="Yamarang", + head={ name="Anwig Salade", augments={'CHR+4','"Waltz" ability delay -2','CHR+2','"Fast Cast"+2',}}, + body="Maxixi Casaque +2", + hands=waltz_hands, + legs={ name="Desultor Tassets", augments={'"Waltz" TP cost -5',}}, + feet="Maxixi Toeshoes +2", + neck="Unmoving Collar +1", + waist="Aristo Belt", + left_ear="Eabani Earring", + right_ear="Roundel Earring", + left_ring="Carb. Ring +1", + right_ring="Carb. Ring +1", + back={ name="Senuna's Mantle", augments={'"Waltz" potency +10%',}}, + } + + sets.JA.Waltz[1] = { + ammo="Yamarang", + head="Maxixi Tiara +2", + neck="Unmoving Collar +1", + lear="Enchanter Earring +1", + rear="Roundel Earring", + body="Maxixi Casaque +2", + hands=waltz_hands, + lring="Carbuncle Ring +1", + rring="Carbuncle Ring +1", + back={ name="Senuna's Mantle", augments={'"Waltz" potency +10%',}}, + waist="Aristo Belt", + legs="Desultor Tassets", + feet="Maxixi toeshoes +2" + } + + --[[sets.JA.Waltz[2] = { + ammo="Yamarang", + head="Anwig Salade", + neck="Unmoving Collar +1", + lear="Enchanter Earring +1", + rear="Handler's Earring +1", + body="Maxixi Casaque +3", + hands=waltz_hands, + lring="Carbuncle Ring +1", + rring="Carbuncle Ring +1", + back="Senuna Mantle", (Waltz +10%, CHR+30) + waist="Aristo Belt", + legs="Desultor Tassets", + feet="Maxixi toeshoes +3" + }]] + + sets.JA.Samba = {head="Maxixi Tiara +2", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}},} + + sets.JA.Jig = {legs='Horos Tights +1',feet="Maxixi toeshoes +2"} + + sets.JA.Step = { + ammo="Yamarang", + head="Maxixi Tiara +2", + body="Adhemar Jacket +1", + hands="Maxixi Bangles +3", + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Combatant's torque", + waist="Olseni Belt", + left_ear="Mache earring +1", + right_ear="Telos Earring", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}}, + } + + sets.JA['Feather Step'] = set_combine(sets.JA.Step,{feet="Maculele Toeshoes +1"}) + + sets.JA['No Foot Rise'] = {body="Horos Casaque +1"} + + sets.JA['Climactic Flourish'] = {head="Maculele Tiara +1"} + + sets.JA['Striking Flourish'] = {body="Maculele casaque +1"} + + sets.JA['Reverse Flourish'] = {hands="Maculele bangles +1",back={ name="Toetapper Mantle", augments={'"Store TP"+1','"Dual Wield"+5','"Rev. Flourish"+30',}}} + + sets.JA['Violent Flourish'] = { + ammo="Hydrocera", + head="Dampening Tam", + body={ name="Horos Casaque +1", augments={'Enhances "No Foot Rise" effect',}}, + hands="Leyline Gloves", + legs={ name="Herculean Trousers", augments={'Mag. Acc.+16','"Fast Cast"+6','MND+2',}}, + feet={ name="Herculean Boots", augments={'Mag. Acc.+16','"Fast Cast"+6','MND+4',}}, + neck="Sanctity Necklace", + waist="Eschan Stone", + left_ear="Enchanter Earring +1", + right_ear="Dignitary's Earring", + left_ring="Weather. Ring +1", + right_ring="Sangoma Ring", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}}, + } + + sets.Enmity = { + ammo="Iron gobbet", + head="Halitus Helm", + neck="Unmoving Collar +1", + lear="Trux Earring", + rear="Pluto's Pearl", -- Cryptic Earring from Rancibus is better + body="Emet Harness +1", + hands="Kurys Gloves", + lring="Provocare Ring", + rring="Eihwaz Ring", + back="Reiki Cloak", -- Augmented Senuna's is better + waist="Kasiri Belt", -- Trance Belt is better + legs="Zoar Subligar +1", + feet="Ahosi Leggings", + } + + sets.JA['Animated Flourish'] = sets.Enmity + sets.JA.Provoke = sets.Enmity + sets.JA.Warcry = sets.Enmity + + + ------------------ Idle Sets --------------------- + sets.Idle = {} + sets.Idle.index = {'Normal','MDT'} + Idle_ind = 1 + sets.Idle.Normal = {main="Terpsichore",sub="Twashtar",ammo="Tengu-no-Hane", + head="Meghanada Visor +1",neck="Ej Necklace +1",lear="Eabani Earring",rear="Infused Earring", + body="Emet Harness +1",hands=dt_hands,lring="Sheltered Ring",rring="Vengeful Ring", + back={ name="Senuna's Mantle", augments={'AGI+20','Eva.+20 /Mag. Eva.+20','Haste+10',}},waist="Kasiri Belt",legs="Maculele Tights +1",feet="Skadi's Jambeaux +1"} + + sets.Idle.MDT={head="Dampening Tam",neck="Loricate Torque +1",lear="Etiolation Earring", + body="Emet Harness +1",hands=dt_hands,lring="Dark Ring",rring="Defending Ring", + back="Mollusca Mantle",waist="Wanion Belt",legs="Maculele Tights +1",feet="Maxixi toeshoes +2"} + + ------------------- TP Sets ---------------------- + sets.TP={} + sets.TP.index = {'Acc0','Acc1','Acc2','Eva'} + TP_ind = 1 + sets.DT_on = false + + -- Works for Haste 2 + Haste Samba + sets.TP.Acc0 = { + main="Terpsichore", + sub="Twashtar", + ammo="Charis Feather", + head="Adhemar Bonnet +1", + body="Adhemar Jacket +1", + hands=ta_hands, + legs={ name="Samnuha Tights", augments={'STR+8','DEX+9','"Dbl.Atk."+3','"Triple Atk."+2',}}, + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Anu Torque", + waist="Windbuffet Belt +1", + left_ear="Sherida Earring", + right_ear="Telos Earring", + left_ring="Epona's Ring", + right_ring="Rajas Ring", + back={ name="Senuna's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + } + + sets.TP.Acc1 = { + main="Terpsichore", + sub="Twashtar", + ammo="Yamarang", + head="Mummu Bonnet +1", + body={ name="Adhemar Jacket +1", augments={'STR+12','DEX+12','Attack+20',}}, + hands={ name="Adhemar Wrist. +1", augments={'STR+12','DEX+12','Attack+20',}}, + legs="Mummu Kecks +1", + feet="Ahosi Leggings", + neck="Combatant's Torque", + waist="Windbuffet Belt +1", + left_ear="Sherida Earring", + right_ear="Telos Earring", + left_ring="Epona's Ring", + right_ring="Rajas Ring", + back={ name="Senuna's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','"Store TP"+10',}}, + } + + --[[sets.TP.Acc1 = { + main="Terpsichore", + sub="Twashtar", + ammo="Yamarang", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Adhemar Jacket +1", + hands=ta_hands, + legs={ name="Samnuha Tights", augments={'STR+8','DEX+9','"Dbl.Atk."+3','"Triple Atk."+2',}}, + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Combatant's Torque", + waist="Grunfeld Rope", + left_ear="Sherida Earring", + right_ear="Telos Earring", + left_ring="Epona's Ring", + right_ring="Rajas Ring", + back={ name="Senuna's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + }]] + + sets.TP.Acc2 = { + main="Terpsichore", + sub="Twashtar", + ammo="Yamarang", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Adhemar Jacket +1", + hands=ta_hands, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Combatant's Torque", + waist="Olseni Belt", + left_ear="Mache earring +1", + right_ear="Telos Earring", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}}, + } + + sets.TP.DT = { + main="Terpsichore", + sub="Twashtar", + ammo="Yamarang", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Emet Harness +1", + hands=dt_hands, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+20 Attack+20','Phys. dmg. taken -5%','DEX+10','Accuracy+6',}}, + neck="Loricate Torque +1", + waist="Windbuffet belt +1", + right_ear="Suppanomimi", + left_ear="Eabani Earring", + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + right_ring="Defending Ring", + back="Mollusca Mantle", + } + + sets.TP.Eva = { + main="Terpsichore", + sub="Twashtar", + ammo="Yamarang", + head="Maxixi Tiara +2", + body="Maxixi Casaque +2", + hands="Maxixi Bangles +3", + legs="Maculele Tights +1", + feet="Maxixi Toeshoes +2", + neck="Ej Necklace +1", + waist="Svelt. Gouriz +1", + left_ear="Eabani Earring", + right_ear="Infused Earring", + left_ring="Beeline Ring", + right_ring="Vengeful Ring", + back={ name="Senuna's Mantle", augments={'AGI+20','Eva.+20 /Mag. Eva.+20','Haste+10',}}, + } + + sets.TP.CHR = { + main="Terpsichore", + sub="Twashtar", + head="Dampening Tam", + } + + ------------------- WS Sets ---------------------- + sets.WS={} + + -- Moonshade option sets the left ear to be Moonshade if TP < 2800 + -- Madrigal option sets the right ear to be Kuwaliaoi Attack+17/STR/DEX+2 if you have a madrigal on + + sets.WS.Exenterator = {Moonshade=false,Madrigal=false,Tengu=true} + + sets.WS.Exenterator[0] = { + ammo="Floestone", + head="Meghanada Visor +1", + body={ name="Adhemar Jacket +1", augments={'STR+12','DEX+12','Attack+20',}}, + hands="Maxixi Bangles +3", + legs="Meg. Chausses +1", + feet="Meg. Jam. +1", + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Sherida Earring", + right_ear="Infused Earring", + left_ring="Epona's Ring", + right_ring="Ilabrat Ring", + back={ name="Senuna's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','"Dbl.Atk."+10',}}, + } + + sets.WS['Shark Bite'] = {Moonshade=false,Madrigal=true,Tengu=true} + + sets.WS['Shark Bite'][0] = { + ammo="Floestone", + head="Meghanada Visor +1", + body="Adhemar Jacket +1", + hands=wsd_hands, + legs="Mummu Kecks +1", + feet="Adhemar Gamashes +1", + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Mache earring +1", + right_ear="Ishvara Earring", + left_ring="Ramuh Ring +1", + right_ring="Ilabrat Ring", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','DEX+10','Weapon skill damage +10%',}}, + } + + sets.WS.Evisceration = {Moonshade=false,Madrigal=true} + + sets.WS.Evisceration[0] = { + ammo="Charis Feather", + head="Adhemar Bonnet +1", + body="Abnoba Kaftan", + hands=crit_hands, + legs="Lustratio Subligar +1", + feet={ name="Herculean Boots", augments={'Accuracy+29','Crit. hit damage +5%','DEX+7','Attack+13',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Sherida Earring", + right_ear="Mache earring +1", + left_ring="Begrudging Ring", + right_ring="Ilabrat Ring", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}}, + } + + sets.WS.Evisceration[1] = { + ammo="Falcon Eye", + head="Dampening Tam", + body="Adhemar Jacket +1", + hands=crit_hands, + legs="Lustratio Subligar +1", + feet={ name="Herculean Boots", augments={'Accuracy+29','Crit. hit damage +5%','DEX+7','Attack+13',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Sherida Earring", + right_ear="Mache earring +1", + left_ring="Begrudging Ring", + right_ring="Ramuh Ring +1", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','Crit.hit rate+10',}}, + } + + sets.WS['Pyrrhic Kleos'] = {Moonshade=false,Madrigal=true,Tengu=true} + + sets.WS['Pyrrhic Kleos'][0] ={ + ammo="Floestone", + head="Lustratio Cap +1", + body="Adhemar Jacket +1", + hands=ta_hands, + legs={ name="Samnuha Tights", augments={'STR+8','DEX+9','"Dbl.Atk."+3','"Triple Atk."+2',}}, + feet="Lustratio Leggings +1", + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Sherida Earring", + right_ear="Brutal Earring", + left_ring="Apate Ring", + right_ring="Rajas Ring", + back={ name="Senuna's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','"Dbl.Atk."+10',}}, + } + sets.WS['Pyrrhic Kleos'][1] ={ + ammo="Falcon Eye", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Adhemar Jacket +1", + hands=acc_hands, + legs="Lustratio Subligar +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Mache earring +1", + right_ear="Telos Earring", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','DEX+10','Weapon skill damage +10%',}}, + } + + sets.WS['Dancing Edge'] = {Moonshade=false,Madrigal=true,Tengu=true} + + sets.WS['Dancing Edge'][0] = { + ammo="Floestone", + head="Adhemar Bonnet +1", + body="Adhemar Jacket +1", + hands=ta_hands, + legs="Mummu Kecks +1", + feet="Adhemar Gamashes +1", + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Steelflash Earring", + right_ear="Bladeborn Earring", + left_ring="Ifrit Ring +1", + right_ring="Ilabrat Ring", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','DEX+10','Weapon skill damage +10%',}}, + } + + sets.WS['Aeolian Edge'] = {Moonshade=true,Madrigal=nil} + + sets.WS['Aeolian Edge'][0] = { + ammo="Pemphredo Tathlum", + head="Highwing Helm", + body={ name="Samnuha Coat", augments={'Mag. Acc.+15','"Mag.Atk.Bns."+15','"Fast Cast"+5','"Dual Wield"+5',}}, + hands=wsd_hands, + legs={ name="Horos Tights +1", augments={'Enhances "Saber Dance" effect',}}, + feet="Adhemar Gamashes +1", + neck="Sanctity Necklace", + waist="Wanion Belt", + left_ear="Crematio Earring", + right_ear="Friomisi Earring", + left_ring="Shiva Ring +1", + right_ring="Shiva Ring +1", + back="Toro Cape", + } + + sets.WS["Rudra's Storm"] = {Moonshade=true} + + sets.WS["Rudra's Storm"][0] = { + ammo="Charis Feather", + head="Lustratio Cap +1", + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + hands=wsd_hands, + legs="Lustratio Subligar +1", + feet="Lustratio Leggings +1", + neck="Caro Necklace", + waist="Grunfeld Rope", + left_ear="Mache earring +1", + right_ear="Ishvara earring", + left_ring="Ramuh Ring +1", + right_ring="Ilabrat Ring", + back={ name="Senuna's Mantle", augments={'DEX+20','Accuracy+20 Attack+20','DEX+10','Weapon skill damage +10%',}}, + } + + sets.WS["Rudra's Storm"][1] = set_combine(sets.WS["Rudra's Storm"][0],{ + ammo="Falcon Eye", + head="Dampening Tam", + neck="Fotia gorget", + right_ring="Ramuh Ring +1", + }) + + sets.WS.Madrigal = {left_ear="Kuwunga Earring"} + sets.WS.Moonshade = {right_ear="Moonshade Earring"} + sets.WS.Sherida = {left_ear="Sherida Earring"} -- Will cause conflict with the DT set, but this is still the easiest way to handle it at the moment. + + ------------------- MA Sets ---------------------- + sets.MA={} + + sets.MA.Utsusemi = {} + sets.MA.Utsusemi.Eva = { + ammo="Yamarang", + head="Maxixi Tiara +2", + neck="Ej Necklace +1", + ear1="Eabani Earring", + ear2="Infused Earring", + body="Maxixi Casaque +2", + hands="Maxixi Bangles +3", + lring="Vengeful Ring", + rring="Defending Ring", + back={ name="Senuna's Mantle", augments={'AGI+20','Eva.+20 /Mag. Eva.+20','Haste+10',}}, + waist="Svelt. Gouriz +1", + legs="Maculele Tights +1", + feet="Maxixi Toeshoes +2", + } + + sets.MA.Utsusemi.DT = sets.TP.DT + + sets.MA.FastCast = { + ammo="Impatiens", + head={ name="Herculean Helm", augments={'"Fast Cast"+6','Mag. Acc.+2',}}, + body={ name="Taeon Tabard", augments={'Accuracy+22','"Fast Cast"+5','Crit. hit damage +3%',}}, + hands="Leyline Gloves", + legs={ name="Herculean Trousers", augments={'Mag. Acc.+16','"Fast Cast"+6','MND+2',}}, + feet={ name="Herculean Boots", augments={'Mag. Acc.+16','"Fast Cast"+6','MND+4',}}, + Neck="Orunmila's Torque", + left_ear="Loquac. Earring", + right_ear="Enchntr. Earring +1", + left_ring="Rahab Ring", + right_ring="Weather. Ring +1", + back={ name="Senuna's Mantle", augments={'"Fast Cast"+10',}}, + Utsusemi = { + neck="Magoraga Beads", + body="Passion Jacket", + }, + } + + sets.tengu = {ammo="Tengu-No-Hane"} + sets.frenzy = {head="Frenzy Sallet"} + + send_command('input /macro book 9;wait .1;input /macro set 2') + dur_table = {} + utsu_index = 'DT' + send_command('lua l daze') +end + +function file_unload() + send_command('lua u daze') +end + +function precast(spell) + if spell.action_type == 'Magic' then + equip(sets.MA.FastCast) + if string.find(spell.name,'Utsusemi') then + equip(sets.MA.FastCast.Utsusemi) + end + elseif spell.type == 'Waltz' then + if buffactive['saber dance'] then + windower.ffxi.cancel_buff(410) + end + equip(sets.JA.Precast_Waltz) + elseif spell.type == 'Samba' and buffactive['fan dance'] then + windower.ffxi.cancel_buff(411) + elseif spell.name == 'Spectral Jig' and buffactive.sneak then + windower.ffxi.cancel_buff(71) + end +end + + +local function get_target_type(perspective,str) + local user = windower.ffxi.get_mob_by_name(perspective) + local mob = windower.ffxi.get_mob_by_id(tonumber(str) or -1) or windower.ffxi.get_mob_by_target(tostring(str)) or windower.ffxi.get_mob_by_name(tostring(str)) + if user and mob then + if mob.id == user.id then + return 'Self' + elseif mob.hpp == 0 then + return 'Corpse' + elseif mob.in_party and user.in_party then + return 'Party' + elseif mob.in_alliance and user.in_alliance then + return 'Ally' + elseif not mob.is_npc or mob.spawn_type==14 then + return 'Player' + elseif mob.is_npc then + return 'Enemy' + -- Not sure how to differentiate NPCs and enemies + else + return 'NPC' + end + end +end + +function filtered_action(spell) + cancel_spell() + noti.message('Requesting Sjugy use '..spell.name) + local targ_typ = get_target_type('Sjugy',spell.target.raw) + + if spell.targets[targ_typ] then + noti.command('Sjugy',spell.name..' '..spell.target.raw) + else + noti.command('Sjugy',spell.name) + end +end + +function midcast(spell) + if sets.JA[spell.name] then + equip(sets.JA[spell.name]) + if spell.name == "Feather Step" then + tengu_handler() + end + elseif sets.WS[spell.name] then + if sets.TP.index[TP_ind] == 'Acc2' and sets.WS[spell.name][1] then + equip(sets.WS[spell.name][1]) + elseif sets.WS[spell.name][0] then + equip(sets.WS[spell.name][0]) + end + if sets.WS[spell.name].Tengu then + tengu_handler() + end + if buffactive['Madrigal'] and sets.WS[spell.name].Madrigal == true then + equip(sets.WS.Madrigal) + end + if player.tp < 2800 and sets.WS[spell.name].Moonshade == true then + equip(sets.WS.Moonshade) + if spell.name == "Pyrrhic Kleos" or spell.name == "Dancing Edge" then -- Steelflash/Sherida combo + equip(sets.WS.Sherida) + end + end + elseif spell.type=='Jig' then + equip(sets.JA.Jig) + elseif spell.type=='Samba' then + equip(sets.JA.Samba) + elseif spell.type=='Waltz' then + equip(sets.JA.Waltz[waltz_mode]) + elseif spell.type=='Step' then + equip(sets.JA.Step) + tengu_handler() + elseif string.find(spell.name,'Utsusemi') then + equip(sets.MA.Utsusemi[utsu_index]) + elseif string.find(spell.name,'Monomi') then + send_command('@wait 1.7;cancel 71') + end + + if spell.type == 'WeaponSkill' then + if buffactive['climactic flourish'] then + equip(sets.JA['Climactic Flourish']) + elseif buffactive['striking flourish'] then + equip(sets.JA['Striking Flourish']) + end + end +end + +function tengu_handler() + if world.time >= 360 and world.time < 1080 then -- 6~18 + equip(sets.tengu) + end +end + +function aftercast(spell) + local dur,id + if not spell or not spell.name then windower.add_to_chat(8,'Not spell') return end + if spell.name:sub(1,11) == 'Chocobo Jig' and (not dur_table['Chocobo Jig'] or not dur_table['Chocobo Jig'] == 190) then + id = 176 + dur = 190 + elseif spell.name == 'Spectral Jig' and (not dur_table['Spectral Jig'] or not dur_table['Spectral Jig'] == 270) then + id = 69 + dur = 270 + send_command('st setduration 71 269;') + elseif spell.name:sub(7,11) == 'Samba' then + if spell.name:sub(1,11) == 'Drain Samba' then + id = 368 + if #spell.name == 11 and (not dur_table['Drain Samba'] or not dur_table['Drain Samba'] == 165) then + dur = 165 + dur_table['Drain Samba'] = 165 + elseif not dur['Drain Samba'] or not dur['Drain Samba'] == 135 then + dur = 135 + dur_table['Drain Samba'] = 135 + end + elseif spell.name:sub(1,11) == 'Aspir Samba' then + id = 369 + if #spell.name == 11 and (not dur_table['Aspir Samba'] or not dur_table['Aspir Samba'] == 165) then + dur = 165 + dur_table['Aspir Samba'] = 165 + elseif not dur['Aspir Samba'] or not dur['Aspir Samba'] == 135 then + dur = 135 + dur_table['Aspir Samba'] = 135 + end + elseif spell.name == 'Haste Samba' and (not dur_table['Haste Samba'] or not dur_table['Haste Samba'] == 135) then + id = 370 + dur = 135 + dur_table['Haste Samba'] = 135 + end + +-- if buffactive['saber dance'] then +-- dur = math.floor(dur*1.2) +-- end + elseif spell.name == 'Trance' and (not dur_table['Trance'] or not dur_table['Trance'] == 80) then + id = 376 + dur = 80 + dur_table['Trance'] = 165 +-- elseif spell.name == 'Grand Pas' then +-- id = 507 +-- dur = 60 +-- elseif spell.name == 'Presto' then +-- id = 442 +-- dur = 30 + end + if id then + send_command('st setduration '..id..' '..(dur-1)..';') + end + + equip_inactive_set(spell) +end + +function status_change(new,old) + equip_inactive_set(nil,new) +end + +function buff_change(buff,gain) + if not gain and not midaction() and (buff == 'Climactic Flourish' or buff=='Striking Flourish' or buff=='Sleep') then + equip_inactive_set() + elseif gain and buff == 'Sleep' and player.hp > 99 then + equip(sets.frenzy) + end +end + +function equip_inactive_set(spell,status) + status = status or player.status + if status == 'Engaged' then + equip(sets.TP[sets.TP.index[TP_ind]]) + tengu_handler() + if (buffactive['Climactic Flourish'] or spell and spell.english == 'Climactic Flourish') and sets.TP.index[TP_ind] ~= 'Acc' then + equip(sets.TP.CHR,sets.JA['Climactic Flourish']) + elseif (buffactive['Striking Flourish'] or spell and spell.english == 'Striking Flourish') and sets.TP.index[TP_ind] ~= 'Acc' then + equip(sets.TP.CHR,sets.JA['Striking Flourish']) + end + else + equip(sets.Idle[sets.Idle.index[Idle_ind]]) + end + if sets.DT_on then equip(sets.TP.DT) end +end + +function self_command(command) + if command == 'toggle TP set' then + TP_ind = TP_ind +1 + if TP_ind > #sets.TP.index then TP_ind = 1 end + windower.add_to_chat(8,'----- TP Set changed to '..sets.TP.index[TP_ind]..' -----') + equip(sets.TP[sets.TP.index[TP_ind]]) + elseif command == 'toggle TP set back' then + TP_ind = TP_ind -1 + if TP_ind < 1 then TP_ind = #sets.TP.index end + windower.add_to_chat(8,'----- TP Set changed to '..sets.TP.index[TP_ind]..' -----') + equip(sets.TP[sets.TP.index[TP_ind]]) + elseif command == 'DT' then + sets.DT_on = not sets.DT_on + if sets.DT_on then + equip(sets.TP.DT) + elseif not midaction() then + equip_inactive_set() + end + windower.add_to_chat(8,'----- DT mode is '..tostring(sets.DT_on)..' -----') + elseif command == 'toggle Idle set' then + Idle_ind = Idle_ind +1 + if Idle_ind > #sets.Idle.index then Idle_ind = 1 end + windower.add_to_chat(8,'----- Idle Set changed to '..sets.Idle.index[Idle_ind]..' -----') + equip(sets.Idle[sets.Idle.index[Idle_ind]]) + elseif command == 'equip TP set' then + equip_inactive_set() + elseif command == 'toggle Utsu set' then + if utsu_index == 'DT' then utsu_index = 'Eva' + else utsu_index = 'DT' end + windower.add_to_chat(8,'----- Utsu Set changed to '..utsu_index..' -----') + elseif command == 'waltz_mode' then + waltz_mode = (waltz_mode + 1)%2 + if waltz_mode == 1 then + windower.add_to_chat(8,'----- Waltz Efficiency Setting -----') + else + windower.add_to_chat(8,'----- Waltz Recast Setting (Default) -----') + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_GEO.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_GEO.lua new file mode 100644 index 0000000..6fa9750 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_GEO.lua @@ -0,0 +1,496 @@ +include('organizer-lib') + +function get_sets() + MP_efficiency = 0 + macc_level = 0 + + sets.TH = { + hands={ name="Merlinic Dastanas", augments={'"Treasure Hunter"+2',},hp=9,mp=20}, + legs={ name="Merlinic Shalwar", augments={'Pet: Accuracy+16 Pet: Rng. Acc.+16','Pet: Haste+1','"Treasure Hunter"+1','Mag. Acc.+9 "Mag.Atk.Bns."+9',},hp=29,mp=44}, + waist="Chaac Belt", + } + + sets.precast = {} + sets.Idris = {main="Idris"} + sets.Mafic = {main="Mafic Cudgel"} + + sets.precast.FastCast = {} + + sets.precast.FastCast.Default = { + main="Marin Staff +1", + sub="Niobid Strap", + range=empty, + ammo="Impatiens", + feet={ name="Merlinic Crackows", augments={'"Mag.Atk.Bns."+11','"Fast Cast"+7',}}, + body="Zendik Robe", + hands={ name="Merlinic Dastanas", augments={'Mag. Acc.+29','"Fast Cast"+7','INT+1',}}, + legs="Psycloth Lappas", + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',}}, + neck="Orunmila's Torque", + waist="Witful Belt", + left_ear="Enchntr. Earring +1", + right_ear="Loquac. Earring", + left_ring="Lebeche Ring", + right_ring="Weather. Ring +1", + back={ name="Lifestream Cape", augments={'Geomancy Skill +3','Indi. eff. dur. +20','Damage taken-3%',}}, + } + + sets.precast.FastCast['Elemental Magic'] = set_combine(sets.precast.FastCast.Default,{ + ear1="Barkarole Earring", + body={ name="Dalmatica +1", augments={'Occ. quickens spellcasting +3%','"Fast Cast"+6','Pet: "Mag.Def.Bns."+6',}}, + hands="Bagua mitaines +1", + lring="Kishar Ring", + }) + + sets.precast.FastCast['Healing Magic'] = set_combine(sets.precast.FastCast.Default,{main="Vadose Rod",sub="Genmei Shield",body="Heka's Kalasiris",back="Pahtli Cape"}) + +-- sets.precast.FastCast['Enhancing Magic'] = set_combine(sets.precast.FastCast.Default,{waist="Siegel Sash"}) + + sets.Impact = {head=empty,body="Twilight Cloak"} + + sets.midcast = {} + + sets.midcast.Stun = {main="Marin Staff +1",sub="Enki Strap",range=empty,ammo="Hasty Pinion +1", + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',}}, neck="Erra Pendant", ear1="Enchanter Earring +1",ear2="Loquacious Earring", + body="Geomancy Tunic +1",hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',}},lring="Sangoma Ring",rring="Angha Ring", + back={ name="Lifestream Cape", augments={'Geomancy Skill +3','Indi. eff. dur. +20','Damage taken-3%',}},waist="Ninurta's Sash",legs="Psycloth Lappas",feet="Amalric Nails +1"} + + + + sets.midcast['Elemental Magic'] = { + [0] = {}, + [1] = {} + } + + sets.ElementalMagicMAB = { + Ice={main="Ngqoqwanb",sub="Enki Strap"}, + Wind={main="Marin Staff +1",sub="Enki Strap"}, + Earth={neck="Quanpur Necklace"}, + Dark={head="Pixie Hairpin +1", rring="Archon Ring"} + } + + -- MAcc level 0 (Macc and Enmity irrelevant) + sets.midcast['Elemental Magic'][0][0] = { + main="Idris", + sub="Ammurapi Shield", + range=empty, + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',}}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',}}, + hands="Amalric Gages +1", + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',}}, + feet="Amalric Nails +1", + neck="Baetyl Pendant", + waist="Yamabuki-no-Obi", + left_ear="Barkaro. Earring", + right_ear="Crematio Earring", + left_ring="Shiva Ring +1", + right_ring="Shiva Ring +1", + back={ name="Nantosuelta's Cape", augments={'INT+20','Mag. Acc+20 /Mag. Dmg.+20','"Mag.Atk.Bns."+10',}}, + } + + sets.midcast['Elemental Magic'][0][1] = set_combine(sets.midcast['Elemental Magic'][0][0],{body="Seidr Cotehardie"}) + + -- MAcc level 1 (MAcc and Enmity relevant) + sets.midcast['Elemental Magic'][1][0] = { + main="Idris", + sub="Ammurapi Shield", + range=empty, + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',}}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',}}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',}}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',}}, + feet="Amalric Nails +1", + neck="Sanctity Necklace", + waist={name="Yamabuki-no-Obi", stats={INT=8}}, + ear2={name="Novia Earring", stats={Enmity=-7}}, + ear1="Barkarole Earring", + ring1={name="Shiva Ring +1", stats={INT=9,MAB=3}}, + ring2={name="Shiva Ring +1", stats={INT=9,MAB=3}}, + back={ name="Nantosuelta's Cape", augments={'INT+20','Mag. Acc+20 /Mag. Dmg.+20','"Mag.Atk.Bns."+10',}}, + } + + sets.midcast['Elemental Magic'][1][1] = set_combine(sets.midcast['Elemental Magic'][1][0],{body="Seidr Cotehardie"}) + + + + sets.midcast['Dark Magic'] = { + main={ name="Rubicundity", augments={'Mag. Acc.+10','"Mag.Atk.Bns."+10','Dark magic skill +10','"Conserve MP"+7',}}, + sub="Ammurapi Shield", + range=empty, + ammo="Hasty Pinion +1", + head="Pixie Hairpin +1", + body="Psycloth Vest", + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',}}, + legs="Psycloth Lappas", + feet={ name="Merlinic Crackows", augments={'Mag. Acc.+21','"Drain" and "Aspir" potency +11','INT+1',}}, + neck="Erra Pendant", + waist="Austerity Belt +1", + left_ear="Hirudinea Earring", + right_ear="Loquac. Earring", + left_ring="Evanescence Ring", + right_ring="Archon Ring", + back={ name="Nantosuelta's Cape", augments={'INT+20','Mag. Acc+20 /Mag. Dmg.+20','"Mag.Atk.Bns."+10',}}, + } + + sets.midcast['Enfeebling Magic'] = { + main="Idris", + sub="Ammurapi Shield", + range=empty, + ammo="Pemphredo Tathlum", + head="Amalric Coif +1", + neck="Erra Pendant", + ear1="Barkarole Earring", + ear2="Dignitary's Earring", + body="Zendik Robe", + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',}}, + ring1="Kishar Ring", + ring2="Weather. Ring +1", + back={ name="Nantosuelta's Cape", augments={'INT+20','Mag. Acc+20 /Mag. Dmg.+20','"Mag.Atk.Bns."+10',}}, + waist="Luminary Sash", + legs="Psycloth Lappas", + feet="Amalric Nails +1", + } + + sets.midcast.EnhancingDuration = { + main={ name="Gada", augments={'Enh. Mag. eff. dur. +6','"Mag.Atk.Bns."+9',}}, + sub="Ammurapi Shield", + hands={ name="Telchine Gloves", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',}}, + head={ name="Telchine Cap", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',}}, + body={ name="Telchine Chas.", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',}}, + legs={ name="Telchine Braconi", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',}}, + feet={ name="Telchine Pigaches", augments={'Song spellcasting time -7%','Enh. Mag. eff. dur. +10',}}, + } + + sets.midcast['Flash Nova'] = { + range=empty, + ammo="Mana Ampulla", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',}}, + body={ name="Merlinic Jubbah", augments={'"Mag.Atk.Bns."+29','Magic burst dmg.+11%','INT+10',}}, + hands="Amalric Gages +1", + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+30','"Occult Acumen"+10','INT+9','Mag. Acc.+5',}}, + feet="Amalric Nails +1", + neck="Baetyl Pendant", + waist="Fotia Belt", + left_ear="Friomisi Earring", + right_ear="Crematio Earring", + left_ring="Shiva Ring +1", + right_ring="Weather. Ring +1", + back={ name="Nantosuelta's Cape", augments={'INT+20','Mag. Acc+20 /Mag. Dmg.+20','"Mag.Atk.Bns."+10',}}, + } + + sets.midcast.Exudation = {range=empty,ammo="Mana Ampulla", + head="Geomancy Galero +1",neck="Phalaina Locket", + body="Onca Suit",hands=empty,lring="Rajas Ring",rring="Ifrit Ring +1", + back="Pahtli Cape",waist="Luminary Sash",legs=empty,feet=empty, + } + + sets.midcast['Spirit Taker'] = sets.midcast.Exudation + + sets.midcast['Healing Magic'] = {neck="Incanter's Torque"} + + sets.midcast['Divine Magic'] = {neck="Incanter's Torque"} + + sets.midcast['Enhancing Magic'] = set_combine(sets.midcast.EnhancingDuration,{neck="Incanter's Torque"}) + + sets.midcast.Dia = sets.TH + sets.midcast['Dia II'] = sets.TH + sets.midcast.Diaga = sets.TH + + sets.midcast.Cure = { + main="Vadose Rod", + sub="Genmei Shield", + range=empty, + ammo="Mana Ampulla", + head="Amalric Coif +1", + neck="Phalaina Locket", + lear="Novia Earring", + rear="Mendicant's Earring", + body="Heka's Kalasiris", + hands="Revealer's Mitts +1", + lring="Celestial Ring", + rring="Sangoma Ring", + back="Pahtli Cape", + waist="Luminary Sash", + legs="Vanya Slops", + feet="Amalric Nails +1" + } + + sets.midcast.Stoneskin = set_combine(sets.midcast.EnhancingDuration,{neck="Nodens Gorget",waist="Siegel Sash",legs="Shedir Seraweels"}) + + sets.midcast.Aquaveil = set_combine(sets.midcast.EnhancingDuration,{main="Vadose Rod",head="Amalric Coif +1",sub="Genmei Shield",waist="Emphatikos Rope",legs="Shedir Seraweels"}) + + sets.midcast.Refresh = set_combine(sets.midcast.EnhancingDuration,{head="Amalric Coif +1",back="Grapevine Cape",feet="Inspirited Boots"}) + + sets.midcast.Cursna = { + main={ name="Divinity", augments={'Attack+10','Accuracy+10','Phys. dmg. taken -3%','DMG:+15',}}, + sub="Genmei Shield", + head={ name="Vanya Hood", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',}}, + body="Vanya Robe", + hands="Hieros Mittens", + legs={ name="Vanya Slops", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',}}, + left_ring="Haoma's Ring", + right_ring="Haoma's Ring", + back="Oretan. Cape +1", + feet="Vanya Clogs", + neck="Debilis Medallion", + } + + sets.midcast.Bolster = {body="Bagua Tunic +1"} + + sets.midcast['Full Circle'] = {head="Azimuth Hood +1",hands="Bagua Mitaines +1"} + + sets.midcast['Life Cycle'] = {body="Geomancy Tunic +1", + back={ name="Nantosuelta's Cape", augments={'HP+60','Eva.+20 /Mag. Eva.+20','Pet: "Regen"+10',}},} + + sets.midcast['Radial Arcana'] = {hands="Bagua Sandals +1"} + + sets.midcast['Mending Halation'] = {Legs="Bagua Pants +1"} + + sets.midcast.Indi = { + main="Idris", + sub="Genmei Shield", + range="Dunna", + ammo=empty, + head="Azimuth Hood +1", + body={ name="Bagua Tunic +1", augments={'Enhances "Bolster" effect',}}, + legs={ name="Bagua Pants +1", augments={'Enhances "Mending Halation" effect',}}, + feet="Azimuth Gaiters +1", + neck="Incanter's Torque", + back={ name="Lifestream Cape", augments={'Geomancy Skill +3','Indi. eff. dur. +20','Damage taken-3%',}}, + } + + sets.midcast.Geo = { + main="Idris", + sub="Genmei Shield", + range="Dunna", + ammo=empty, + head="Azimuth Hood +1", + body={ name="Bagua Tunic +1", augments={'Enhances "Bolster" effect',}}, + legs={ name="Bagua Pants +1", augments={'Enhances "Mending Halation" effect',}}, + feet="Azimuth Gaiters +1", + neck="Incanter's Torque", + back={ name="Lifestream Cape", augments={'Geomancy Skill +3','Indi. eff. dur. +20','Damage taken-3%',}}, + } + sets.midcast.Geo = set_combine(sets.midcast.Geo,sets.TH) + + sets.midcast['Entrusted Indi'] = set_combine(sets.midcast.Indi,{main="Solstice"}) + + + + sets.aftercast = {} + sets.aftercast.Idle = {} + sets.aftercast.Idle[false] = { + main="Mafic Cudgel", + sub="Genmei Shield", + range="Dunna", + ammo=empty, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',}}, + neck="Loricate Torque +1", + ear1="Gifted earring", + ear2="Etiolation Earring", + body="Witching Robe", + hands="Bagua Mitaines +1", + ring1="Dark Ring", + ring2="Defending Ring", + back="Umbra Cape", + waist="Yamabuki-no-Obi", + legs="Assid. Pants +1", + feet="Geomancy Sandals +2"} + + sets.aftercast.Idle[true] = { + main="Idris", + sub="Genmei Shield", + range={ name="Dunna", augments={'MP+20','Mag. Acc.+10','"Fast Cast"+3',}}, + head="Azimuth Hood +1", + body={ name="Telchine Chas.", augments={'Mag. Evasion+21','Pet: "Regen"+3','Pet: Damage taken -3%',}}, + hands={ name="Telchine Gloves", augments={'Mag. Evasion+25','Pet: "Regen"+3','Pet: Damage taken -3%',}}, + legs={ name="Telchine Braconi", augments={'Mag. Evasion+25','Pet: "Regen"+3','Pet: Damage taken -3%',}}, + feet={ name="Bagua Sandals +1", augments={'Enhances "Radial Arcana" effect',}}, + neck="Loricate Torque +1", + waist="Isa belt", + left_ear="Genmei Earring", + right_ear="Etiolation Earring", + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + right_ring="Defending Ring", + back={ name="Nantosuelta's Cape", augments={'HP+60','Eva.+20 /Mag. Eva.+20','Pet: "Regen"+10',}}, + } + + sets.aftercast.Resting = {main="Numen Staff",sub="Ariesian Grip",range="Dunna",ammo=empty, + head={ name="Merlinic Hood", augments={'"Mag.Atk.Bns."+28','"Fast Cast"+3','"Refresh"+1','Mag. Acc.+6 "Mag.Atk.Bns."+6',}},neck="Eidolon Pendant +1",ear1="Relaxing Earring",ear2="Antivenom Earring", + body="Witching Robe",hands="Nares Cuffs",ring1="Celestial Ring",ring2="Angha Ring", + back="Felicitas Cape +1",waist="Austerity Belt +1",legs="Assid. Pants +1",feet="Chelona Boots +1"} + + sets.aftercast.Engaged = {main="Mafic Cudgel",sub="Genmei Shield", + head={name="Hagondes Hat +1", augments={'Phys. dmg. taken -3%','Magic dmg. taken -2%','"Mag.Atk.Bns."+26',}},neck="Loricate Torque +1",ear1="Brutal Earring",ear2="Genmei Earring", + body="Onca Suit",hands=empty,ring1="Dark Ring",ring2="Defending Ring", + back="Umbra Cape",waist="Ninurta's Sash",legs=empty,feet=empty} + --body="Hagondes Coat +1",hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',}},ring1="Dark Ring",ring2="Defending Ring", + --back="Umbra Cape",waist="Ninurta's Sash",legs={ name="Hagondes Pants +1", augments={'Phys. dmg. taken -4%','Magic dmg. taken -4%','Magic burst dmg.+10%',}},feet="Battlecast Gaiters"} + + sets.Obis = {} + sets.Obis.Fire = {waist='Hachirin-no-Obi'} + sets.Obis.Earth = {waist='Hachirin-no-Obi'} + sets.Obis.Water = {waist='Hachirin-no-Obi'} + sets.Obis.Wind = {waist='Hachirin-no-Obi'} + sets.Obis.Ice = {waist='Hachirin-no-Obi'} + sets.Obis.Lightning = {waist='Hachirin-no-Obi'} + sets.Obis.Light = {waist='Hachirin-no-Obi'} + sets.Obis.Dark = {waist='Hachirin-no-Obi'} + sets.Zodiac = {lring="Zodiac Ring"} + + sets.midcast.MB={ + head={ name="Merlinic Hood", augments={'Mag. Acc.+24 "Mag.Atk.Bns."+24','Magic burst dmg.+11%','INT+1','Mag. Acc.+8',}}, + body={ name="Merlinic Jubbah", augments={'"Mag.Atk.Bns."+29','Magic burst dmg.+11%','INT+10',}}, + legs={ name="Merlinic Shalwar", augments={'"Mag.Atk.Bns."+26','Magic burst dmg.+11%','INT+10','Mag. Acc.+13',}}, + neck="Mizu. Kubikazari", + right_ring="Mujin Band", + } + + stuntarg = 'Shantotto' + send_command('input /macro book 2;wait .1;input /macro set 1') + + AMII = {['Freeze II']=true,['Burst II']=true,['Quake II'] = true, ['Tornado II'] = true,['Flood II']=true,['Flare II']=true} +end + +function precast(spell) + if sets.precast[spell.english] then + equip(sets.precast[spell.english][macc_level] or sets.precast[spell.english]) + elseif spell.english == 'Impact' then + equip(sets.precast.FastCast.Default,sets.Impact) + if not buffactive['Elemental Seal'] then + add_to_chat(8,'--------- Elemental Seal is down ---------') + end + elseif spell.action_type == 'Magic' then + equip(sets.precast.FastCast.Default) + end + + if spell.english == 'Stun' and stuntarg ~= 'Shantotto' then + send_command('@input /t '..stuntarg..' ---- Byrth Stunned!!! ---- ') + end +end + +function midcast(spell) + equip_idle_set() + if buffactive.manawell or spell.mppaftercast > 50 then mp_efficiency = 0 + else mp_efficiency = 1 end + + if spell and spell.english and string.find(spell.english,'Cur') then + weathercheck(spell.element,sets.midcast.Cure) + elseif spell.english == 'Impact' then + weathercheck(spell.element,set_combine(sets.midcast['Elemental Magic'][macc_level][mp_efficiency],sets.Impact)) + elseif spell.english:sub(1,4) == 'Geo-' then + equip(sets.midcast.Geo) + elseif spell.english:sub(1,5) == 'Indi-' then + if buffactive.Entrust then + equip(sets.midcast["Entrusted Indi"]) + else + equip(sets.midcast.Indi) + end + elseif sets.midcast[spell.name] then + weathercheck(spell.element,sets.midcast[spell.name]) + elseif spell.skill == 'Elemental Magic' then + weathercheck(spell.element,sets.midcast['Elemental Magic'][macc_level][mp_efficiency]) + if sets.ElementalMagicMAB[spell.element] then + equip(sets.ElementalMagicMAB[spell.element]) + end + elseif spell.skill then + equip(sets.aftercast.Idle) + weathercheck(spell.element,sets.midcast[spell.skill]) + end + + if spell.english == 'Sneak' then + send_command('cancel 71;') + end +end + +function aftercast(spell) + if midaction() or (spell and (spell.name == 'Mending Halation' or spell.name == 'Radial Arcana')) then + elseif player.status == 'Idle' then + + if not spell.english then print('NO SPELL IN AFTERCAST') return end + if spell and spell.english and player.status == 'Idle' and string.find(spell.english,'Geo') then + equip(sets.aftercast.Idle[true]) + else + equip_idle_set() + end + elseif sets.aftercast[player.status] then + equip(sets.aftercast[player.status]) + else + equip_idle_set() + end + if not spell.interrupted then + if spell.english == 'Sleep' or spell.english == 'Sleepga' then + send_command('@wait 55;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + elseif spell.english == 'Sleep II' or spell.english == 'Sleepga II' then + send_command('@wait 85;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + elseif spell.english == 'Break' or spell.english == 'Breakga' then + send_command('@wait 25;input /echo ------- '..spell.english..' is wearing off in 5 seconds -------') + end + end +end + +function pet_aftercast(spell) + if (spell and (spell.name == 'Mending Halation' or spell.name == 'Radial Arcana')) then + equip_idle_set() + end +end + +function status_change(new,old) + if new == 'Resting' then + equip(sets.aftercast.Resting) + elseif new == 'Engaged' then + if not midaction() then + equip(sets.aftercast.Engaged) + end + -- disable('main','sub') + else + equip_idle_set() + end +end + +function equip_idle_set(bool) + if bool == nil then + bool = pet.isvalid + end + equip(sets.aftercast.Idle[bool]) +end + +function weathercheck(spell_element,set) + if not set then return end + if spell_element == world.weather_element or spell_element == world.day_element then + equip(set,sets.Obis[spell_element]) + else + equip(set) + end + if set[spell_element] then equip(set[spell_element]) end +end + +function indi_change(spell,bool) + if not bool then windower.add_to_chat(8,"------------------- Indi effect wears off! -------------------------") end +end + +function buff_change(name,gain,tab) + if gain then + print('gain',name,buffactive[name],math.floor(tab.duration/60),math.floor(tab.duration%60)) + else + print('loss',name,buffactive[name],math.floor(tab.duration/60),math.floor(tab.duration%60)) + end +end + +function self_command(command) + if command:lower() == 'stuntarg' then + stuntarg = player.target.name + print('StunTarg: ',stuntarg) + elseif command:lower() == 'macc' then + macc_level = (macc_level+1)%2 + print('Magic Accuracy level ',macc_level) + end +end + +function pet_change(pet,gain) + if not midaction() then + equip_idle_set(gain) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_MNK.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_MNK.lua new file mode 100644 index 0000000..16f12a2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_MNK.lua @@ -0,0 +1,160 @@ +include('organizer-lib') + +function get_sets() + sets.weapons = {main="Suwaiyas"} + sets.precast = {} + --sets.precast.Boost = {hands="Anchorite's Gloves +1"} + sets.precast.Chakra = {ammo="Iron Gobbet",body="Anchorite's Cyclas +1",hands="Hes. Gloves +1"} + sets.precast.Counterstance = {feet="Hes. Gaiters +1"} + sets.precast.Focus = {head="Anchorite's Crown +1"} + sets.precast.Dodge = {feet="Anchorite's Gaiters +1"} + sets.precast.Mantra = {feet="Hes. Gaiters +1"} + sets.precast.Footwork = {feet="Shukuyu Sune-Ate"} + sets.precast['Hundred Fists'] = {legs="Hes. Hose +1"} + sets.Waltz = {head="Anwig Salade",neck="Unmoving Collar +1",ring1="Valseur's Ring",ring2="Carbuncle Ring +1", + waist="Aristo Belt",legs="Desultor Tassets",feet="Dance Shoes"} + + sets.precast['Victory Smite'] = { + ammo="Floestone", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + hands={ name="Adhemar Wrist. +1", augments={'DEX+12','AGI+12','Accuracy+20',}}, + legs="Hiza. Hizayoroi +1", + feet={ name="Herculean Boots", augments={'Accuracy+29','Crit. hit damage +5%','DEX+7','Attack+13',}}, + neck="Fotia Gorget", + waist="Moonbow Belt +1", + left_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + right_ear="Sherida Earring", + left_ring="Begrudging Ring", + left_ring="Niqmaddu Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','Weapon skill damage +10%',}}, + } + + sets.test = {lring="Ramuh Ring +1",rring="Ramuh Ring +1"} + sets.test2 = {main="Numen Staff"} + + sets.precast['Howling Fist'] = { + ammo="Floestone", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body="Adhemar Jacket +1", + hands="Adhemar Wrist. +1", + legs="Hiza. Hizayoroi +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Caro Necklace", + waist="Moonbow Belt +1", + left_ear="Cessance Earring", + right_ear="Sherida Earring", + left_ring="Ifrit Ring +1", + left_ring="Niqmaddu Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','Weapon skill damage +10%',}}, + } + sets.precast['Tornado Kick'] = { + ammo="Floestone", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body="Adhemar Jacket +1", + hands="Adhemar Wrist. +1", + legs="Hiza. Hizayoroi +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Caro Necklace", + waist="Moonbow Belt +1", + left_ear="Moonshade Earring", + right_ear="Sherida Earring", + left_ring="Ifrit Ring +1", + left_ring="Niqmaddu Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','Weapon skill damage +10%',}}, + } + sets.precast['Spinning Attack'] = { + ammo="Floestone", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body="Adhemar Jacket +1", + hands="Adhemar Wrist. +1", + legs="Hiza. Hizayoroi +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Caro Necklace", + waist="Moonbow Belt +1", + left_ear="Cessance Earring", + right_ear="Sherida Earring", + left_ring="Ifrit Ring +1", + left_ring="Niqmaddu Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','Weapon skill damage +10%',}}, + } + + sets.WS = { + ammo="Floestone", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + hands="Adhemar Wrist. +1", + legs="Hiza. Hizayoroi +1", + feet={ name="Herculean Boots", augments={'Accuracy+29','Crit. hit damage +5%','DEX+7','Attack+13',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Moonshade Earring", + right_ear="Sherida Earring", + left_ring="Begrudging Ring", + left_ring="Niqmaddu Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','STR+10','Weapon skill damage +10%',}}, + } + + sets.TP = {} + + sets.TP.DD = { + ammo="Ginsen", + head={ name="Adhemar Bonnet +1", augments={'STR+12','DEX+12','Attack+20',}}, + body="Adhemar Jacket +1", + hands="Adhemar Wrist. +1", + legs={ name="Samnuha Tights", augments={'STR+8','DEX+9','"Dbl.Atk."+3','"Triple Atk."+2',}}, + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Combatant's Torque", + waist="Moonbow Belt +1", + left_ear="Cessance Earring", + right_ear="Sherida Earring", + left_ring="Niqmaddu Ring", + right_ring="Epona's Ring", + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + } + + sets.status = {} + sets.status.Engaged = sets.TP.DD + + sets.status.Idle = { + ammo="Iron Gobbet", + head="Lithelimb Cap", + body="Emet Harness +1", + hands={ name="Herculean Gloves", augments={'Accuracy+30','Damage taken-4%','STR+9','Attack+4',}}, + legs="Mummu Kecks +1", + feet="Herald's Gaiters", + neck="Wiglen Gorget", + waist="Moonbow Belt +1", + left_ear="Novia Earring", + right_ear="Genmei Earring", + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + back={ name="Segomo's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + } + send_command('input /macro book 15;wait .1;input /macro set 1') +end + +function precast(spell) + if spell.english == 'Tornado Kick' and buffactive.Footwork then + equip(sets.precast[spell.english]) + equip({feet="Shukuyu Sune-Ate"}) + elseif sets.precast[spell.english] then + equip(sets.precast[spell.english]) + elseif spell.type=="WeaponSkill" then + equip(sets.WS) + elseif string.find(spell.english,'Waltz') then + equip(sets.Waltz) + end +end + +function aftercast(spell) + if sets.status[player.status] then + equip(sets.status[player.status]) + end +end + +function status_change(new,old) + if sets.status[new] then + equip(sets.status[new]) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_PLD.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_PLD.lua new file mode 100644 index 0000000..5ee699a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_PLD.lua @@ -0,0 +1,539 @@ +include('organizer-lib') + +function get_sets() + + + sets.weapons = {sub="Burtgang",main="Xiutleato",ammo="Excalibur"} + + sets.aegis = {sub={name="Aegis"}} + sets.ochain = {sub={name="Ochain"}} + sets.priwen = {sub={name="Priwen",hp=80}} + sets.srivatsa = {sub={name="Srivatsa",hp=150,mp=150}} + current_shield = {name="Ochain"} + + sets.items = {sub="Echo Drops"} + + sets.Enmity = { + main="Burtgang", + sub={name="Srivatsa",hp=150,mp=150}, + ammo="Iron Gobbet", + head={ name="Cab. Coronet +1", augments={'Enhances "Iron Will" effect',}, hp=96, mp=78}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={ name="Yorium Gauntlets", augments={'Mag. Evasion+8','Enmity+10','Phys. dmg. taken -4%',}, hp=29}, + legs={ name="Odyssean Cuisses", augments={'Attack+9','Enmity+8',}, hp=54, mp=41}, + feet={ name="Eschite Greaves", augments={'HP+80','Enmity+7','Phys. dmg. taken -4',}, hp=98}, + neck="Unmoving Collar +1", + waist="Goading Belt", + left_ear="Trux Earring", + right_ear={name="Pluto's Pearl",mp=24}, + ring1={name="Eihwaz Ring",hp=70}, + right_ring="Provocare Ring", + back={ name="Weard Mantle", augments={'VIT+1','DEX+1','Enmity+7','Phalanx +4',}, hp=40, mp=40}, + } + + sets.precast = {} + sets.precast.FC = { + sub={ name="Svalinn", augments={'"Mag.Atk.Bns."+10','Occ. quickens spellcasting +3%',}}, + ammo="Impatiens", + head={name="Carmine Mask +1",hp=38}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={name="Leyline Gloves",hp=25}, + legs={name="Enif Cosciales", hp=40,mp=40}, + feet={name="Carmine Greaves +1", hp=95, mp=80}, + neck={name="Orunmila's Torque", mp=30}, + left_ear={name="Loquac. Earring", mp=30}, + right_ear={name="Etiolation Earring", hp=50, mp=50}, + left_ring="Kishar Ring", + right_ring="Weather. Ring +1", + back={name="Reiki Cloak",hp=130}, + waist={name="Eschan Stone",hp=20}, + } + + sets.precast.Cure = { + body={ name="Jumalik Mail", augments={'HP+50','Attack+15','Enmity+9','"Refresh"+2',}, hp=116, mp=59}, + right_ear="Nourish. Earring +1"} + + sets.precast['Enhancing Magic'] = {waist="Siegel Sash"} + + sets.midcast = {} + sets.midcast['Shield Bash'] = set_combine(sets.Enmity,sets.aegis,{hands={name="Caballarius Gauntlets +1",hp=104},rring="Fenian Ring"}) + sets.midcast.Chivalry = {hands={name="Caballarius Gauntlets +1",hp=104}} -- Should make a real Chivalry set + sets.midcast.Sentinel = {feet={name="Cab. Leggings +1",hp=43,mp=23},} + sets.midcast.Rampart = set_combine(sets.Enmity,{head={ name="Cab. Coronet +1", augments={'Enhances "Iron Will" effect',}, hp=96, mp=78}}) + sets.midcast.Invincible = set_combine(sets.Enmity,{legs={name="Cab. Breeches +1",hp=52,mp=80}}) + sets.midcast.Fealty = { + body={ name="Cab. Surcoat +1", augments={'Enhances "Fealty" effect',}, hp=118, mp=90}, + } + sets.midcast['Holy Circle'] = {feet={name="Reverence Leggings +1",hp=48,mp=30}} + sets.midcast.Provoke = sets.Enmity + sets.midcast.Flash = set_combine(sets.Enmity,{waist="Chaac Belt"}) + sets.midcast.Palisade = sets.Enmity + sets.midcast['Stinking Gas'] = sets.Enmity + sets.midcast['Geist Wall'] = sets.Enmity + sets.midcast['Jettatura'] = sets.Enmity + sets.midcast['Soporific'] = sets.Enmity + sets.midcast['Blank Gaze'] = sets.Enmity + sets.midcast['Sounds Blast'] = sets.Enmity + sets.midcast['Sheep Song'] = sets.Enmity + sets.midcast['Chaotic Eye'] = sets.Enmity + sets['Divine Emblem'] = {feet={name="Chev. Sabatons +1",hp=22,mp=14}} + + sets.midcast.Cover={ + sub="Ochain", + ammo="Iron Gobbet", + head={name="Reverence Coronet +1", hp=41, mp=23}, + body={ name="Cab. Surcoat +1", augments={'Enhances "Fealty" effect',}, hp=118, mp=90}, + hands={name="Caballarius Gauntlets +1",hp=104}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + neck="Unmoving Collar +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + back={ name="Weard Mantle", augments={'VIT+3','DEX+5','Phalanx +5',}, hp=40, mp=40}, + } + + sets.midcast['Chant du Cygne'] = { + ammo="Ginsen", + head={name="Flam. Zucchetto +1",hp=80, mp=20}, + body={name="Flamma Korazin +1",hp=140, mp=35}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Thereoid Greaves",hp=13}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + right_ear="Brutal Earring", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back="Rancorous Mantle", + } + + sets.midcast.Atonement = set_combine(sets.Enmity,{neck="Fotia Gorget", + ear1="Moonshade Earring", + head={ name="Valorous Mask", augments={'Weapon skill damage +5%','Attack+4',}, hp=38}, + hands={ name="Odyssean Gauntlets", augments={'Attack+20','Weapon skill damage +5%','VIT+3',}, hp=31, mp=14}, + waist="Fotia Belt", + feet={name="Sulevia's Leggings +1",hp=20,mp=20}}) + + sets.midcast['Swift Blade'] = {ammo="Ginsen", + head={name="Carmine Mask +1",hp=38}, + neck="Fotia Gorget", + ear1="Steelflash Earring", + ear2="Bladeborn Earring", + body={name="Flamma Korazin +1",hp=140, mp=35}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + ring1="Ifrit Ring +1", + ring2="Rajas Ring", + back="Bleating Mantle", + waist="Fotia Belt", + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Flam. Gambieras +1",hp=40,mp=10}, + } + + sets.midcast['Savage Blade'] = { + ammo="Ginsen", + head={name="Carmine Mask +1",hp=38}, + body={name="Flamma Korazin +1",hp=140, mp=35}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Sulevia's Leggings +1",hp=20,mp=20}, + neck="Fotia Gorget", + waist="Windbuffet Belt +1", + left_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + right_ear="Brutal Earring", + left_ring="Ifrit Ring +1", + right_ring="Rajas Ring", + back="Bleating Mantle", + } + + sets.midcast['Knights of Round'] = { + ammo="Ginsen", + head={name="Carmine Mask +1",hp=38}, + neck="Fotia Gorget", + ear1="Steelflash Earring", + ear2="Bladeborn Earring", + body={name="Flamma Korazin +1",hp=140, mp=35}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + ring1="Ifrit Ring +1", + ring2="Rajas Ring", + back="Bleating Mantle", + waist="Fotia Belt", + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Sulevia's Leggings +1",hp=20,mp=20}, + } + + sets.midcast.WS = { + head={name="Flam. Zucchetto +1",hp=80, mp=20}, + neck="Fotia Gorget", + ear1="Moonshade Earring", + ear2="Brutal Earring", + body={name="Flamma Korazin +1",hp=140, mp=35}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + ring1="Ramuh Ring +1", + ring2="Rajas Ring", + back="Bleating Mantle", + waist="Windbuffet Belt +1", + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Flam. Gambieras +1",hp=40,mp=10}, + } + + + sets.midcast.WS_Day = { head={name="Gavialis helm", hp=115, mp=23} } + + sets.midcast.Cure = { + neck="Phalaina Locket", + rear="Nourishing Earring +1", + --body={ name="Jumalik Mail", augments={'HP+50','Attack+15','Enmity+9','"Refresh"+2',}, hp=116, mp=59}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={name="Souveran Handschuhs +1",hp=239,mp=14}, + ring2={name="Meridian Ring",hp=90}, + ring1={name="Eihwaz Ring",hp=70}, + back={ name="Weard Mantle", augments={'VIT+1','DEX+1','Enmity+7','Phalanx +4',}, hp=40, mp=40}, + legs={name="Souveran Diechlings +1",hp=162,mp=41}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + back={name="Reiki Cloak",hp=130}, + } + + sets.midcast.Phalanx = {main="Deacon Sword", -- +4 + sub="Priwen", -- +2 + neck="Incanter's Torque", -- +1 + head={ name="Yorium Barbuta", augments={'Phalanx +3',}, hp=41, mp=23}, -- +3 + body={ name="Yorium Cuirass", augments={'Mag. Evasion+7','Enmity+10','Phalanx +3',}, hp=113, mp=85}, -- +3 + hands={name="Souveran Handschuhs +1",hp=239,mp=14}, -- +5 + back={ name="Weard Mantle", augments={'VIT+3','DEX+5','Phalanx +5',}, hp=40, mp=40}, -- +5 + legs={ name="Yorium Cuisses", augments={'Enmity+7','Phalanx +3',}, hp=52}, -- +3 + feet={name="Souveran Schuhs +1",hp=227,mp=14} -- +5 + } + -- Next tier is about 26 skill away, which isn't worth it. + + sets.midcast.Enlight = { + main={ name="Brilliance", augments={'Shield skill +10','Divine magic skill +15','Enmity+7','DMG:+15',}}, + head={ name="Jumalik Helm", augments={'MND+10','"Mag.Atk.Bns."+15','Magic burst dmg.+10%','"Refresh"+1',}, hp=45, mp=29}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={ name="Eschite Gauntlets", hp=109}, + waist="Asklepian Belt", + neck="Incanter's Torque",} + + sets.midcast['Enlight II'] = sets.midcast.Enlight + -- This puts me over 500 skill. Need to test how much more divine magic skill I would need for another tier. + + sets.midcast.Stoneskin = {neck="Nodens Gorget",hands={name="Stone Mufflers",hp=10,mp=10},waist="Siegel Sash"} + + sets.aftercast = {} + + TP_sets = {'DD','Acc','DT'} + TP_ind = 3 + + sets.aftercast.non_DW = { + main="Burtgang", + sub=current_shield, + waist="Windbuffet Belt +1", + left_ear="Steelflash Earring", + right_ear="Bladeborn Earring", + ammo="Ginsen", + head={name="Flam. Zucchetto +1",hp=80, mp=20}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}, hp=95}, + feet={name="Flam. Gambieras +1",hp=40,mp=10}, + neck="Lissome Necklace", + left_ring="Ifrit Ring +1", + right_ring="Rajas Ring", + back="Bleating Mantle", + } + + sets.aftercast.Acc = { + main="Burtgang", + sub=current_shield, + ammo="Staunch Tathlum", + head={name="Carmine Mask +1",hp=38}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + legs={ name="Carmine Cuisses +1", augments={'Accuracy+20','Attack+12','"Dual Wield"+6',}, hp=50}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear={name="Thureous Earring",hp=30,mp=30}, + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + back="Impassive Mantle", + } + + sets.aftercast.DW = { + main="Burtgang", + ammo="Ginsen", + head={name="Flam. Zucchetto +1",hp=80, mp=20}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}, hp=31, mp=14}, + legs={ name="Carmine Cuisses +1", augments={'Accuracy+20','Attack+12','"Dual Wield"+6',}, hp=50}, + feet={name="Flam. Gambieras +1",hp=40,mp=10}, + neck="Lissome Necklace", + waist="Reiki Yotai", + left_ear="Suppanomimi", + right_ear="Brutal Earring", + left_ring="Ifrit Ring +1", + right_ring="Rajas Ring", + back="Bleating Mantle", + } + + sets.aftercast.DD = sets.aftercast.non_DW + + function sets.aftercast.wield(equip_sub) + if equip_sub == 'Aegis' or equip_sub =='Ochain' or equip_sub == 'Priwen' then + sets.aftercast.DD = sets.aftercast.non_DW +-- elseif equip_sub == 'Bloodrain Strap' then +-- sets.aftercast.DD = sets.aftercast.Ragnarok + else + sets.aftercast.DD = sets.aftercast.DW + end + end + + sets.aftercast.DT = { + main="Burtgang", + sub=current_shield, + ammo="Staunch Tathlum", + head={ name="Odyssean Helm", augments={'Accuracy+16','Phys. dmg. taken -5%','CHR+1','Attack+14',}, hp=112, mp=89}, + body={ name="Reverence Surcoat +2", hp=244, mp=52}, + hands={name="Souveran Handschuhs +1",hp=239,mp=14}, + legs={name="Souveran Diechlings +1",hp=162,mp=41}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear={name="Thureous Earring",hp=30,mp=30}, + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + back="Impassive Mantle", + } + + Idle_sets = {'Idle','Supertanking'} + Idle_ind = 1 + sets.aftercast.Idle = { + main="Burtgang", + sub=current_shield, + ammo="Staunch Tathlum", + head={ name="Odyssean Helm", augments={'Accuracy+16','Phys. dmg. taken -5%','CHR+1','Attack+14',}, hp=112, mp=89}, + body={ name="Jumalik Mail", augments={'HP+50','Attack+15','Enmity+9','"Refresh"+2',}, hp=116, mp=59}, + hands={name="Souveran Handschuhs +1",hp=239,mp=14}, + legs={ name="Carmine Cuisses +1", augments={'Accuracy+20','Attack+12','"Dual Wield"+6',}, hp=50}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear={name="Thureous Earring",hp=30,mp=30}, + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + back="Impassive Mantle", + } + + sets.aftercast.Supertanking = { + main="Burtgang", + sub=current_shield, + ammo="Staunch Tathlum", + head={ name="Odyssean Helm", augments={'Accuracy+16','Phys. dmg. taken -5%','CHR+1','Attack+14',}, hp=112, mp=89}, + body={ name="Jumalik Mail", augments={'HP+50','Attack+15','Enmity+9','"Refresh"+2',}, hp=116, mp=59}, + hands={name="Souveran Handschuhs +1",hp=239,mp=14}, + legs={name="Souveran Diechlings +1",hp=162,mp=41}, + feet={name="Souveran Schuhs +1",hp=227,mp=14}, + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear={name="Thureous Earring",hp=30,mp=30}, + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + back="Impassive Mantle", + } + + + sets.pretarget = {} + sets.pretarget.HP_Down = { + head=empty, + body=empty, + hands={ name="Yorium Gauntlets", augments={'Mag. Evasion+8','Enmity+10','Phys. dmg. taken -4%',}, hp=29}, + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}, hp=-20,mp=20}, + lear=empty, + rear=empty, + lring="Defending Ring", + back=empty, + waist="Flume Belt +1", + legs=empty, + feet=empty, + neck=empty,} + + Cure_force = false + send_command('input /macro book 20;wait .1;input /macro set 1') + disable('main','sub') + lock_mode = false +end + +function pretarget(spell) + if (spell.name == 'Cure IV' or spell.name == 'Cure III') and player.max_hp - player.hp < 328 and spell.target and spell.target.name == player.name then + print('equipping HP Down') + equip(sets.pretarget.HP_Down) + end +end + +function precast(spell) + sets.aftercast.wield(player.equipment.sub) + if player.equipment.head == 'Twilight Helm' and player.equipment.body == 'Twilight Mail' then disable('head','body') end + if spell.action_type == 'Magic' then + equip(sets.precast.FC) + if spell.name:sub(1,3) == "Cur" and spell.name ~= "Cursna" then + equip(sets.precast.Cure) + elseif spell.skill == 'Enhancing Magic' then + equip(sets.precast['Enhancing Magic']) + end + end + set_priorities('hp','mp') +end + +function midcast(spell) + midaction(false) + if player.status =='Engaged' then + equip(sets.aftercast[TP_sets[TP_ind]]) + else + equip(sets.aftercast[Idle_sets[Idle_ind]]) + end + + if sets.midcast[spell.name] then + equip(sets.midcast[spell.name]) + day_equip(spell) + elseif spell.type == 'WeaponSkill' then + equip(sets.precast.WS) + day_equip(spell) + elseif string.find(spell.name,'Cure') then + equip(sets.Enmity,sets.midcast.Cure) + end + + if spell.english == 'Flash' and buffactive['Divine Emblem'] then + equip(sets['Divine Emblem']) + end + if lockall then + aftercast(spell) + end + set_priorities('hp','mp') +end + +function day_equip(spell) + if not spell.skillchain_a or spell.skillchain_a == '' then return end + if (check_sc_properties(spell, "Light") or check_sc_properties(spell, "Fusion") or check_sc_properties(spell, "Liquefaction")) and world.day_element == 'Fire' then + elseif (check_sc_properties(spell, "Light") or check_sc_properties(spell, "Fusion") or check_sc_properties(spell, "Transfixion")) and world.day_element == 'Light' then + elseif (check_sc_properties(spell, "Light") or check_sc_properties(spell, "Fragmentation") or check_sc_properties(spell, "Detonation")) and world.day_element == 'Wind' then + elseif (check_sc_properties(spell, "Light") or check_sc_properties(spell, "Fragmentation") or check_sc_properties(spell, "Impaction")) and world.day_element == 'Lightning' then + elseif (check_sc_properties(spell, "Dark") or check_sc_properties(spell, "Distortion") or check_sc_properties(spell, "Reverberation")) and world.day_element == 'Water' then + elseif (check_sc_properties(spell, "Dark") or check_sc_properties(spell, "Distortion") or check_sc_properties(spell, "Induration")) and world.day_element == 'Ice' then + elseif (check_sc_properties(spell, "Dark") or check_sc_properties(spell, "Gravitation") or check_sc_properties(spell, "Scission")) and world.day_element == 'Earth' then + elseif (check_sc_properties(spell, "Dark") or check_sc_properties(spell, "Gravitation") or check_sc_properties(spell, "Compression")) and world.day_element == 'Dark' then + else + return + end + equip(sets.midcast.WS_Day) +end + +function check_sc_properties(spell,str) + if spell.skillchain_a == str or spell.skillchain_b == str or spell.skillchain_c == str then return true end + return false +end + +function aftercast(spell) + if player.status =='Engaged' then + equip(sets.aftercast[TP_sets[TP_ind]]) + else + equip(sets.aftercast[Idle_sets[Idle_ind]]) + end + set_priorities('hp','mp') +end + +function status_change(new,old) + if T{'Idle','Resting'}:contains(new) then + equip(sets.aftercast[Idle_sets[Idle_ind]]) + elseif new == 'Engaged' then + equip(sets.aftercast[TP_sets[TP_ind]]) + end + set_priorities('hp','mp') +end + +function buff_change(new,bool) + if new == 'Reprisal' and bool then + if current_shield.name == 'Ochain' then + table.reassign(current_shield,sets.priwen.sub) + equip({sub=current_shield}) + end + elseif new == 'Reprisal' and not bool then + if current_shield.name == 'Priwen' then + table.reassign(current_shield,sets.ochain.sub) + equip({sub=current_shield}) + end + end + set_priorities('hp','mp') +end + +function self_command(command) + if command == 'toggle TP set' then + TP_ind = TP_ind%#TP_sets +1 + send_command('@input /echo '..TP_sets[TP_ind]..' SET') + elseif command == 'toggle Idle set' then + if Idle_ind == 1 then + Idle_ind = 2 + send_command('@input /echo SUPERTANKING SET') + elseif Idle_ind == 2 then + Idle_ind = 1 + send_command('@input /echo NORMAL SET') + end + elseif command == 'toggle LOCK mode' then + lock_mode = not lock_mode + if lock_mode then + send_command('input /echo MAIN/SUB SWAPPING ENABLED') + enable('main','sub') + else + send_command('input /echo MAIN/SUB SWAPPING DISABLED') + disable('main','sub') + end + elseif command == 'toggle lockall' then + lockall = not lockall + if lockall then + send_command('input /echo SWAPPING DISABLED') + disable('main','sub') + else + send_command('input /echo SWAPPING ENABLED') + disable('main','sub') + end + elseif command == 'DT' then + equip(sets.DT) + elseif command == 'PDT Shield' then + if buffactive['Reprisal'] then + current_shield = table.reassign(current_shield,sets.priwen.sub) + else + current_shield = table.reassign(current_shield,sets.ochain.sub) + end + if not lock_mode then send_command('input /equip sub '..current_shield.name) + else equip({sub=current_shield}) end + elseif command == 'MDT Shield' then + current_shield = table.reassign(current_shield,sets.aegis.sub) + if not lock_mode then send_command('input /equip sub '..current_shield.name) + else equip({sub=current_shield}) end + end + set_priorities('hp','mp') +end + +function set_priorities(key1,key2) + local future,current = gearswap.equip_list,gearswap.equip_list_history + function get_val(piece,key) + if piece and type(piece)=='table' and piece[key] and type(piece[key])=='number' then + return piece[key] + end + return 0 + end + local diff = {} + for i,v in pairs(future) do + local priority = get_val(future[i],key1) - get_val(current[i],key1) + (get_val(future[i],key2) - get_val(current[i],key2)) + if type(v) == 'table' then + future[i].priority = priority + else + future[i] = {name=v,priority=priority} + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_SMN.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_SMN.lua new file mode 100644 index 0000000..b70ce39 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_SMN.lua @@ -0,0 +1,593 @@ +include('organizer-lib') + +function get_sets() + + sets.TH = { + hands={ name="Merlinic Dastanas", augments={'"Treasure Hunter"+2',},hp=9,mp=20}, + legs={ name="Merlinic Shalwar", augments={'Pet: Accuracy+16 Pet: Rng. Acc.+16','Pet: Haste+1','"Treasure Hunter"+1','Mag. Acc.+9 "Mag.Atk.Bns."+9',},hp=29,mp=44}, + waist="Chaac Belt", + } + + -- Precast Sets + sets.precast = {} + + sets.precast.FC = { + ammo="Impatiens", + head={ name="Merlinic Hood", augments={'"Fast Cast"+7','MND+10','Mag. Acc.+10','"Mag.Atk.Bns."+10',},mp=56,hp=22}, + body={name="Zendik Robe",hp=57,mp=61}, + hands={ name="Merlinic Dastanas", augments={'Mag. Acc.+29','"Fast Cast"+7','INT+1',},mp=20,hp=9}, + legs={ name="Psycloth Lappas", augments={'MP+80','Mag. Acc.+15','"Fast Cast"+7',},mp=109,hp=43}, + feet={ name="Merlinic Crackows", augments={'"Mag.Atk.Bns."+11','"Fast Cast"+7',},hp=4,mp=20}, + neck={ name="Orunmila's Torque",mp=30}, + waist="Witful Belt", + left_ear={ name="Loquac. Earring",mp=30}, + left_ring="Weather. Ring +1", + right_ear={name="Etiolation Earring",hp=50,mp=50}, + right_ring={name="Lebeche Ring",mp=40}, + back={ name="Campestres's Cape", augments={'Pet: M.Acc.+20 Pet: M.Dmg.+20','Pet: Mag. Acc.+10','"Fast Cast"+10',}}, + } + -- 15+13+7+7+12+5+3+2+6+1+10 = 81% FC + -- 2+3+4+2 = 11% Quickens + sets.precast['Astral Flow'] = { + head={ name="Glyphic Horn +1",hp=31,mp=95}, + } + + -- Midcast sets + sets.midcast = {} + + sets.midcast.BP = { -- -10 from Gifts + main={ name="Espiritus", augments={'Enmity-6','Pet: "Mag.Atk.Bns."+30','Pet: Damage taken -4%',}, mp=88}, + ammo="Sancus Sachet +1", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + right_ear={name="Evans Earring",mp=50}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + legs={ name="Glyphic Spats +1", augments={'Increases Sp. "Blood Pact" accuracy',},hp=38,mp=85}, + } -- This set technically has too much BP Recast-, but it compensates for times when I lock gear. + + sets.midcast['Mana Cede'] = {hands="Beckoner's Bracers +1"} + + sets.midcast['Elemental Siphon'] = { + main="Nirvana", + sub="Vox Grip", + ammo="Esper Stone +1", + neck="Caller's Pendant", + rear="Smn. Earring", + lear={ name="Andoaa Earring", mp=30}, + hands={ name="Telchine Gloves", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=52,mp=44}, + head={ name="Telchine Cap", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=36,mp=32}, + body={ name="Telchine Chas.", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=54,mp=59}, + legs={ name="Telchine Braconi", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=43,mp=29}, + waist={name="Kobo Obi",mp=20}, + lring={name="Evoker's Ring",mp=25}, + rring="Globidonta Ring", + back={ name="Conveyance Cape", augments={'Summoning magic skill +5','Pet: Enmity+15','Blood Pact Dmg.+4',},mp=100}, + feet={ name="Beckoner's Pigaches +1",hp=9,mp=97}, + day = { + rring = "Zodiac Ring", + } + } + + sets.midcast.Cur = { + main="Vadose Rod", + sub="Genbu's Shield", + head="Marduk's Tiara +1", + neck={name="Nodens Gorget",hp=25,mp=25}, + ear2="Novia earring", + hands={name="Revealer's Mitts +1",hp=22,mp=44}, + } + + sets.midcast.Stoneskin = { + neck={name="Nodens Gorget",hp=25,mp=25}, + waist="Siegel Sash", + legs="Shedir Seraweels" + } + + sets.midcast.Cursna = { + head={ name="Vanya Hood", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',},hp=36,mp=32}, + body={name="Vanya Robe",hp=54,mp=59}, + hands={name="Hieros Mittens",mp=30}, + legs={ name="Vanya Slops", augments={'Healing magic skill +20','"Cure" spellcasting time -7%','Magic dmg. taken -3',},hp=43,mp=29}, + left_ring="Haoma's Ring", + right_ring="Haoma's Ring", + back={name="Oretan. Cape +1",hp=30}, + feet={name="Vanya Clogs",hp=13,mp=14}, + neck="Debilis Medallion", + } + + sets.midcast.EnhancingDuration = { + main={ name="Gada", augments={'Enh. Mag. eff. dur. +6','"Mag.Atk.Bns."+9',}}, + sub={name="Ammurapi Shield",hp=22,mp=58}, + hands={ name="Telchine Gloves", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=52,mp=44}, + head={ name="Telchine Cap", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=36,mp=32}, + body={ name="Telchine Chas.", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=54,mp=59}, + legs={ name="Telchine Braconi", augments={'"Elemental Siphon"+35','Enh. Mag. eff. dur. +10',},hp=43,mp=29}, + feet={ name="Telchine Pigaches", augments={'Song spellcasting time -7%','Enh. Mag. eff. dur. +10',}}, + } + + sets.midcast.Refresh = set_combine(sets.midcast.EnhancingDuration,{ + head={name="Amalric Coif +1",hp=27,mp=61}, + feet={name="Inspirited Boots",hp=9,mp=20}, + waist="Gishdubar Sash", + back="Grapevine Cape", + }) + + sets.midcast['Diaga'] = sets.TH + sets.midcast['Dia'] = sets.TH + sets.midcast['Dia II'] = sets.TH + sets.midcast['Swipe'] = sets.TH + sets.midcast['Lunge'] = sets.TH + + -- Pet Midcast Sets + sets.pet_midcast = {} + + sets.BP_Base = { + main={ name="Espiritus", augments={'Enmity-6','Pet: "Mag.Atk.Bns."+30','Pet: Damage taken -4%',}, mp=88}, + sub="Elan Strap +1", + ammo="Sancus Sachet +1", + head={name="Apogee Crown +1",hp=-110,mp=139}, + body={name="Convoker's Doublet +1",hp=50,mp=134}, + hands={ name="Merlinic Dastanas", augments={'Pet: Accuracy+30 Pet: Rng. Acc.+30','Blood Pact Dmg.+9','Pet: INT+9','Pet: "Mag.Atk.Bns."+14',},mp=20,hp=9}, + legs={ name="Enticer's Pants", augments={'MP+50','Pet: Accuracy+15 Pet: Rng. Acc.+15','Pet: Mag. Acc.+15','Pet: Damage taken -5%',},hp=38,mp=106}, + feet={name="Apogee Pumps +1",hp=-90,mp=121}, + left_ear="Lugalbanda Earring", + right_ear={name="Gelos Earring",mp=35}, + left_ring="Varar Ring +1", + right_ring="Varar Ring +1", + } + + sets.pet_midcast.Phys_BP = set_combine(sets.BP_Base,{ + main="Nirvana", + neck="Shulmanu Collar", + waist="Klouskap Sash", + legs={name="Apogee Slacks +1",hp=-110,mp=56}, + back={ name="Campestres's Cape", augments={'Pet: Acc.+20 Pet: R.Acc.+20 Pet: Atk.+20 Pet: R.Atk.+20','Eva.+20 /Mag. Eva.+20','Pet: Haste+10',}}, + }) + + sets.pet_midcast.MAB_BP = set_combine(sets.BP_Base,{ + neck={name="Adad amulet",hp=25}, + --body={name="Apogee Dalmatica +1",hp=-160,mp=85}, + waist={name="Caller's sash",mp=20}, + ring2={name="Speaker's Ring",mp=40}, + back={ name="Campestres's Cape", augments={'Pet: M.Acc.+20 Pet: M.Dmg.+20','"Fast Cast"+10',}}, + }) + + sets.pet_midcast.MAB_Spell = set_combine(sets.BP_Base,{ + neck={name="Adad amulet",hp=25}, + --body={name="Apogee Dalmatica +1",hp=-160,mp=85}, + waist={name="Caller's sash",mp=20}, + ring2={name="Speaker's Ring",mp=40}, + right_ring="Globidonta Ring", + back={ name="Campestres's Cape", augments={'Pet: M.Acc.+20 Pet: M.Dmg.+20','"Fast Cast"+10',}}, + }) + + sets.pet_midcast.MAcc_BP = set_combine(sets.BP_Base,{ + main="Nirvana", + sub="Vox Grip", + neck={name="Adad amulet",hp=25}, + right_ear="Smn. Earring", + body={name="Beckoner's Doublet +1",hp=54,mp=151}, + hands={name="Lamassu mitts +1",hp=18,mp=44}, + feet={ name="Beckoner's Pigaches +1",hp=9,mp=97}, + lring={name="Evoker's Ring",mp=25}, + right_ring="Globidonta Ring", + back={ name="Campestres's Cape", augments={'Pet: M.Acc.+20 Pet: M.Dmg.+20','"Fast Cast"+10',}}, + }) + + sets.pet_midcast.Buff_BP = set_combine(sets.BP_Base,{ -- Did not check + main="Nirvana", + sub="Vox Grip", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + neck="Caller's Pendant", + lear={ name="Andoaa Earring", mp=30}, + right_ear="Smn. Earring", + --body={name="Apogee Dalmatica +1",hp=-160,mp=85}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + legs={name="Assid. Pants +1",hp=43,mp=29}, + lring={name="Evoker's Ring",mp=25}, + right_ring="Globidonta Ring", + back={ name="Campestres's Cape", augments={'Pet: Acc.+20 Pet: R.Acc.+20 Pet: Atk.+20 Pet: R.Atk.+20','Eva.+20 /Mag. Eva.+20','Pet: Haste+10',}}, + }) + + sets.pet_midcast['Shock Squall'] = { + main="Nirvana", + sub="Vox Grip", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + neck={name="Adad amulet",hp=25}, + right_ear="Smn. Earring", + body={name="Beckoner's Doublet +1",hp=54,mp=151}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + lring={name="Evoker's Ring",mp=25}, + rring="Globidonta Ring", + back={ name="Campestres's Cape", augments={'Pet: M.Acc.+20 Pet: M.Dmg.+20','"Fast Cast"+10',}}, + legs={ name="Enticer's Pants", augments={'MP+50','Pet: Accuracy+15 Pet: Rng. Acc.+15','Pet: Mag. Acc.+15','Pet: Damage taken -5%',},hp=38,mp=106}, + } + + --Aftercast Sets + sets.aftercast = {} + + sets.aftercast.None = { + main="Mafic Cudgel", + sub="Genmei Shield", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + neck="Loricate Torque +1", + left_ear={ name="Loquac. Earring",mp=30}, + right_ear={name="Evans Earring",mp=50}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + ring1={name="Dark Ring",hp=-20,mp=20}, + ring2="Defending Ring", + back="Umbra Cape", + waist="Klouskap Sash", + legs={name="Assid. Pants +1",hp=43,mp=29}, + feet={name="Herald's Gaiters",mp=12}, + } + + -- Including Auto-Refresh II, unaffected by Avatar's Favor: + -- Carbuncle: 11MP/tick : 10 Perp- + Refresh ideal (4 + Mitts + Refresh) + -- Diabolos/Celestials: 15MP/tick : 14 Perp- + Refresh ideal + -- Fenrir: 13MP/tick : 12 Perp- + Refresh ideal + -- Spirit: 7MP/tick : 6 Perp- + Refresh ideal (Beckoner's Doublet +1) + + sets.aftercast.Avatar = {} + sets.aftercast.Avatar.Carbuncle = { + main="Nirvana", + sub="Vox Grip", + ammo="Sancus Sachet +1", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Asteria Mitts +1",hp=18,mp=44}, + legs={name="Assid. Pants +1",hp=43,mp=29}, + feet={name="Herald's Gaiters",mp=12}, + neck="Shulmanu Collar", + waist="Lucidity Sash", + lear={ name="Andoaa Earring", mp=30}, + right_ear={name="Evans Earring",mp=50}, + left_ring="Varar Ring +1", + right_ring="Defending Ring", + back={ name="Campestres's Cape", augments={'Pet: Acc.+20 Pet: R.Acc.+20 Pet: Atk.+20 Pet: R.Atk.+20','Eva.+20 /Mag. Eva.+20','Pet: Haste+10',}}, + } + + sets.aftercast.Avatar.Garuda = { -- 16 MP/tick, currently negated at -15 Perp with 512 skill + main="Nirvana", + sub="Vox Grip", + ammo="Sancus Sachet +1", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + legs={name="Assid. Pants +1",hp=43,mp=29}, + feet={name="Apogee Pumps +1",hp=-90,mp=121}, + neck="Shulmanu Collar", + waist="Klouskap Sash", + lear={ name="Andoaa Earring", mp=30}, + right_ear={name="Evans Earring",mp=50}, + left_ring="Varar Ring +1", + right_ring="Defending Ring", + back={ name="Campestres's Cape", augments={'Pet: Acc.+20 Pet: R.Acc.+20 Pet: Atk.+20 Pet: R.Atk.+20','Eva.+20 /Mag. Eva.+20','Pet: Haste+10',}}, + } -- Celestials + + sets.aftercast.Avatar.Ifrit = sets.aftercast.Avatar.Garuda + sets.aftercast.Avatar.Shiva = sets.aftercast.Avatar.Garuda + sets.aftercast.Avatar.Ramuh = sets.aftercast.Avatar.Garuda + sets.aftercast.Avatar.Leviathan = sets.aftercast.Avatar.Garuda + sets.aftercast.Avatar.Titan = sets.aftercast.Avatar.Garuda + sets.aftercast.Avatar.Diabolos = sets.aftercast.Avatar.Garuda + + sets.aftercast.Avatar.Fenrir = { -- ? MP/tick, currently negated at -15 Perp with 512 skill + main="Nirvana", + sub="Vox Grip", + ammo="Sancus Sachet +1", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + legs={name="Assid. Pants +1",hp=43,mp=29}, + feet={name="Apogee Pumps +1",hp=-90,mp=121}, + neck="Shulmanu Collar", + waist="Klouskap Sash", + lear={ name="Andoaa Earring", mp=30}, + right_ear={name="Evans Earring",mp=50}, + left_ring="Varar Ring +1", + right_ring="Defending Ring", + back={ name="Campestres's Cape", augments={'Pet: Acc.+20 Pet: R.Acc.+20 Pet: Atk.+20 Pet: R.Atk.+20','Eva.+20 /Mag. Eva.+20','Pet: Haste+10',}}, + } -- Fenrir + sets.aftercast.Avatar['Cait Sith'] = set_combine(sets.aftercast.Avatar.Fenrir,{ + hands={name="Lamassu mitts +1",hp=18,mp=44}, + }) + + sets.aftercast.Avatar.Spirit = { + main={ name="Espiritus", augments={'Enmity-6','Pet: "Mag.Atk.Bns."+30','Pet: Damage taken -4%',}, mp=88}, + sub="Vox Grip", + ammo="Sancus Sachet +1", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body={name="Beckoner's Doublet +1",hp=54,mp=151}, + hands={ name="Glyphic Bracers +1", augments={'Inc. Sp. "Blood Pact" magic burst dmg.',},hp=18,mp=41}, + legs={ name="Glyphic Spats +1", augments={'Increases Sp. "Blood Pact" accuracy',}}, + feet="Marduk's Crackows +1", + neck="Caller's Pendant", + waist={name="Caller's sash",mp=20}, + lear={ name="Andoaa Earring", mp=30}, + right_ear="Smn. Earring", + lring={name="Evoker's Ring",mp=25}, + left_ring="Globidonta Ring", + back={ name="Conveyance Cape", augments={'Summoning magic skill +5','Pet: Enmity+15','Blood Pact Dmg.+4',},mp=100}, + } -- Spirits + + sets.aftercast.Resting = { + main={name="Numen Staff",mp=45}, + sub="Ariesian Grip", + ammo={name="Mana Ampulla",mp=20}, + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + neck={name="Eidolon Pendant +1",mp=15}, + ear1="Relaxing Earring", + ear2={name="Antivenom Earring",mp=15}, + body={ name="Witching Robe", augments={'MP+50','Mag. Acc.+15','"Mag.Atk.Bns."+15','"Refresh"+1',},hp=50,mp=117}, + hands={name="Nares Cuffs",mp=50},-- Not technically accurate + ring1={name="Celestial Ring",mp=20}, + ring2="Angha Ring", + back={name="Felicitas Cape +1",mp=15}, + waist="Austerity Belt +1", + legs={name="Assid. Pants +1",hp=43,mp=29}, + feet={name="Chelona Boots +1",mp=40}, + } + + sets.aftercast.Idle = sets.aftercast.None + + sets.aftercast.Engaged = { + body={ name="Hagondes Coat +1", hp=54,mp=59}, + neck="Loricate Torque +1", + ring1={name="Dark Ring",hp=-20,mp=20}, + hands={ name="Hagondes Cuffs +1", augments={'Phys. dmg. taken -3%','Mag. Acc.+23',},hp=30,mp=22}, + lear="Genmei Earring", + } + --[[{ + main="Nirvana", + sub="Bloodrain Strap", + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body="Onca Suit", + hands=empty, + legs=empty, + feet=empty, + neck="Lissome Necklace", + waist="Klouskap Sash", + left_ear="Cessance Earring", + right_ear="Telos Earring", + left_ring="Rajas Ring", + right_ring="Petrov Ring", + back="Umbra Cape", + }]] + + sets.midcast['Garland of Bliss'] = { + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',},mp=56,hp=22}, + body={ name="Merlinic Jubbah", augments={'"Mag.Atk.Bns."+29','Magic burst dmg.+11%','INT+10',},hp=49,mp=67}, + hands={ name="Amalric Gages +1", augments={'MP+80','Mag. Acc.+20','"Mag.Atk.Bns."+20',},hp=13,mp=106}, + legs={ name="Merlinic Shalwar", augments={'Mag. Acc.+25 "Mag.Atk.Bns."+25','MND+4','Mag. Acc.+15','"Mag.Atk.Bns."+12',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", augments={'MP+80','Mag. Acc.+20','"Mag.Atk.Bns."+20',},hp=4,mp=106}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Friomisi Earring", + right_ear="Ishvara Earring", + left_ring="Weather. Ring +1", + right_ring="Shiva Ring +1", + back={name="Pahtli Cape",mp=50}, + } + + sets.midcast['Elemental Magic'] ={ + main="Marin Staff +1", + sub="Enki Strap", + ammo="Pemphredo Tathlum", + head={ name="Merlinic Hood", augments={'VIT+8','"Mag.Atk.Bns."+27','Accuracy+5 Attack+5','Mag. Acc.+18 "Mag.Atk.Bns."+18',},mp=56,hp=22}, + body="Seidr Cotehardie", + hands={ name="Amalric Gages +1", augments={'MP+80','Mag. Acc.+20','"Mag.Atk.Bns."+20',},hp=13,mp=106}, + legs={ name="Merlinic Shalwar", augments={'Mag. Acc.+25 "Mag.Atk.Bns."+25','MND+4','Mag. Acc.+15','"Mag.Atk.Bns."+12',},hp=29,mp=44}, + feet={ name="Amalric Nails +1", augments={'MP+80','Mag. Acc.+20','"Mag.Atk.Bns."+20',},hp=4,mp=106}, + neck={name="Saevus Pendant +1",mp=20}, + waist={name="Yamabuki-no-Obi",mp=35}, + left_ear="Friomisi Earring", + right_ear="Crematio Earring", + left_ring="Shiva Ring +1", + right_ring="Shiva Ring +1", + back="Toro Cape", + Ice={main="Ngqoqwanb"}, + Earth={neck={name="Quanpur Necklace",mp=10}} + } + + sets.midcast.Myrkr={ + ammo={name="Ghastly Tathlum +1",mp=35}, + head={name= "Beckoner's Horn +1",hp=31,mp=134}, + body={name="Beckoner's Doublet +1",hp=54,mp=151}, + hands={ name="Amalric Gages +1", augments={'MP+80','Mag. Acc.+20','"Mag.Atk.Bns."+20',},hp=13,mp=106}, + legs={ name="Psycloth Lappas", augments={'MP+80','Mag. Acc.+15','"Fast Cast"+7',},mp=109,hp=43}, + feet={name="Apogee Pumps +1",hp=-90,mp=121}, + neck={name="Sanctity Necklace",hp=35,mp=35}, + waist={name="Mujin Obi",mp=60}, + left_ear={name="Etiolation Earring",hp=50,mp=50}, + right_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + left_ring={name="Lebeche Ring",mp=40}, + right_ring={name="Sangoma Ring",mp=70}, + back={ name="Conveyance Cape", augments={'Summoning magic skill +5','Pet: Enmity+15','Blood Pact Dmg.+4',},mp=100}, + } + + -- Variables and notes to myself + Debuff_BPs = T{'Diamond Storm','Sleepga','Slowga','Tidal Roar','Shock Squall','Nightmare','Pavor Nocturnus','Ultimate Terror','Somnolence','Lunar Cry','Lunar Roar'} + Magical_BPs = T{'Heavenly Strike','Wind Blade','Holy Mist','Night Terror','Thunderstorm','Geocrush','Meteor Strike','Grand Fall','Lunar Bay','Thunderspark','Nether Blast', + 'Aerial Blast','Searing Light','Diamond Dust','Earthen Fury','Zantetsuken','Tidal Wave','Judgment Bolt','Inferno','Howling Moon','Ruinous Omen'} + Additional_effect_BPs = T{'Rock Throw'} + AvatarList = S{'Shiva','Ramuh','Garuda','Leviathan','Diabolos','Titan','Fenrir','Ifrit','Carbuncle', + 'Fire Spirit','Air Spirit','Ice Spirit','Thunder Spirit','Light Spirit','Dark Spirit','Earth Spirit','Water Spirit', + 'Cait Sith','Alexander','Odin','Atomos'} + send_command('input /macro book 8;wait .1;input /macro set 1') +end + +function pet_change(pet,gain) + equip_aftercast(player.status,pet) + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function pet_status_change(a,b) + windower.add_to_chat(8,'Pet status change: '..tostring(a)..' '..tostring(b)) -- Useful for knowing when you got aggroed +end + +function precast(spell) + if spell.action_type == 'Magic' then + equip(sets.precast.FC) + end + + if sets.precast.FC[spell.element] then equip(sets.precast.FC[spell.element]) end + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function midcast(spell) + if pet_midaction() then + return + end + equip_aftercast(player.status,pet) -- Put DT gear on + if string.find(spell.type,'BloodPact') then + if buffactive['Astral Conduit'] then + pet_midcast(spell) + else + equip(sets.midcast.BP) + end + elseif string.find(spell.english,'Cur') then + equip(sets.midcast.Cur) + elseif sets.midcast[spell.english] then + equip(sets.midcast[spell.english]) + if spell.english == 'Elemental Siphon' then + if pet.element and pet.element == world.day_element and world.day_element ~= "Light" and world.day_element ~= 'Dark' then + equip(sets.midcast['Elemental Siphon'].day) -- Zodiac Ring affects Siphon, but only on Fires-Lightningsday + end + end + elseif spell.skill == 'Elemental Magic' then + equip(sets.midcast['Elemental Magic']) + if sets.midcast['Elemental Magic'][spell.element] then + equip(sets.midcast['Elemental Magic'][spell.element]) + end + if world.day_element == spell.element or world.weather_element == spell.element then + equip({waist="Hachirin-no-Obi"}) + end + elseif spell.skill == 'Enhancing Magic' then + equip(sets.midcast.EnhancingDuration) + end + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function aftercast(spell) + if pet_midaction() then + return + elseif spell and string.find(spell.type,'BloodPact') and not spell.interrupted then + pet_midcast(spell) + else + -- Don't want to swap away too quickly if I'm about to put BP damage gear on + -- Need to wait 1 in order to allow pet information to update on Release. + equip_aftercast(player.status,pet) + end + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function status_change(new,old) + equip_aftercast(new,pet) + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function pet_midcast(spell) + if spell.name == 'Perfect Defense' then + equip(sets.midcast['Elemental Siphon'],{feet="Marduk's Crackows +1"}) + elseif spell.type=='BloodPactWard' then + if Debuff_BPs:contains(spell.name) then + equip(sets.pet_midcast.MAcc_BP) + else + equip(sets.pet_midcast.Buff_BP) + end + elseif spell.type=='BloodPactRage' then + if Magical_BPs:contains(spell.name) or string.find(spell.name,' II') or string.find(spell.name,' IV') then + equip(sets.pet_midcast.MAB_BP) + elseif Additional_effect_BPs:contains(spell.name) then -- for BPs where the additional effect matters more than the damage + equip(sets.pet_midcast.MAcc_BP) + else + equip(sets.pet_midcast.Phys_BP) + end + elseif spell.type=='BlackMagic' then + equip(sets.pet_midcast.MAB_Spell) + end + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function pet_aftercast(spell) + windower.add_to_chat(8,'pet_aftercast: '..tostring(spell.name)) + equip_aftercast(player.status,pet) + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function self_command(command) + if command == 'Idle' then + equip_aftercast('Idle',pet) + end + + if player.mpp > 80 then + set_priorities('mp','hp') + else + set_priorities('hp','mp') + end +end + +function equip_aftercast(status,pet) + if sets.aftercast[status] then + equip(sets.aftercast[status]) + end + if pet.isvalid then + if string.find(pet.name,'Spirit') then + equip(sets.aftercast.Avatar.Spirit) + elseif sets.aftercast.Avatar[pet.name] then + equip(sets.aftercast.Avatar[pet.name]) + end + end + if status == "Engaged" then + equip(sets.aftercast[status]) + end +end + +function set_priorities(key1,key2) + local future,current = gearswap.equip_list,gearswap.equip_list_history + function get_val(piece,key) + if piece and type(piece)=='table' and piece[key] and type(piece[key])=='number' then + return piece[key] + end + return 0 + end + local diff = {} + for i,v in pairs(future) do + local priority = get_val(future[i],key1) - get_val(current[i],key1) + (get_val(future[i],key2) - get_val(current[i],key2)) + if type(v) == 'table' then + future[i].priority = priority + else + future[i] = {name=v,priority=priority} + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_THF.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_THF.lua new file mode 100644 index 0000000..ecc84d6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_THF.lua @@ -0,0 +1,321 @@ +include('organizer-lib') + +function get_sets() + TP_Index = 1 + Idle_Index = 1 + + + ta_hands = {name="Adhemar Wristbands +1"} + acc_hands = {name="Adhemar Wristbands +1"} + wsd_hands = {name="Meg. Gloves +1",} + crit_hands = {name="Adhemar Wristbands +1"} + dt_hands = { name="Herculean Gloves", augments={'Accuracy+30','Damage taken-4%','STR+9','Attack+4',}} + waltz_hands = { name="Herculean Gloves", augments={'Accuracy+22','"Waltz" potency +11%','STR+9',}} + + sets.weapons = {} + sets.weapons[1] = {main="Taming Sari"} + sets.weapons[2]={main="Twashtar"} + sets.weapons[3]={main="Atoyac"} + + sets.JA = {} +-- sets.JA.Conspirator = {body="Raider's Vest +2"} +-- sets.JA.Accomplice = {head="Raider's Bonnet +2"} +-- sets.JA.Collaborator = {head="Raider's Bonnet +2"} + sets.JA['Perfect Dodge'] = {hands="Plun. Armlets +1"} + sets.JA.Steal = {ammo="Barathrum",neck="Pentagalus Charm",hands="Thief's Kote", + waist="Key Ring Belt",legs="Pillager's Culottes +1",feet="Pillager's Poulaines +1"} + sets.JA.Flee = {feet="Pillager's Poulaines +1"} + sets.JA.Despoil = {ammo="Barathrum",legs="Raider's Culottes +2",feet="Skulker's Poulaines"} +-- sets.JA.Mug = {head="Assassin's Bonnet +2"} + sets.JA.Waltz = {head="Anwig Salade", + neck="Unmoving Collar +1", + body="Passion Jacket", + hands=waltz_hands, + ring1="Valseur's Ring", + ring2="Carbuncle Ring +1", + waist="Aristo Belt", + legs="Desultor Tassets", + feet="Dance Shoes" + } + + sets.WS = {} + sets.WS.SA = {} + sets.WS.TA = {} + sets.WS.SATA = {} + + sets.WS.Evisceration = { + ammo="Yetshila", + head="Adhemar Bonnet +1", + body="Abnoba Kaftan", + hands=crit_hands, + legs={ name="Lustr. Subligar +1", augments={'Accuracy+20','DEX+8','Crit. hit rate+3%',}}, + feet="Adhe. Gamashes +1", + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + right_ear="Mache Earring +1", + left_ring="Begrudging Ring", + right_ring="Ramuh Ring +1", + back={ name="Toutatis's Cape", augments={'DEX+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + } + + sets.WS["Rudra's Storm"] = {ammo="Seething Bomblet +1", + head="Lustratio Cap +1", + neck="Caro Necklace", + ear1="Moonshade Earring", + ear2="Ishvara Earring", + body="Adhemar Jacket +1", + hands=wsd_hands, + ring1="Ramuh Ring +1", + ring2="Ramuh Ring +1", + back={ name="Toutatis's Cape", augments={'DEX+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + waist="Grunfeld Rope", + legs="Lustratio Subligar +1", + feet="Lustratio Leggings +1", + } + + sets.WS.SA["Rudra's Storm"] = set_combine(sets.WS["Rudra's Storm"],{ammo="Yetshila", + head="Adhemar Bonnet +1", + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + } + ) + + sets.WS.TA["Rudra's Storm"] = set_combine(sets.WS["Rudra's Storm"],{ammo="Yetshila", + head="Adhemar Bonnet +1", + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + } + ) + + sets.WS["Mandalic Stab"] = sets.WS["Rudra's Storm"] + + sets.WS.SA["Mandalic Stab"] = sets.WS.SA["Rudra's Storm"] + + sets.WS.TA["Mandalic Stab"] = sets.WS.TA["Rudra's Storm"] + + sets.WS.Exenterator = {ammo="Seething Bomblet +1", + head="Meghanada Visor +1",neck="Fotia Gorget",ear1="Steelflash Earring",ear2="Bladeborn Earring", + body="Adhemar Jacket +1", + hands=ta_hands, + right_ring="Ilabrat Ring", + ring2="Epona's Ring", + back={ name="Toutatis's Cape", augments={'DEX+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + waist="Fotia Belt", + legs="Mummu Kecks +1", + feet="Adhemar Gamashes +1" + } + + TP_Set_Names = {"Low Man","Delay Cap","Evasion","TH","Acc","DT"} + sets.TP = {} + sets.TP['Low Man'] = { + ammo="Seething Bomblet +1", + head="Adhemar Bonnet +1", + body="Adhemar Jacket +1", + hands={ name="Floral Gauntlets", augments={'Rng.Acc.+15','Accuracy+15','"Triple Atk."+3','Magic dmg. taken -4%',}}, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Lissome Necklace", + waist="Reiki Yotai", + left_ear="Suppanomimi", + right_ear="Brutal Earring", + left_ring="Hetairoi Ring", + right_ring="Epona's Ring", + back={ name="Toutatis's Cape", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + } + + sets.TP['TH'] = set_combine(sets.TP['Low Man'],{ + hands={ name="Plun. Armlets +1", augments={'Enhances "Perfect Dodge" effect',}}, + feet="Skulker's Poulaines", + }) + + sets.TP['Acc'] = { + ammo="Falcon Eye", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Adhemar Jacket +1", + hands=acc_hands, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Combatant's Torque", + waist="Olseni Belt", + left_ear="Suppanomimi", + right_ear="Telos Earring", + left_ring="Ramuh Ring +1", + right_ring="Ramuh Ring +1", + back={ name="Toutatis's Cape", augments={'DEX+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + } + + sets.TP['Delay Cap'] = { + ammo="Seething Bomblet +1", + head="Adhemar Bonnet +1", + body="Adhemar Jacket +1", + hands=ta_hands, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+25','"Triple Atk."+4','DEX+10',}}, + neck="Lissome Necklace", + waist="Windbuffet Belt +1", + left_ear="Cessance Earring", + right_ear="Telos Earring", + left_ring="Hetairoi Ring", + right_ring="Epona's Ring", + back={ name="Toutatis's Cape", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + } + + sets.TP.Evasion = { + ammo="Yamarang", + head="Adhemar Bonnet +1", + body="Adhemar Jacket +1", + hands=ta_hands, + legs="Mummu Kecks +1", + feet="Adhe. Gamashes +1", + neck="Combatant's Torque", + waist="Kasiri Belt", + left_ear="Eabani Earring", + right_ear="Infused Earring", + left_ring="Vengeful Ring", + right_ring="Epona's Ring", + back={ name="Canny Cape", augments={'DEX+5','"Dual Wield"+5',}}, + } + + sets.TP.DT = { + ammo="Seething Bomblet +1", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Emet Harness +1", + hands=dt_hands, + legs="Mummu Kecks +1", + feet={ name="Herculean Boots", augments={'Accuracy+20 Attack+20','Phys. dmg. taken -5%','DEX+10','Accuracy+6',}}, + neck="Loricate Torque +1", + waist="Reiki Yotai", + left_ear="Suppanomimi", + right_ear="Genmei Earring", + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + back={ name="Canny Cape", augments={'DEX+5','"Dual Wield"+5',}}, + } + + Idle_Set_Names = {'Normal','MDT',"STP"} + sets.Idle = {} + sets.Idle.Normal = { + ammo="Yamarang", + head="Meghanada Visor +1", + body="Emet Harness +1", + hands=dt_hands, + legs="Mummu Kecks +1", + feet="Skd. Jambeaux +1", + neck="Wiglen Gorget", + waist="Kasiri Belt", + left_ear="Etiolation Earring", + right_ear="Genmei Earring", + left_ring="Paguroidea Ring", + right_ring="Sheltered Ring", + back={ name="Toutatis's Cape", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + } + + sets.Idle.MDT = { + ammo="Yamarang", + head={ name="Dampening Tam", augments={'DEX+10','Accuracy+15','Mag. Acc.+15','Quadruple Attack +3',}}, + body="Emet Harness +1", + hands=dt_hands, + legs="Mummu Kecks +1", + feet="Skd. Jambeaux +1", + neck="Loricate Torque +1", + waist="Wanion Belt", + left_ear="Etiolation Earring", + right_ear="Genmei Earring", + left_ring="Defending Ring", + right_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + back="Mollusca Mantle", + } + + sets.Idle['STP'] = { + main="Vajra", + sub="Twashtar", + ammo="Ginsen", + head="Meghanada Visor +1", + body={ name="Herculean Vest", augments={'Accuracy+21','Crit. hit damage +5%','DEX+9',}}, + hands="Adhemar Wristbands +1", + legs={ name="Samnuha Tights", augments={'STR+8','DEX+9','"Dbl.Atk."+3','"Triple Atk."+2',}}, + feet="Skd. Jambeaux +1", + neck="Combatant's Torque", + waist="Goading Belt", + left_ear="Telos Earring", + right_ear="Digni. Earring", + left_ring="Apate Ring", + right_ring="Rajas Ring", + back={ name="Toutatis's Cape", augments={'STR+20','Accuracy+20 Attack+20','"Store TP"+10',}}, + } + + send_command('input /macro book 12;wait .1;input /macro set 2') + + + sets.FastCast = { + ammo="Impatiens", + head={ name="Herculean Helm", augments={'"Fast Cast"+6','Mag. Acc.+2',}}, + body={ name="Taeon Tabard", augments={'Accuracy+22','"Fast Cast"+5','Crit. hit damage +3%',}}, + hands="Leyline Gloves", + legs="Enif Cosciales", + feet={ name="Herculean Boots", augments={'Mag. Acc.+16','"Fast Cast"+6','MND+4',}}, + neck="Orunmila's Torque", + left_ear="Loquac. Earring", + right_ear="Enchntr. Earring +1", + left_ring="Rahab Ring", + right_ring="Weather. Ring +1", + } + + + sets.frenzy = {head="Frenzy Sallet"} +end + +function precast(spell) + if sets.JA[spell.english] then + equip(sets.JA[spell.english]) + elseif spell.type=="WeaponSkill" then + if sets.WS[spell.english] then equip(sets.WS[spell.english]) end + if buffactive['sneak attack'] and buffactive['trick attack'] and sets.WS.SATA[spell.english] then equip(sets.WS.SATA[spell.english]) + elseif buffactive['sneak attack'] and sets.WS.SA[spell.english] then equip(sets.WS.SA[spell.english]) + elseif buffactive['trick attack'] and sets.WS.TA[spell.english] then equip(sets.WS.TA[spell.english]) end + elseif string.find(spell.english,'Waltz') then + equip(sets.JA.Waltz) + elseif spell.action_type == "Magic" then + equip(sets.FastCast) + end +end + +function aftercast(spell) + if player.status=='Engaged' then + equip(sets.TP[TP_Set_Names[TP_Index]]) + else + equip(sets.Idle[Idle_Set_Names[Idle_Index]]) + end +end + +function status_change(new,old) + if T{'Idle','Resting'}:contains(new) then + equip(sets.Idle[Idle_Set_Names[Idle_Index]]) + elseif new == 'Engaged' then + equip(sets.TP[TP_Set_Names[TP_Index]]) + end +end + +function buff_change(buff,gain_or_loss) + if buff=="Sneak Attack" then + soloSA = gain_or_loss + elseif buff=="Trick Attack" then + soloTA = gain_or_loss + elseif gain_or_loss and buff == 'Sleep' and player.hp > 99 then + print('putting on Frenzy sallet!') + equip(sets.frenzy) + end +end + +function self_command(command) + if command == 'toggle TP set' then + TP_Index = TP_Index +1 + if TP_Index > #TP_Set_Names then TP_Index = 1 end + send_command('@input /echo ----- TP Set changed to '..TP_Set_Names[TP_Index]..' -----') + equip(sets.TP[TP_Set_Names[TP_Index]]) + elseif command == 'toggle Idle set' then + Idle_Index = Idle_Index +1 + if Idle_Index > #Idle_Set_Names then Idle_Index = 1 end + send_command('@input /echo ----- Idle Set changed to '..Idle_Set_Names[Idle_Index]..' -----') + equip(sets.Idle[Idle_Set_Names[Idle_Index]]) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_WAR.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_WAR.lua new file mode 100644 index 0000000..fb67ba9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Byrth_WAR.lua @@ -0,0 +1,235 @@ +include('organizer-lib') + +function get_sets() + + sets.JA = {} + sets.JA.Berserk = {body="Pumm. Lorica +1", back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, feet="Agoge Calligae +1"} + sets.JA.Aggressor = {head="Pumm. Mask +1", body="Agoge Lorica +1"} + sets.JA.Warcry = {head="Agoge Mask +1"} + sets.JA['Blood Rage'] = {body="Boii Lorica +1"} + sets.JA['Mighty Strikes'] = {hands="Agoge Mufflers +1"} + sets.JA.Tomahawk = {ammo="Thr. Tomahawk",feet="Agoge Calligae +1"} + sets.JA.Provoke = sets.Enmity + + sets.TP = {} + TP_mode = 'Acc' + sets.TP.Ragnarok = {main="Ragnarok"} + sets.TP.Ukonvasara = {main="Ukonvasara"} + + sets.TP.Normal = { + sub="Bloodrain Strap", + ammo="Ginsen", + head="Flam. Zucchetto +1", + body="Flamma Korazin +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}}, + legs="Jokushu Haidate",--{ name="Odyssean Cuisses", augments={'Attack+30','"Dbl.Atk."+5','AGI+6','Accuracy+10',}}, + feet="Boii Calligae +1", + neck="Lissome Necklace", + waist="Windbuffet Belt +1", + left_ear="Telos Earring", + right_ear="Brutal Earring", + left_ring="Petrov Ring", + right_ring="Rajas Ring", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + } + + sets.TP.Acc = { + ammo="Seeth. Bomblet +1", + head="Flam. Zucchetto +1", + body="Flamma Korazin +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}}, + legs="Jokushu Haidate", + feet="Flam. Gambieras +1", + neck="Combatant's Torque", + waist="Grunfeld Rope", + left_ear="Cessance Earring", + right_ear="Telos Earring", + left_ring="Ramuh Ring +1", + right_ring="Rajas Ring", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + } + + sets.TP.DT = sets.DT + + sets.Enmity = { + ammo="Iron Gobbet", + head="Pummeler's Mask +1", + body="Yorium Cuirass", + hands={ name="Yorium Gauntlets", augments={'Mag. Evasion+8','Enmity+10','Phys. dmg. taken -4%',}}, + legs={ name="Odyssean Cuisses", augments={'Attack+9','Enmity+8',}}, + feet={ name="Eschite Greaves", augments={'HP+80','Enmity+7','Phys. dmg. taken -4',}}, + neck="Unmoving Collar +1", + waist="Goading Belt", + left_ear="Trux Earring", + right_ear="Pluto's Pearl", + left_ring="Eihwaz Ring", + right_ring="Provocare Ring", + back="Impassive Mantle", + } + sets.FC = { + ammo="Impatiens", + body={ name="Odyss. Chestplate", augments={'"Fast Cast"+6'}}, + hands="Leyline Gloves", + feet={ name="Odyssean Greaves", augments={'Accuracy+17','"Fast Cast"+6','Attack+13',}}, + neck="Orunmila's Torque", + left_ear="Loquac. Earring", + right_ear="Enchanter Earring +1", + left_ring="Rahab Ring", + right_ring="Weather. Ring +1", + } + + sets.WS = {} + sets.WS['Raging Rush'] = { + sub="Bloodrain Strap", + ammo="Yetshila", + head={ name="Lustratio Cap +1", augments={'INT+35','STR+8','DEX+8',}}, + body="Flamma Korazin +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}}, + feet={ name="Lustra. Leggings +1", augments={'HP+65','STR+15','DEX+15',}}, + neck="Fotia Gorget", + waist="Metalsinger Belt", + left_ear="Brutal Earring", + right_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + left_ring="Begrudging Ring", + right_ring="Ifrit Ring +1", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + } + + sets.WS["Ukko's Fury"] = { + sub="Bloodrain Strap", + ammo="Yetshila", + head={ name="Lustratio Cap +1", augments={'INT+35','STR+8','DEX+8',}}, + body="Flamma Korazin +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}}, + legs={ name="Valor. Hose", augments={'Accuracy+27','Crit. hit damage +5%','DEX+3',}}, + feet={ name="Lustra. Leggings +1", augments={'HP+65','STR+15','DEX+15',}}, + neck="Fotia Gorget", + waist="Windbuffet Belt +1", + left_ear="Brutal Earring", + right_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + left_ring="Begrudging Ring", + right_ring="Ifrit Ring +1", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + } + + sets.WS["Fell Cleave"] = { + ammo="Seeth. Bomblet +1", + head={ name="Lustratio Cap +1", augments={'INT+35','STR+8','DEX+8',}}, + body="Sulevia's Plate. +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+23','"Dbl.Atk."+5','STR+9','Accuracy+15',}}, + legs="Sulevi. Cuisses +1", + feet={ name="Lustra. Leggings +1", augments={'HP+65','STR+15','DEX+15',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + left_ear="Ishvara Earring", + right_ear="Brutal Earring", + left_ring="Ifrit Ring +1", + right_ring="Rajas Ring", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','Weapon skill damage +10%',}}, + } + + sets.WS.Resolution = { + ammo="Seeth. Bomblet +1", + head={ name="Argosy Celata +1", augments={'STR+12','DEX+12','Attack+20',}}, + body="Flamma Korazin +1", + hands={ name="Argosy Mufflers +1", augments={'STR+12','DEX+12','Attack+20',}}, + legs={ name="Argosy Breeches +1", augments={'STR+12','DEX+12','Attack+20',}}, + feet={ name="Argosy Sollerets +1", augments={'STR+12','DEX+12','Attack+20',}}, + neck="Fotia Gorget", + waist="Fotia Belt", + right_ear="Telos Earring", + left_ear={ name="Moonshade Earring", augments={'Attack+4','TP Bonus +25',}}, + left_ring="Rajas Ring", + right_ring="Apate Ring", + back={ name="Cichol's Mantle", augments={'STR+20','Accuracy+20 Attack+20','"Dbl.Atk."+10',}}, + Gavialis = S{'Lightsday','Earthsday','Windsday','Firesday','Lightningsday'}, + } + + sets.WS.Gavialis = {head="Gavialis Helm"} + + sets.Idle = { + ammo="Staunch Tathlum", + head="Sulevia's Mask +1", + body="Sulevia's Plate. +1", + hands={ name="Odyssean Gauntlets", augments={'Attack+29','Damage taken-4%','AGI+3',}}, + legs="Sulevi. Cuisses +1", + feet="Hermes' Sandals +1", + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear="Etiolation Earring", + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + right_ring="Defending Ring", + back="Impassive Mantle", + } + + sets.DT = { + ammo="Staunch Tathlum", + head="Sulevia's Mask +1", + body="Sulevia's Plate. +1", + hands="Souveran Handschuhs +1", + legs="Sulevi. Cuisses +1", + feet={ name="Souveran Schuhs +1", augments={'HP+105','Enmity+9','Potency of "Cure" effect received +15%',}}, + neck="Loricate Torque +1", + waist="Flume Belt +1", + left_ear="Genmei Earring", + right_ear="Etiolation Earring", + left_ring={ name="Dark Ring", augments={'Breath dmg. taken -4%','Phys. dmg. taken -6%','Magic dmg. taken -5%',}}, + right_ring="Defending Ring", + back="Impassive Mantle", + } + + sets.TP.DT = sets.DT + + send_command('input /macro book 6;wait .1;input /macro set 2') +end + +function precast(spell) + if spell.cast_time then + equip(sets.FC) + end +end + +function midcast(spell) + if sets.JA[spell.english] then + equip(sets.JA[spell.english]) + elseif sets.WS[spell.english] then + equip(sets.WS[spell.english]) + if sets.WS[spell.english].Gavialis and sets.WS[spell.english].Gavialis[world.day] then + equip(sets.WS.Gavialis) + end + end +end + +function aftercast(spell) + if player.status == 'Engaged' then + equip(sets.TP[TP_mode]) + else + equip(sets.Idle) + end +end + +function status_change(new,old) + if T{'Idle','Resting'}:contains(new) then + equip(sets.Idle) + elseif new == 'Engaged' then + equip(sets.TP[TP_mode]) + end +end + +function self_command(command) + if command == 'DT' then + equip(sets.DT) + elseif command == 'TP' then + if TP_mode=="Acc" then + TP_mode="Normal" + elseif TP_mode=="Normal" then + TP_mode="DT" + elseif TP_mode=="DT" then + TP_mode='Acc' + end + windower.add_to_chat('TP mode is now: '..TP_mode) + equip(sets.TP[TP_mode]) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Snprphnx_SCH.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Snprphnx_SCH.lua new file mode 100644 index 0000000..b83b3fe --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Snprphnx_SCH.lua @@ -0,0 +1,256 @@ +function get_sets() + + sets.aftercast_Idle_noSub = {main="Owleyes",sub="Genbu's Shield",ammo="Incantor Stone", + head="Savant's bonnet +2",neck="Twilight Torque",ear1="Lifestorm Earring",ear2="Loquacious Earring", + body="Hagondes Coat",hands="Serpentes Cuffs",ring1="Sangoma Ring",ring2="Maquette Ring", + back="Shadow Mantle",waist="Korin Obi",legs="Nares Trews",feet="Serpentes Sabots"} + + sets.aftercast_Idle_Sub = {main="Owleyes",sub="Genbu's Shield",ammo="Incantor Stone", + head="Savant's bonnet +2",neck="Twilight Torque",ear1="Savant's Earring",ear2="Loquacious Earring", + body="Hagondes Coat",hands="Serpentes Cuffs",ring1="Sangoma Ring",ring2="Maquette Ring", + back="Shadow Mantle",waist="Korin Obi",legs="Nares Trews",feet="Serpentes Sabots"} + + sets.aftercast_Idle = sets.aftercast_Idle_noSub + + sets.precast_FastCast = {ammo="Incantor Stone",head="Nahtirah Hat",ear2="Loquacious Earring", + body="Anhur Robe",hands="Gendewitha Gages",back="Swith Cape",legs="Orvail Pants",feet="Chelona Boots"} + + sets.Resting = {main="Chatoyant Staff",sub="Mephitis Grip", + head="Nahtirah Hat",neck="Twilight Torque",ear1="Lifestorm Earring",ear2="Loquacious Earring", + body="Hagondes Coat",hands="Nares Cuffs",ring1="Sangoma Ring",ring2="Maquette Ring", + back="Shadow Mantle",waist="Korin Obi",legs="Nares Trews",feet="Chelona Boots"} + + sets.midcast_ElementalMagic = {main="Atinian Staff",sub="Wizzan Grip",ammo="Witchstone", + head="Nares Cap",neck="Eddy Necklace",ear1="Hecate's Earring",ear2="Novio Earring", + body="Hagondes Coat",hands="Yaoyotl Gloves",ring1="Strendu Ring",ring2="Icesoul Ring", + back="Searing Cape",waist="Maniacus Sash",legs="Hagondes Pants",feet="Bokwus Boots"} + + sets.midcast_DarkMagic = {main="Chatoyant Staff",sub="Arbuda Grip",ammo="Hasty Pinion +1", + head="Appetence Crown",neck="Aesir Torque",ear1="Hirudinea Earring",ear2="Loquacious Earring", + body="Hedera Cotehardie",hands="Ayao's Gages",ring1="Balrahn's Ring",ring2="Excelsis Ring", + back="Merciful Cape",waist="Goading Belt",legs="Auspex Slops",feet="Bokwus Boots"} + + sets.midcast_EnfeeblingMagic = {main="Atinian Staff",sub="Mephitis Grip",ammo="Savant's Treatise", + head="Nahtirah Hat",neck="Eddy Necklace",ear1="Lifestorm Earring",ear2="Psystorm Earring", + body="Hedera Cotehardie",hands="Hagondes Cuffs",ring1="Sangoma Ring",ring2="Maquette Ring", + back="Merciful Cape",waist="Cascade Belt",legs="Orvail Pants",feet="Rubeus Boots"} + + sets.midcast_Impact = {main="Atinian Staff",sub="Wizzan Grip",ammo="Witchstone", + neck="Eddy Necklace",ear1="Hecate's Earring",ear2="Novio Earring", + hands="Yaoyotl Gloves",ring1="Strendu Ring",ring2="Icesoul Ring", + back="Searing Cape",waist="Maniacus Sash",legs="Hagondes Pants",feet="Bokwus Boots"} + + sets.midcast_Embrava = {main="Kirin's Pole",sub="Fulcio Grip",ammo="Savant's Treatise", + head="Svnt. Bonnet +2",neck="Colossus's Torque",ear1="Lifestorm Earring",ear2="Loquacious Earring", + body="Anhur Robe",hands="Savant's Bracers +2", + back="Merciful Cape",waist="Cascade Belt",legs="Shedir Seraweels",feet="Rubeus Boots"} + + sets.midcast_EnhancingMagic = {main="Kirin's Pole",sub="Fulcio Grip",ammo="Incantor Stone", + head="Nahtirah Hat",neck="Colossus's Torque",ear1="Lifestorm Earring",ear2="Loquacious Earring", + body="Anhur Robe",hands="Gendewitha Gages", + back="Swith Cape",waist="Ninurta's Sash",legs="Orvail Pants",feet="Chelona Boots"} + + sets.precast_Stun = {main="Apamajas II",sub="Mephitis Grip",ranged="Aureole", + head="Nahtirah Hat",neck="Eddy Necklace",ear1="Lifestorm Earring",ear2="Psystorm Earring", + body="Hedera Cotehardie",hands="Gendewitha Gages",ring1="Sangoma Ring",ring2="Maquette Ring", + back="Swith Cape",waist="Ninurta's Sash",legs="Bokwus Slops",feet="Argute Loafers +2"} + + + sets.midcast_Cure = {main="Chatoyant Staff",head="Paean Mitra",neck="Phalaina Locket", + body="Anhur Robe",hands="Bokwus Gloves",back="Oretanis's Cape",legs="Nares Trews"} + + sets.midcast_Helix = {main="Chatoyant Staff",sub="Wizzan Grip",ammo="Snow Sachet", + head="Nahtirah Hat",neck="Stoicheion Medal",ear1="Hecate's Earring",ear2="Novio Earring", + body="Nares Saio",hands="Nares Cuffs",ring1="Icesoul Ring",ring2="Icesoul Ring", + back="Twilight Cape",waist="Wanion Belt",legs="Akasha Chaps",feet="Nares Clogs"} + + sets.midcast_Stoneskin = {main="Kirin's Pole",neck="Stone Gorget",waist="Siegel Sash",legs="Shedir Seraweels"} + + sets.Obi = {} + sets.Obi.Fire = {waist='Karin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Earth = {waist='Dorin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Water = {waist='Suirin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Wind = {waist='Furin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Ice = {waist='Hyorin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Thunder = {waist='Rairin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Light = {waist='Korin Obi',back='Twilight Cape',lring='Zodiac Ring'} + sets.Obi.Dark = {waist='Anrin Obi',back='Twilight Cape',lring='Zodiac Ring'} + + sets.staves = {} + + sets.staves.damage = {} + sets.staves.damage.Thunder = {main="Apamajas I"} + sets.staves.damage.Fire = {main="Atar I"} + + sets.staves.accuracy = {} + sets.staves.damage.Thunder = {main="Apamajas II"} + sets.staves.damage.Ice = {main="Vourukasha II"} + + stuntarg = 'Shantotto' + + +end + +function precast(spell) + if spell.english == 'Impact' then + equip(sets['precast_FastCast'],{body="Twilight Cloak"}) + if not buffactive['elemental seal'] then + add_to_chat(8,'--------- Elemental Seal is down ---------') + end + + elseif spell.skill=='ElementalMagic' and spell.cast_time < 3 then + equip(sets.midcast_ElementalMagic) + if spell.element == 'Earth' then + equip({neck="Quanpur Necklace"}) + end + if spell.element == world.weather_element or spell_element == world.day_element and sets.Obi[spell.element] then + equip(sets.Obi[spell.element]) + end + elseif spell.english == 'Stun' then + equip(sets['precast_Stun']) + if not buffactive.thunderstorm then + add_to_chat(8,'--------- Thunderstorm is down ---------') + elseif not buffactive.klimaform then + add_to_chat(8,'----------- Klimaform is down -----------') + end + if stuntarg ~= 'Shantotto' then + send_command('@input /t '..stuntarg..' ---- Byrth Stunned!!! ---- ') + end + else + equip(sets['precast_FastCast']) + end + + if (buffactive.alacrity or buffactive.celerity) and world.weather_element == spell.element then + equip({feet='Argute Loafers +2'}) + end +end + +function midcast(spell) + if string.find(spell.english,'Cur') then + equip(sets.midcast_Cure) + if spell.element == world.weather_element or spell_element == world.day_element then + equip({main="Chatoyant Staff"},sets.Obi[spell.element]) + end + if buffactive.rapture then + equip({head="Savant's Bonnet +2"}) + end + elseif spell.english == 'Impact' then + local tempset = sets['midcast_Impact'] + tempset['body'] = 'Twilight Cloak' + tempset['head'] = empty + equip(tempset) + if spell.element == world.weather_element or spell_element == world.day_element then + equip(sets.Obi[spell.element]) + end + if sets.staves.damage[spell.element] then + equip(sets.staves.damage[spell.element]) + end + elseif spell.skill=="ElementalMagic" then + if string.find(spell.english,'helix') then + equip(sets['midcast_Helix']) + else + equip(sets.midcast_ElementalMagic) + if spell.element=='Earth' then + equip({neck="Quanpur Necklace"}) + end + if spell.element == world.weather_element or spell_element == world.day_element then + equip(sets.Obi[spell.element]) + end + end + if buffactive.ebullience then + equip({head="Savant's Bonnet +2"}) + end + if buffactive.klimform then + equip ({feet="Savant's Loafers +2"}) + end + + elseif spell.english == 'Stoneskin' then + equip(sets['midcast_Stoneskin']) + elseif spell.skill == 'EnhancingMagic' then + if spell.english == 'Embrava' then + equip(sets['midcast_Embrava']) + if not buffactive.perpetuance then + add_to_chat(8,'--------- Perpetuance is down ---------') + end + if not buffactive.accession then + add_to_chat(8,'--------- Accession is down ---------') + end + if not buffactive.penury then + add_to_chat(8,'--------- Penury is down ---------') + end + end + if buffactive.perpetuance then + equip(sets['midcast_EnhancingMagic'],{hands="Savant's Bracers +2"}) + else + equip(sets['midcast_EnhancingMagic']) + end + else + weathercheck(spell.element,sets['midcast_'..spell.skill]) + end + + if spell.english == 'Sneak' then + send_command('@wait 1.8;cancel 71;') + end +end + +function aftercast(spell) + equip(sets['aftercast_Idle']) + + if spell.english == 'Sleep' or spell.english == 'Sleepga' then + send_command('@wait 50;input /echo ------- '..spell.english..' is wearing off in 10 seconds -------') + elseif spell.english == 'Sleep II' or spell.english == 'Sleepga II' then + send_command('@wait 80;input /echo ------- '..spell.english..' is wearing off in 10 seconds -------') + elseif spell.english == 'Break' or spell.english == 'Breakga' then + send_command('@wait 20;input /echo ------- '..spell.english..' is wearing off in 10 seconds -------') + end +end + +function status_change(new,tab) + if new == 'Resting' then + equip(sets['Resting']) + else + equip(sets['aftercast_Idle']) + end +end + +function buff_change(status,gain_or_loss) + if status == 'Sublimation: Complete' and gain_or_loss and not 'stunmode' then -- True whether gained or lost + sets.aftercast_Idle = sets.aftercast_Idle_noSub + elseif status == 'Sublimation: Activated' and gain_or_loss and not 'stunmode' then + sets.aftercast_Idle = sets.aftercast_Idle_Sub + end + equip(sets.aftercast_Idle) +end + + + +function self_command(command) + if command == 'stuntarg' then + stuntarg = target.name + elseif command == 'stunmode' then + windower.add_to_chat(100,'Stun Mode') + if sets.aftercast_Idle ~= sets.precast_Stun then + stunmode = true + sets.aftercast_Idle = sets.precast_Stun + elseif buffactive['Sublimation: Activated'] then + stunmode = false + sets.aftercast_Idle = sets.aftercast_Idle_Sub + else + stunmode = false + sets.aftercast_Idle = sets.aftercast_Idle_noSub + end + equip(sets.aftercast_Idle) + end +end + + + +-- This function is user defined, but never called by GearSwap itself. It's just a user function that's only called from user functions. I wanted to check the weather and equip a weather-based set for some spells, so it made sense to make a function for it instead of replicating the conditional in multiple places. + +function weathercheck(spell_element,set) + if spell_element == world.weather_element or spell_element == world.day_element then + equip(set,sets['Obi_'..spell_element]) + else + equip(set) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Variables.xlsx b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Variables.xlsx Binary files differnew file mode 100644 index 0000000..e5846c1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/beta_examples_and_information/Variables.xlsx diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/data/Instructions.txt b/Data/DefaultContent/Libraries/addons/addons/GearSwap/data/Instructions.txt new file mode 100644 index 0000000..8ff2d45 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/data/Instructions.txt @@ -0,0 +1,6 @@ +This is where user .lua files go. Check out the beta examples and instructions for more information. + +Be aware of four things: +1) If Gearswap fails to execute a command and does not detect this, it will currently block all input until it is reloaded. +2) verify_equip() is not very robust, and in Delve it tends to not be usable. cast_delay() is recommended instead. +3) Logging is on at the moment, so that's what the log files are. You can turn it off towards the top of gearswap.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/equip_processing.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/equip_processing.lua new file mode 100644 index 0000000..bb6b861 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/equip_processing.lua @@ -0,0 +1,292 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +----------------------------------------------------------------------------------- +--Name: check_wearable(item_id) +--Args: +---- item_id - Item ID to be examined +----------------------------------------------------------------------------------- +--Returns: +---- boolean indicating whether the given piece of gear can be worn or not +---- Checks for main job, level, superior level, and gender/race +----------------------------------------------------------------------------------- +function check_wearable(item_id) + if not item_id or item_id == 0 then -- 0 codes for an empty slot, but Arcon will probably make it nil at some point + elseif not res.items[item_id] then + msg.debugging("Item "..item_id.." has not been added to resources yet.") + elseif not res.items[item_id].jobs then -- Make sure item can be equipped by specific jobs (unlike pearlsacks). + --msg.debugging('GearSwap (Debug Mode): Item '..(res.items[item_id][language] or item_id)..' does not have a jobs field in the resources.') + elseif not res.items[item_id].slots then + -- Item is not equippable + else + return (res.items[item_id].jobs[player.main_job_id]) and (res.items[item_id].level<=player.jobs[res.jobs[player.main_job_id].ens]) and (res.items[item_id].races[player.race_id]) and + (player.superior_level >= (res.items[item_id].superior_level or 0)) + end + return false +end + +----------------------------------------------------------------------------------- +--Name: name_match(item_id,name) +--Args: +---- item_id - Item ID to be compared +---- name - Name to be compared +----------------------------------------------------------------------------------- +--Returns: +---- boolean indicating whether the name matches the resources entry for the itemID +----------------------------------------------------------------------------------- +function name_match(item_id,name) + if res.items[item_id] then + return (res.items[item_id][language..'_log']:lower() == name:lower() or res.items[item_id][language]:lower() == name:lower()) + else + return false + end +end + +----------------------------------------------------------------------------------- +--Name: expand_entry(v) +--Args: +---- entry - Table or string ostensibly from an equipment set +----------------------------------------------------------------------------------- +--Returns: +---- name - Name of the current piece of equipment +---- priority - Priority of the current piece as defined in the advanced table +---- augments - Augments for the current piece as defined in the advanced table +---- designated_bag - Bag for the current piece as defined in the advanced table +----------------------------------------------------------------------------------- +function expand_entry(entry) + if not entry then + return + end + local augments,name,priority,designated_bag + if type(entry) == 'table' and entry == empty then + name = empty + elseif type(entry) == 'table' and entry.name and type(entry.name) == 'string' then + name = entry.name + priority = entry.priority + if entry.augments then + augments = entry.augments + elseif entry.augment then + augments = {entry.augment} + end + if entry.bag and type(entry.bag) == 'string' then + designated_bag = bag_string_lookup[to_windower_bag_api(entry.bag)] + end + elseif type(entry) == 'string' and entry ~= '' then + name = entry + end + return name,priority,augments,designated_bag -- all nil if they don't exist +end + +----------------------------------------------------------------------------------- +--Name: unpack_equip_list(inventory,equip_list) +--Args: +---- inventory - Current inventory (potentially avoids a get_items() call) +---- equip_list - Keys are standard slot names, values are item names. +----------------------------------------------------------------------------------- +--Returns: +---- Table with keys that are slot numbers with values that are inventory slot #s. +----------------------------------------------------------------------------------- +function unpack_equip_list(equip_list,cur_equip) + local ret_list = {} -- Gear that is designated to be equipped + local used_list = {} -- Gear that is scheduled to be equipped but is already being worn + local error_list = {} -- Gear that cannot be equipped for whatever reason + local priorities = Priorities:new() + for slot_id,slot_name in pairs(default_slot_map) do + local name,priority,augments,designated_bag = expand_entry(equip_list[slot_name]) + priorities[slot_id] = priority + if name == empty then + equip_list[slot_name] = nil + if cur_equip[slot_name].slot ~= empty then + ret_list[slot_id] = {bag_id=0,slot=empty} + end + elseif name and cur_equip[slot_name].slot ~= empty then + local item_tab = items[to_windower_bag_api(res.bags[cur_equip[slot_name].bag_id].en)][cur_equip[slot_name].slot] + if name_match(item_tab.id,name) and + (not augments or (#augments ~= 0 and extdata.compare_augments(augments,extdata.decode(item_tab).augments))) and + (not designated_bag or designated_bag == cur_equip[slot_name].bag_id) then + equip_list[slot_name] = nil + used_list[slot_id] = {bag_id=cur_equip[slot_name].bag_id,slot=cur_equip[slot_name].slot} + end + end + end + + for _,bag in pairs(equippable_item_bags) do + for _,item_tab in ipairs(items[to_windower_bag_api(bag.en)]) do -- Iterate over the current bag + if type(item_tab) == 'table' and check_wearable(item_tab.id) then + if item_tab.status == 0 or item_tab.status == 5 then + for slot_id in res.items[item_tab.id].slots:it() do + local slot_name = default_slot_map[slot_id] + -- equip_list[slot_name] can also be a table (that doesn't contain a "name" property) or a number, which are both cases that should not generate any kind of equipment changing. + -- Hence the "and name" below. + if not ret_list[slot_id] and equip_list[slot_name] then -- If we haven't already found something for this slot and still want to equip something there + -- Make sure we're not already planning to equip this item in another slot. + if (slot_id == 0 and used_list[1] and used_list[1].bag_id == bag.id and used_list[1].slot == item_tab.slot) or -- main vs. sub + (slot_id == 1 and used_list[0] and used_list[0].bag_id == bag.id and used_list[0].slot == item_tab.slot) or -- sub vs. main + (slot_id == 11 and used_list[12] and used_list[12].bag_id == bag.id and used_list[12].slot == item_tab.slot) or --left_earring vs. right_earring + (slot_id == 12 and used_list[11] and used_list[11].bag_id == bag.id and used_list[11].slot == item_tab.slot) or --right_earring vs. left_earring + (slot_id == 13 and used_list[14] and used_list[14].bag_id == bag.id and used_list[14].slot == item_tab.slot) or --left_ring vs. right_ring + (slot_id == 14 and used_list[13] and used_list[13].bag_id == bag.id and used_list[13].slot == item_tab.slot) then --right_ring vs. left_ring + break + end + local name,priority,augments,designated_bag = expand_entry(equip_list[slot_name]) + + if (not designated_bag or designated_bag == bag.id) and name and name_match(item_tab.id,name) then + if augments and #augments ~= 0 then + if res.items[item_tab.id].flags.Rare or extdata.compare_augments(augments,extdata.decode(item_tab).augments) then + -- Check if the augments are right + -- If the item is Rare, then even if the augments are wrong try to equip it anyway because you only have one + equip_list[slot_name] = nil + ret_list[slot_id] = {bag_id=bag.id,slot=item_tab.slot} + used_list = ret_list[slot_id] + break + --else the piece specifies augments that don't match the current piece, so don't break and keep trying. + end + else + equip_list[slot_name] = nil + ret_list[slot_id] = {bag_id=bag.id,slot=item_tab.slot} + used_list = ret_list[slot_id] + break + end + end + end + end + else -- item_tab.status > 0 + for slot_id in res.items[item_tab.id].slots:it() do + local slot_name = default_slot_map[slot_id] + local name = expand_entry(equip_list[slot_name]) + if name and name ~= empty then -- If "name" isn't a piece of gear, then it won't have a valid value at this point and should be ignored. + if name_match(item_tab.id,name) then + if item_tab.status == 25 then + error_list[slot_name] = name..' (bazaared)' + else + error_list[slot_name] = name..' (status unknown: '..item_tab.status..' )' + end + break + end + end + end + end + else + for __,slot_name in pairs(default_slot_map) do + local name = expand_entry(equip_list[slot_name]) + if name ~= empty and name_match(item_id,name) then + if not res.items[item_tab.id].jobs[player.main_job_id] then + equip_list[slot_name] = nil + error_list[slot_name] = name..' (cannot be worn by this job)' + elseif not (res.items[item_tab.id].level<=player.jobs[player.main_job]) then + equip_list[slot_name] = nil + error_list[slot_name] = name..' (job level is too low)' + elseif not res.items[item_tab.id].races[player.race_id] then + equip_list[slot_name] = nil + error_list[slot_name] = name..' (cannot be worn by your race)' + elseif not res.items[item_tab.id].slots then + equip_list[slot_name] = nil + error_list[slot_name] = name..' (cannot be worn)' + end + break + end + end + end + end + end + + if _settings.debug_mode and table.length(error_list) > 0 then + print_set(error_list,'Debug Mode (error list)') + end + if _settings.debug_mode and table.length(equip_list) > 0 then + print_set(equip_list,'Debug Mode (gear not equipped)') + end + + return ret_list,priorities +end + +----------------------------------------------------------------------------------- +--Name: to_names_set(equipment) +--Args: +---- equipment - Mapping of equipment slot ID or slot name to a table containing +---- bag_id and inventory slot ID. If already indexed to a number, treat it as a slot index. +---- Otherwise, damn the torpedoes and tostring it. +----------------------------------------------------------------------------------- +--Returns: +---- Set with a mapping of slot name to equipment name. +---- 'empty' is used as a replacement for the empty table. +----------------------------------------------------------------------------------- +function to_names_set(equipment) + local equip_package = {} + + for ind,cur_item in pairs(equipment) do + local name = 'empty' + if type(cur_item) == 'table' and cur_item.slot ~= empty then + if items[to_bag_api(res.bags[cur_item.bag_id].english)][cur_item.slot].id == 0 then return {} end + -- refresh_player() can run after equip packets arrive but before the item array is fully loaded, + -- which results in the id still being the initialization value. + name = res.items[items[to_bag_api(res.bags[cur_item.bag_id].english)][cur_item.slot].id][language] + end + + if tonumber(ind) and ind >= 0 and ind <= 15 and math.floor(ind) == ind then + equip_package[toslotname(ind)] = name + else + equip_package[tostring(ind)] = name + end + end + + return equip_package +end + + +----------------------------------------------------------------------------------- +--Name: equip_piece(eq_slot_id,bag_id,inv_slot_id) +--Desc: Cleans up the global table and leaves equip_sets properly. +--Args: +---- eq_slot_id - Equipment Slot ID +---- bag_id - Bag ID of the item to be equipped +---- inv_slot_id - Inventory Slot ID of the item to be equipped +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function equip_piece(eq_slot_id,bag_id,inv_slot_id) + -- Many complicated, wow! + local cur_eq_tab = items.equipment[toslotname(eq_slot_id)] + + if cur_eq_tab.slot ~= empty then + items[to_bag_api(res.bags[cur_eq_tab.bag_id].english)][cur_eq_tab.slot].status = 0 + -- This does not account for items like Onca Suit which take up multiple slots + end + + if inv_slot_id ~= empty then + --items.equipment[toslotname(eq_slot_id)] = {slot=inv_slot_id,bag_id=bag_id} + items[to_bag_api(res.bags[bag_id].english)][inv_slot_id].status = 5 + local minichunk = string.char(inv_slot_id,eq_slot_id,bag_id,0) + injected_equipment_registry[minichunk:byte(2)]:append(minichunk:sub(1,3)) + return minichunk + else + --items.equipment[toslotname(eq_slot_id)] = {slot=empty,bag_id=0} + local minichunk = string.char(0,eq_slot_id,0,0) + injected_equipment_registry[minichunk:byte(2)]:append(minichunk:sub(1,3)) + return minichunk + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/export.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/export.lua new file mode 100644 index 0000000..454bab4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/export.lua @@ -0,0 +1,317 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +function export_set(options) + local item_list = T{} + local targinv,all_items,xml,all_sets,use_job_in_filename,use_subjob_in_filename,overwrite_existing,named_file + if #options > 0 then + for _,v in ipairs(options) do + if S{'inventory','inv','i'}:contains(v:lower()) then + targinv = true + elseif v:lower() == 'all' then + all_items = true + elseif v:lower() == 'wearable' then + wearable = true + elseif S{'xml'}:contains(v:lower()) then + xml = true + elseif S{'sets','set','s'}:contains(v:lower()) then + all_sets = true + if not user_env or not user_env.sets then + msg.addon_msg(123,'Cannot export the sets table of the current file because there is no file loaded.') + return + end + elseif v:lower() == 'mainjob' then + use_job_in_filename = true + elseif v:lower() == 'mainsubjob' then + use_subjob_in_filename = true + elseif v:lower() == 'overwrite' then + overwrite_existing = true + elseif S{'filename','file','f'}:contains(v:lower()) then + named_file = true + else + if named_file then + filename = v + end + end + end + end + + local buildmsg = 'Exporting ' + if all_items then + buildmsg = buildmsg..'all your items' + elseif wearable then + buildmsg = buildmsg..'all your items in inventory and wardrobes' + elseif targinv then + buildmsg = buildmsg..'your current inventory' + elseif all_sets then + buildmsg = buildmsg..'your current sets table' + else + buildmsg = buildmsg..'your currently equipped gear' + end + + if xml then + buildmsg = buildmsg..' as an xml file.' + else + buildmsg = buildmsg..' as a lua file.' + end + + if use_job_in_filename then + buildmsg = buildmsg..' (Naming format: Character_JOB)' + elseif use_subjob_in_filename then + buildmsg = buildmsg..' (Naming format: Character_JOB_SUB)' + elseif named_file then + buildmsg = buildmsg..' (Named: Character_'..filename..')' + end + + if overwrite_existing then + buildmsg = buildmsg..' Will overwrite existing files with same name.' + end + + msg.addon_msg(123,buildmsg) + + if not windower.dir_exists(windower.addon_path..'data/export') then + windower.create_dir(windower.addon_path..'data/export') + end + + if all_items then + for i = 0, #res.bags do + item_list:extend(get_item_list(items[res.bags[i].english:gsub(' ', ''):lower()])) + end + elseif wearable then + for _, v in pairs(equippable_item_bags) do + item_list:extend(get_item_list(items[v.english:gsub(' ', ''):lower()])) + end + elseif targinv then + item_list:extend(get_item_list(items.inventory)) + elseif all_sets then + -- Iterate through user_env.sets and find all the gear. + item_list,exported = unpack_names({},'L1',user_env.sets,{},{empty=true}) + else + -- Default to loading the currently worn gear. + + for i = 1,16 do -- ipairs will be used on item_list + if not item_list[i] then + item_list[i] = {} + item_list[i].name = empty + item_list[i].slot = toslotname(i-1) + end + end + + for slot_name,gs_item_tab in pairs(items.equipment) do + if gs_item_tab.slot ~= empty then + local item_tab + local bag_name = to_windower_bag_api(res.bags[gs_item_tab.bag_id].en) + if res.items[items[bag_name][gs_item_tab.slot].id] then + item_tab = items[bag_name][gs_item_tab.slot] + item_list[slot_map[slot_name]+1] = { + name = res.items[item_tab.id][language], + slot = slot_name + } + if not xml then + local augments = extdata.decode(item_tab).augments or {} + local aug_str = '' + for aug_ind,augment in pairs(augments) do + if augment ~= 'none' then aug_str = aug_str.."'"..augment:gsub("'","\\'").."'," end + end + if string.len(aug_str) > 0 then + item_list[slot_map[slot_name]+1].augments = aug_str + end + end + else + msg.addon_msg(123,'You are wearing an item that is not in the resources yet.') + end + end + end + end + + if #item_list == 0 then + msg.addon_msg(123,'There is nothing to export.') + return + else + local not_empty + for i,v in pairs(item_list) do + if v.name ~= empty then + not_empty = true + break + end + end + if not not_empty then + msg.addon_msg(123,'There is nothing to export.') + return + end + end + + + if not windower.dir_exists(windower.addon_path..'data/export') then + windower.create_dir(windower.addon_path..'data/export') + end + + local path = windower.addon_path..'data/export/'..player.name + + if use_job_in_filename then + path = path..'_'..windower.ffxi.get_player().main_job + elseif use_subjob_in_filename then + path = path..'_'..windower.ffxi.get_player().main_job..'_'..windower.ffxi.get_player().sub_job + elseif named_file then + path = path..'_'..filename + else + path = path..os.date(' %Y-%m-%d %H-%M-%S') + end + if xml then + -- Export in .xml + if (not overwrite_existing) and windower.file_exists(path..'.xml') then + path = path..' '..os.clock() + end + local f = io.open(path..'.xml','w+') + f:write('<spellcast>\n <sets>\n <group name="exported">\n <set name="exported">\n') + for i,v in ipairs(item_list) do + if v.name ~= empty then + local slot = xmlify(tostring(v.slot)) + local name = xmlify(tostring(v.name)) + f:write(' <'..slot..'>'..name..'</'..slot..'>\n') + end + end + f:write(' </set>\n </group>\n </sets>\n</spellcast>') + f:close() + else + -- Default to exporting in .lua + if (not overwrite_existing) and windower.file_exists(path..'.lua') then + path = path..' '..os.clock() + end + local f = io.open(path..'.lua','w+') + f:write('sets.exported={\n') + for i,v in ipairs(item_list) do + if v.name ~= empty then + if v.augments then + --Advanced set table + f:write(' '..v.slot..'={ name="'..v.name..'", augments={'..v.augments..'}},\n') + else + f:write(' '..v.slot..'="'..v.name..'",\n') + end + end + end + f:write('}') + f:close() + end +end + +function unpack_names(ret_tab,up,tab_level,unpacked_table,exported) + for i,v in pairs(tab_level) do + local flag,alt + if type(v)=='table' and i ~= 'augments' and not ret_tab[tostring(tab_level[i])] then + ret_tab[tostring(tab_level[i])] = true + unpacked_table,exported = unpack_names(ret_tab,i,v,unpacked_table,exported) + elseif i=='name' and type(v) == 'string' then + alt = up + flag = true + elseif type(v) == 'string' and v~='augment' and v~= 'augments' and v~= 'priority' then + alt = i + flag = true + end + if flag then + if not exported[v:lower()] then + unpacked_table[#unpacked_table+1] = {} + local tempname,tempslot = unlogify_unpacked_name(v) + unpacked_table[#unpacked_table].name = tempname + unpacked_table[#unpacked_table].slot = tempslot or alt + if tab_level.augments then + local aug_str = '' + for aug_ind,augment in pairs(tab_level.augments) do + if augment ~= 'none' then aug_str = aug_str.."'"..augment:gsub("'","\\'").."'," end + end + if aug_str ~= '' then unpacked_table[#unpacked_table].augments = aug_str end + end + if tab_level.augment then + local aug_str = unpacked_table[#unpacked_table].augments or '' + if tab_level.augment ~= 'none' then aug_str = aug_str.."'"..augment:gsub("'","\\'").."'," end + if aug_str ~= '' then unpacked_table[#unpacked_table].augments = aug_str end + end + exported[tempname:lower()] = true + exported[v:lower()] = true + end + end + end + return unpacked_table,exported +end + +function unlogify_unpacked_name(name) + local slot + name = name:lower() + for i,v in pairs(res.items) do + if type(v) == 'table' then + if v[language..'_log']:lower() == name then + name = v[language] + local potslots = v.slots + if potslots then potslots = to_windower_api(res.slots[potslots:it()()].english) end + slot = potslots or 'item' + break + elseif v[language]:lower() == name then + name = v[language] + local potslots = v.slots + if potslots then potslots = to_windower_api(res.slots[potslots:it()()].english) end + slot = potslots or 'item' + break + end + end + end + return name,slot +end + +function xmlify(phrase) + if tonumber(phrase:sub(1,1)) then phrase = 'NUM'..phrase end + return phrase --:gsub('"','"'):gsub("'","'"):gsub('<','<'):gsub('>','>'):gsub('&&','&') +end + +function get_item_list(bag) + local items_in_bag = {} + -- Load the entire inventory + for _,v in pairs(bag) do + if type(v) == 'table' and v.id ~= 0 then + if res.items[v.id] then + items_in_bag[#items_in_bag+1] = {} + items_in_bag[#items_in_bag].name = res.items[v.id][language] + local potslots,slot = copy_entry(res.items[v.id].slots) + if potslots then + slot = res.slots[potslots:it()()].english:gsub(' ','_'):lower() -- Multi-lingual support requires that we add more languages to slots.lua + end + items_in_bag[#items_in_bag].slot = slot or 'item' + if not xml then + local augments = extdata.decode(v).augments or {} + local aug_str = '' + for aug_ind,augment in pairs(augments) do + if augment ~= 'none' then aug_str = aug_str.."'"..augment:gsub("'","\\'").."'," end + end + if string.len(aug_str) > 0 then + items_in_bag[#items_in_bag].augments = aug_str + end + end + else + msg.addon_msg(123,'You possess an item that is not in the resources yet.') + end + end + end + return items_in_bag +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/flow.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/flow.lua new file mode 100644 index 0000000..ef5a1ee --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/flow.lua @@ -0,0 +1,442 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + + +----------------------------------------------------------------------------------- +--Name: equip_sets(swap_type,ts,val1,val2) +--Desc: General purpose equipment pipeline / user function caller. +--Args: +---- swap_type - Determines equip_sets' behavior in terms of which user function it +-- attempts to call +---- ts - index of command_registry or nil for pretarget/commands +---- val1 - First argument to be passed to the user function +---- val2 - Second argument to be passed to the user function +----------------------------------------------------------------------------------- +--Return (varies by swap type): +---- pretarget : empty string to blank packet or full string +---- Everything else : nil +----------------------------------------------------------------------------------- +function equip_sets(swap_type,ts,...) + local results + local var_inps = {...} + local val1 = var_inps[1] + local val2 = var_inps[2] + table.reassign(_global,command_registry[ts] or {pretarget_cast_delay = 0,precast_cast_delay=0,cancel_spell = false, new_target=false,target_arrow={x=0,y=0,z=0}}) + _global.current_event = tostring(swap_type) + + if _global.current_event == 'precast' and val1 and val1.english and val1.english:find('Geo-') then + _global.target_arrow = initialize_arrow_offset(val1.target) + end + + windower.debug(tostring(swap_type)..' enter') + if showphase or debugging.general then msg.debugging(8,windower.to_shift_jis(tostring(swap_type))..' enter') end + + local cur_equip = table.reassign({},update_equipment()) + + table.reassign(equip_list,{}) + table.reassign(player.equipment,to_names_set(cur_equip)) + for i,v in pairs(slot_map) do + if not player.equipment[i] then + player.equipment[i] = player.equipment[toslotname(v)] + end + end + + logit('\n\n'..tostring(os.clock)..'(15) equip_sets: '..tostring(swap_type)) + if val1 then + if type(val1) == 'table' and val1.english then + logit(' : '..val1.english) + else + logit(' : Unknown type val1- '..tostring(val1)) + end + else + logit(' : nil-or-false') + end + if val2 then + if type(val2) == 'table' and val2.type then logit(' : '..val2.type) + else + logit(' : Unknown type val2- '..tostring(val2)) + end + else + logit(' : nil-or-false') + end + + if type(swap_type) == 'string' then + msg.debugging("Entering "..swap_type) + else + msg.debugging("Entering User Event "..tostring(swap_type)) + end + + if not val1 then val1 = {} + if debugging.general then + msg.debugging(8,'val1 error') + end + end + + + if type(swap_type) == 'function' then + results = { pcall(swap_type,...) } + if not table.remove(results,1) then error('\nUser Event Error: '..results[1]) end + elseif swap_type == 'equip_command' then + equip(val1) + else + user_pcall(swap_type,...) + end + +--[[ local c + if type(swap_type) == 'function' then + c = coroutine.create(swap_type) + elseif swap_type == 'equip_command' then + equip(val1) + elseif type(swap_type) == 'string' and user_env[swap_type] and type(user_env[swap_type]) == 'function' then + c = coroutine.create(user_env[swap_type]) + elseif type(swap_type) == 'string' and user_env[swap_type] then + msg.addon_msg(123,windower.to_shift_jis(tostring(str))..'() exists but is not a function') + end + + if c then + while coroutine.status(c) == 'suspended' do + local err, typ, val = coroutine.resume(c,unpack(var_inputs)) + if not err then + error('\nGearSwap has detected an error in the user function '..tostring(swap_type)..':\n'..typ) + elseif typ then + if typ == 'sleep' and type(val) == 'number' and val >= 0 then + -- coroutine slept + err, typ, val = coroutine.schedule(c,val) + else + -- Someone yielded or slept with a nonsensical argument. + err, typ, val = coroutine.resume(c) + end + else + -- coroutine finished + end + end + end]] + + + if type(swap_type) == 'string' and (swap_type == 'pretarget' or swap_type == 'filtered_action') then -- Target may just have been changed, so make the ind now. + ts = command_registry:new_entry(val1) +-- elseif type(swap_type) == 'string' and swap_type == 'precast' and not command_registry[ts] and debugging.command_registry then +-- print_set(spell,'precast nil error') -- spell's scope changed to local + end + + if player.race ~= 'Precomposed NPC' then + -- Short circuits the routine and gets out before equip processing + -- if there's no swapping to be done because the user is a monster. + + for v,i in pairs(default_slot_map) do + if equip_list[i] and encumbrance_table[v] then + not_sent_out_equip[i] = equip_list[i] + equip_list[i] = nil + msg.debugging(i..' slot was not equipped because you are encumbered.') + end + end + + table.update(equip_list_history,equip_list) + + -- Attempts to identify the player-specified item in inventory + -- Starts with (i=slot name, v=item name) + -- Ends with (i=slot id and v={bag_id=bag_id, slot=inventory slot}). + local equip_next,priorities = unpack_equip_list(equip_list,cur_equip) + + if (_settings.show_swaps and table.length(equip_next) > 0) or _settings.demo_mode then --and table.length(equip_next)>0 then + local tempset = to_names_set(equip_next) + print_set(tempset,tostring(swap_type)) + end + + if (buffactive.charm or player.charmed) or (player.status == 2 or player.status == 3) then -- dead or engaged dead statuses + local failure_reason + if (buffactive.charm or player.charmed) then + failure_reason = 'Charmed' + elseif player.status == 2 or player.status == 3 then + failure_reason = 'KOed' + end + msg.debugging("Cannot change gear right now: "..tostring(failure_reason)) + logit('\n\n'..tostring(os.clock)..'(69) failure_reason: '..tostring(failure_reason)) + else + local chunk_table = L{} + for eq_slot_id,priority in priorities:it() do + if equip_next[eq_slot_id] and not encumbrance_table[eq_slot_id] and not _settings.demo_mode then + local minichunk = equip_piece(eq_slot_id,equip_next[eq_slot_id].bag_id,equip_next[eq_slot_id].slot) + chunk_table:append(minichunk) + end + end + + if swap_type == 'midcast' and command_registry[ts] and command_registry[ts].proposed_packet and not _settings.demo_mode then + windower.packets.inject_outgoing(command_registry[ts].proposed_packet:byte(1),command_registry[ts].proposed_packet) + end + + if chunk_table.n >= 3 then + local big_chunk = string.char(0x51,0x24,0,0,chunk_table.n,0,0,0) + for i=1,chunk_table.n do + big_chunk = big_chunk..chunk_table[i] + end + while string.len(big_chunk) < 0x48 do big_chunk = big_chunk..string.char(0) end + windower.packets.inject_outgoing(0x51,big_chunk) + elseif chunk_table.n > 0 then + for i=1,chunk_table.n do + local chunk = string.char(0x50,4,0,0)..chunk_table[i] + windower.packets.inject_outgoing(0x50,chunk) + end + end + end + else + if swap_type == 'midcast' and command_registry[ts] and command_registry[ts].proposed_packet and not _settings.demo_mode then + windower.packets.inject_outgoing(command_registry[ts].proposed_packet:byte(1),command_registry[ts].proposed_packet) + end + end + + windower.debug(tostring(swap_type)..' exit') + + if type(swap_type) == 'function' then + return unpack(results) + end + + return equip_sets_exit(swap_type,ts,val1) +end + + +----------------------------------------------------------------------------------- +--Name: equip_sets_exit(swap_type,ind,val1) +--Desc: Cleans up the global table and leaves equip_sets properly. +--Args: +---- swap_type - Current swap type for equip_sets +---- ts - Current index of command_registry +---- val1 - First argument of equip_sets +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function equip_sets_exit(swap_type,ts,val1) + if command_registry[ts] then + table.update(command_registry[ts],_global) + end + if type(swap_type) == 'string' then + if swap_type == 'pretarget' then + + if command_registry[ts].cancel_spell then + msg.debugging("Action canceled ("..storedcommand..' '..val1.target.raw..")") + storedcommand = nil + command_registry:delete_entry(ts) + return true + elseif not ts or not command_registry[ts] or not storedcommand then + msg.debugging('This case should not be hittable - 1') + return true + end + + if command_registry[ts].new_target then + val1.target = command_registry[ts].new_target -- Switch target, if it is requested. + end + + -- Compose a proposed packet for the given action (this should be possible after pretarget) + command_registry[ts].spell = val1 + if val1.target and val1.target.id and val1.target.index and val1.prefix and unify_prefix[val1.prefix] then + if val1.prefix == '/item' then + -- Item use packet handling here + if bit.band(val1.target.spawn_type, 2) == 2 and find_inventory_item(val1.id) then + -- 0x36 packet + if val1.target.distance <= 6 then + command_registry[ts].proposed_packet = assemble_menu_item_packet(val1.target.id,val1.target.index,val1.id) + else + windower.add_to_chat(67, "Target out of range.") + end + elseif find_usable_item(val1.id) then + -- 0x37 packet + command_registry[ts].proposed_packet = assemble_use_item_packet(val1.target.id,val1.target.index,val1.id) + end + if not command_registry[ts].proposed_packet then + command_registry:delete_entry(ts) + end + elseif outgoing_action_category_table[unify_prefix[val1.prefix]] then + if filter_precast(val1) then + command_registry[ts].proposed_packet = assemble_action_packet(val1.target.id,val1.target.index,outgoing_action_category_table[unify_prefix[val1.prefix]],val1.id,command_registry[ts].target_arrow) + if not command_registry[ts].proposed_packet then + command_registry:delete_entry(ts) + + msg.debugging("Unable to create a packet for this command because the target is still invalid after pretarget ("..storedcommand..' '..val1.target.raw..")") + storedcommand = nil + return storedcommand..' '..val1.target.raw + end + end + else + msg.debugging(8,"Hark, what weird prefix through yonder window breaks? "..tostring(val1.prefix)) + end + end + + if ts and command_registry[ts] and val1.target then + if st_targs[val1.target.raw] then + -- st targets + st_flag = true + elseif not val1.target.name then + -- Spells with invalid pass_through_targs, like using <t> without a target + command_registry:delete_entry(ts) + msg.debugging("Change target was used to pick an invalid target ("..storedcommand..' '..val1.target.raw..")") + local ret = storedcommand..' '..val1.target.raw + storedcommand = nil + return ret + else + -- Spells with complete target information + -- command_registry[ts] is deleted for cancelled spells + if command_registry[ts].pretarget_cast_delay == 0 then + equip_sets('precast',ts,val1) + else + windower.send_command('@wait '..command_registry[ts].pretarget_cast_delay..';lua i '.._addon.name..' pretarget_delayed_cast '..ts) + end + return true + end + elseif not ts or not command_registry[ts] then + msg.debugging('This case should not be hittable - 2') + return true + end + + elseif swap_type == 'precast' then + -- Update the target_arrow + if val1.prefix ~= '/item' then + command_registry[ts].proposed_packet = assemble_action_packet(val1.target.id,val1.target.index,outgoing_action_category_table[unify_prefix[val1.prefix]],val1.id,command_registry[ts].target_arrow) + end + return precast_send_check(ts) + elseif swap_type == 'filtered_action' and command_registry[ts] and command_registry[ts].cancel_spell then + storedcommand = nil + command_registry:delete_entry(ts) + return true + elseif swap_type == 'midcast' and _settings.demo_mode then + command_registry[ts].midaction = false + equip_sets('aftercast',ts,val1) + elseif swap_type == 'aftercast' then + if ts then + command_registry:delete_entry(ts) + end + elseif swap_type == 'pet_aftercast' then + if ts then + command_registry:delete_entry(ts) + end + end + end +end + + +----------------------------------------------------------------------------------- +--Name: user_pcall(str,val1,val2,exit_funct) +--Desc: Calls a user function, if it exists. If not, throws an error. +--Args: +---- str - Function's key in user_env. +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function user_pcall(str,...) + if user_env then + if type(user_env[str]) == 'function' then + bool,err = pcall(user_env[str],...) + if not bool then error('\nGearSwap has detected an error in the user function '..str..':\n'..err) end + elseif user_env[str] then + msg.addon_msg(123,windower.to_shift_jis(tostring(str))..'() exists but is not a function') + end + end +end + + +----------------------------------------------------------------------------------- +--Name: pretarget_delayed_cast(ts) +--Desc: Triggers an outgoing action packet (if the passed key is valid). +--Args: +---- ts - Timestamp argument to precast_delayed_cast +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function pretarget_delayed_cast(ts) + ts = tonumber(ts) + if ts then + equip_sets('precast',ts,command_registry[ts].spell) + else + msg.debugging("Bad index passed to pretarget_delayed_cast") + end +end + + + +----------------------------------------------------------------------------------- +--Name: precast_send_check(ts) +--Desc: Determines whether or not to send the current packet. +-- Cancels if _global.cancel_spell is true +-- If command_registry[ts].precast_cast_delay is not 0, cues precast_delayed_cast with the proper +-- delay instead of sending immediately. +--Args: +---- ts - key of command_registry +----------------------------------------------------------------------------------- +--Returns: +---- true (to block) or the outgoing packet +----------------------------------------------------------------------------------- +function precast_send_check(ts) + if ts and command_registry[ts] then + if command_registry[ts].cancel_spell then + command_registry:delete_entry(ts) + else + if command_registry[ts].precast_cast_delay == 0 then + send_action(ts) + return + else + windower.send_command('@wait '..command_registry[ts].precast_cast_delay..';lua i '.._addon.name..' precast_delayed_cast '..ts) + end + end + end + return true +end + + +----------------------------------------------------------------------------------- +--Name: precast_delayed_cast(ts) +--Desc: Triggers an outgoing action packet (if the passed key is valid). +--Args: +---- ts - Timestamp argument to precast_delayed_cast +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function precast_delayed_cast(ts) + ts = tonumber(ts) + if ts then + send_action(ts) + else + msg.debugging("Bad index passed to precast_delayed_cast") + end +end + + +----------------------------------------------------------------------------------- +--Name: send_action(ts) +--Desc: Sends the cued action packet, if it exists. +--Args: +---- ts - index for a command_registry entry that includes an action packet (hopefully) +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function send_action(ts) + command_registry[ts].midaction = true + equip_sets('midcast',ts,command_registry[ts].spell) +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/gearswap.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/gearswap.lua new file mode 100644 index 0000000..5168406 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/gearswap.lua @@ -0,0 +1,333 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'GearSwap' +_addon.version = '0.937' +_addon.author = 'Byrth' +_addon.commands = {'gs','gearswap'} + +if windower.file_exists(windower.addon_path..'data/bootstrap.lua') then + debugging = {windower_debug = true,command_registry = false,general=false,logging=false} +else + debugging = {} +end + +__raw = {lower = string.lower, upper = string.upper, debug=windower.debug,text={create=windower.text.create, + delete=windower.text.delete,registry = {}},prim={create=windower.prim.create,delete=windower.prim.delete,registry={}}} + + +language = 'english' +file = require 'files' +require 'strings' +require 'tables' +require 'logger' +-- Restore the normal error function (logger changes it) +error = _raw.error + +require 'lists' +require 'sets' + + +windower.text.create = function (str) + if __raw.text.registry[str] then + msg.addon_msg(123,'Text object cannot be created because it already exists.') + else + __raw.text.registry[str] = true + __raw.text.create(str) + end +end + +windower.text.delete = function (str) + if __raw.text.registry[str] then + local library = false + if windower.text.saved_texts then + for i,v in pairs(windower.text.saved_texts) do + if v._name == str then + __raw.text.registry[str] = nil + windower.text.saved_texts[i]:destroy() + library = true + break + end + end + end + if not library then + -- Text was not created through the library, so delete it normally + __raw.text.registry[str] = nil + __raw.text.delete(str) + end + else + __raw.text.delete(str) + end +end + +windower.prim.create = function (str) + if __raw.prim.registry[str] then + msg.addon_msg(123,'Primitive cannot be created because it already exists.') + else + __raw.prim.registry[str] = true + __raw.prim.create(str) + end +end + +windower.prim.delete = function (str) + if __raw.prim.registry[str] then + __raw.prim.registry[str] = nil + __raw.prim.delete(str) + else + __raw.prim.delete(str) + end +end + +texts = require 'texts' +require 'pack' +bit = require 'bit' +socket = require 'socket' +mime = require 'mime' +res = require 'resources' +extdata = require 'extdata' +require 'helper_functions' +require 'actions' +packets = require 'packets' + +-- Resources Checks +if res.items and res.bags and res.slots and res.statuses and res.jobs and res.elements and res.skills and res.buffs and res.spells and res.job_abilities and res.weapon_skills and res.monster_skills and res.action_messages and res.skills and res.monstrosity and res.weather and res.moon_phases and res.races then +else + error('Missing resources!') +end + +require 'packet_parsing' +require 'statics' +require 'equip_processing' +require 'targets' +require 'user_functions' +require 'refresh' +require 'export' +require 'validate' +require 'flow' +require 'triggers' + +initialize_packet_parsing() + +windower.register_event('load',function() + windower.debug('load') + refresh_globals() + + if world.logged_in then + refresh_user_env() + if debugging.general then windower.send_command('@unload spellcast;') end + end +end) + +windower.register_event('unload',function () + windower.debug('unload') + user_pcall('file_unload') + if logging then logfile:close() end +end) + +windower.register_event('addon command',function (...) + windower.debug('addon command') + logit('\n\n'..tostring(os.clock)..table.concat({...},' ')) + local splitup = {...} + if not splitup[1] then return end -- handles //gs + + for i,v in pairs(splitup) do splitup[i] = windower.from_shift_jis(windower.convert_auto_trans(v)) end + + local cmd = table.remove(splitup,1):lower() + + if cmd == 'c' then + if gearswap_disabled then return end + if splitup[1] then + refresh_globals() + equip_sets('self_command',nil,_raw.table.concat(splitup,' ')) + else + msg.addon_msg(123,'No self command passed.') + end + elseif cmd == 'equip' then + if gearswap_disabled then return end + local key_list = parse_set_to_keys(splitup) + local set = get_set_from_keys(key_list) + if set then + refresh_globals() + equip_sets('equip_command',nil,set) + else + msg.addon_msg(123,'Equip command cannot be completed. That set does not exist.') + end + elseif cmd == 'export' then + export_set(splitup) + elseif cmd == 'validate' then + if user_env and user_env.sets then + refresh_globals() + validate(splitup) + else + msg.addon_msg(123,'There is nothing to validate because there is no file loaded.') + end + elseif cmd == 'l' or cmd == 'load' then + if splitup[1] then + local f_name = table.concat(splitup,' ') + if f_name:sub(-4):lower() ~= '.lua' then + f_name = f_name..'.lua' + end + if pathsearch({f_name}) then + refresh_globals() + command_registry = Command_Registry.new() + load_user_files(false,f_name) + else + msg.addon_msg(123,'File not found.') + end + else + msg.addon_msg(123,'No file name was provided.') + end + elseif cmd == 'enable' then + disenable(splitup,command_enable,'enable',false) + elseif cmd == 'disable' then + disenable(splitup,disable,'disable',true) + elseif cmd == 'reload' or cmd == 'r' then + refresh_user_env() + elseif strip(cmd) == 'debugmode' then + _settings.debug_mode = not _settings.debug_mode + print('GearSwap: Debug Mode set to '..tostring(_settings.debug_mode)..'.') + elseif strip(cmd) == 'demomode' then + _settings.demo_mode = not _settings.demo_mode + print('GearSwap: Demo Mode set to '..tostring(_settings.demo_mode)..'.') + elseif strip(cmd) == 'showswaps' then + _settings.show_swaps = not _settings.show_swaps + print('GearSwap: Show Swaps set to '..tostring(_settings.show_swaps)..'.') + elseif strip(cmd) == 'help' then + print('GearSwap: Valid commands are:') + print(' c <string> : passes the string to the user\'s self_command function.') + print(' equip <string> : attempts to equip the set indicated by the string.') + print(' debugmode : toggles debugmode on or off.') + print(' demomode : toggles demomode on or off.') + print(' showswaps : toggles whether gearswap displays equipment changes in the chat log.') + print(' load <string> : attempts to load the user file indicated by the string.') + print(' reload : reloads the current user file.') + print(' export <opts> : Exports your item collections based on the passed options.') + print(' disable <slot> : Disables equip commands targeting a specified slot.') + print(' validate <opts> : Checks your current inventory against your item collections (or vice versa).') + print(' Please see the gearswap/README.md file for more details.') + elseif _settings.debug_mode and strip(cmd) == 'eval' then + assert(loadstring(table.concat(splitup,' ')))() + else + local handled = false + if not gearswap_disabled then + for i,v in ipairs(unhandled_command_events) do + handled = equip_sets(v,nil,cmd,unpack(splitup)) + if handled then break end + end + end + if not handled then + print('GearSwap: Command not found') + end + end +end) + +function disenable(tab,funct,functname,pol) + local slot_name = '' + local ltab = L{} + for i,v in pairs(tab) do + ltab:append(v:gsub('[^%a_%d]',''):lower()) + end + if ltab:contains('all') then + funct('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') + print('GearSwap: All slots '..functname..'d.') + elseif ltab.n > 0 then + local found = L{} + local not_found = L{} + for slot_name in ltab:it() do + if slot_map[slot_name] then + funct(slot_name) + found:append(slot_name) + else + not_found:append(slot_name) + end + end + if found.n > 0 then + print('GearSwap: '..found:tostring()..' slot'..(found.n>1 and 's' or '')..' '..functname..'d.') + end + if not_found.n > 0 then + print('GearSwap: Unable to find slot'..(not_found.n>1 and 's' or '')..' '..not_found:tostring()..'.') + end + elseif gearswap_disabled ~= pol and not tab[2] then + print('GearSwap: User file '..functname..'d') + gearswap_disabled = pol + end +end + +function incoming_chunk(id,data,modified,injected,blocked) + windower.debug('incoming chunk '..id) + + if next_packet_events and next_packet_events.sequence_id ~= data:unpack('H',3) then + if not next_packet_events.globals_update or next_packet_events.globals_update ~= data:unpack('H',3) then + refresh_globals() + next_packet_events.globals_update = data:unpack('H',3) + end + if next_packet_events.pet_status_change and not gearswap_disabled then + equip_sets('pet_status_change',nil,next_packet_events.pet_status_change.newstatus,next_packet_events.pet_status_change.oldstatus) + next_packet_events.pet_status_change = nil + end + if next_packet_events.pet_change then + if next_packet_events.pet_change.pet and not gearswap_disabled then -- Losing a pet + equip_sets('pet_change',nil,next_packet_events.pet_change.pet,false) + next_packet_events.pet_change = nil + elseif pet.isvalid and not gearswap_disabled then -- Gaining a pet + equip_sets('pet_change',nil,pet,true) + next_packet_events.pet_change = nil + end + end + if not next_packet_events.pet_status_change and not next_packet_events.pet_change then + next_packet_events = nil + end + end + + if not injected and parse.i[id] then + parse.i[id](data,blocked) + end +end + +function outgoing_chunk(id,original,data,injected,blocked) + windower.debug('outgoing chunk '..id) + + if not blocked and parse.o[id] then + parse.o[id](data,injected) + end +end + +windower.register_event('incoming chunk',incoming_chunk) +windower.register_event('outgoing chunk',outgoing_chunk) + +windower.register_event('status change',function(new,old) + windower.debug('status change '..new) + if gearswap_disabled or T{2,3,4}:contains(old) or T{2,3,4}:contains(new) then return end + + refresh_globals() + equip_sets('status_change',nil,res.statuses[new].english,res.statuses[old].english) +end) + +windower.register_event('login',function(name) + windower.debug('login '..name) + initialize_globals() + windower.send_command('@wait 2;lua i gearswap refresh_user_env;') +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/helper_functions.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/helper_functions.lua new file mode 100644 index 0000000..0034d60 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/helper_functions.lua @@ -0,0 +1,1255 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +----------------------------------------------------------------------------------- +--Name: string.lower() +--Args: +---- message (string): Message to be forced to lower case +----------------------------------------------------------------------------------- +--Returns: +---- Lower case message (or not, if the language or message is invalid) +----------------------------------------------------------------------------------- +function string.lower(message) + if message and type(message) == 'string' and language == 'english' then + return __raw.lower(message) + elseif message and type(message) == 'string' then + return message:gsub('[A-Z]',function (letter) return string.char(letter:byte(1)+32) end) + else + return message + end +end + + +----------------------------------------------------------------------------------- +--Name: string.upper() +--Args: +---- message (string): Message to be forced to upper case +----------------------------------------------------------------------------------- +--Returns: +---- Upper case message (or not, if the language or message is invalid) +----------------------------------------------------------------------------------- +function string.upper(message) + if message and type(message) == 'string' and language == 'english' then + return __raw.upper(message) + elseif message and type(message) == 'string' then + return message:gsub('[a-z]',function (letter) return string.char(letter:byte(1)-32) end) + else + return message + end +end + + +----------------------------------------------------------------------------------- +--Name: fieldsearch() +--Args: +---- message (string): Message to be searched +----------------------------------------------------------------------------------- +--Returns: +---- Table of strings that contained {something}. +---- Seems to be trying to exclude ${actor} and ${target}, but not. +----------------------------------------------------------------------------------- +function fieldsearch(message) + local fields = T{} + string.gsub(message,"{(.-)}", function(a) if a ~= '${actor}' and a ~= '${target}' then fields:append(a) end end) + return fields +end + + +----------------------------------------------------------------------------------- +--Name: strip() +--Args: +---- name (string): Name to be slugged +----------------------------------------------------------------------------------- +--Returns: +---- string with a gsubbed version of name that converts numbers to Roman numerals +-------- removes non-letter/numbers, and forces it to lower case. +----------------------------------------------------------------------------------- +function strip(name) + return name:gsub('4','iv'):gsub('9','ix'):gsub('0','p'):gsub('3','iii'):gsub('2','ii'):gsub('1','i'):gsub('8','viii'):gsub('7','vii'):gsub('6','vi'):gsub('5','v'):gsub('[^%a]',''):lower() +end + + +----------------------------------------------------------------------------------- +--Name: user_key_filter() +--Args: +---- val (key): potential key to be modified +----------------------------------------------------------------------------------- +--Returns: +---- Filtered key +----------------------------------------------------------------------------------- +function user_key_filter(val) + return type(val) == 'string' and string.lower(val) or val +end + + +----------------------------------------------------------------------------------- +--Name: make_user_table() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- Table with case-insensitive keys +----------------------------------------------------------------------------------- +function make_user_table() + return setmetatable({}, user_data_table) +end + + +----------------------------------------------------------------------------------- +----Name: unify_slots(g) +-- Filters the provided gear table to only known slots, and then runs a map +-- on the table to make sure all keys are the accepted versions for each. +----Args: +-- g - A dictionary table containing a gear set. +----------------------------------------------------------------------------------- +----Returns: +-- A table simplified to only acceptable slots. +----------------------------------------------------------------------------------- +function unify_slots(g) + local g1 = table.key_filter(g, is_slot_key) + return table.key_map(g1, get_default_slot) +end + + +----------------------------------------------------------------------------------- +----Name: is_slot_key(k) +-- Checks to see if key 'k' is known in the slot_map array, and that slot has not +-- been disabled. +----Args: +-- k - A key to a gear slot in a gear table. +----------------------------------------------------------------------------------- +----Returns: +-- True if the key is recognized in the slot_map table, and that slot is enabled; +-- otherwise false. +----------------------------------------------------------------------------------- +function is_slot_key(k) + return slot_map[k] +end + + +----------------------------------------------------------------------------------- +----Name: make_empty_item_table(slot) +-- Make an empty item table with slot = slot +----Args: +-- slot - The index of the item table +----------------------------------------------------------------------------------- +----Returns: +-- A zero'd table with slot = slot +----------------------------------------------------------------------------------- +function make_empty_item_table(slot) + return {id=0, + count = 0, + bazaar = 0, + extdata = string.char(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + status = 0, + slot = slot} +end + + +----------------------------------------------------------------------------------- +----Name: make_inventory_table() +-- Make a table of empty item tables +----Args: +-- none +----------------------------------------------------------------------------------- +----Returns: +-- A table of 80 empty item tables indexed 1-80 +----------------------------------------------------------------------------------- +function make_inventory_table() + local tab = {} + for i = 0,80 do + tab[i] = make_empty_item_table(i) + end + return tab +end + + +----------------------------------------------------------------------------------- +----Name: to_windower_api(str) +-- Takes strings and converts them to resources table key format +----Args: +-- str - String to be converted to the windower API version +----------------------------------------------------------------------------------- +----Returns: +-- a lower case string with ' ' replaced with '_' +----------------------------------------------------------------------------------- +function to_windower_api(str) + return __raw.lower(str:gsub(' ','_')) +end + + +----------------------------------------------------------------------------------- +----Name: to_windower_bag_api(str) +-- Takes strings and converts them to resources table key format +----Args: +-- str - String to be converted to the windower bag API version +----------------------------------------------------------------------------------- +----Returns: +-- a lower case string with ' ' replaced with '' +----------------------------------------------------------------------------------- +function to_windower_bag_api(str) + return __raw.lower(str:gsub(' ','')) +end + +----------------------------------------------------------------------------------- +----Name: to_bag_api(str) +-- Takes strings and converts them to resources table key format +----Args: +-- str - String to be converted to the windower bag API version +----------------------------------------------------------------------------------- +----Returns: +-- a lower case string with ' ' eliminated +----------------------------------------------------------------------------------- +function to_bag_api(str) + return __raw.lower(str:gsub(' ','')) +end + +----------------------------------------------------------------------------------- +----Name: to_windower_compact(str) +-- Takes strings and converts them to a compact version of the resource table key +----Args: +-- str - String to be converted to the windower API version +----------------------------------------------------------------------------------- +----Returns: +-- a lower case string with ' ' replaced with '' +----------------------------------------------------------------------------------- +function to_windower_compact(str) + return __raw.lower(str:gsub(' ','')) +end + +----------------------------------------------------------------------------------- +----Name: get_job_names() +-- Returns the short and long form of the job name +----Args: +-- id - Job ID +----------------------------------------------------------------------------------- +----Returns: +-- short and long form of the job name +----------------------------------------------------------------------------------- +function get_job_names(id) + if res.jobs[id] then + return res.jobs[id][language..'_short'], res.jobs[id][language] + else + return 'NONE', 'None' + end +end + + +----------------------------------------------------------------------------------- +----Name: update_job_names() +-- Updates job names in the global player array +----Args: +-- none +----------------------------------------------------------------------------------- +----Returns: +-- none +----------------------------------------------------------------------------------- +function update_job_names() + player.main_job,player.main_job_full = get_job_names(player.main_job_id) + player.sub_job, player.sub_job_full = get_job_names(player.sub_job_id) + player.job = player.main_job..'/'..player.sub_job +end + + +----------------------------------------------------------------------------------- +----Name: get_default_slot(k) +-- Given a generally known slot key, return the default version of that key. +----Args: +-- k - A gear slot key. +----------------------------------------------------------------------------------- +----Returns: +-- Returns the default slot key that matches the provided key. +----------------------------------------------------------------------------------- +function get_default_slot(k) + if slot_map[k] then + return toslotname(slot_map[k]) + end +end + + +----------------------------------------------------------------------------------- +----Name: set_merge(baseSet, ...) +-- Merges any additional gear sets (...) into the provided base set. +-- Ensures that only valid slot keys/elements are used in the combined set. +----Args: +-- respect_disable - boolean indicating whether the disable_table should be respected. +-- baseSet - The set that all the other sets are combined into. May be an empty set. +----------------------------------------------------------------------------------- +----Returns: +-- Returns the modified base set, after all other sets have been merged into it. +----------------------------------------------------------------------------------- +function set_merge(respect_disable, baseSet, ...) + local combineSets = {...} + + local canCombine = table.all(combineSets, function(t) return type(t) == 'table' end) + if not canCombine then + -- the code that called equip() or set_combine() is #3 on the stack from here + error("Trying to combine non-gear sets.", 3) + end + + -- Take the list of tables we're given and cleans them up, so that they + -- only contain acceptable slot key entries. + local cleanSetsList = table.map(combineSets, unify_slots) + + -- Combine the provided sets into combinedSet. If anything is blocked by having + -- the slot disabled, assign the item to the not_sent_out_equip table. + for _,set in pairs(cleanSetsList) do + for slot,item in pairs(set) do + if respect_disable and disable_table[slot_map[slot]] then + not_sent_out_equip[slot] = item + else + baseSet[slot] = item + end + end + end + + return baseSet +end + + +----------------------------------------------------------------------------------- +----Name: parse_set_to_keys(str) +-- Function to parse a string representation of a table into a list of keys that +-- that can be used to select that table. +----Args: +-- str - Input can be a string, or a table of strings (which will be concatenated +-- into a single string with spaces as intervals). +-- +-- Example: +-- Input: sets.precast.WS["Rudra's Storm"]['Ltng. Threnody'].Acc +-- Output: [sets, precast, WS, Rudra's Storm, Ltng. Threnody, Acc] +----------------------------------------------------------------------------------- +----Returns: +-- Returns a list of keys parsed from the provided input. +----------------------------------------------------------------------------------- +function parse_set_to_keys(str) + if type(str) == 'table' then + str = table.concat(str, ' ') + end + + -- Parsing results get pushed into the result list. + local result = L{} + + local remainder = str + local key + local stop + local sep = '.' + local count = 0 + + -- Loop as long as remainder hasn't been nil'd or reduced to 0 characters, but only to a maximum of 30 tries. + while remainder ~= "" and count < 30 do + -- Try aaa.bbb set names first + while sep == '.' do + _,_,key,sep,remainder = remainder:find("^([^%.%[]*)(%.?%[?)(.*)") + -- "key" is everything that is not . or [ 0 or more times. + -- "sep" is the next divider, which is necessarily . or [ + -- "remainder" is everything after that + result:append(key) + end + + -- Then try aaa['bbb'] set names. + -- Be sure to account for both single and double quote enclosures. + -- Ignore periods contained within quote strings. + while sep == '[' do + _,_,sep,remainder = remainder:find([=[^(%'?%"?)(.*)]=]) --' --block bad text highlighting + -- "sep" is the first ' or " found (or nil) + -- remainder is everything after that (or nil) + if sep == "'" then + _,_,key,stop,sep,remainder = remainder:find("^([^']+)('])(%.?%[?)(.*)") + elseif sep == '"' then + _,_,key,stop,sep,remainder = remainder:find('^([^"]+)("])(%.?%[?)(.*)') + end + if not sep or #sep == 0 then + -- If there is no single or double quote detected, attempt to treat the index as a number or boolean + local _,_,pot_key,pot_stop,pot_sep,pot_remainder = remainder:find('^([^%]]+)(])(%.?%[?)(.*)') + if tonumber(pot_key) then + key,stop,sep,remainder = tonumber(pot_key),pot_stop,pot_sep,pot_remainder + elseif pot_key == 'true' then + key,stop,sep,remainder = true,pot_stop,pot_sep,pot_remainder + elseif pot_key == 'false' then + key,stop,sep,remainder = false,pot_stop,pot_sep,pot_remainder + elseif pot_key and pot_key ~= "" then + key,stop,sep,remainder = pot_key,pot_stop,pot_sep,pot_remainder + end + end + result:append(key) + end + + count = count +1 + end + + return result +end + + +----------------------------------------------------------------------------------- +----Name: get_set_from_keys(keys) +-- Function to take a list of keys select the set they point to, if possible. +----Args: +-- keys - A List of strings intended to be keys in progressively nested tables. +-- The list is presumed to be based on the 'sets' table, and will start from that +-- point if it is not explicitly provided in the key list. +----------------------------------------------------------------------------------- +----Returns: +-- Returns the set if found, or nil if not. +----------------------------------------------------------------------------------- +function get_set_from_keys(keys) + local set = keys[1] == 'sets' and _G or sets + for key in (keys.it or it)(keys) do + if key == nil then + return nil + end + set = set[key] + if not set then + return nil + end + end + + return set +end + + +----------------------------------------------------------------------------------- +--Name: initialize_arrow_offset(mob_table) +--Desc: Returns the current target arrow offset. +--Args: +---- mob_table - Monster table of the target monster +----------------------------------------------------------------------------------- +--Returns: +---- table - Keys x, y, and z with the respective current offsets from the target. +----------------------------------------------------------------------------------- +function initialize_arrow_offset(mob_table) + local backtab = {} + local arrow = windower.ffxi.get_info().target_arrow + + if arrow.x == 0 and arrow.y == 0 and arrow.z == 0 then + return arrow + end + + backtab.x = arrow.x-mob_table.x + backtab.y = arrow.y-mob_table.y + backtab.z = arrow.z-mob_table.z + return backtab +end + + +----------------------------------------------------------------------------------- +--Name: assemble_action_packet(target_id,target_index,category,spell_id) +--Desc: Puts together an "action" packet (0x1A) +--Args: +---- target_id - The target's ID +---- target_index - The target's index +---- category - The action's category. (3 = MA, 7 = WS, 9 = JA, 16 = RA, 25 = MS) +---- spell_ID - The current spell's ID +----------------------------------------------------------------------------------- +--Returns: +---- string - An action packet. First four bytes are dummy bytes. +----------------------------------------------------------------------------------- +function assemble_action_packet(target_id,target_index,category,spell_id,arrow_offset) + local outstr = string.char(0x1A,0x08,0,0) + outstr = outstr..string.char( (target_id%256), math.floor(target_id/256)%256, math.floor( (target_id/65536)%256) , math.floor( (target_id/16777216)%256) ) + outstr = outstr..string.char( (target_index%256), math.floor(target_index/256)%256) + outstr = outstr..string.char( (category%256), math.floor(category/256)%256) + + if category == 16 then + spell_id = 0 + end + + outstr = outstr..string.char( (spell_id%256), math.floor(spell_id/256)%256)..string.char(0,0) .. 'fff':pack(arrow_offset.x,arrow_offset.z,arrow_offset.y) + return outstr +end + + +----------------------------------------------------------------------------------- +--Name: assemble_use_item_packet(target_id,target_index,item) +--Desc: Puts together a "use item" packet (0x37) +--Args: +---- target_id - The target's ID +---- target_index - The target's index +---- item_id - The id for the current item +----------------------------------------------------------------------------------- +--Returns: +---- string - A use item packet. First four bytes are dummy bytes. +----------------------------------------------------------------------------------- +function assemble_use_item_packet(target_id,target_index,item_id) + local outstr = string.char(0x37,0x0A,0,0) + outstr = outstr..string.char( (target_id%256), math.floor(target_id/256)%256, math.floor( (target_id/65536)%256) , math.floor( (target_id/16777216)%256) ) + outstr = outstr..string.char(0,0,0,0) + outstr = outstr..string.char( (target_index%256), math.floor(target_index/256)%256) + inventory_index,bag_id = find_usable_item(item_id) + if inventory_index then + outstr = outstr..string.char(inventory_index%256)..string.char(0,bag_id,0,0,0) + else + msg.debugging('Proposed item: '..(res.items[item_id][language] or item_id)..' not found in inventory.') + return + end + return outstr +end + + +----------------------------------------------------------------------------------- +--Name: assemble_menu_item_packet(target_id,target_index,item) +--Desc: Puts together a "menu item" packet (0x36) +--Args: +---- target_id - The target's ID +---- target_index - The target's index +---- item_id - The id for the current item +----------------------------------------------------------------------------------- +--Returns: +---- string - A use item packet. First four bytes are dummy bytes. +----------------------------------------------------------------------------------- +function assemble_menu_item_packet(target_id,target_index,...) + local outstr = string.char(0x36,0x20,0,0) + -- Message is coming out too short by 12 characters + + -- Target ID + outstr = outstr.."I":pack(target_id) + local item_ids,counts,count = {...},{},0 + for i,v in pairs(item_ids) do + if res.items[v] then + counts[v] = (counts[v] or 0) + 1 + count = count + 1 + end + end + + local unique_items = 0 + for i,v in pairs(counts) do + outstr = outstr.."I":pack(v) + unique_items = unique_items + 1 + end + if unique_items > 9 then + msg.debugging('Too many items ('..unique_items..') passed to the assemble_menu_item_packet function') + return + end + while #outstr < 0x30 do + outstr = outstr..string.char(0) + end + + -- Inventory Index for the one unit + + for i,v in pairs(counts) do + inventory_index = find_inventory_item(i) + if inventory_index then + outstr = outstr..string.char(inventory_index%256) + else + msg.debugging('Proposed item: '..(res.items[i][language] or i)..' not found in inventory.') + return + end + end + while #outstr < 0x3A do + outstr = outstr..string.char(0) + end + -- Target Index + outstr = outstr.."H":pack(target_index) + -- Only one item being traded + outstr = outstr..string.char(unique_items,0,0,0) + return outstr +end + + +----------------------------------------------------------------------------------- +--Name: find_inventory_item(item_id) +--Desc: Finds a npc trade item in normal inventory. Assumes items array +-- is accurate already. +--Args: +---- item_id - The resource line for the current item +----------------------------------------------------------------------------------- +--Returns: +---- inventory_index - The item's use inventory index (if it exists) +---- bag_id - The item's bag ID (if it exists) +----------------------------------------------------------------------------------- +function find_inventory_item(item_id) + for i,v in pairs(items.inventory) do + if type(v) == 'table' and v.id == item_id and v.status == 0 then + return i + end + end +end + + +----------------------------------------------------------------------------------- +--Name: find_usable_item(item_id,bool) +--Desc: Finds a usable item in temporary or normal inventory. Assumes items array +-- is accurate already. +--Args: +---- item_id - The resource line for the current item +----------------------------------------------------------------------------------- +--Returns: +---- inventory_index - The item's use inventory index (if it exists) +---- bag_id - The item's bag ID (if it exists) +----------------------------------------------------------------------------------- +function find_usable_item(item_id) + for _,bag in ipairs(usable_item_bags) do + for i,v in pairs(items[to_windower_bag_api(bag.en)]) do + if type(v) == 'table' and v.id == item_id and is_usable_item(v,bag.id) then + return i, bag.id + end + end + end +end + +----------------------------------------------------------------------------------- +--Name: is_usable_item(i_tab) +--Desc: Determines whether the item table belongs to a usable item. +--Args: +---- i_tab - current item table +---- bag_id - The item's bag ID +----------------------------------------------------------------------------------- +--Returns: +---- true or false to indicate whether the item is usable +----------------------------------------------------------------------------------- +function is_usable_item(i_tab,bag_id) + local ext = extdata.decode(i_tab) + if ext.type == 'Enchanted Equipment' and ext.usable then + return i_tab.status == 5 + elseif i_tab.status == 0 and bag_id < 4 then + return true + end + return false +end + +----------------------------------------------------------------------------------- +--Name: number_of_jps(jp_tab) +--Desc: Gives the total number of job points spent on that job +--Args: +---- jp_tab - One table from windower.ffxi.get_player().job_points[job] +----------------------------------------------------------------------------------- +--Returns: +---- The total number of job points spent on that job. +----------------------------------------------------------------------------------- +function number_of_jps(jp_tab) + local count = 0 + for _,v in pairs(jp_tab) do + count = count + v*(v+1) + end + return count/2 +end + +----------------------------------------------------------------------------------- +--Name: filter_pretarget(spell) +--Desc: Determines whether the current player is capable of using the proposed action +---- at pretarget. +--Args: +---- action - current action +----------------------------------------------------------------------------------- +--Returns: +---- false to cancel further command processing and just return the command. +----------------------------------------------------------------------------------- +function filter_pretarget(action) + local category = outgoing_action_category_table[unify_prefix[action.prefix]] + local bool = true + local err + if world.in_mog_house then + msg.debugging("Unable to execute commands. Currently in a Mog House zone.") + return false + elseif category == 3 then + local available_spells = windower.ffxi.get_spells() + bool,err = check_spell(available_spells,action) + elseif category == 7 then + local available = windower.ffxi.get_abilities().weapon_skills + if not table.contains(available,action.id) then + bool,err = false,"Unable to execute command. You do not have access to that weapon skill." + end + elseif category == 9 then + local available = windower.ffxi.get_abilities().job_abilities + if not table.contains(available,action.id) then + bool,err = false,"Unable to execute command. You do not have access to that job ability." + end + elseif category == 25 and (not player.main_job_id == 23 or not windower.ffxi.get_mjob_data().species or + not res.monstrosity[windower.ffxi.get_mjob_data().species] or not res.monstrosity[windower.ffxi.get_mjob_data().species].tp_moves[action.id] or + not (res.monstrosity[windower.ffxi.get_mjob_data().species].tp_moves[action.id] <= player.main_job_level)) then + -- Monstrosity filtering + msg.debugging("Unable to execute command. You do not have access to that monsterskill ("..(res.monster_skills[action.id][language] or action.id)..")") + return false + end + + if err then + msg.debugging(err) + end + return bool +end + + +----------------------------------------------------------------------------------- +--Name: check_spell(available_spells,spell) +--Desc: Determines whether the current player is capable of using the proposed spell +---- at precast. +--Args: +---- available_spells - current set of available spells +---- spell - current spell table +----------------------------------------------------------------------------------- +--Returns: +---- false if the spell is not currently accessible +----------------------------------------------------------------------------------- +function check_spell(available_spells,spell) + -- Filter for spells that you do not know. Exclude Impact / Dispelga. + local spell_jobs = copy_entry(res.spells[spell.id].levels) + if not available_spells[spell.id] and not (spell.id == 503 or spell.id == 417 or spell.id == 360) then + return false,"Unable to execute command. You do not know that spell ("..(res.spells[spell.id][language] or spell.id)..")" + -- Filter for spells that you know, but do not currently have access to + elseif (not spell_jobs[player.main_job_id] or not (spell_jobs[player.main_job_id] <= player.main_job_level or + (spell_jobs[player.main_job_id] >= 100 and number_of_jps(player.job_points[__raw.lower(res.jobs[player.main_job_id].ens)]) >= spell_jobs[player.main_job_id]) ) ) and + (not spell_jobs[player.sub_job_id] or not (spell_jobs[player.sub_job_id] <= player.sub_job_level)) and not (player.main_job_id == 23) then + return false,"Unable to execute command. You do not have access to that spell ("..(res.spells[spell.id][language] or spell.id)..")" + -- At this point, we know that it is technically castable by this job combination if the right conditions are met. + elseif player.main_job_id == 20 and ((addendum_white[spell.id] and not buffactive[401] and not buffactive[416]) or + (addendum_black[spell.id] and not buffactive[402] and not buffactive[416])) and + not (spell_jobs[player.sub_job_id] and spell_jobs[player.sub_job_id] <= player.sub_job_level) then + return false,"Unable to execute command. Addendum required for that spell ("..(res.spells[spell.id][language] or spell.id)..")" + elseif player.sub_job_id == 20 and ((addendum_white[spell.id] and not buffactive[401] and not buffactive[416]) or + (addendum_black[spell.id] and not buffactive[402] and not buffactive[416])) and + not (spell_jobs[player.main_job_id] and (spell_jobs[player.main_job_id] <= player.main_job_level or + (spell_jobs[player.main_job_id] >= 100 and number_of_jps(player.job_points[__raw.lower(res.jobs[player.main_job_id].ens)]) >= spell_jobs[player.main_job_id]) ) ) then + return false,"Unable to execute command. Addendum required for that spell ("..(res.spells[spell.id][language] or spell.id)..")" + elseif spell.type == 'BlueMagic' and not ((player.main_job_id == 16 and table.contains(windower.ffxi.get_mjob_data().spells,spell.id)) + or unbridled_learning_set[spell.english]) and + not (player.sub_job_id == 16 and table.contains(windower.ffxi.get_sjob_data().spells,spell.id)) then + -- This code isn't hurting anything, but it doesn't need to be here either. + return false,"Unable to execute command. Blue magic must be set to cast that spell ("..(res.spells[spell.id][language] or spell.id)..")" + elseif spell.type == 'Ninjutsu' then + if player.main_job_id ~= 13 and player.sub_job_id ~= 13 then + return false,"Unable to make action packet. You do not have access to that spell ("..(spell[language] or spell.id)..")" + elseif not player.inventory[tool_map[spell.english][language]] and not (player.main_job_id == 13 and player.inventory[universal_tool_map[spell.english][language]]) then + return false,"Unable to make action packet. You do not have the proper tools." + end + end + return true +end + + +----------------------------------------------------------------------------------- +--Name: filter_precast(spell) +--Desc: Determines whether the current player is capable of using the proposed spell +---- at precast. +--Args: +---- spell - current spell table +----------------------------------------------------------------------------------- +--Returns: +---- false to block the outgoing packet +----------------------------------------------------------------------------------- +function filter_precast(spell) + if not spell.target.id or not spell.target.index then + if debugging.general then msg.debugging('No target id or index') end + return false + end + return true +end + + +local cmd_reg = {} +Command_Registry = {} + +function Command_Registry.new() + local new_instance = {_self={last_removed=os.clock()}} + local function remove_old_entries (t) + -- Removes old command registry entries. + for i,v in pairs(t) do + local lim = (type(v) == 'table' and (v.spell and v.spell.cast_time and v.spell.cast_time*1.1+2 or + v.spell and v.spell.prefix=='/pet' and 5 or + v.spell and v.spell.action_type and delay_map_to_action_type[v.spell.action_type] or + 3) + (v.pretarget_cast_delay or 0) + (v.precast_cast_delay or 0)) + -- Sets it to normal casting time + 10% +1 for anything with a defined cast_time, or 1 if there is no defined cast time. + if tonumber(i) and os.time()-i >= lim then + cmd_reg.delete_entry(t,i) + end + end + return os.clock() + end + + return setmetatable(new_instance, {__index = function(t, k) + if os.clock() - rawget(rawget(t,'_self'),'last_removed') > 0.04 then + rawset(rawget(t,'_self'),'last_removed', remove_old_entries(t)) + end + if rawget(cmd_reg, k) ~= nil then + return rawget(cmd_reg,k) + else + return rawget(t,k) + end + end}) +end + + +----------------------------------------------------------------------------------- +--Name: cmd_reg:new_entry(sp) +--Desc: Makes a new entry in command_registry. +--Args: +---- sp - Resources line for the current spell +----------------------------------------------------------------------------------- +--Returns: +---- ts - index for command_registry +----------------------------------------------------------------------------------- +function cmd_reg:new_entry(sp) + local ts = os.time() + while rawget(self,ts) do + ts = ts+0.001 + end + rawset(self,ts,{pretarget_cast_delay=0, precast_cast_delay=0, cancel_spell=false, new_target=false, current_event='nascent', spell=sp, timestamp=ts,target_arrow={x=0,y=0,z=0}}) + if debugging.command_registry then + msg.addon_msg('Creating a new command_registry entry: '..windower.to_shift_jis(tostring(ts)..' '..tostring(self[ts]))) + end + return ts +end + + +----------------------------------------------------------------------------------- +--Name: cmd_reg:delete_entry(ts) +--Desc: Makes a new entry in command_registry. +--Args: +---- ts - timestamp of the command registry entry to be deleted +----------------------------------------------------------------------------------- +--Returns: +---- bool - true indicates a successful deletion +----------------------------------------------------------------------------------- +function cmd_reg:delete_entry(ts) + if rawget(self,ts) then + if debugging.command_registry then + msg.debugging('Deleting a command_registry entry: '..windower.to_shift_jis(tostring(ts)..' '..tostring(rawget(self,ts)))) + end + rawset(self,ts,nil) + return true + elseif debugging.command_registry then + msg.debugging('Attempted to delete a command_registry entry that did not exist: '..windower.to_shift_jis(tostring(ts) )) + end + return false +end + + +----------------------------------------------------------------------------------- +--Name: cmd_reg:find_by_spell(value) +--Desc: Returns the proper unified prefix, or "Monster" in the case of a monster action +--Args: +---- typ - 'spell', 'timestamp', or 'id' +---- value - The spell, timestamp, or id +---- Currently the ID and Timestamp options are unused. +----------------------------------------------------------------------------------- +--Returns: +---- timestamp index of command_registry +----------------------------------------------------------------------------------- +function cmd_reg:find_by_spell(value) + -- Finds all entries of a given spell in the table. + -- Returns the one with the most recent timestamp. + -- Actions that do not have timestamps yet (have not hit midcast) are given lowest priority. + local potential_entries,current_time,winner,ts = {},os.time() + for i,v in pairs(self) do + if type(v) == 'table' and v.spell and v.spell.prefix == value.prefix and v.spell.name == value.name then + potential_entries[i] = v.timestamp or 0 + elseif type(v) == 'table' and v.spell and v.spell.english == 'Double-Up' and value.type == 'CorsairRoll' then + -- Double Up ability uses will return action packets that match Corsair Rolls rather than Double Up + potential_entries[i] = v.timestamp or 0 + end + end + for i,v in pairs(potential_entries) do + if not winner or (current_time - v < current_time - winner) then + winner = v + ts = i + end + end + return ts +end + + +----------------------------------------------------------------------------------- +--Name: cmd_reg:find_by_time() +--Desc: Finds the most recent command_registry entry +--Args: +---- none +----------------------------------------------------------------------------------- +--Returns: +---- ts,discovered entry +----------------------------------------------------------------------------------- +function cmd_reg:find_by_time(target_time) + local time_stamp,ts + target_time = target_time or os.time() + + -- Iterate over command_registry looking for the spell with the closest timestamp. + -- Call aftercast with this spell's information (interrupted) if one is found. + for i,v in pairs(self) do + if not time_stamp or (type(v) == 'table' and v.timestamp and ((target_time - v.timestamp) < (target_time - time_stamp))) then + time_stamp = v.timestamp + ts = i + end + end + if time_stamp then + return ts,table.reassign({},self[ts]) + end +end + + +----------------------------------------------------------------------------------- +--Name: cmd_reg:delete_by_id(id) +--Desc: Deletes all command_registry entry based that match a given target ID. +--Args: +---- id - ID of the target +----------------------------------------------------------------------------------- +--Returns: +---- ts,last_entry for the deleted entry +----------------------------------------------------------------------------------- +function cmd_reg:delete_by_id(id) + local ts,last_entry + for i,v in pairs(self) do + if v.spell and v.spell.target then + if v.spell.target.id == id then + last_entry = table.reassign({},self[i]) + ts = i + self[i] = nil + end + end + end + return ts,last_entry +end + + +----------------------------------------------------------------------------------- +--Name: copy_entry(tab) +--Desc: Copies a table into a new table while preserving its metatable. +-- Designed for copying resources entries. +--Args: +---- tab - Resources table. +----------------------------------------------------------------------------------- +--Returns: +---- ret - New table that has the same metatable and content as the original table. +----------------------------------------------------------------------------------- +function copy_entry(tab) + if not tab then return nil end + local ret = setmetatable(table.reassign({},tab),getmetatable(tab)) + return ret +end + + +----------------------------------------------------------------------------------- +--Name: get_spell(act) +--Desc: Takes an action table and returns a modified resource line +--Args: +---- act - action table in the same format as event_action +----------------------------------------------------------------------------------- +--Returns: +---- spell - Resource line of the current spell +----------------------------------------------------------------------------------- +function get_spell(act) + local spell, abil_ID, effect_val + local msg_ID = act.targets[1].actions[1].message + + if T{7,8,9}:contains(act.category) then + abil_ID = act.targets[1].actions[1].param + elseif T{3,4,5,6,11,13,14,15}:contains(act.category) then + abil_ID = act.param + effect_val = act.targets[1].actions[1].param + end + + if act.category == 12 or act.category == 2 then + spell = copy_entry(resources_ranged_attack) + else + if not res.action_messages[msg_ID] or msg_ID == 31 then + if act.category == 4 or act.category == 8 then + spell = spell_complete(copy_entry(res.spells[abil_ID])) + if act.category == 4 and spell then spell.recast = act.recast end + elseif T{6,13,14,15}:contains(act.category) then + spell = spell_complete(copy_entry(res.job_abilities[abil_ID])) -- May have to correct for charmed pets some day, but I'm not sure there are any monsters with TP moves that give no message. + elseif T{3,7}:contains(act.category) then + spell = spell_complete(copy_entry(res.weapon_skills[abil_ID])) + elseif T{5,9}:contains(act.category) then + spell = copy_entry(res.items[abil_ID]) + else + spell = {name=tostring(msg_ID)} + end + + return spell + end + + + local fields = fieldsearch(res.action_messages[msg_ID].english) -- ENGLISH + + if table.contains(fields,'spell') then + spell = copy_entry(res.spells[abil_ID]) + if act.category == 4 then spell.recast = act.recast end + elseif table.contains(fields,'ability') then + spell = copy_entry(res.job_abilities[abil_ID]) + elseif table.contains(fields,'weapon_skill') then + if abil_ID > 255 then -- WZ_RECOVER_ALL is used by chests in Limbus + spell = copy_entry(res.monster_abilities[abil_ID]) + if not spell then + spell = {id=abil_ID,english='Special Attack'} + end + elseif abil_ID < 256 then + spell = copy_entry(res.weapon_skills[abil_ID]) + end + elseif msg_ID == 303 then + spell = copy_entry(res.job_abilities[74]) -- Divine Seal + elseif msg_ID == 304 then + spell = copy_entry(res.job_abilities[75]) -- 'Elemental Seal' + elseif msg_ID == 305 then + spell = copy_entry(res.job_abilities[76]) -- 'Trick Attack' + elseif msg_ID == 311 or msg_ID == 311 then + spell = copy_entry(res.job_abilities[79]) -- 'Cover' + elseif msg_ID == 240 or msg_ID == 241 then + spell = copy_entry(res.job_abilities[43]) -- 'Hide' + elseif msg_ID == 244 then + spell = copy_entry(res.job_abilities[act.param]) -- Mug failures + elseif msg_ID == 328 then + spell = copy_entry(res.job_abilities[effect_val]) -- BPs that are out of range + end + + + if table.contains(fields,'item') then + if spell then + spell.item = copy_entry(res.items[effect_val]) + else + spell = copy_entry(res.items[abil_ID]) + end + else + spell = spell_complete(spell) + end + end + + if spell then + spell.name = spell[language] + spell.interrupted = false + end + + return spell +end + + +----------------------------------------------------------------------------------- +--Name: spell_complete(rline) +--Desc: Takes a resource line and modifies it so it includes aftercast cost and +-- a few other values +--Args: +---- rline - resource line +----------------------------------------------------------------------------------- +--Returns: +---- rline - modified resource line +----------------------------------------------------------------------------------- +function spell_complete(rline) + -- Hardcoded adjustments + if rline and rline.skill == 40 and buffactive.Pianissimo and rline.cast_time == 8 then + -- Pianissimo halves song casting time for buffs + rline.cast_time = 4 + rline.targets.Party = true + end + if rline and rline.skill == 44 and buffactive.Entrust and string.find(rline.en,"Indi") then + -- Entrust allows Indi- spells to be cast on party members + rline.targets.Party = true + end + + if rline == nil then + return {tpaftercast = player.tp, mpaftercast = player.mp, mppaftercast = player.mpp} + end + if not rline.mp_cost or rline.mp_cost == -1 then rline.mp_cost = 0 end + if not rline.tp_cost and rline.type == 'WeaponSkill' then + rline.tp_cost = player.tp + elseif not rline.tp_cost or rline.tp_cost == -1 then + rline.tp_cost = 0 + end + + if rline.skill and tonumber(rline.skill) then + rline.skill = res.skills[rline.skill][language] + end + + if rline.element and tonumber(rline.element) then + rline.element = res.elements[rline.element][language] + end + + if rline.tp_cost == 0 then rline.tpaftercast = player.tp else + rline.tpaftercast = player.tp - rline.tp_cost end + + if rline.mp_cost == 0 then + rline.mpaftercast = player.mp + rline.mppaftercast = player.mpp + else + rline.mpaftercast = player.mp - rline.mp_cost + rline.mppaftercast = (player.mp - rline.mp_cost)/player.max_mp + end + + return rline +end + +----------------------------------------------------------------------------------- +--Name: logit() +--Args: +---- logfile (file): File to be logged to +---- str (string): String to be logged. +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function logit(str) + if debugging.logging then + if not logfile and windower.dir_exists('../addons/GearSwap/data/logs') then + logfile = io.open('../addons/GearSwap/data/logs/NormalLog'..tostring(os.clock())..'.log','w+') + logfile:write('GearSwap LOGGER HEADER\n') + end + logfile:write(str) + logfile:flush() + end +end + +msg = {} + +----------------------------------------------------------------------------------- +--Name: msg.add_to_chat(col,str) +--Args: +---- col (num): Color to print out in (0x1F,col) +---- str (string): String to be printed. +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function msg.add_to_chat(col,str) + if str == '' then return end + if col == 1 then + windower.add_to_chat(1,str) + else + windower.add_to_chat(1,string.char(0x1F,col%256)..str..string.char(0x1E,0x01)) + end +end + +----------------------------------------------------------------------------------- +--Name: msg.debugging(message) +--Desc: Checks _settings.debug_mode and outputs the message if necessary +--Args: +---- message - The debug message +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function msg.debugging(message) + if _settings.debug_mode or debugging.general or debugging.command_registry then + msg.add_to_chat(8,"GearSwap (Debug Mode): "..windower.to_shift_jis(tostring(message))) + end +end + +----------------------------------------------------------------------------------- +--Name: msg.addon_msg(col,str) +--Args: +---- col (num): Color to print out in (0x1F,col) +---- str (string): String to be printed. +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function msg.addon_msg(col,str) + msg.add_to_chat(col,'GearSwap: '..str) +end + +-- Set up the priority list structure + +----------------------------------------------------------------------------------- +--Name: prioritize() +--Args: +---- priority_list (table): Current list of slot priorities +---- slot_id (number): Desired order of the piece of equipment +---- priority (number): Name for the slot +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +function prioritize(self,slot_id,priority) + if priority and tonumber(priority) then -- Check that priority is number + rawset(self,slot_id,priority) + return + elseif priority then + msg.addon_msg(123,'Invalid priority ('..tostring(priority)..') given') + end + rawset(self,slot_id,0) +end + + +local priority_list = {} + +Priorities = {} +function Priorities.new() + local new_instance = {} + return setmetatable(new_instance, { __index = function(t, k) if rawget(t, k) ~= nil then return rawget(t,k) else return rawget(priority_list,k) end end, + __newindex=prioritize}) +end + +----------------------------------------------------------------------------------- +--Name: priority_list:it() +--Args: +---- self (table): Current list of slot priorities +----------------------------------------------------------------------------------- +--Returns: +---- slot_id : Number from 0~15 +----------------------------------------------------------------------------------- +function priority_list:it() + return function () + local maximum,slot_id = -math.huge + for i=0,15 do + if self[i] and (self[i] > maximum or (self[i] == maximum and self[i] == -math.huge)) then + maximum = self[i] + slot_id = i + end + end + if not slot_id then return end + self[slot_id] = nil + return slot_id,maximum + end +end + + + +----------------------------------------------------------------------------------- +--Name: toslotname(slot_id) +--Args: +---- slot_id: Number from 0-15 representing the slot +----------------------------------------------------------------------------------- +--Returns: +---- slot name (string) +----------------------------------------------------------------------------------- +function toslotname(slot_id) + return rawget(default_slot_map,slot_id) +end + + + +----------------------------------------------------------------------------------- +--Name: toslotid(slot_name) +--Args: +---- slot_name: proposed slot name +----------------------------------------------------------------------------------- +--Returns: +---- slot id (whole number from 0-15) +----------------------------------------------------------------------------------- +function toslotid(slot_name) + return slot_map[slot_name] +end + + + +----------------------------------------------------------------------------------- +--Name: windower.debug(...) +--Args: +---- ...: Anything, to be passed to the real windower.debug if the windower_debugging +---- flag is set. +----------------------------------------------------------------------------------- +--Returns: +---- Nothing +----------------------------------------------------------------------------------- +windower.__raw = {debug = windower.debug} +windower.debug = function(...) + if debugging.windower_debug then __raw.debug(...) end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Modes.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Modes.lua new file mode 100644 index 0000000..4e01890 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Modes.lua @@ -0,0 +1,457 @@ +------------------------------------------------------------------------------------------------------------------- +-- This include library allows use of specially-designed tables for tracking +-- certain types of modes and state. +-- +-- Usage: include('Modes.lua') +-- +-- Construction syntax: +-- +-- 1) Create a new list of mode values. The first value will always be the default. +-- MeleeMode = M{'Normal', 'Acc', 'Att'} -- Construct as a basic table, using braces. +-- MeleeMode = M('Normal', 'Acc', 'Att') -- Pass in a list of strings, using parentheses. +-- +-- Optional: If constructed as a basic table, and the table contains a key value +-- of 'description', that description will be saved for future reference. +-- If a simple list of strings is passed in, no description will be set. +-- +-- MeleeMode = M{['description']='Melee Mode', 'Normal', 'Acc', 'Att'} +-- +-- +-- 2) Create a boolean mode with a specified default value (note parentheses): +-- UseLuzafRing = M(true) +-- UseLuzafRing = M(false) +-- +-- Optional: A string may be provided that will be used as the mode description: +-- UseLuzafRing = M(false, 'description') +-- UseLuzafRing = M(true, 'description') +-- +-- +-- 3) Create a string mode with a specified default value. Construct with a table: +-- CombatWeapon = M{['description']='Melee Mode', ['string']='Dagger'} +-- CombatWeapon = M{['description']='Melee Mode', ['string']=''} +-- +-- +-- +-- Public information fields (all are case-insensitive): +-- +-- 1) m.description -- Get a text description of the mode table, if it's been set. +-- 2) m.current -- Gets the current mode text value. Booleans will return the strings "on" or "off". +-- 3) m.value -- Gets the current mode value. Booleans will return the boolean values of true or false. +-- 3) m.index -- Gets the current index value, or true/false for booleans. +-- +-- Typically use m.current when using the mode to create a set name, and m.value when +-- testing the mode value in conditional rules. +-- +-- Public class functions: +-- +-- 1) m:describe(str) -- Sets the description for the mode table to the provided string value. +-- 2) m:options(options) -- Redefine the options for a list mode table. Cannot be used on a boolean table. +-- +-- +-- Public mode manipulation functions: +-- +-- 1) m:cycle() -- Cycles through the list going forwards. Acts as a toggle on boolean mode vars. +-- 2) m:cycleback() -- Cycles through the list going backwards. Acts as a toggle on boolean mode vars. +-- 3) m:toggle() -- Toggles a boolean Mode between true and false. +-- 4) m:set(n) -- Sets the current mode value to n. +-- Note: If m is boolean, n can be boolean true/false, or string of on/off/true/false. +-- Note: If m is boolean and no n is given, this forces m to true. +-- 5) m:unset() -- Sets a boolean mode var to false. +-- 6) m:reset() -- Returns the mode var to its default state. +-- 7) m:default() -- Same as reset() +-- +-- All public functions return the current value after completion. +-- +-- +-- Example usage: +-- +-- sets.MeleeMode.Normal = {} +-- sets.MeleeMode.Acc = {} +-- sets.MeleeMode.Att = {} +-- +-- MeleeMode = M{'Normal', 'Acc', 'Att', ['description']='Melee Mode'} +-- MeleeMode:cycle() +-- equip(sets.engaged[MeleeMode.current]) +-- MeleeMode:options('Normal', 'LowAcc', 'HighAcc') +-- >> Changes the option list, but the description stays 'Melee Mode' +-- +-- +-- sets.LuzafRing.on = {ring2="Luzaf's Ring"} +-- sets.LuzafRing.off = {} +-- +-- UseLuzafRing = M(false) +-- UseLuzafRing:toggle() +-- equip(sets.precast['Phantom Roll'], sets.LuzafRing[UseLuzafRing.value]) +------------------------------------------------------------------------------------------------------------------- + + +_meta = _meta or {} +_meta.M = {} +_meta.M.__class = 'mode' +_meta.M.__methods = {} + + +-- Default constructor for mode tables +-- M{'a', 'b', etc, ['description']='a'} -- defines a mode list, description 'a' +-- M('a', 'b', etc) -- defines a mode list, no description +-- M('a') -- defines a mode list, default 'a' +-- M{['description']='a'} -- defines a mode list, default 'Normal', description 'a' +-- M{} -- defines a mode list, default 'Normal', no description +-- M(false) -- defines a mode boolean, default false, no description +-- M(true) -- defines a mode boolean, default true, no description +-- M(false, 'a') -- defines a mode boolean, default false, description 'a' +-- M(true, 'a') -- defines a mode boolean, default true, description 'a' +-- M() -- defines a mode boolean, default false, no description +function M(t, ...) + local m = {} + m._track = {} + m._track._class = 'mode' + + -- If we're passed a list of strings (that is, the first element is a string), + -- convert them to a table + local args = {...} + if type(t) == 'string' then + t = {[1] = t} + + for ind, val in ipairs(args) do + t[ind+1] = val + end + end + + -- Construct the table that we'll be adding the metadata to + + -- If we have a table of values, it's either a list or a string + if type(t) == 'table' then + -- Save the description, if provided + if t['description'] then + m._track._description = t['description'] + end + + -- If we were given an explicit 'string' field, construct a string mode class. + if t.string and type(t.string) == 'string' then + m._track._type = 'string' + m._track._count = 1 + m._track._default = 'defaultstring' + + if t.string then + m['string'] = t.string + m['defaultstring'] = t.string + end + -- Otherwise put together a standard list mode class. + else + m._track._type = 'list' + m._track._invert = {} + m._track._count = 0 + m._track._default = 1 + + -- Only copy numerically indexed values + for ind, val in ipairs(t) do + m[ind] = val + m._track._invert[val] = ind + m._track._count = ind + end + + if m._track._count == 0 then + m[1] = 'Normal' + m._track._invert['Normal'] = 1 + m._track._count = 1 + end + end + -- If the first argument is a bool, construct a boolean mode class. + elseif type(t) == 'boolean' or t == nil then + m._track._type = 'boolean' + m._track._count = 2 + m._track._default = t or false + m._track._description = args[1] + -- Text lookups for bool values + m[true] = 'on' + m[false] = 'off' + else + -- Construction failure + error("Unable to construct a mode table with the provided parameters.", 2) + end + + -- Initialize current value to the default. + m._track._current = m._track._default + + return setmetatable(m, _meta.M) +end + +-------------------------------------------------------------------------- +-- Metamethods +-- Functions that will be used as metamethods for the class +-------------------------------------------------------------------------- + +-- Handler for __index when trying to access the current mode value. +-- Handles indexing 'current' or 'value' keys. +_meta.M.__index = function(m, k) + if type(k) == 'string' then + local lk = k:lower() + if lk == 'current' then + return m[m._track._current] + elseif lk == 'value' then + if m._track._type == 'boolean' then + return m._track._current + else + return m[m._track._current] + end + elseif lk == 'has_value' then + return _meta.M.__methods.f_has_value(m) + elseif lk == 'default' then + if m._track._type == 'boolean' then + return m._track._default + else + return m[m._track._default] + end + elseif lk == 'description' then + return m._track._description + elseif lk == 'index' then + return m._track._current + elseif m._track[lk] then + return m._track[lk] + elseif m._track['_'..lk] then + return m._track['_'..lk] + else + return _meta.M.__methods[lk] + end + end +end + +-- Tostring handler for printing out the table and its current state. +_meta.M.__tostring = function(m) + local res = '' + if m._track._description then + res = res .. m._track._description .. ': ' + end + + if m._track._type == 'list' then + res = res .. '{' + for k,v in ipairs(m) do + res = res..tostring(v) + if m[k+1] ~= nil then + res = res..', ' + end + end + res = res..'}' + elseif m._track._type == 'string' then + res = res .. 'String' + else + res = res .. 'Boolean' + end + + res = res .. ' ('..tostring(m.Current).. ')' + + -- Debug addition + --res = res .. ' [' .. m._track._type .. '/' .. tostring(m._track._current) .. ']' + + return res +end + +-- Length handler for the # value lookup. +_meta.M.__len = function(m) + return m._track._count +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that can be used as public methods for manipulating the class. +-------------------------------------------------------------------------- + +-- Function to set the table's description. +_meta.M.__methods['describe'] = function(m, str) + if type(str) == 'string' then + m._track._description = str + else + error("Invalid argument type: " .. type(str), 2) + end +end + +-- Function to change the list of options available. +-- Leaves the description intact. +-- Cannot be used on boolean classes. +_meta.M.__methods['options'] = function(m, ...) + if m._track._type ~= 'list' then + error("Can only revise the options list for a list mode class.", 2) + end + + local options = {...} + -- Always include a default option if nothing else is given. + if #options == 0 then + options = {'Normal'} + end + + -- Zero-out existing values and clear the tracked inverted list + -- and member count. + for key,val in ipairs(m) do + m[key] = nil + end + m._track._invert = {} + m._track._count = 0 + + -- Insert in new data. + for key,val in ipairs(options) do + m[key] = val + m._track._invert[val] = key + m._track._count = key + end + + m._track._current = m._track._default +end + + +-- Function to test whether the list table contains the specified value. +_meta.M.__methods['contains'] = function(m, str) + if m._track._invert then + if type(str) == 'string' then + return (m._track._invert[str] ~= nil) + else + error("Invalid argument type: " .. type(str), 2) + end + else + error("Cannot test for containment on a " .. m._track._type .. " mode class.", 2) + end +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that will be used as public methods for manipulating state. +-------------------------------------------------------------------------- + +-- Cycle forwards through the list +_meta.M.__methods['cycle'] = function(m) + if m._track._type == 'list' then + m._track._current = (m._track._current % m._track._count) + 1 + elseif m._track._type == 'boolean' then + m:toggle() + end + + return m.Current +end + +-- Cycle backwards through the list +_meta.M.__methods['cycleback'] = function(m) + if m._track._type == 'list' then + m._track._current = m._track._current - 1 + if m._track._current < 1 then + m._track._current = m._track._count + end + elseif m._track._type == 'boolean' then + m:toggle() + end + + return m.Current +end + +-- Toggle a boolean value +_meta.M.__methods['toggle'] = function(m) + if m._track._type == 'boolean' then + m._track._current = not m._track._current + else + error("Can only toggle a boolean mode.", 2) + end + + return m.Current +end + + +-- Set the current value +_meta.M.__methods['set'] = function(m, val) + if m._track._type == 'boolean' then + if val == nil then + m._track._current = true + elseif type(val) == 'boolean' then + m._track._current = val + elseif type(val) == 'string' then + val = val:lower() + if val == 'on' or val == 'true' then + m._track._current = true + elseif val == 'off' or val == 'false' then + m._track._current = false + else + error("Unrecognized value: "..tostring(val), 2) + end + else + error("Unrecognized value type: "..type(val), 2) + end + elseif m._track._type == 'list' then + if not val then + error("List variable cannot be set to nil.", 2) + end + if m._track._invert[val] then + m._track._current = m._track._invert[val] + else + local found = false + for v, ind in pairs(m._track._invert) do + if val:lower() == v:lower() then + m._track._current = ind + found = true + break + end + end + + if not found then + error("Unknown mode value: " .. tostring(val), 2) + end + end + elseif m._track._type == 'string' then + if type(val) == 'string' then + m._track._current = 'string' + m.string = val + else + error("Unrecognized value type: "..type(val), 2) + end + end + + return m.Current +end + + +-- Forces a boolean mode to false, or a string to an empty string. +_meta.M.__methods['unset'] = function(m) + if m._track._type == 'boolean' then + m._track._current = false + elseif m._track._type == 'string' then + m._track._current = 'string' + m.string = '' + else + error("Cannot unset a list mode class.", 2) + end + + return m.Current +end + + +-- Reset to the default value +_meta.M.__methods['reset'] = function(m) + m._track._current = m._track._default + + return m.Current +end + + +-- Check to be sure that the mode var has a valid (for its type) value. +-- String vars must be non-empty. +-- List vars must not be empty strings, or the word 'none'. +-- Boolean are always considered valid (can only be true or false). +_meta.M.__methods['f_has_value'] = function(m) + local cur = m.value + if m._track._type == 'string' then + if cur and cur ~= '' then + return true + else + return false + end + elseif m._track._type == 'boolean' then + return true + else + if not cur or cur == '' or cur:lower() == 'none' then + return false + else + return true + end + end +end + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Globals.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Globals.lua new file mode 100644 index 0000000..d7b2e04 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Globals.lua @@ -0,0 +1,114 @@ +------------------------------------------------------------------------------------------------------------------- +-- Tables and functions for commonly-referenced gear that job files may need, but +-- doesn't belong in the global Mote-Include file since they'd get clobbered on each +-- update. +-- Creates the 'gear' table for reference in other files. +-- +-- Note: Function and table definitions should be added to user, but references to +-- the contained tables via functions (such as for the obi function, below) use only +-- the 'gear' table. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Modify the sets table. Any gear sets that are added to the sets table need to +-- be defined within this function, because sets isn't available until after the +-- include is complete. It is called at the end of basic initialization in Mote-Include. +------------------------------------------------------------------------------------------------------------------- + +function define_global_sets() + -- Special gear info that may be useful across jobs. + + -- Staffs + gear.Staff = {} + gear.Staff.HMP = 'Chatoyant Staff' + gear.Staff.PDT = 'Earth Staff' + + -- Dark Rings + gear.DarkRing = {} + gear.DarkRing.physical = {name="Dark Ring",augments={'Magic dmg. taken -3%','Spell interruption rate down -5%','Phys. dmg. taken -6%'}} + gear.DarkRing.magical = {name="Dark Ring", augments={'Magic dmg. taken -6%','Breath dmg. taken -5%'}} + + -- Default items for utility gear values. + gear.default.weaponskill_neck = "Asperity Necklace" + gear.default.weaponskill_waist = "Caudata Belt" + gear.default.obi_waist = "Cognition Belt" + gear.default.obi_back = "Toro Cape" + gear.default.obi_ring = "Strendu Ring" + gear.default.fastcast_staff = "" + gear.default.recast_staff = "" +end + +------------------------------------------------------------------------------------------------------------------- +-- Functions to set user-specified binds, generally on load and unload. +-- Kept separate from the main include so as to not get clobbered when the main is updated. +------------------------------------------------------------------------------------------------------------------- + +-- Function to bind GearSwap binds when loading a GS script. +function global_on_load() + send_command('bind f9 gs c cycle OffenseMode') + send_command('bind ^f9 gs c cycle HybridMode') + send_command('bind !f9 gs c cycle RangedMode') + send_command('bind @f9 gs c cycle WeaponskillMode') + send_command('bind f10 gs c set DefenseMode Physical') + send_command('bind ^f10 gs c cycle PhysicalDefenseMode') + send_command('bind !f10 gs c toggle Kiting') + send_command('bind f11 gs c set DefenseMode Magical') + send_command('bind ^f11 gs c cycle CastingMode') + send_command('bind f12 gs c update user') + send_command('bind ^f12 gs c cycle IdleMode') + send_command('bind !f12 gs c reset DefenseMode') + + send_command('bind ^- gs c toggle selectnpctargets') + send_command('bind ^= gs c cycle pctargetmode') +end + +-- Function to revert binds when unloading. +function global_on_unload() + send_command('unbind f9') + send_command('unbind ^f9') + send_command('unbind !f9') + send_command('unbind @f9') + send_command('unbind f10') + send_command('unbind ^f10') + send_command('unbind !f10') + send_command('unbind f11') + send_command('unbind ^f11') + send_command('unbind !f11') + send_command('unbind f12') + send_command('unbind ^f12') + send_command('unbind !f12') + + send_command('unbind ^-') + send_command('unbind ^=') +end + +------------------------------------------------------------------------------------------------------------------- +-- Global event-handling functions. +------------------------------------------------------------------------------------------------------------------- + +-- Global intercept on precast. +function user_precast(spell, action, spellMap, eventArgs) + cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + refine_waltz(spell, action, spellMap, eventArgs) +end + +-- Global intercept on midcast. +function user_midcast(spell, action, spellMap, eventArgs) + -- Default base equipment layer of fast recast. + if spell.action_type == 'Magic' and sets.midcast and sets.midcast.FastRecast then + equip(sets.midcast.FastRecast) + end +end + +-- Global intercept on buff change. +function user_buff_change(buff, gain, eventArgs) + -- Create a timer when we gain weakness. Remove it when weakness is gone. + if buff:lower() == 'weakness' then + if gain then + send_command('timers create "Weakness" 300 up abilities/00255.png') + else + send_command('timers delete "Weakness"') + end + end +end + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Include.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Include.lua new file mode 100644 index 0000000..e5527f0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Include.lua @@ -0,0 +1,1125 @@ +------------------------------------------------------------------------------------------------------------------- +-- Common variables and functions to be included in job scripts, for general default handling. +-- +-- Include this file in the get_sets() function with the command: +-- include('Mote-Include.lua') +-- +-- It will then automatically run its own init_include() function. +-- +-- IMPORTANT: This include requires supporting include files: +-- Mote-Utility +-- Mote-Mappings +-- Mote-SelfCommands +-- Mote-Globals +-- +-- Place the include() directive at the start of a job's get_sets() function. +-- +-- Included variables and functions are considered to be at the same scope level as +-- the job script itself, and can be used as such. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Initialization function that defines variables to be used. +-- These are accessible at the including job lua script's scope. +-- +-- Auto-initialize after defining this function. +------------------------------------------------------------------------------------------------------------------- + +current_mote_include_version = 2 + +function init_include() + -- Used to define various types of data mappings. These may be used in the initialization, so load it up front. + include('Mote-Mappings') + + -- Modes is the include for a mode-tracking variable class. Used for state vars, below. + include('Modes') + + -- Var for tracking state values + state = {} + + -- General melee offense/defense modes, allowing for hybrid set builds, as well as idle/resting/weaponskill. + -- This just defines the vars and sets the descriptions. List modes with no values automatically + -- get assigned a 'Normal' default value. + state.OffenseMode = M{['description'] = 'Offense Mode'} + state.HybridMode = M{['description'] = 'Hybrid Mode'} + state.RangedMode = M{['description'] = 'Ranged Mode'} + state.WeaponskillMode = M{['description'] = 'Weaponskill Mode'} + state.CastingMode = M{['description'] = 'Casting Mode'} + state.IdleMode = M{['description'] = 'Idle Mode'} + state.RestingMode = M{['description'] = 'Resting Mode'} + + state.DefenseMode = M{['description'] = 'Defense Mode', 'None', 'Physical', 'Magical'} + state.PhysicalDefenseMode = M{['description'] = 'Physical Defense Mode', 'PDT'} + state.MagicalDefenseMode = M{['description'] = 'Magical Defense Mode', 'MDT'} + + state.Kiting = M(false, 'Kiting') + state.SelectNPCTargets = M(false, 'Select NPC Targets') + state.PCTargetMode = M{['description'] = 'PC Target Mode', 'default', 'stpt', 'stal', 'stpc'} + + state.EquipStop = M{['description'] = 'Stop Equipping Gear', 'off', 'precast', 'midcast', 'pet_midcast'} + + state.CombatWeapon = M{['description']='Combat Weapon', ['string']=''} + state.CombatForm = M{['description']='Combat Form', ['string']=''} + + -- Non-mode vars that are used for state tracking. + state.MaxWeaponskillDistance = 0 + state.Buff = {} + + -- Classes describe a 'type' of action. They are similar to state, but + -- may have any free-form value, or describe an entire table of mapped values. + classes = {} + -- Basic spell mappings are based on common spell series. + -- EG: 'Cure' for Cure, Cure II, Cure III, Cure IV, Cure V, or Cure VI. + classes.SpellMaps = spell_maps + -- List of spells and spell maps that don't benefit from greater skill (though + -- they may benefit from spell-specific augments, such as improved regen or refresh). + -- Spells that fall under this category will be skipped when searching for + -- spell.skill sets. + classes.NoSkillSpells = no_skill_spells_list + classes.SkipSkillCheck = false + -- Custom, job-defined class, like the generic spell mappings. + -- Takes precedence over default spell maps. + -- Is reset at the end of each spell casting cycle (ie: at the end of aftercast). + classes.JAMode = nil + classes.CustomClass = nil + -- Custom groups used for defining melee and idle sets. Persists long-term. + classes.CustomMeleeGroups = L{} + classes.CustomRangedGroups = L{} + classes.CustomIdleGroups = L{} + classes.CustomDefenseGroups = L{} + + -- Class variables for time-based flags + classes.Daytime = false + classes.DuskToDawn = false + + + -- Var for tracking misc info + info = {} + options = {} + + -- Special control flags. + mote_vars = {} + mote_vars.set_breadcrumbs = L{} + mote_vars.res_buffs = S{} + for index,struct in pairs(gearswap.res.buffs) do + mote_vars.res_buffs:add(struct.en) + end + + -- Sub-tables within the sets table that we expect to exist, and are annoying to have to + -- define within each individual job file. We can define them here to make sure we don't + -- have to check for existence. The job file should be including this before defining + -- any sets, so any changes it makes will override these anyway. + sets.precast = {} + sets.precast.FC = {} + sets.precast.JA = {} + sets.precast.WS = {} + sets.precast.RA = {} + sets.midcast = {} + sets.midcast.RA = {} + sets.midcast.Pet = {} + sets.idle = {} + sets.resting = {} + sets.engaged = {} + sets.defense = {} + sets.buff = {} + + gear = {} + gear.default = {} + + gear.ElementalGorget = {name=""} + gear.ElementalBelt = {name=""} + gear.ElementalObi = {name=""} + gear.ElementalCape = {name=""} + gear.ElementalRing = {name=""} + gear.FastcastStaff = {name=""} + gear.RecastStaff = {name=""} + + + -- Load externally-defined information (info that we don't want to change every time this file is updated). + + -- Used to define misc utility functions that may be useful for this include or any job files. + include('Mote-Utility') + + -- Used for all self-command handling. + include('Mote-SelfCommands') + + -- Include general user globals, such as custom binds or gear tables. + -- Load Mote-Globals first, followed by User-Globals, followed by <character>-Globals. + -- Any functions re-defined in the later includes will overwrite the earlier versions. + include('Mote-Globals') + optional_include({'user-globals.lua'}) + optional_include({player.name..'-globals.lua'}) + + -- *-globals.lua may define additional sets to be added to the local ones. + if define_global_sets then + define_global_sets() + end + + -- Global default binds (either from Mote-Globals or user-globals) + (binds_on_load or global_on_load)() + + -- Load a sidecar file for the job (if it exists) that may re-define init_gear_sets and file_unload. + load_sidecar(player.main_job) + + -- General var initialization and setup. + if job_setup then + job_setup() + end + + -- User-specific var initialization and setup. + if user_setup then + user_setup() + end + + -- Load up all the gear sets. + init_gear_sets() +end + +if not mote_include_version or mote_include_version < current_mote_include_version then + add_to_chat(123,'Warning: Your job file is out of date. Please update to the latest repository baseline.') + add_to_chat(123,'For details, visit https://github.com/Kinematics/GearSwap-Jobs/wiki/Upgrading') + rev = mote_include_version or 1 + include_path('rev' .. tostring(rev)) + include('Mote-Include') + return +end + +-- Auto-initialize the include +init_include() + +-- Called when this job file is unloaded (eg: job change) +-- Conditional definition so that it doesn't overwrite explicit user +-- versions of this function. +if not file_unload then + file_unload = function() + if user_unload then + user_unload() + elseif job_file_unload then + job_file_unload() + end + _G[(binds_on_unload and 'binds_on_unload') or 'global_on_unload']() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Generalized functions for handling precast/midcast/aftercast for player-initiated actions. +-- This depends on proper set naming. +-- Global hooks can be written as user_xxx() to override functions at a global level. +-- Each job can override any of these general functions using job_xxx() hooks. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------ +-- Generic function to map a set processing order to all action events. +------------------------------------------------------------------------ + + +-- Process actions in a specific order of events: +-- Filter - filter_xxx() functions determine whether to run any of the code for this action. +-- Global - user_xxx() functions get called first. Define in Mote-Globals or User-Globals. +-- Local - job_xxx() functions get called next. Define in JOB.lua file. +-- Default - default_xxx() functions get called next. Defined in this file. +-- Cleanup - cleanup_xxx() functions always get called before exiting. +-- +-- Parameters: +-- spell - standard spell table passed in by GearSwap +-- action - string defining the function mapping to use (precast, midcast, etc) +function handle_actions(spell, action) + -- Init an eventArgs that allows cancelling. + local eventArgs = {handled = false, cancel = false} + + mote_vars.set_breadcrumbs:clear() + + -- Get the spell mapping, since we'll be passing it to various functions and checks. + local spellMap = get_spell_map(spell) + + -- General filter checks to see whether this function should be run. + -- If eventArgs.cancel is set, cancels this function, not the spell. + if _G['filter_'..action] then + _G['filter_'..action](spell, spellMap, eventArgs) + end + + -- If filter didn't cancel it, process user and default actions. + if not eventArgs.cancel then + -- Global user handling of this action + if _G['user_'..action] then + _G['user_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Job-specific handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['job_'..action] then + _G['job_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Default handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['default_'..action] then + _G['default_'..action](spell, spellMap) + display_breadcrumbs(spell, spellMap, action) + end + + -- Global post-handling of this action + if not eventArgs.cancel and _G['user_post_'..action] then + _G['user_post_'..action](spell, action, spellMap, eventArgs) + end + + -- Job-specific post-handling of this action + if not eventArgs.cancel and _G['job_post_'..action] then + _G['job_post_'..action](spell, action, spellMap, eventArgs) + end + end + + -- Cleanup once this action is done + if _G['cleanup_'..action] then + _G['cleanup_'..action](spell, spellMap, eventArgs) + end +end + + +-------------------------------------- +-- Action hooks called by GearSwap. +-------------------------------------- + +function pretarget(spell) + handle_actions(spell, 'pretarget') +end + +function precast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = true + end + handle_actions(spell, 'precast') +end + +function midcast(spell) + handle_actions(spell, 'midcast') +end + +function aftercast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = not spell.interrupted or buffactive[spell.english] or false + end + handle_actions(spell, 'aftercast') +end + +function pet_midcast(spell) + handle_actions(spell, 'pet_midcast') +end + +function pet_aftercast(spell) + handle_actions(spell, 'pet_aftercast') +end + +-------------------------------------- +-- Default code for each action. +-------------------------------------- + +function default_pretarget(spell, spellMap) + auto_change_target(spell, spellMap) +end + +function default_precast(spell, spellMap) + equip(get_precast_set(spell, spellMap)) +end + +function default_midcast(spell, spellMap) + equip(get_midcast_set(spell, spellMap)) +end + +function default_aftercast(spell, spellMap) + if not pet_midaction() then + handle_equipping_gear(player.status) + end +end + +function default_pet_midcast(spell, spellMap) + equip(get_pet_midcast_set(spell, spellMap)) +end + +function default_pet_aftercast(spell, spellMap) + handle_equipping_gear(player.status) +end + +-------------------------------------- +-- Filters for each action. +-- Set eventArgs.cancel to true to stop further processing. +-- May show notification messages, but should not do any processing here. +-------------------------------------- + +function filter_midcast(spell, spellMap, eventArgs) + if state.EquipStop.value == 'precast' then + eventArgs.cancel = true + end +end + +function filter_aftercast(spell, spellMap, eventArgs) + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' or state.EquipStop.value == 'pet_midcast' then + eventArgs.cancel = true + elseif spell.name == 'Unknown Interrupt' then + eventArgs.cancel = true + end +end + +function filter_pet_midcast(spell, spellMap, eventArgs) + -- If we have show_set active for precast or midcast, don't try to equip pet midcast gear. + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' then + add_to_chat(104, 'Show Sets: Pet midcast not equipped.') + eventArgs.cancel = true + end +end + +function filter_pet_aftercast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast or midcast, don't try to equip aftercast gear. + if state.EquipStop.value == 'precast' or state.EquipStop.value == 'midcast' or state.EquipStop.value == 'pet_midcast' then + eventArgs.cancel = true + end +end + +-------------------------------------- +-- Cleanup code for each action. +-------------------------------------- + +function cleanup_precast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'precast' then + add_to_chat(104, 'Show Sets: Stopping at precast.') + end +end + +function cleanup_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for midcast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'midcast' then + add_to_chat(104, 'Show Sets: Stopping at midcast.') + end +end + +function cleanup_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + -- If we're in the middle of a pet action, pet_aftercast will handle clearing it. + if not pet_midaction() then + reset_transitory_classes() + end +end + +function cleanup_pet_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for pet midcast, notify that we won't try to equip later gear. + if state.EquipStop.value == 'pet_midcast' then + add_to_chat(104, 'Show Sets: Stopping at pet midcast.') + end +end + +function cleanup_pet_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + reset_transitory_classes() +end + + +-- Clears the values from classes that only exist til the action is complete. +function reset_transitory_classes() + classes.CustomClass = nil + classes.JAMode = nil +end + + + +------------------------------------------------------------------------------------------------------------------- +-- High-level functions for selecting and equipping gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Central point to call to equip gear based on status. +-- Status - Player status that we're using to define what gear to equip. +function handle_equipping_gear(playerStatus, petStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_handle_equipping_gear then + job_handle_equipping_gear(playerStatus, eventArgs) + end + + -- Equip default gear if job didn't handle it. + if not eventArgs.handled then + equip_gear_by_status(playerStatus, petStatus) + end +end + + +-- Function to wrap logic for equipping gear on aftercast, status change, or user update. +-- @param status : The current or new player status that determines what sort of gear to equip. +function equip_gear_by_status(playerStatus, petStatus) + if _global.debug_mode then add_to_chat(123,'Debug: Equip gear for status ['..tostring(status)..'], HP='..tostring(player.hp)) end + + playerStatus = playerStatus or player.status or 'Idle' + + -- If status not defined, treat as idle. + -- Be sure to check for positive HP to make sure they're not dead. + if (playerStatus == 'Idle' or playerStatus == '') and player.hp > 0 then + equip(get_idle_set(petStatus)) + elseif playerStatus == 'Engaged' then + equip(get_melee_set(petStatus)) + elseif playerStatus == 'Resting' then + equip(get_resting_set(petStatus)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on status. +------------------------------------------------------------------------------------------------------------------- + +-- Returns the appropriate idle set based on current state values and location. +-- Set construction order (all of which are optional): +-- sets.idle[idleScope][state.IdleMode][Pet[Engaged]][CustomIdleGroups] +-- +-- Params: +-- petStatus - Optional explicit definition of pet status. +function get_idle_set(petStatus) + local idleSet = sets.idle + + if not idleSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('idle') + + local idleScope + + if buffactive.weakness then + idleScope = 'Weak' + elseif areas.Cities:contains(world.area) then + idleScope = 'Town' + else + idleScope = 'Field' + end + + if idleSet[idleScope] then + idleSet = idleSet[idleScope] + mote_vars.set_breadcrumbs:append(idleScope) + end + + if idleSet[state.IdleMode.current] then + idleSet = idleSet[state.IdleMode.current] + mote_vars.set_breadcrumbs:append(state.IdleMode.current) + end + + if (pet.isvalid or state.Buff.Pet) and idleSet.Pet then + idleSet = idleSet.Pet + petStatus = petStatus or pet.status + mote_vars.set_breadcrumbs:append('Pet') + + if petStatus == 'Engaged' and idleSet.Engaged then + idleSet = idleSet.Engaged + mote_vars.set_breadcrumbs:append('Engaged') + end + end + + for _,group in ipairs(classes.CustomIdleGroups) do + if idleSet[group] then + idleSet = idleSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + idleSet = apply_defense(idleSet) + idleSet = apply_kiting(idleSet) + + if user_customize_idle_set then + idleSet = user_customize_idle_set(idleSet) + end + + if customize_idle_set then + idleSet = customize_idle_set(idleSet) + end + + return idleSet +end + + +-- Returns the appropriate melee set based on current state values. +-- Set construction order (all sets after sets.engaged are optional): +-- sets.engaged[state.CombatForm][state.CombatWeapon][state.OffenseMode][state.DefenseMode][classes.CustomMeleeGroups (any number)] +function get_melee_set() + local meleeSet = sets.engaged + + if not meleeSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('engaged') + + if state.CombatForm.has_value and meleeSet[state.CombatForm.value] then + meleeSet = meleeSet[state.CombatForm.value] + mote_vars.set_breadcrumbs:append(state.CombatForm.value) + end + + if state.CombatWeapon.has_value and meleeSet[state.CombatWeapon.value] then + meleeSet = meleeSet[state.CombatWeapon.value] + mote_vars.set_breadcrumbs:append(state.CombatWeapon.value) + end + + if meleeSet[state.OffenseMode.current] then + meleeSet = meleeSet[state.OffenseMode.current] + mote_vars.set_breadcrumbs:append(state.OffenseMode.current) + end + + if meleeSet[state.HybridMode.current] then + meleeSet = meleeSet[state.HybridMode.current] + mote_vars.set_breadcrumbs:append(state.HybridMode.current) + end + + for _,group in ipairs(classes.CustomMeleeGroups) do + if meleeSet[group] then + meleeSet = meleeSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + meleeSet = apply_defense(meleeSet) + meleeSet = apply_kiting(meleeSet) + + if customize_melee_set then + meleeSet = customize_melee_set(meleeSet) + end + + if user_customize_melee_set then + meleeSet = user_customize_melee_set(meleeSet) + end + + return meleeSet +end + + +-- Returns the appropriate resting set based on current state values. +-- Set construction order: +-- sets.resting[state.RestingMode] +function get_resting_set() + local restingSet = sets.resting + + if not restingSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('resting') + + if restingSet[state.RestingMode.current] then + restingSet = restingSet[state.RestingMode.current] + mote_vars.set_breadcrumbs:append(state.RestingMode.current) + end + + return restingSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on action. +------------------------------------------------------------------------------------------------------------------- + +-- Get the default precast gear set. +function get_precast_set(spell, spellMap) + -- If there are no precast sets defined, bail out. + if not sets.precast then + return {} + end + + local equipSet = sets.precast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('precast') + + -- Determine base sub-table from type of action being performed. + + local cat + + if spell.action_type == 'Magic' then + cat = 'FC' + elseif spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Ability' then + if spell.type == 'WeaponSkill' then + cat = 'WS' + elseif spell.type == 'JobAbility' then + cat = 'JA' + else + -- Allow fallback to .JA table if spell.type isn't found, for all non-weaponskill abilities. + cat = (sets.precast[spell.type] and spell.type) or 'JA' + end + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = false + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + + -- Once we have a named base set, do checks for specialized modes (casting mode, weaponskill mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.type == 'WeaponSkill' then + equipSet = get_weaponskill_set(equipSet, spell, spellMap) + elseif spell.action_type == 'Ability' then + if classes.JAMode and equipSet[classes.JAMode] then + equipSet = equipSet[classes.JAMode] + mote_vars.set_breadcrumbs:append(classes.JAMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Update defintions for element-specific gear that may be used. + set_elemental_gear(spell) + + -- Return whatever we've constructed. + return equipSet +end + + + +-- Get the default midcast gear set. +-- This builds on sets.midcast. +function get_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast then + return {} + end + + local equipSet = sets.midcast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + + -- Determine base sub-table from type of action being performed. + -- Only ranged attacks and items get specific sub-categories here. + + local cat + + if spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = classes.NoSkillSpells:contains(spell.english) + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- After the default checks, do checks for specialized modes (casting mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Return whatever we've constructed. + return equipSet +end + + +-- Get the default pet midcast gear set. +-- This is built in sets.midcast.Pet. +function get_pet_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast or not sets.midcast.Pet then + return {} + end + + local equipSet = sets.midcast.Pet + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + mote_vars.set_breadcrumbs:append('Pet') + + if sets.midcast and sets.midcast.Pet then + classes.SkipSkillCheck = false + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- We can only generally be certain about whether the pet's action is + -- Magic (ie: it cast a spell of its own volition) or Ability (it performed + -- an action at the request of the player). Allow CastinMode and + -- OffenseMode to refine whatever set was selected above. + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode.current] then + equipSet = equipSet[state.CastingMode.current] + mote_vars.set_breadcrumbs:append(state.CastingMode.current) + end + elseif spell.action_type == 'Ability' then + if equipSet[state.OffenseMode.current] then + equipSet = equipSet[state.OffenseMode.current] + mote_vars.set_breadcrumbs:append(state.OffenseMode.current) + end + end + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper weaponskill set. +function get_weaponskill_set(equipSet, spell, spellMap) + -- Custom handling for weaponskills + local ws_mode = state.WeaponskillMode.current + + if ws_mode == 'Normal' then + -- If a particular weaponskill mode isn't specified, see if we have a weaponskill mode + -- corresponding to the current offense mode. If so, use that. + if spell.skill == 'Archery' or spell.skill == 'Marksmanship' then + if state.RangedMode.current ~= 'Normal' and state.WeaponskillMode:contains(state.RangedMode.current) then + ws_mode = state.RangedMode.current + end + else + if state.OffenseMode.current ~= 'Normal' and state.WeaponskillMode:contains(state.OffenseMode.current) then + ws_mode = state.OffenseMode.current + end + end + end + + local custom_wsmode + + -- Allow the job file to specify a preferred weaponskill mode + if get_custom_wsmode then + custom_wsmode = get_custom_wsmode(spell, spellMap, ws_mode) + end + + -- If the job file returned a weaponskill mode, use that. + if custom_wsmode then + ws_mode = custom_wsmode + end + + if equipSet[ws_mode] then + equipSet = equipSet[ws_mode] + mote_vars.set_breadcrumbs:append(ws_mode) + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper ranged set. +function get_ranged_set(equipSet, spell, spellMap) + -- Attach Combat Form and Combat Weapon to set checks + if state.CombatForm.has_value and equipSet[state.CombatForm.value] then + equipSet = equipSet[state.CombatForm.value] + mote_vars.set_breadcrumbs:append(state.CombatForm.value) + end + + if state.CombatWeapon.has_value and equipSet[state.CombatWeapon.value] then + equipSet = equipSet[state.CombatWeapon.value] + mote_vars.set_breadcrumbs:append(state.CombatWeapon.value) + end + + -- Check for specific mode for ranged attacks (eg: Acc, Att, etc) + if equipSet[state.RangedMode.current] then + equipSet = equipSet[state.RangedMode.current] + mote_vars.set_breadcrumbs:append(state.RangedMode.current) + end + + -- Tack on any additionally specified custom groups, if the sets are defined. + for _,group in ipairs(classes.CustomRangedGroups) do + if equipSet[group] then + equipSet = equipSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + return equipSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for optional supplemental gear overriding the default sets defined above. +------------------------------------------------------------------------------------------------------------------- + +-- Function to apply any active defense set on top of the supplied set +-- @param baseSet : The set that any currently active defense set will be applied on top of. (gear set table) +function apply_defense(baseSet) + if state.DefenseMode.current ~= 'None' then + local defenseSet = sets.defense + + defenseSet = sets.defense[state[state.DefenseMode.current .. 'DefenseMode'].current] or defenseSet + + for _,group in ipairs(classes.CustomDefenseGroups) do + defenseSet = defenseSet[group] or defenseSet + end + + if customize_defense_set then + defenseSet = customize_defense_set(defenseSet) + end + + baseSet = set_combine(baseSet, defenseSet) + end + + return baseSet +end + + +-- Function to add kiting gear on top of the base set if kiting state is true. +-- @param baseSet : The gear set that the kiting gear will be applied on top of. +function apply_kiting(baseSet) + if state.Kiting.value then + if sets.Kiting then + baseSet = set_combine(baseSet, sets.Kiting) + end + end + + return baseSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for constructing default gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Get a spell mapping for the spell. +function get_spell_map(spell) + local defaultSpellMap = classes.SpellMaps[spell.english] + local jobSpellMap + + if job_get_spell_map then + jobSpellMap = job_get_spell_map(spell, defaultSpellMap) + end + + return jobSpellMap or defaultSpellMap +end + + +-- Select the equipment set to equip from a given starting table, based on standard +-- selection order: custom class, spell name, spell map, spell skill, and spell type. +-- Spell skill and spell type may further refine their selections based on +-- custom class, spell name and spell map. +function select_specific_set(equipSet, spell, spellMap) + -- Take the determined base equipment set and try to get the simple naming extensions that + -- may apply to it (class, spell name, spell map). + local namedSet = get_named_set(equipSet, spell, spellMap) + + -- If no simple naming sub-tables were found, and we simply got back the original equip set, + -- check for spell.skill and spell.type, then check the simple naming extensions again. + if namedSet == equipSet then + if spell.skill and equipSet[spell.skill] and not classes.SkipSkillCheck then + namedSet = equipSet[spell.skill] + mote_vars.set_breadcrumbs:append(spell.skill) + elseif spell.type and equipSet[spell.type] then + namedSet = equipSet[spell.type] + mote_vars.set_breadcrumbs:append(spell.type) + else + return equipSet + end + + namedSet = get_named_set(namedSet, spell, spellMap) + end + + return namedSet or equipSet +end + + +-- Simple utility function to handle a portion of the equipment set determination. +-- It attempts to select a sub-table of the provided equipment set based on the +-- standard search order of custom class, spell name, and spell map. +-- If no such set is found, it returns the original base set (equipSet) provided. +function get_named_set(equipSet, spell, spellMap) + if equipSet then + if classes.CustomClass and equipSet[classes.CustomClass] then + mote_vars.set_breadcrumbs:append(classes.CustomClass) + return equipSet[classes.CustomClass] + elseif equipSet[spell.english] then + mote_vars.set_breadcrumbs:append(spell.english) + return equipSet[spell.english] + elseif spellMap and equipSet[spellMap] then + mote_vars.set_breadcrumbs:append(spellMap) + return equipSet[spellMap] + else + return equipSet + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Hooks for other events. +------------------------------------------------------------------------------------------------------------------- + +-- Called when the player's subjob changes. +function sub_job_change(newSubjob, oldSubjob) + if user_setup then + user_setup() + end + + if job_sub_job_change then + job_sub_job_change(newSubjob, oldSubjob) + end + + send_command('gs c update') +end + + +-- Called when the player's status changes. +function status_change(newStatus, oldStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + mote_vars.set_breadcrumbs:clear() + + -- Allow a global function to be called on status change. + if user_status_change then + user_status_change(newStatus, oldStatus, eventArgs) + end + + -- Then call individual jobs to handle status change events. + if not eventArgs.handled then + if job_status_change then + job_status_change(newStatus, oldStatus, eventArgs) + end + end + + -- Handle equipping default gear if the job didn't mark this as handled. + if not eventArgs.handled then + handle_equipping_gear(newStatus) + display_breadcrumbs() + end +end + + +-- Called when a player gains or loses a buff. +-- buff == buff gained or lost +-- gain == true if the buff was gained, false if it was lost. +function buff_change(buff, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + if state.Buff[buff] ~= nil then + state.Buff[buff] = gain + end + + -- Allow a global function to be called on buff change. + if user_buff_change then + user_buff_change(buff, gain, eventArgs) + end + + -- Allow jobs to handle buff change events. + if not eventArgs.handled then + if job_buff_change then + job_buff_change(buff, gain, eventArgs) + end + end +end + + +-- Called when a player gains or loses a pet. +-- pet == pet gained or lost +-- gain == true if the pet was gained, false if it was lost. +function pet_change(pet, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to handle pet change events. + if job_pet_change then + job_pet_change(pet, gain, eventArgs) + end + + -- Equip default gear if not handled by the job. + if not eventArgs.handled then + handle_equipping_gear(player.status) + end +end + + +-- Called when the player's pet's status changes. +-- Note that this is also called after pet_change when the pet is released. +-- As such, don't automatically handle gear equips. Only do so if directed +-- to do so by the job. +function pet_status_change(newStatus, oldStatus) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_pet_status_change then + job_pet_status_change(newStatus, oldStatus, eventArgs) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Debugging functions. +------------------------------------------------------------------------------------------------------------------- + +-- This is a debugging function that will print the accumulated set selection +-- breadcrumbs for the default selected set for any given action stage. +function display_breadcrumbs(spell, spellMap, action) + if not _settings.debug_mode then + return + end + + local msg = 'Default ' + + if action and spell then + msg = msg .. action .. ' set selection for ' .. spell.name + end + + if spellMap then + msg = msg .. ' (' .. spellMap .. ')' + end + msg = msg .. ' : ' + + local cons + + for _,name in ipairs(mote_vars.set_breadcrumbs) do + if not cons then + cons = name + else + if name:contains(' ') or name:contains("'") then + cons = cons .. '["' .. name .. '"]' + else + cons = cons .. '.' .. name + end + end + end + + if cons then + if action and cons == ('sets.' .. action) then + msg = msg .. "None" + else + msg = msg .. tostring(cons) + end + add_to_chat(123, msg) + end +end + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Mappings.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Mappings.lua new file mode 100644 index 0000000..47e8b06 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Mappings.lua @@ -0,0 +1,283 @@ +------------------------------------------------------------------------------------------------------------------- +-- Mappings, lists and sets to describe game relationships that aren't easily determinable otherwise. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Elemental mappings for element relationships and certain types of spells and gear. +------------------------------------------------------------------------------------------------------------------- + +-- Basic elements +elements = {} + +elements.list = S{'Light','Dark','Fire','Ice','Wind','Earth','Lightning','Water'} + +elements.weak_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Ice', ['Ice']='Wind', ['Wind']='Earth', ['Earth']='Lightning', + ['Lightning']='Water', ['Water']='Fire'} + +elements.strong_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Water', ['Ice']='Fire', ['Wind']='Ice', ['Earth']='Wind', + ['Lightning']='Earth', ['Water']='Lightning'} + +storms = S{"Aurorastorm", "Voidstorm", "Firestorm", "Sandstorm", "Rainstorm", "Windstorm", "Hailstorm", "Thunderstorm", + "Aurorastorm II", "Voidstorm II", "Firestorm II", "Sandstorm II", "Rainstorm II", "Windstorm II", "Hailstorm II", "Thunderstorm II"} + +elements.storm_of = {['Light']="Aurorastorm", ['Dark']="Voidstorm", ['Fire']="Firestorm", ['Earth']="Sandstorm", + ['Water']="Rainstorm", ['Wind']="Windstorm", ['Ice']="Hailstorm", ['Lightning']="Thunderstorm",['Light']="Aurorastorm II", + ['Dark']="Voidstorm II", ['Fire']="Firestorm II", ['Earth']="Sandstorm II", ['Water']="Rainstorm II", ['Wind']="Windstorm II", + ['Ice']="Hailstorm II", ['Lightning']="Thunderstorm II"} + +spirits = S{"LightSpirit", "DarkSpirit", "FireSpirit", "EarthSpirit", "WaterSpirit", "AirSpirit", "IceSpirit", "ThunderSpirit"} +elements.spirit_of = {['Light']="Light Spirit", ['Dark']="Dark Spirit", ['Fire']="Fire Spirit", ['Earth']="Earth Spirit", + ['Water']="Water Spirit", ['Wind']="Air Spirit", ['Ice']="Ice Spirit", ['Lightning']="Thunder Spirit"} + +runes = S{'Lux', 'Tenebrae', 'Ignis', 'Gelus', 'Flabra', 'Tellus', 'Sulpor', 'Unda'} +elements.rune_of = {['Light']='Lux', ['Dark']='Tenebrae', ['Fire']='Ignis', ['Ice']='Gelus', ['Wind']='Flabra', + ['Earth']='Tellus', ['Lightning']='Sulpor', ['Water']='Unda'} + +elements.obi_of = {['Light']='Hachirin-no-obi', ['Dark']='Hachirin-no-obi', ['Fire']='Hachirin-no-obi', ['Ice']='Hachirin-no-obi', ['Wind']='Hachirin-no-obi', + ['Earth']='Hachirin-no-obi', ['Lightning']='Hachirin-no-obi', ['Water']='Hachirin-no-obi'} + +elements.gorget_of = {['Light']='Fotia Gorget', ['Dark']='Fotia Gorget', ['Fire']='Fotia Gorget', ['Ice']='Fotia Gorget', + ['Wind']='Fotia Gorget', ['Earth']='Fotia Gorget', ['Lightning']='Fotia Gorget', ['Water']='Fotia Gorget'} + +elements.belt_of = {['Light']='Fotia Belt', ['Dark']='Fotia Belt', ['Fire']='Fotia Belt', ['Ice']='Fotia Belt', + ['Wind']='Fotia Belt', ['Earth']='Fotia Belt', ['Lightning']='Fotia Belt', ['Water']='Fotia Belt'} + +elements.fastcast_staff_of = {['Light']='Arka I', ['Dark']='Xsaeta I', ['Fire']='Atar I', ['Ice']='Vourukasha I', + ['Wind']='Vayuvata I', ['Earth']='Vishrava I', ['Lightning']='Apamajas I', ['Water']='Haoma I', ['Thunder']='Apamajas I'} + +elements.recast_staff_of = {['Light']='Arka II', ['Dark']='Xsaeta II', ['Fire']='Atar II', ['Ice']='Vourukasha II', + ['Wind']='Vayuvata II', ['Earth']='Vishrava II', ['Lightning']='Apamajas II', ['Water']='Haoma II', ['Thunder']='Apamajas II'} + +elements.perpetuance_staff_of = {['Light']='Arka III', ['Dark']='Xsaeta III', ['Fire']='Atar III', ['Ice']='Vourukasha III', + ['Wind']='Vayuvata III', ['Earth']='Vishrava III', ['Lightning']='Apamajas III', ['Water']='Haoma III', ['Thunder']='Apamajas III'} + + +-- Elements for skillchain names +skillchain_elements = {} +skillchain_elements.Light = S{'Light','Fire','Wind','Lightning'} +skillchain_elements.Darkness = S{'Dark','Ice','Earth','Water'} +skillchain_elements.Fusion = S{'Light','Fire'} +skillchain_elements.Fragmentation = S{'Wind','Lightning'} +skillchain_elements.Distortion = S{'Ice','Water'} +skillchain_elements.Gravitation = S{'Dark','Earth'} +skillchain_elements.Transfixion = S{'Light'} +skillchain_elements.Compression = S{'Dark'} +skillchain_elements.Liquification = S{'Fire'} +skillchain_elements.Induration = S{'Ice'} +skillchain_elements.Detonation = S{'Wind'} +skillchain_elements.Scission = S{'Earth'} +skillchain_elements.Impaction = S{'Lightning'} +skillchain_elements.Reverberation = S{'Water'} + + +------------------------------------------------------------------------------------------------------------------- +-- Mappings for weaponskills +------------------------------------------------------------------------------------------------------------------- + +-- REM weapons and their corresponding weaponskills +data = {} +data.weaponskills = {} +data.weaponskills.relic = { + ["Spharai"] = "Final Heaven", + ["Mandau"] = "Mercy Stroke", + ["Excalibur"] = "Knights of Round", + ["Ragnarok"] = "Scourge", + ["Guttler"] = "Onslaught", + ["Bravura"] = "Metatron Torment", + ["Apocalypse"] = "Catastrophe", + ["Gungnir"] = "Gierskogul", + ["Kikoku"] = "Blade: Metsu", + ["Amanomurakumo"] = "Tachi: Kaiten", + ["Mjollnir"] = "Randgrith", + ["Claustrum"] = "Gates of Tartarus", + ["Annihilator"] = "Coronach", + ["Yoichinoyumi"] = "Namas Arrow"} +data.weaponskills.mythic = { + ["Conqueror"] = "King's Justice", + ["Glanzfaust"] = "Ascetic's Fury", + ["Yagrush"] = "Mystic Boon", + ["Laevateinn"] = "Vidohunir", + ["Murgleis"] = "Death Blossom", + ["Vajra"] = "Mandalic Stab", + ["Burtgang"] = "Atonement", + ["Liberator"] = "Insurgency", + ["Aymur"] = "Primal Rend", + ["Carnwenhan"] = "Mordant Rime", + ["Gastraphetes"] = "Trueflight", + ["Kogarasumaru"] = "Tachi: Rana", + ["Nagi"] = "Blade: Kamu", + ["Ryunohige"] = "Drakesbane", + ["Nirvana"] = "Garland of Bliss", + ["Tizona"] = "Expiacion", + ["Death Penalty"] = "Leaden Salute", + ["Kenkonken"] = "Stringing Pummel", + ["Terpsichore"] = "Pyrrhic Kleos", + ["Tupsimati"] = "Omniscience", + ["Idris"] = "Exudation", + ["Epeolatry"] = "Dimidiation"} +data.weaponskills.empyrean = { + ["Verethragna"] = "Victory Smite", + ["Twashtar"] = "Rudra's Storm", + ["Almace"] = "Chant du Cygne", + ["Caladbolg"] = "Torcleaver", + ["Farsha"] = "Cloudsplitter", + ["Ukonvasara"] = "Ukko's Fury", + ["Redemption"] = "Quietus", + ["Rhongomiant"] = "Camlann's Torment", + ["Kannagi"] = "Blade: Hi", + ["Masamune"] = "Tachi: Fudo", + ["Gambanteinn"] = "Dagann", + ["Hvergelmir"] = "Myrkr", + ["Gandiva"] = "Jishnu's Radiance", + ["Armageddon"] = "Wildfire"} + +-- Weaponskills that can be used at range. +data.weaponskills.ranged = S{"Flaming Arrow", "Piercing Arrow", "Dulling Arrow", "Sidewinder", "Arching Arrow", + "Empyreal Arrow", "Refulgent Arrow", "Apex Arrow", "Namas Arrow", "Jishnu's Radiance", + "Hot Shot", "Split Shot", "Sniper Shot", "Slug Shot", "Heavy Shot", "Detonator", "Last Stand", + "Coronach", "Trueflight", "Leaden Salute", "Wildfire", + "Myrkr"} + +ranged_weaponskills = data.weaponskills.ranged + +------------------------------------------------------------------------------------------------------------------- +-- Spell mappings allow defining a general category or description that each of sets of related +-- spells all fall under. +------------------------------------------------------------------------------------------------------------------- + +spell_maps = { + ['Cure']='Cure',['Cure II']='Cure',['Cure III']='Cure',['Cure IV']='Cure',['Cure V']='Cure',['Cure VI']='Cure', + ['Full Cure']='Cure', + ['Cura']='Curaga',['Cura II']='Curaga',['Cura III']='Curaga', + ['Curaga']='Curaga',['Curaga II']='Curaga',['Curaga III']='Curaga',['Curaga IV']='Curaga',['Curaga V']='Curaga', + -- Status Removal doesn't include Esuna or Sacrifice, since they work differently than the rest + ['Poisona']='StatusRemoval',['Paralyna']='StatusRemoval',['Silena']='StatusRemoval',['Blindna']='StatusRemoval',['Cursna']='StatusRemoval', + ['Stona']='StatusRemoval',['Viruna']='StatusRemoval',['Erase']='StatusRemoval', + ['Barfire']='BarElement',['Barstone']='BarElement',['Barwater']='BarElement',['Baraero']='BarElement',['Barblizzard']='BarElement',['Barthunder']='BarElement', + ['Barfira']='BarElement',['Barstonra']='BarElement',['Barwatera']='BarElement',['Baraera']='BarElement',['Barblizzara']='BarElement',['Barthundra']='BarElement', + ['Raise']='Raise',['Raise II']='Raise',['Raise III']='Raise',['Arise']='Raise', + ['Reraise']='Reraise',['Reraise II']='Reraise',['Reraise III']='Reraise',['Reraise IV']='Reraise', + ['Protect']='Protect',['Protect II']='Protect',['Protect III']='Protect',['Protect IV']='Protect',['Protect V']='Protect', + ['Shell']='Shell',['Shell II']='Shell',['Shell III']='Shell',['Shell IV']='Shell',['Shell V']='Shell', + ['Protectra']='Protectra',['Protectra II']='Protectra',['Protectra III']='Protectra',['Protectra IV']='Protectra',['Protectra V']='Protectra', + ['Shellra']='Shellra',['Shellra II']='Shellra',['Shellra III']='Shellra',['Shellra IV']='Shellra',['Shellra V']='Shellra', + ['Regen']='Regen',['Regen II']='Regen',['Regen III']='Regen',['Regen IV']='Regen',['Regen V']='Regen', + ['Refresh']='Refresh',['Refresh II']='Refresh',['Refresh III']='Refresh', + ['Teleport-Holla']='Teleport',['Teleport-Dem']='Teleport',['Teleport-Mea']='Teleport',['Teleport-Altep']='Teleport',['Teleport-Yhoat']='Teleport', + ['Teleport-Vahzl']='Teleport',['Recall-Pashh']='Teleport',['Recall-Meriph']='Teleport',['Recall-Jugner']='Teleport', + ['Valor Minuet']='Minuet',['Valor Minuet II']='Minuet',['Valor Minuet III']='Minuet',['Valor Minuet IV']='Minuet',['Valor Minuet V']='Minuet', + ["Knight's Minne"]='Minne',["Knight's Minne II"]='Minne',["Knight's Minne III"]='Minne',["Knight's Minne IV"]='Minne',["Knight's Minne V"]='Minne', + ['Advancing March']='March',['Victory March']='March', + ['Sword Madrigal']='Madrigal',['Blade Madrigal']='Madrigal', + ["Hunter's Prelude"]='Prelude',["Archer's Prelude"]='Prelude', + ['Sheepfoe Mambo']='Mambo',['Dragonfoe Mambo']='Mambo', + ['Raptor Mazurka']='Mazurka',['Chocobo Mazurka']='Mazurka', + ['Sinewy Etude']='Etude',['Dextrous Etude']='Etude',['Vivacious Etude']='Etude',['Quick Etude']='Etude',['Learned Etude']='Etude',['Spirited Etude']='Etude',['Enchanting Etude']='Etude', + ['Herculean Etude']='Etude',['Uncanny Etude']='Etude',['Vital Etude']='Etude',['Swift Etude']='Etude',['Sage Etude']='Etude',['Logical Etude']='Etude',['Bewitching Etude']='Etude', + ["Mage's Ballad"]='Ballad',["Mage's Ballad II"]='Ballad',["Mage's Ballad III"]='Ballad', + ["Army's Paeon"]='Paeon',["Army's Paeon II"]='Paeon',["Army's Paeon III"]='Paeon',["Army's Paeon IV"]='Paeon',["Army's Paeon V"]='Paeon',["Army's Paeon VI"]='Paeon', + ['Fire Carol']='Carol',['Ice Carol']='Carol',['Wind Carol']='Carol',['Earth Carol']='Carol',['Lightning Carol']='Carol',['Water Carol']='Carol',['Light Carol']='Carol',['Dark Carol']='Carol', + ['Fire Carol II']='Carol',['Ice Carol II']='Carol',['Wind Carol II']='Carol',['Earth Carol II']='Carol',['Lightning Carol II']='Carol',['Water Carol II']='Carol',['Light Carol II']='Carol',['Dark Carol II']='Carol', + ['Foe Lullaby']='Lullaby',['Foe Lullaby II']='Lullaby',['Horde Lullaby']='Lullaby',['Horde Lullaby II']='Lullaby', + ['Fire Threnody']='Threnody',['Ice Threnody']='Threnody',['Wind Threnody']='Threnody',['Earth Threnody']='Threnody',['Lightning Threnody']='Threnody',['Water Threnody']='Threnody',['Light Threnody']='Threnody',['Dark Threnody']='Threnody', + ['Fire Threnody II']='Threnody',['Ice Threnody II']='Threnody',['Wind Threnody II']='Threnody',['Earth Threnody II']='Threnody',['Lightning Threnody II']='Threnody',['Water Threnody II']='Threnody',['Light Threnody II']='Threnody',['Dark Threnody II']='Threnody', + ['Battlefield Elegy']='Elegy',['Carnage Elegy']='Elegy', + ['Foe Requiem']='Requiem',['Foe Requiem II']='Requiem',['Foe Requiem III']='Requiem',['Foe Requiem IV']='Requiem',['Foe Requiem V']='Requiem',['Foe Requiem VI']='Requiem',['Foe Requiem VII']='Requiem', + ['Utsusemi: Ichi']='Utsusemi',['Utsusemi: Ni']='Utsusemi',['Utsusemi: San']='Utsusemi', + ['Katon: Ichi'] = 'ElementalNinjutsu',['Suiton: Ichi'] = 'ElementalNinjutsu',['Raiton: Ichi'] = 'ElementalNinjutsu', + ['Doton: Ichi'] = 'ElementalNinjutsu',['Huton: Ichi'] = 'ElementalNinjutsu',['Hyoton: Ichi'] = 'ElementalNinjutsu', + ['Katon: Ni'] = 'ElementalNinjutsu',['Suiton: Ni'] = 'ElementalNinjutsu',['Raiton: Ni'] = 'ElementalNinjutsu', + ['Doton: Ni'] = 'ElementalNinjutsu',['Huton: Ni'] = 'ElementalNinjutsu',['Hyoton: Ni'] = 'ElementalNinjutsu', + ['Katon: San'] = 'ElementalNinjutsu',['Suiton: San'] = 'ElementalNinjutsu',['Raiton: San'] = 'ElementalNinjutsu', + ['Doton: San'] = 'ElementalNinjutsu',['Huton: San'] = 'ElementalNinjutsu',['Hyoton: San'] = 'ElementalNinjutsu', + ['Banish']='Banish',['Banish II']='Banish',['Banish III']='Banish',['Banishga']='Banish',['Banishga II']='Banish', + ['Holy']='Holy',['Holy II']='Holy',['Drain']='Drain',['Drain II']='Drain',['Drain III']='Drain',['Aspir']='Aspir',['Aspir II']='Aspir', + ['Absorb-Str']='Absorb',['Absorb-Dex']='Absorb',['Absorb-Vit']='Absorb',['Absorb-Agi']='Absorb',['Absorb-Int']='Absorb',['Absorb-Mnd']='Absorb',['Absorb-Chr']='Absorb', + ['Absorb-Acc']='Absorb',['Absorb-TP']='Absorb',['Absorb-Attri']='Absorb', + ['Enlight']='Enlight',['Enlight II']='Enlight',['Endark']='Endark',['Endark II']='Endark', + ['Burn']='ElementalEnfeeble',['Frost']='ElementalEnfeeble',['Choke']='ElementalEnfeeble',['Rasp']='ElementalEnfeeble',['Shock']='ElementalEnfeeble',['Drown']='ElementalEnfeeble', + ['Pyrohelix']='Helix',['Cryohelix']='Helix',['Anemohelix']='Helix',['Geohelix']='Helix',['Ionohelix']='Helix',['Hydrohelix']='Helix',['Luminohelix']='Helix',['Noctohelix']='Helix', + ['Pyrohelix II']='Helix',['Cryohelix II']='Helix',['Anemohelix II']='Helix',['Geohelix II']='Helix',['Ionohelix II']='Helix',['Hydrohelix II']='Helix',['Luminohelix II']='Helix',['Noctohelix II']='Helix', + ['Firestorm']='Storm',['Hailstorm']='Storm',['Windstorm']='Storm',['Sandstorm']='Storm',['Thunderstorm']='Storm',['Rainstorm']='Storm',['Aurorastorm']='Storm',['Voidstorm']='Storm', + ['Firestorm II']='Storm',['Hailstorm II']='Storm',['Windstorm II']='Storm',['Sandstorm II']='Storm',['Thunderstorm II']='Storm',['Rainstorm II']='Storm',['Aurorastorm II']='Storm',['Voidstorm II']='Storm', + ['Fire Maneuver']='Maneuver',['Ice Maneuver']='Maneuver',['Wind Maneuver']='Maneuver',['Earth Maneuver']='Maneuver',['Thunder Maneuver']='Maneuver', + ['Water Maneuver']='Maneuver',['Light Maneuver']='Maneuver',['Dark Maneuver']='Maneuver', +} + +no_skill_spells_list = S{'Haste', 'Refresh', 'Regen', 'Protect', 'Protectra', 'Shell', 'Shellra', + 'Raise', 'Reraise', 'Sneak', 'Invisible', 'Deodorize'} + + +------------------------------------------------------------------------------------------------------------------- +-- Tables to specify general area groupings. Creates the 'areas' table to be referenced in job files. +-- Zone names provided by world.area/world.zone are currently in all-caps, so defining the same way here. +------------------------------------------------------------------------------------------------------------------- + +areas = {} + +-- City areas for town gear and behavior. +areas.Cities = S{ + "Ru'Lude Gardens", + "Upper Jeuno", + "Lower Jeuno", + "Port Jeuno", + "Port Windurst", + "Windurst Waters", + "Windurst Woods", + "Windurst Walls", + "Heavens Tower", + "Port San d'Oria", + "Northern San d'Oria", + "Southern San d'Oria", + "Port Bastok", + "Bastok Markets", + "Bastok Mines", + "Metalworks", + "Aht Urhgan Whitegate", + "Tavnazian Safehold", + "Nashmau", + "Selbina", + "Mhaura", + "Norg", + "Eastern Adoulin", + "Western Adoulin", + "Kazham", + "Rabao", + "Chocobo Circuit", +} +-- Adoulin areas, where Ionis will grant special stat bonuses. +areas.Adoulin = S{ + "Yahse Hunting Grounds", + "Ceizak Battlegrounds", + "Foret de Hennetiel", + "Morimar Basalt Fields", + "Yorcia Weald", + "Yorcia Weald [U]", + "Cirdas Caverns", + "Cirdas Caverns [U]", + "Marjami Ravine", + "Kamihr Drifts", + "Sih Gates", + "Moh Gates", + "Dho Gates", + "Woh Gates", + "Rala Waterways", + "Rala Waterways [U]", + "Outer Ra'Kaznar", + "Outer Ra'Kaznar [U]" +} + + +------------------------------------------------------------------------------------------------------------------- +-- Lists of certain NPCs. (Not up to date) +------------------------------------------------------------------------------------------------------------------- + +npcs = {} +npcs.Trust = S{'Ajido-Marujido','Aldo','Ayame','Cherukiki','Curilla','D.Shantotto','Elivira','Excenmille', + 'Fablinix','FerreousCoffin','Gadalar','Gessho','Ingrid','IronEater','Joachim','Klara','Kupipi', + 'LehkoHabhoka','LhuMhakaracca','Lion','Luzaf','Maat','MihliAliapoh','Mnejing','Moogle','Mumor', + 'NajaSalaheem','Najelith','Naji','NanaaMihgo','Nashmeira','Noillurie','Ovjang','Prishe','Rainemard', + 'RomaaMihgo','Sakura','Shantotto','StarSibyl','Tenzen','Trion','UkaTotlihn','Ulmia','Valaineral', + 'Volker','Zazarg','Zeid'} + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-SelfCommands.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-SelfCommands.lua new file mode 100644 index 0000000..3015e1f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-SelfCommands.lua @@ -0,0 +1,466 @@ +------------------------------------------------------------------------------------------------------------------- +-- General functions for manipulating state values via self-commands. +-- Only handles certain specific states that we've defined, though it +-- allows the user to hook into the cycle command. +------------------------------------------------------------------------------------------------------------------- + +-- Routing function for general known self_commands. Mappings are at the bottom of the file. +-- Handles splitting the provided command line up into discrete words, for the other functions to use. +function self_command(commandArgs) + local commandArgs = commandArgs + if type(commandArgs) == 'string' then + commandArgs = T(commandArgs:split(' ')) + if #commandArgs == 0 then + return + end + end + + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_self_command then + job_self_command(commandArgs, eventArgs) + end + + if not eventArgs.handled then + -- Of the original command message passed in, remove the first word from + -- the list (it will be used to determine which function to call), and + -- send the remaining words as parameters for the function. + local handleCmd = table.remove(commandArgs, 1) + + if selfCommandMaps[handleCmd] then + selfCommandMaps[handleCmd](commandArgs) + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for manipulating state vars. +------------------------------------------------------------------------------------------------------------------- + +-- Function to set various states to specific values directly. +-- User command format: gs c set [field] [value] +-- If a boolean [field] is used, but not given a [value], it will be set to true. +function handle_set(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Set parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:set(cmdParams[2]) + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + local msg = descrip..' is now '..state_var.current + if state_var == state.DefenseMode and newVal ~= 'None' then + msg = msg .. ' (' .. state[newVal .. 'DefenseMode'].current .. ')' + end + msg = msg .. '.' + + add_to_chat(122, msg) + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Set: Unknown field ['..cmdParams[1]..']') + end + + -- handle string states: CombatForm, CombatWeapon, etc +end + +-- Function to reset values to their defaults. +-- User command format: gs c reset [field] +-- Or: gs c reset all +function handle_reset(cmdParams) + if #cmdParams == 0 then + if _global.debug_mode then add_to_chat(123,'handle_reset: parameter failure: reset type not specified') end + return + end + + local state_var = get_state(cmdParams[1]) + + local oldVal + local newVal + local descrip + + if state_var then + oldVal = state_var.value + state_var:reset() + newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + elseif cmdParams[1]:lower() == 'all' then + for k,v in pairs(state) do + if v._type == 'mode' then + oldVal = v.value + v:reset() + newVal = v.value + + descrip = state_var.description + if descrip and job_state_change then + job_state_change(descrip, newVal, oldVal) + end + end + end + + if job_reset_state then + job_reset_state('all') + end + + if job_state_change then + job_state_change('Reset All') + end + + add_to_chat(122,"All state vars have been reset.") + handle_update({'auto'}) + elseif job_reset_state then + job_reset_state(cmdParams[1]) + else + add_to_chat(123,'Mote-Libs: Reset: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Handle cycling through the options list of a state var. +-- User command format: gs c cycle [field] +function handle_cycle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Cycle parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + if cmdParams[2] and S{'reverse', 'backwards', 'r'}:contains(cmdParams[2]:lower()) then + state_var:cycleback() + else + state_var:cycle() + end + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Cycle: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Handle cycling backwards through the options list of a state var. +-- User command format: gs c cycleback [field] +function handle_cycleback(cmdParams) + cmdParams[2] = 'reverse' + handle_cycle(cmdParams) +end + + +-- Handle toggling of boolean mode vars. +-- User command format: gs c toggle [field] +function handle_toggle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Toggle parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:toggle() + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Toggle: Unknown field ['..cmdParams[1]..']') + end +end + + +-- Function to force a boolean field to false. +-- User command format: gs c unset [field] +function handle_unset(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-Libs: Unset parameter failure: field not specified.') + return + end + + local state_var = get_state(cmdParams[1]) + + if state_var then + local oldVal = state_var.value + state_var:unset() + local newVal = state_var.value + + local descrip = state_var.description or cmdParams[1] + if job_state_change then + job_state_change(descrip, newVal, oldVal) + end + + add_to_chat(122,descrip..' is now '..state_var.current..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-Libs: Toggle: Unknown field ['..cmdParams[1]..']') + end +end + +------------------------------------------------------------------------------------------------------------------- + +-- User command format: gs c update [option] +-- Where [option] can be 'user' to display current state. +-- Otherwise, generally refreshes current gear used. +function handle_update(cmdParams) + -- init a new eventArgs + local eventArgs = {handled = false} + + reset_buff_states() + + -- Allow jobs to override this code + if job_update then + job_update(cmdParams, eventArgs) + end + + if not eventArgs.handled then + if handle_equipping_gear then + handle_equipping_gear(player.status) + end + end + + if cmdParams[1] == 'user' then + display_current_state() + end +end + + +-- showtp: equip the current TP set for examination. +function handle_showtp(cmdParams) + local msg = 'Showing current TP set: ['.. state.OffenseMode.value + if state.HybridMode.value ~= 'Normal' then + msg = msg .. '/' .. state.HybridMode.value + end + msg = msg .. ']' + + if #classes.CustomMeleeGroups > 0 then + msg = msg .. ' [' + for i = 1,#classes.CustomMeleeGroups do + msg = msg .. classes.CustomMeleeGroups[i] + if i < #classes.CustomMeleeGroups then + msg = msg .. ', ' + end + end + msg = msg .. ']' + end + + add_to_chat(122, msg) + equip(get_melee_set()) +end + + +-- Minor variation on the GearSwap "gs equip naked" command, that ensures that +-- all slots are enabled before removing gear. +-- Command: "gs c naked" +function handle_naked(cmdParams) + enable('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') + equip(sets.naked) +end + + +------------------------------------------------------------------------------------------------------------------- + +-- Get the state var that matches the requested name. +-- Only returns mode vars. +function get_state(name) + if state[name] then + return state[name]._class == 'mode' and state[name] or nil + else + local l_name = name:lower() + for key,var in pairs(state) do + if key:lower() == l_name then + return var._class == 'mode' and var or nil + end + end + end +end + + +-- Function to reset state.Buff values (called from update). +function reset_buff_states() + if state.Buff then + for buff,present in pairs(state.Buff) do + if mote_vars.res_buffs:contains(buff) then + state.Buff[buff] = buffactive[buff] or false + end + end + end +end + + +-- Function to display the current relevant user state when doing an update. +-- Uses display_current_job_state instead if that is defined in the job lua. +function display_current_state() + local eventArgs = {handled = false} + if display_current_job_state then + display_current_job_state(eventArgs) + end + + if not eventArgs.handled then + local msg = 'Melee' + + if state.CombatForm.has_value then + msg = msg .. ' (' .. state.CombatForm.value .. ')' + end + + msg = msg .. ': ' + + msg = msg .. state.OffenseMode.value + if state.HybridMode.value ~= 'Normal' then + msg = msg .. '/' .. state.HybridMode.value + end + msg = msg .. ', WS: ' .. state.WeaponskillMode.value + + if state.DefenseMode.value ~= 'None' then + msg = msg .. ', Defense: ' .. state.DefenseMode.value .. ' (' .. state[state.DefenseMode.value .. 'DefenseMode'].value .. ')' + end + + if state.Kiting.value == true then + msg = msg .. ', Kiting' + end + + if state.PCTargetMode.value ~= 'default' then + msg = msg .. ', Target PC: '..state.PCTargetMode.value + end + + if state.SelectNPCTargets.value == true then + msg = msg .. ', Target NPCs' + end + + add_to_chat(122, msg) + end + + if state.EquipStop.value ~= 'off' then + add_to_chat(122,'Gear equips are blocked after ['..state.EquipStop.value..']. Use "//gs c reset equipstop" to turn it off.') + end +end + +-- Generic version of this for casters +function display_current_caster_state() + local msg = '' + + if state.OffenseMode.value ~= 'None' then + msg = msg .. 'Melee' + + if state.CombatForm.has_value then + msg = msg .. ' (' .. state.CombatForm.value .. ')' + end + + msg = msg .. ', ' + end + + msg = msg .. 'Casting ['..state.CastingMode.value..'], Idle ['..state.IdleMode.value..']' + + if state.DefenseMode.value ~= 'None' then + msg = msg .. ', ' .. 'Defense: ' .. state.DefenseMode.value .. ' (' .. state[state.DefenseMode.value .. 'DefenseMode'].value .. ')' + end + + if state.Kiting.value == true then + msg = msg .. ', Kiting' + end + + if state.PCTargetMode.value ~= 'default' then + msg = msg .. ', Target PC: '..state.PCTargetMode.value + end + + if state.SelectNPCTargets.value == true then + msg = msg .. ', Target NPCs' + end + + add_to_chat(122, msg) +end + + +------------------------------------------------------------------------------------------------------------------- + +-- Function to show what commands are available, and their syntax. +-- Syntax: gs c help +-- Or: gs c +function handle_help(cmdParams) + if cmdParams[1] and cmdParams[1]:lower():startswith('field') then + print('Predefined Library Fields:') + print('--------------------------') + print('OffenseMode, HybridMode, RangedMode, WeaponskillMode') + print('CastingMode, IdleMode, RestingMode, Kiting') + print('DefenseMode, PhysicalDefenseMode, MagicalDefenseMode') + print('SelectNPCTargets, PCTargetMode') + print('EquipStop (precast, midcast, pet_midcast)') + else + print('Custom Library Self-commands:') + print('-----------------------------') + print('Show TP Set: gs c showtp') + print('Toggle bool: gs c toggle [field]') + print('Cycle list: gs c cycle [field] [(r)everse]') + print('Cycle list back: gs c cycleback [field]') + print('Reset a state: gs c reset [field]') + print('Reset all states: gs c reset all') + print('Set state var: gs c set [field] [value]') + print('Set bool true: gs c set [field]') + print('Set bool false: gs c unset [field]') + print('Remove gear: gs c naked') + print('Show TP Set: gs c showtp') + print('State vars: gs c help field') + end +end + + +-- A function for testing lua code. Called via "gs c test". +function handle_test(cmdParams) + if user_test then + user_test(cmdParams) + elseif job_test then + job_test(cmdParams) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- The below table maps text commands to the above handler functions. +------------------------------------------------------------------------------------------------------------------- + +selfCommandMaps = { + ['toggle'] = handle_toggle, + ['cycle'] = handle_cycle, + ['cycleback']= handle_cycleback, + ['set'] = handle_set, + ['reset'] = handle_reset, + ['unset'] = handle_unset, + ['update'] = handle_update, + ['showtp'] = handle_showtp, + ['naked'] = handle_naked, + ['help'] = handle_help, + ['test'] = handle_test} + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-TreasureHunter.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-TreasureHunter.lua new file mode 100644 index 0000000..d1fd52a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-TreasureHunter.lua @@ -0,0 +1,297 @@ +------------------------------------------------------------------------------------------------------------------- +-- Utility include for applying and tracking Treasure Hunter effects. +-- +-- Include this if you want a means of applying TH on the first contact +-- with a mob, then resume using normal gear. +-- Thf also has modes to use TH gear for SATA and for keeping it on fulltime. +-- +-- Command: include('Mote-TreasureHunter') +-- Place this in your job_setup() function, or user_setup() function if using +-- a sidecar file, or get_sets() function if your job file isn't based +-- on my includes. +-- Be sure to define your own sets.TreasureHunter gear set after the include. +-- If using a job file based on my includes, simply place it in the +-- standard init_gear_sets() function. +-- +-- If you define TH gear sets for common actions (eg: Provoke, Box Step, etc), +-- then make sure they are accounted for in a th_action_check function +-- (signature: th_action_check(category, param)) in the job file. It's passed +-- category and param value for actions the user takes, and if it returns true, +-- that means that it's considered a valid tagging action. +-- +-- If using this in a job file that isn't based on my includes, you must +-- handle cycling the options values on your own, unless you also include +-- Mote-SelfCommands. +-- +-- The job file must handle the 'update' self-command (gs c update auto). +-- This is automatically handled if using my includes, but must be ensured +-- if being used with a user-built job file. +-- When called, it merely needs to equip standard melee gear for the current +-- configuration. +-- +-- Create a macro or keybind to cycle the Treasure Mode value: +-- gs c cycle TreasureMode +------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------- +-- Setup vars and events when first running the include. +------------------------------------------------------------------------------------------------------------------- + +-- Ensure base tables are defined +options = options or {} +state = state or {} +info = info or {} +state.TreasureMode = M{['description']='Treasure Mode'} + +-- TH mode handling +if player.main_job == 'THF' then + state.TreasureMode:options('None','Tag','SATA','Fulltime') + state.TreasureMode:set('Tag') +else + state.TreasureMode:options('None','Tag') +end + +-- Tracking vars for TH. +info.tagged_mobs = T{} +info.last_player_target_index = 0 +state.th_gear_is_locked = false + +-- Required gear set. Expand this in the job file when defining sets. +sets.TreasureHunter = {} + +-- Event registration is done at the bottom of this file. + + +------------------------------------------------------------------------------------------------------------------- +-- User-callable functions for TH handling utility. +------------------------------------------------------------------------------------------------------------------- + +-- Can call to force a status refresh. +-- Also displays the current tagged mob table if in debug mode. +function th_update(cmdParams, eventArgs) + if (cmdParams and cmdParams[1] == 'user') or not cmdParams then + TH_for_first_hit() + + if _settings.debug_mode then + print_set(info.tagged_mobs, 'Tagged mobs') + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Local functions to support TH handling. +------------------------------------------------------------------------------------------------------------------- + +-- Set locked TH flag to true, and disable relevant gear slots. +function lock_TH() + state.th_gear_is_locked = true + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + disable(slots) +end + + +-- Set locked TH flag to false, and enable relevant gear slots. +function unlock_TH() + state.th_gear_is_locked = false + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + enable(slots) + send_command('gs c update auto') +end + + +-- For any active TH mode, if we haven't already tagged this target, equip TH gear and lock slots until we manage to hit it. +function TH_for_first_hit() + if player.status == 'Engaged' and state.TreasureMode.value ~= 'None' then + if not info.tagged_mobs[player.target.id] then + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..tostring(player.target.id)..'.') end + equip(sets.TreasureHunter) + lock_TH() + elseif state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'Target '..player.target.id..' has been tagged. Unlocking.') end + unlock_TH() + else + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..player.target.id..'. Target has already been tagged.') end + end + else + unlock_TH() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event handlers to allow tracking TH application. +------------------------------------------------------------------------------------------------------------------- + +-- On engaging a mob, attempt to add TH gear. For any other status change, unlock TH gear slots. +function on_status_change_for_th(new_status_id, old_status_id) + if gearswap.gearswap_disabled or T{2,3,4}:contains(old_status_id) or T{2,3,4}:contains(new_status_id) then return end + + local new_status = gearswap.res.statuses[new_status_id].english + local old_status = gearswap.res.statuses[old_status_id].english + + if new_status == 'Engaged' then + if _settings.debug_mode then add_to_chat(123,'Engaging '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + elseif old_status == 'Engaged' then + if _settings.debug_mode and state.th_gear_is_locked then add_to_chat(123,'Disengaging. Unlocking TH.') end + info.last_player_target_index = 0 + unlock_TH() + end +end + + +-- On changing targets, attempt to add TH gear. +function on_target_change_for_th(new_index, old_index) + -- Only care about changing targets while we're engaged, either manually or via current target death. + if player.status == 'Engaged' then + -- If the current player.target is the same as the new mob then we're actually + -- engaged with it. + -- If it's different than the last known mob, then we've actually changed targets. + if player.target.index == new_index and new_index ~= info.last_player_target_index then + if _settings.debug_mode then add_to_chat(123,'Changing target to '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + end + end +end + + +-- On any action event, mark mobs that we tag with TH. Also, update the last time tagged mobs were acted on. +function on_action_for_th(action) + --add_to_chat(123,'cat='..action.category..',param='..action.param) + -- If player takes action, adjust TH tagging information + if state.TreasureMode.value ~= 'None' then + if action.actor_id == player.id then + -- category == 1=melee, 2=ranged, 3=weaponskill, 4=spell, 6=job ability, 14=unblinkable JA + if state.TreasureMode.value == 'Fulltime' or + (state.TreasureMode.value == 'SATA' and (action.category == 1 or ((state.Buff['Sneak Attack'] or state.Buff['Trick Attack']) and action.category == 3))) or + (state.TreasureMode.value == 'Tag' and action.category == 1 and state.th_gear_is_locked) or -- Tagging with a melee hit + (th_action_check and th_action_check(action.category, action.param)) -- Any user-specified tagging actions + then + for index,target in pairs(action.targets) do + if not info.tagged_mobs[target.id] and _settings.debug_mode then + add_to_chat(123,'Mob '..target.id..' hit. Adding to tagged mobs table.') + end + info.tagged_mobs[target.id] = os.time() + end + + if state.th_gear_is_locked then + unlock_TH() + end + end + elseif info.tagged_mobs[action.actor_id] then + -- If mob acts, keep an update of last action time for TH bookkeeping + info.tagged_mobs[action.actor_id] = os.time() + else + -- If anyone else acts, check if any of the targets are our tagged mobs + for index,target in pairs(action.targets) do + if info.tagged_mobs[target.id] then + info.tagged_mobs[target.id] = os.time() + end + end + end + end + + cleanup_tagged_mobs() +end + + +-- Need to use this event handler to listen for deaths in case Battlemod is loaded, +-- because Battlemod blocks the 'action message' event. +-- +-- This function removes mobs from our tracking table when they die. +function on_incoming_chunk_for_th(id, data, modified, injected, blocked) + if id == 0x29 then + local target_id = data:unpack('I',0x09) + local message_id = data:unpack('H',0x19)%32768 + + -- Remove mobs that die from our tagged mobs list. + if info.tagged_mobs[target_id] then + -- 6 == actor defeats target + -- 20 == target falls to the ground + if message_id == 6 or message_id == 20 then + if _settings.debug_mode then add_to_chat(123,'Mob '..target_id..' died. Removing from tagged mobs table.') end + info.tagged_mobs[target_id] = nil + end + end + end +end + + +-- Clear out the entire tagged mobs table when zoning. +function on_zone_change_for_th(new_zone, old_zone) + if _settings.debug_mode then add_to_chat(123,'Zoning. Clearing tagged mobs table.') end + info.tagged_mobs:clear() +end + + +-- Save the existing function, if it exists, and call it after our own handling. +if job_state_change then + job_state_change_via_th = job_state_change +end + + +-- Called if we change any user state fields. +function job_state_change(stateField, newValue, oldValue) + if stateField == 'Treasure Mode' then + if newValue == 'None' and state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'TH Mode set to None. Unlocking gear.') end + unlock_TH() + elseif oldValue == 'None' then + TH_for_first_hit() + end + end + + if job_state_change_via_th then + job_state_change_via_th(stateField, newValue, oldValue) + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Extra utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Remove mobs that we've marked as tagged with TH if we haven't seen any activity from or on them +-- for over 3 minutes. This is to handle deagros, player deaths, or other random stuff where the +-- mob is lost, but doesn't die. +function cleanup_tagged_mobs() + -- If it's been more than 3 minutes since an action on or by a tagged mob, + -- remove them from the tagged mobs list. + local current_time = os.time() + local remove_mobs = S{} + -- Search list and flag old entries. + for target_id,action_time in pairs(info.tagged_mobs) do + local time_since_last_action = current_time - action_time + if time_since_last_action > 180 then + remove_mobs:add(target_id) + if _settings.debug_mode then add_to_chat(123,'Over 3 minutes since last action on mob '..target_id..'. Removing from tagged mobs list.') end + end + end + -- Clean out mobs flagged for removal. + for mob_id,_ in pairs(remove_mobs) do + info.tagged_mobs[mob_id] = nil + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event function registration calls. +-- Can call these now that the above functions have been defined. +------------------------------------------------------------------------------------------------------------------- + +-- Register events to allow us to manage TH application. +windower.register_event('status change', on_status_change_for_th) +windower.register_event('target change', on_target_change_for_th) +windower.raw_register_event('action', on_action_for_th) +windower.raw_register_event('incoming chunk', on_incoming_chunk_for_th) +windower.raw_register_event('zone change', on_zone_change_for_th) + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Utility.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Utility.lua new file mode 100644 index 0000000..ad7a0ff --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-Utility.lua @@ -0,0 +1,663 @@ +------------------------------------------------------------------------------------------------------------------- +-- General utility functions that can be used by any job files. +-- Outside the scope of what the main include file deals with. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Buff utility functions. +------------------------------------------------------------------------------------------------------------------- + +local cancel_spells_to_check = S{'Sneak', 'Stoneskin', 'Spectral Jig', 'Trance', 'Monomi: Ichi', 'Utsusemi: Ichi'} +local cancel_types_to_check = S{'Waltz', 'Samba'} + +-- Function to cancel buffs if they'd conflict with using the spell you're attempting. +-- Requirement: Must have Cancel addon installed and loaded for this to work. +function cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + if cancel_spells_to_check:contains(spell.english) or cancel_types_to_check:contains(spell.type) then + if spell.action_type == 'Ability' then + local abil_recasts = windower.ffxi.get_ability_recasts() + if abil_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Ability waiting on recast.') + eventArgs.cancel = true + return + end + elseif spell.action_type == 'Magic' then + local spell_recasts = windower.ffxi.get_spell_recasts() + if spell_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Spell waiting on recast.') + eventArgs.cancel = true + return + end + end + + if spell.english == 'Spectral Jig' and buffactive.sneak then + cast_delay(0.2) + send_command('cancel sneak') + elseif spell.english == 'Sneak' and spell.target.type == 'SELF' and buffactive.sneak then + send_command('cancel sneak') + elseif spell.english == ('Stoneskin') then + send_command('@wait 1.0;cancel stoneskin') + elseif spell.english:startswith('Monomi') then + send_command('@wait 1.7;cancel sneak') + elseif spell.english == 'Utsusemi: Ichi' then + send_command('@wait 1.7;cancel copy image,copy image (2)') + elseif (spell.english == 'Trance' or spell.type=='Waltz') and buffactive['saber dance'] then + cast_delay(0.2) + send_command('cancel saber dance') + elseif spell.type=='Samba' and buffactive['fan dance'] then + cast_delay(0.2) + send_command('cancel fan dance') + end + end +end + + +-- Some mythics have special durations for level 1 and 2 aftermaths +local special_aftermath_mythics = S{'Tizona', 'Kenkonken', 'Murgleis', 'Yagrush', 'Carnwenhan', 'Nirvana', 'Tupsimati', 'Idris'} + +-- Call from job_precast() to setup aftermath information for custom timers. +function custom_aftermath_timers_precast(spell) + if spell.type == 'WeaponSkill' then + info.aftermath = {} + + local relic_ws = data.weaponskills.relic[player.equipment.main] or data.weaponskills.relic[player.equipment.range] + local mythic_ws = data.weaponskills.mythic[player.equipment.main] or data.weaponskills.mythic[player.equipment.range] + local empy_ws = data.weaponskills.empyrean[player.equipment.main] or data.weaponskills.empyrean[player.equipment.range] + + if not relic_ws and not mythic_ws and not empy_ws then + return + end + + info.aftermath.weaponskill = spell.english + info.aftermath.duration = 0 + + info.aftermath.level = math.floor(player.tp / 1000) + if info.aftermath.level == 0 then + info.aftermath.level = 1 + end + + if spell.english == relic_ws then + info.aftermath.duration = math.floor(0.2 * player.tp) + if info.aftermath.duration < 20 then + info.aftermath.duration = 20 + end + elseif spell.english == empy_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- duration is based on aftermath level + info.aftermath.duration = 30 * info.aftermath.level + elseif spell.english == mythic_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- Assume mythic is lvl 80 or higher, for duration + + if info.aftermath.level == 1 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 90 + elseif info.aftermath.level == 2 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 120 + else + info.aftermath.duration = 180 + end + end + end +end + + +-- Call from job_aftercast() to create the custom aftermath timer. +function custom_aftermath_timers_aftercast(spell) + if not spell.interrupted and spell.type == 'WeaponSkill' and + info.aftermath and info.aftermath.weaponskill == spell.english and info.aftermath.duration > 0 then + + local aftermath_name = 'Aftermath: Lv.'..tostring(info.aftermath.level) + send_command('timers d "Aftermath: Lv.1"') + send_command('timers d "Aftermath: Lv.2"') + send_command('timers d "Aftermath: Lv.3"') + send_command('timers c "'..aftermath_name..'" '..tostring(info.aftermath.duration)..' down abilities/00027.png') + + info.aftermath = {} + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for changing spells and target types in an automatic manner. +------------------------------------------------------------------------------------------------------------------- + +local waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Curing Waltz IV'] = 650, ['Curing Waltz V'] = 800} + +-- Utility function for automatically adjusting the waltz spell being used to match HP needs and TP limits. +-- Handle spell changes before attempting any precast stuff. +function refine_waltz(spell, action, spellMap, eventArgs) + if spell.type ~= 'Waltz' then + return + end + + -- Don't modify anything for Healing Waltz or Divine Waltzes + if spell.english == "Healing Waltz" or spell.english == "Divine Waltz" or spell.english == "Divine Waltz II" then + return + end + + local newWaltz = spell.english + local waltzID + + local missingHP + + -- If curing ourself, get our exact missing HP + if spell.target.type == "SELF" then + missingHP = player.max_hp - player.hp + -- If curing someone in our alliance, we can estimate their missing HP + elseif spell.target.isallymember then + local target = find_player_in_alliance(spell.target.name) + local est_max_hp = target.hp / (target.hpp/100) + missingHP = math.floor(est_max_hp - target.hp) + end + + -- If we have an estimated missing HP value, we can adjust the preferred tier used. + if missingHP ~= nil then + if player.main_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 200 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 600 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + elseif missingHP < 1100 then + newWaltz = 'Curing Waltz III' + waltzID = 192 + elseif missingHP < 1500 then + newWaltz = 'Curing Waltz IV' + waltzID = 193 + else + newWaltz = 'Curing Waltz V' + waltzID = 311 + end + elseif player.sub_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 150 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 300 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + else + newWaltz = 'Curing Waltz III' + waltzID = 192 + end + else + -- Not dnc main or sub; bail out + return + end + end + + local tpCost = waltz_tp_cost[newWaltz] + + local downgrade + + -- Downgrade the spell to what we can afford + if player.tp < tpCost and not buffactive.trance then + --[[ Costs: + Curing Waltz: 200 TP + Curing Waltz II: 350 TP + Curing Waltz III: 500 TP + Curing Waltz IV: 650 TP + Curing Waltz V: 800 TP + Divine Waltz: 400 TP + Divine Waltz II: 800 TP + --]] + + if player.tp < 200 then + add_to_chat(122, 'Insufficient TP ['..tostring(player.tp)..']. Cancelling.') + eventArgs.cancel = true + return + elseif player.tp < 350 then + newWaltz = 'Curing Waltz' + elseif player.tp < 500 then + newWaltz = 'Curing Waltz II' + elseif player.tp < 650 then + newWaltz = 'Curing Waltz III' + elseif player.tp < 800 then + newWaltz = 'Curing Waltz IV' + end + + downgrade = 'Insufficient TP ['..tostring(player.tp)..']. Downgrading to '..newWaltz..'.' + end + + + if newWaltz ~= spell.english then + send_command('@input /ja "'..newWaltz..'" '..tostring(spell.target.raw)) + if downgrade then + add_to_chat(122, downgrade) + end + eventArgs.cancel = true + return + end + + if missingHP and missingHP > 0 then + add_to_chat(122,'Trying to cure '..tostring(missingHP)..' HP using '..newWaltz..'.') + end +end + + +-- Function to allow for automatic adjustment of the spell target type based on preferences. +function auto_change_target(spell, spellMap) + -- Don't adjust targetting for explicitly named targets + if not spell.target.raw:startswith('<') then + return + end + + -- Do not modify target for spells where we get <lastst> or <me>. + if spell.target.raw == ('<lastst>') or spell.target.raw == ('<me>') then + return + end + + -- init a new eventArgs with current values + local eventArgs = {handled = false, PCTargetMode = state.PCTargetMode.value, SelectNPCTargets = state.SelectNPCTargets.value} + + -- Allow the job to do custom handling, or override the default values. + -- They can completely handle it, or set one of the secondary eventArgs vars to selectively + -- override the default state vars. + if job_auto_change_target then + job_auto_change_target(spell, action, spellMap, eventArgs) + end + + -- If the job handled it, we're done. + if eventArgs.handled then + return + end + + local pcTargetMode = eventArgs.PCTargetMode + local selectNPCTargets = eventArgs.SelectNPCTargets + + + local validPlayers = S{'Self', 'Player', 'Party', 'Ally', 'NPC'} + + local intersection = spell.targets * validPlayers + local canUseOnPlayer = not intersection:empty() + + local newTarget + + -- For spells that we can cast on players: + if canUseOnPlayer and pcTargetMode ~= 'default' then + -- Do not adjust targetting for player-targettable spells where the target was <t> + if spell.target.raw ~= ('<t>') then + if pcTargetMode == 'stal' then + -- Use <stal> if possible, otherwise fall back to <stpt>. + if spell.targets.Ally then + newTarget = '<stal>' + elseif spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpt' then + -- Even ally-possible spells are limited to the current party. + if spell.targets.Ally or spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpc' then + -- If it's anything other than a self-only spell, can change to <stpc>. + if spell.targets.Player or spell.targets.Party or spell.targets.Ally or spell.targets.NPC then + newTarget = '<stpc>' + end + end + end + -- For spells that can be used on enemies: + elseif spell.targets and spell.targets.Enemy and selectNPCTargets then + -- Note: this means macros should be written for <t>, and it will change to <stnpc> + -- if the flag is set. It won't change <stnpc> back to <t>. + newTarget = '<stnpc>' + end + + -- If a new target was selected and is different from the original, call the change function. + if newTarget and newTarget ~= spell.target.raw then + change_target(newTarget) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Environment utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Function to get the current weather intensity: 0 for none, 1 for single weather, 2 for double weather. +function get_weather_intensity() + return gearswap.res.weather[world.weather_id].intensity +end + + +-- Returns true if you're in a party solely comprised of Trust NPCs. +-- TODO: Do we need a check to see if we're in a party partly comprised of Trust NPCs? +function is_trust_party() + -- Check if we're solo + if party.count == 1 then + return false + end + + -- Can call a max of 3 Trust NPCs, so parties larger than that are out. + if party.count > 4 then + return false + end + + -- If we're in an alliance, can't be a Trust party. + if alliance[2].count > 0 or alliance[3].count > 0 then + return false + end + + -- Check that, for each party position aside from our own, the party + -- member has one of the Trust NPC names, and that those party members + -- are flagged is_npc. + for i = 2,4 do + if party[i] then + if not npcs.Trust:contains(party[i].name) then + return false + end + if party[i].mob and party[i].mob.is_npc == false then + return false + end + end + end + + -- If it didn't fail any of the above checks, return true. + return true +end + + +-- Call these function with a list of equipment slots to check ('head', 'neck', 'body', etc) +-- Returns true if any of the specified slots are currently encumbered. +-- Returns false if all specified slots are unencumbered. +function is_encumbered(...) + local check_list = {...} + -- Compensate for people passing a table instead of a series of strings. + if type(check_list[1]) == 'table' then + check_list = check_list[1] + end + local check_set = S(check_list) + + for slot_id,slot_name in pairs(gearswap.default_slot_map) do + if check_set:contains(slot_name) then + if gearswap.encumbrance_table[slot_id] then + return true + end + end + end + + return false +end + +------------------------------------------------------------------------------------------------------------------- +-- Elemental gear utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- General handler function to set all the elemental gear for an action. +function set_elemental_gear(spell) + set_elemental_gorget_belt(spell) + set_elemental_obi_cape_ring(spell) + set_elemental_staff(spell) +end + + +-- Set the name field of the predefined gear vars for gorgets and belts, for the specified weaponskill. +function set_elemental_gorget_belt(spell) + if spell.type ~= 'WeaponSkill' then + return + end + + -- Get the union of all the skillchain elements for the weaponskill + local weaponskill_elements = S{}: + union(skillchain_elements[spell.skillchain_a]): + union(skillchain_elements[spell.skillchain_b]): + union(skillchain_elements[spell.skillchain_c]) + + gear.ElementalGorget.name = get_elemental_item_name("gorget", weaponskill_elements) or gear.default.weaponskill_neck or "" + gear.ElementalBelt.name = get_elemental_item_name("belt", weaponskill_elements) or gear.default.weaponskill_waist or "" +end + + +-- Function to get an appropriate obi/cape/ring for the current action. +function set_elemental_obi_cape_ring(spell) + if spell.element == 'None' then + return + end + + local world_elements = S{world.day_element} + if world.weather_element ~= 'None' then + world_elements:add(world.weather_element) + end + + local obi_name = get_elemental_item_name("obi", S{spell.element}, world_elements) + gear.ElementalObi.name = obi_name or gear.default.obi_waist or "" + + if obi_name then + if player.inventory['Twilight Cape'] or player.wardrobe['Twilight Cape'] or player.wardrobe2['Twilight Cape'] or player.wardrobe3['Twilight Cape'] or player.wardrobe4['Twilight Cape'] then + gear.ElementalCape.name = "Twilight Cape" + end + if (player.inventory['Zodiac Ring'] or player.wardrobe['Zodiac Ring'] or player.wardrobe2['Zodiac Ring'] or player.wardrobe3['Zodiac Ring'] or player.wardrobe4['Zodiac Ring']) and spell.english ~= 'Impact' and + not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then + gear.ElementalRing.name = "Zodiac Ring" + end + else + gear.ElementalCape.name = gear.default.obi_back + gear.ElementalRing.name = gear.default.obi_ring + end +end + + +-- Function to get the appropriate fast cast and/or recast staves for the current spell. +function set_elemental_staff(spell) + if spell.action_type ~= 'Magic' then + return + end + + gear.FastcastStaff.name = get_elemental_item_name("fastcast_staff", S{spell.element}) or gear.default.fastcast_staff or "" + gear.RecastStaff.name = get_elemental_item_name("recast_staff", S{spell.element}) or gear.default.recast_staff or "" +end + + +-- Gets the name of an elementally-aligned piece of gear within the player's +-- inventory that matches the conditions set in the parameters. +-- +-- item_type: Type of item as specified in the elemental_map mappings. +-- EG: gorget, belt, obi, fastcast_staff, recast_staff +-- +-- valid_elements: Elements that are valid for the action being taken. +-- IE: Weaponskill skillchain properties, or spell element. +-- +-- restricted_to_elements: Secondary elemental restriction that limits +-- whether the item check can be considered valid. +-- EG: Day or weather elements that have to match the spell element being queried. +-- +-- Returns: Nil if no match was found (either due to elemental restrictions, +-- or the gear isn't in the player inventory), or the name of the piece of +-- gear that matches the query. +function get_elemental_item_name(item_type, valid_elements, restricted_to_elements) + local potential_elements = restricted_to_elements or elements.list + local item_map = elements[item_type:lower()..'_of'] + + for element in (potential_elements.it or it)(potential_elements) do + if valid_elements:contains(element) and (player.inventory[item_map[element]] or player.wardrobe[item_map[element]] or player.wardrobe2[item_map[element]]) then + return item_map[element] + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Function to easily change to a given macro set or book. Book value is optional. +------------------------------------------------------------------------------------------------------------------- + +function set_macro_page(set,book) + if not tonumber(set) then + add_to_chat(123,'Error setting macro page: Set is not a valid number ('..tostring(set)..').') + return + end + if set < 1 or set > 10 then + add_to_chat(123,'Error setting macro page: Macro set ('..tostring(set)..') must be between 1 and 10.') + return + end + + if book then + if not tonumber(book) then + add_to_chat(123,'Error setting macro page: book is not a valid number ('..tostring(book)..').') + return + end + if book < 1 or book > 20 then + add_to_chat(123,'Error setting macro page: Macro book ('..tostring(book)..') must be between 1 and 20.') + return + end + send_command('@input /macro book '..tostring(book)..';wait 1.1;input /macro set '..tostring(set)) + else + send_command('@input /macro set '..tostring(set)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for including local user files. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to load user gear files in place of default gear sets. +-- Return true if one exists and was loaded. +function load_sidecar(job) + if not job then return false end + + -- filename format example for user-local files: whm_gear.lua, or playername_whm_gear.lua + local filenames = {player.name..'_'..job..'_gear.lua', job..'_gear.lua', + 'gear/'..player.name..'_'..job..'_gear.lua', 'gear/'..job..'_gear.lua', + 'gear/'..player.name..'_'..job..'.lua', 'gear/'..job..'.lua'} + return optional_include(filenames) +end + +-- Attempt to include user-globals. Return true if it exists and was loaded. +function load_user_globals() + local filenames = {player.name..'-globals.lua', 'user-globals.lua'} + return optional_include(filenames) +end + +-- Optional version of include(). If file does not exist, does not +-- attempt to load, and does not throw an error. +-- filenames takes an array of possible file names to include and checks +-- each one. +function optional_include(filenames) + for _,v in pairs(filenames) do + local path = gearswap.pathsearch({v}) + if path then + include(v) + return true + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for vars or other data manipulation. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to locate a specified name within the current alliance. +function find_player_in_alliance(name) + for party_index,ally_party in ipairs(alliance) do + for player_index,_player in ipairs(ally_party) do + if _player.name == name then + return _player + end + end + end +end + + +-- buff_set is a set of buffs in a library table (any of S{}, T{} or L{}). +-- This function checks if any of those buffs are present on the player. +function has_any_buff_of(buff_set) + return buff_set:any( + -- Returns true if any buff from buff set that is sent to this function returns true: + function (b) return buffactive[b] end + ) +end + + +-- Invert a table such that the keys are values and the values are keys. +-- Use this to look up the index value of a given entry. +function invert_table(t) + if t == nil then error('Attempting to invert table, received nil.', 2) end + + local i={} + for k,v in pairs(t) do + i[v] = k + end + return i +end + + +-- Gets sub-tables based on baseSet from the string str that may be in dot form +-- (eg: baseSet=sets, str='precast.FC', this returns the table sets.precast.FC). +function get_expanded_set(baseSet, str) + local cur = baseSet + for i in str:gmatch("[^.]+") do + if cur then + cur = cur[i] + end + end + + return cur +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions data and event tracking. +------------------------------------------------------------------------------------------------------------------- + +-- This is a function that can be attached to a registered event for 'time change'. +-- It will send a call to the update() function if the time period changes. +-- It will also call job_time_change when any of the specific time class values have changed. +-- To activate this in your job lua, add this line to your user_setup function: +-- windower.register_event('time change', time_change) +-- +-- Variables it sets: classes.Daytime, and classes.DuskToDawn. They are set to true +-- if their respective descriptors are true, or false otherwise. +function time_change(new_time, old_time) + local was_daytime = classes.Daytime + local was_dusktime = classes.DuskToDawn + + if new_time >= 6*60 and new_time < 18*60 then + classes.Daytime = true + else + classes.Daytime = false + end + + if new_time >= 17*60 or new_time < 7*60 then + classes.DuskToDawn = true + else + classes.DuskToDawn = false + end + + if was_daytime ~= classes.Daytime or was_dusktime ~= classes.DuskToDawn then + if job_time_change then + job_time_change(new_time, old_time) + end + + handle_update({'auto'}) + end +end + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-documentation.txt b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-documentation.txt new file mode 100644 index 0000000..8f17241 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/Mote-documentation.txt @@ -0,0 +1 @@ +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation on the usage of these include files.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/closetCleaner.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/closetCleaner.lua new file mode 100644 index 0000000..50a35ef --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/closetCleaner.lua @@ -0,0 +1,377 @@ +--Copyright © 2016-2017, Brimstone +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of closetCleaner nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL Brimstone BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-- _addon.version = '1.0' + +local cc = {} +config = require ('config') +cc.sandbox = {} +cc.sandbox.windower = setmetatable({}, {__index = windower}) +cc.sandbox.windower.coroutine = functions.empty +cc.sandbox.windower.register_event = functions.empty +cc.sandbox.windower.raw_register_event = functions.empty +cc.sandbox.windower.register_unhandled_command = functions.empty + +defaults = T{} +-- Jobs you want to execute with, recomment put all active jobs you have lua for will look for <job>.lua or <playername>_<job>.lua files +defaults.ccjobs = { 'BLM', 'BLU', 'BRD', 'BST', 'COR', 'DNC', 'DRG', 'DRK', 'GEO', 'MNK', 'NIN', 'PLD', 'PUP', 'RDM', 'RNG', 'RUN', 'SAM', 'SCH', 'SMN', 'THF', 'WAR', 'WHM' } +-- Put any items in your inventory here you don't want to show up in the final report +-- recommended for furniture, food, meds, pop items or any gear you know you want to keep for some reason +-- use * for anything. +defaults.ccignore = S{ "Rem's Tale*", "Storage Slip *" } +-- Set to nil or delete for unlimited +defaults.ccmaxuse = nil +-- List bags you want to not check against, needs to match "Location" column in <player>_report.txt +defaults.ccskipBags = S{ 'Storage', 'Temporary' } +-- this prints out the _sets _ignored and _inventory files +ccDebug = false + +settings = config.load('ccConfig.xml',defaults) + +register_unhandled_command(function(command) + command = command and command:lower() or nil + if command ~= 'cc' and command ~= 'closetcleaner' then + return + end + setmetatable(cc.sandbox, {__index = gearswap.user_env}) + cc.sandbox.itemsBylongName = T{} + cc.sandbox.itemsByName = T{} + cc.sandbox.inventoryGear = T{} + cc.sandbox.gsGear = T{} + for k,v in pairs(gearswap.res.items) do + cc.sandbox.itemsBylongName[gearswap.res.items[k].name_log:lower()] = k + cc.sandbox.itemsByName[gearswap.res.items[k].name:lower()] = k + end + cc.sandbox.jobs = {} + for k,v in pairs(gearswap.res.jobs) do + cc.sandbox.jobs[gearswap.res.jobs[k].english_short] = k + end + if not windower.dir_exists(windower.addon_path..'report') then + windower.create_dir(windower.addon_path..'report') + end + local path = windower.addon_path:gsub('\\','/') + path = path..'report/'..player.name + cc.run_report(path) + cc.sandbox = {} + cc.sandbox.windower = setmetatable({}, {__index = windower}) + cc.sandbox.windower.register_event = functions.empty + cc.sandbox.windower.raw_register_event = functions.empty + cc.sandbox.windower.register_unhandled_command = functions.empty + return true +end) + +-- This function creates the report and generates the calls to the other functions +function cc.run_report(path) + mainReportName = path..'_report.txt' + local f = io.open(mainReportName,'w+') + f:write('closetCleaner Report:\n') + f:write('=====================\n\n') + cc.export_inv(path) + cc.export_sets(path) + for k,v in pairs(cc.sandbox.inventoryGear) do + if cc.sandbox.gsGear[k] == nil then + cc.sandbox.gsGear[k] = 0 + end + end + data = T{"Name", " | ", "Count", " | ", "Location", " | ", "Jobs Used", " | ", "Long Name"} + form = T{"%25s", "%3s", "%10s", "%3s", "%20s", "%3s", "%-88s", "%3s", "%60s"} + cc.print_row(f, data, form) + cc.print_break(f, form) + if ccDebug then + ignoredReportName = path..'_ignored.txt' + f2 = io.open(ignoredReportName,'w+') + f2:write('closetCleaner ignored Report:\n') + f2:write('=====================\n\n') + cc.print_row(f2, data, form) + cc.print_break(f2, form) + end + for k,v in cc.spairs(cc.sandbox.gsGear, function(t,a,b) return t[b] > t[a] end) do + if settings.ccmaxuse == nil or v <= settings.ccmaxuse then + printthis = 1 + if not cc.job_used[k] then + cc.job_used[k] = " " + end + for s in pairs(settings.ccignore) do + if windower.wc_match(gearswap.res.items[k].english, s) then + printthis = nil + if cc.sandbox.inventoryGear[k] == nil then + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", "NOT FOUND", " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + else + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.sandbox.inventoryGear[k], " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + end + if ccDebug then + cc.print_row(f2, data, form) + end + break + end + end + if printthis then + if cc.sandbox.inventoryGear[k] == nil then + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", "NOT FOUND", " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + else + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.sandbox.inventoryGear[k], " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + end + cc.print_row(f, data, form) + end + end + end + if ccDebug then + f2:close() + print("File created: "..ignoredReportName) + end + f:close() + print("File created: "..mainReportName) +end + + -- This function tallies all the gear in your inventory +function cc.export_inv(path) + if ccDebug then + reportName = path..'_inventory.txt' + finv = io.open(reportName,'w+') + finv:write('closetCleaner Inventory Report:\n') + finv:write('=====================\n\n') + end + + local item_list = T{} + checkbag = true + for n = 0, #gearswap.res.bags do + if not settings.ccskipBags:contains(gearswap.res.bags[n].english) then + for i,v in ipairs(gearswap.get_item_list(gearswap.items[gearswap.res.bags[n].english:gsub(' ', ''):lower()])) do + if v.name ~= empty then + local slot = gearswap.xmlify(tostring(v.slot)) + local name = gearswap.xmlify(tostring(v.name)):gsub('NUM1','1') + + if cc.sandbox.itemsByName[name:lower()] ~= nil then + itemid = cc.sandbox.itemsByName[name:lower()] + elseif cc.sandbox.itemsBylongName[name:lower()] ~= nil then + itemid = cc.sandbox.itemsBylongName[name:lower()] + else + print("Item: "..name.." not found in gearswap.resources!") + end + if ccDebug then + finv:write("Name: "..name.." Slot: "..slot.." Bag: "..gearswap.res.bags[n].english.."\n") + end + if cc.sandbox.inventoryGear[itemid] == nil then + cc.sandbox.inventoryGear[itemid] = gearswap.res.bags[n].english + else + cc.sandbox.inventoryGear[itemid] = cc.sandbox.inventoryGear[itemid]..", "..gearswap.res.bags[n].english + end + end + end + end + end + if ccDebug then + finv:close() + print("File created: "..reportName) + end +end + +-- loads all the relevant jobs.lua files and inserts the sets tables into a supersets table: +-- supersets.<JOB>.sets.... +function cc.export_sets(path) + if ccDebug then + reportName = path..'_sets.txt' + fsets = io.open(reportName,'w+') + fsets:write('closetCleaner sets Report:\n') + fsets:write('=====================\n\n') + end + cc.supersets = {} + cc.job_used = T{} + cc.job_logged = T() + fpath = windower.addon_path:gsub('\\','/') + fpath = fpath:gsub('//','/') + fpath = string.lower(fpath) + dpath = fpath..'data/' + for i,v in ipairs(settings.ccjobs) do + dname = string.lower(dpath..player.name..'/'..v..'.lua') + lname = string.lower(dpath..player.name..'_'..v..'.lua') + lgname = string.lower(dpath..player.name..'_'..v..'_gear.lua') + sname = string.lower(dpath..v..'.lua') + sgname = string.lower(dpath..v..'_gear.lua') + if windower.file_exists(lgname) then + cc.supersets[v] = cc.extract_sets(lgname) + elseif windower.file_exists(lname) then + cc.supersets[v] = cc.extract_sets(lname) + elseif windower.file_exists(sgname) then + cc.supersets[v] = cc.extract_sets(sgname) + elseif windower.file_exists(sname) then + cc.supersets[v] = cc.extract_sets(sname) + elseif windower.file_exists(dname) then + cc.supersets[v] = cc.extract_sets(dname) + else + print('lua file for '..v..' not found!') + end + end + cc.list_sets(cc.supersets, fsets) + cc.supersets = nil + if ccDebug then + fsets:close() + print("File created: "..reportName) + end +end + +-- sets the 'sets' and puts them into supersets based off file name. +function cc.extract_sets(file) + local user_file = gearswap.loadfile(file) + if user_file then + gearswap.setfenv(user_file, cc.sandbox) + cc.sandbox.sets = {} + user_file() + local def_gear = cc.sandbox.init_get_sets or cc.sandbox.get_sets + if def_gear then + def_gear() + end + return table.copy(cc.sandbox.sets) + else + print('lua file for '..file..' not found!') + end +end + +-- this function tallies the items used in each lua file +function cc.list_sets(t, f) + write_sets = T{} + local print_r_cache={} + local function sub_print_r(t,fromTab) + if (type(t)=="table") then + for pos,val in pairs(t) do + if S{"WAR", "MNK", "WHM", "BLM", "RDM", "THF", "PLD", "DRK", "BST", "BRD", "RNG", "SAM", "NIN", "DRG", "SMN", "BLU", "COR", "PUP", "DNC", "SCH", "GEO", "RUN"}:contains(pos) then + job = pos + end + if (type(val)=="table") then + sub_print_r(val,job) + elseif (type(val)=="string") then + if val ~= "" and val ~= "empty" then + if S{"name", "main", "sub", "range", "ammo", "head", "neck", "left_ear", "right_ear", "body", "hands", "left_ring", "right_ring", "back", "waist", "legs", "feet", "ear1", "ear2", "ring1", "ring2", "lear", "rear", "lring", "rring"}:contains(pos) then + if cc.sandbox.itemsByName[val:lower()] ~= nil then + itemid = cc.sandbox.itemsByName[val:lower()] + elseif cc.sandbox.itemsBylongName[val:lower()] ~= nil then + itemid = cc.sandbox.itemsBylongName[val:lower()] + else + print("Item: '"..val.."' not found in gearswap.resources! "..pos) + end + if ccDebug then + f:write('Processing '..job..' name for val '..val..' id '..itemid..'\n') + end + if write_sets[itemid] == nil then + write_sets[itemid] = 1 + if cc.job_used[itemid] == nil then + cc.job_used[itemid] = job + cc.job_logged[itemid..job] = 1 + else + cc.job_used[itemid] = cc.job_used[itemid]..","..job + cc.job_logged[itemid..job] = 1 + end + else + write_sets[itemid] = write_sets[itemid] + 1 + if cc.job_logged[itemid..job] == nil then + cc.job_used[itemid] = cc.job_used[itemid]..","..job + cc.job_logged[itemid..job] = 1 + end + end + end + end + elseif (type(val)=="number") then + print("Found Number: "..val.." from "..pos.." table "..t) + else + print("Error: Val needs to be table or string "..type(val)) + end + end + end + end + sub_print_r(t,nil) + if ccDebug then + data = T{"Name", " | ", "Count", " | ", "Jobs", " | ", "Long Name"} + form = T{"%22s", "%3s", "%10s", "%3s", "%88s", "%3s", "%60s"} + cc.print_row(f, data, form) + cc.print_break(f, form) + f:write('\n') + for k,v in pairs(write_sets) do + data = T{gearswap.res.items[k].english, " | ", tostring(v), " | ", cc.job_used[k], " | ", gearswap.res.items[k].english_log} + cc.print_row(f, data, form) + cc.sandbox.gsGear[k] = v + end + f:write() + else + for k,v in pairs(write_sets) do + cc.sandbox.gsGear[k] = v + end + end +end + +-- interate throught table in a sorted order. +function cc.spairs(t, order) + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys+1] = k end + + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a,b) return order(t, a, b) end) + else + table.sort(keys) + end + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +-- pass in file handle and a table of formats and table of data +function cc.print_row(f, data, form) + for k,v in pairs(data) do + f:write(string.format(form[k], v)) + end + f:write('\n') +end + +-- pass in file handle and a table of formats and table of data +function cc.print_break(f, form) + for k,v in pairs(form) do + number = string.match(v,"%d+") + for i=1,number do + f:write('-') + end + -- f:write(' ') -- can add characters to end here like spaces but subtract from number in the for loop above + end + f:write('\n') +end + + +function cc.include(str) + str = str:lower() + if not (str == 'closetcleaner' or str == 'closetcleaner.lua') then + include(str, cc.sandbox) + end +end + +cc.sandbox.include = cc.include +cc.sandbox.require = cc.include
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/organizer-lib.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/organizer-lib.lua new file mode 100644 index 0000000..c3111f6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/organizer-lib.lua @@ -0,0 +1,237 @@ +-- Organizer library v2 + +local org = {} +register_unhandled_command(function(...) + local cmds = {...} + for _,v in ipairs(cmds) do + if S{'organizer','organize','org','o'}:contains(v:lower()) then + org.export_set() + return true + end + end + return false +end) + + +function org.export_set() + if not sets then + windower.add_to_chat(123,'Organizer Library: Cannot export your sets for collection because the table is nil.') + return + elseif not windower.dir_exists(windower.windower_path..'addons/organizer/') then + windower.add_to_chat(123,'Organizer Library: The organizer addon is not installed. Activate it in the launcher.') + return + end + + -- Makes a big table keyed to item resource tables, with values that are 1-based + -- numerically indexed tables of different entries for each of the items from the sets table. + local item_list = org.unpack_names({},'L1',sets,{}) + + local trans_item_list = org.identify_items(item_list) + + for i,v in pairs(trans_item_list) do + trans_item_list[i] = org.simplify_entry(v) + end + + if trans_item_list:length() == 0 then + windower.add_to_chat(123,'Organizer Library: Your sets table is empty.') + return + end + + local flattab = T{} + for name,tab in pairs(trans_item_list) do + for _,info in ipairs(tab) do + flattab:append({id=tab.id,name=tab.name,log_name=tab.log_name,augments=info.augments,count=info.count}) + end + end + + -- See if we have any non-equipment items to drag along + if organizer_items then + local organizer_item_list = org.unpack_names({}, 'L1', organizer_items, {}) + + for _,tab in pairs(org.identify_items(organizer_item_list)) do + count = gearswap.res.items[tab.id].stack + flattab:append({id=tab.id,name=tab.name,log_name=tab.log_name,count=count}) + end + end + + -- At this point I have a table of equipment pieces indexed by the inventory name. + -- I need to make a function that will translate that into a list of pieces in + -- inventory or wardrobe. + -- #trans_item_list[i] = Number of a given item + -- trans_item_list[i].id = item ID + + local ward_ids = {8,10,11,12} + local wardrobes = {} + local ward = {} + + for _,id in pairs(ward_ids) do + wardrobes[id] = windower.ffxi.get_items(id) + wardrobes[id].max = windower.ffxi.get_bag_info(id).max + ward[id] = T{} + end + + local inv = T{} + for i,v in ipairs(flattab) do + local found + local ward_id + -- Iterate over the wardrobes and look for gear from the list that is already in wardrobes, then eliminate it from the list + for id,wardrobe in pairs(wardrobes) do + for n,m in ipairs(wardrobe) do + if m.id == v.id and (not v.augments or v.augments and gearswap.extdata.decode(m).augments and gearswap.extdata.compare_augments(v.augments,gearswap.extdata.decode(m).augments)) then + found = n + break + end + end + if found then + ward_id = id + break + end + end + if found then + table.remove(wardrobes[ward_id],found) + ward[ward_id]:append(v) + else + inv:append(v) + end + end + + local inventory_max = windower.ffxi.get_bag_info(0).max + + for id=1,4 do + if #inv > inventory_max and #ward[id] + (#inv-inventory_max) < wardrobes[id].max then + local available = wardrobes[id].max - #ward[id] + local length = math.min(#inv-80,available) + ward:extend(inv:slice(1-length)) + end + end + + if #inv > inventory_max then + windower.add_to_chat(123,'Organizer Library: Your sets table contains too many items.') + return + end + + -- Scan wardrobe, eliminate items from your table that are in wardrobe + -- Scan inventory + + local fi = file.new('../organizer/data/inventory/organizer-lib-file.lua') + fi:write('-- Generated by the Organizer Library ('..os.date()..')\nreturn '..(inv:tovstring({'augments','log_name','name','id','count'}))) + + for _,id in ipairs(ward_ids) do + local fw = file.new('../organizer/data/'..gearswap.res.bags[id].command..'/organizer-lib-file.lua') + fw:write('-- Generated by the Organizer Library ('..os.date()..')\nreturn '..(ward[id]:tovstring({'augments','log_name','name','id','count'}))) + end + + windower.send_command('wait 0.5;org o organizer-lib-file') +end + +function org.simplify_entry(tab) + -- Some degree of this needs to be done in unpack_names or I won't be able to detect when two identical augmented items are equipped. + local output = T{id=tab.id,name=tab.name,log_name=tab.log_name} + local rare = gearswap.res.items[tab.id].flags:contains('Rare') + for i,v in ipairs(tab) do + local handled = false + if v.augment then + v.augments = {v.augment} + v.augment = nil + end + + for n,m in ipairs(output) do + if (not v.bag or v.bag and v.bag == m.bag) and v.slot == m.slot and + (not v.augments or ( m.augments and gearswap.extdata.compare_augments(v.augments,m.augments))) then + output[n].count = math.min(math.max(output[n].count,v.count),gearswap.res.items[tab.id].stack) + handled = true + break + elseif (not v.bag or v.bag and v.bag == m.bag) and v.slot == m.slot and v.augments and not m.augments then + -- v has augments, but there currently exists a matching version of the + -- item without augments in the output table. Replace the entry with the augmented entry + local countmax = math.min(math.max(output[n].count,v.count),gearswap.res.items[tab.id].stack) + output[n] = v + output[n].count = countmax + handled = true + break + elseif rare then + handled = true + break + end + end + if not handled then + output:append(v) + end + + end + return output +end + +function org.identify_items(tab) + local name_to_id_map = {} + local items = windower.ffxi.get_items() + for id,inv in pairs(items) do + if type(inv) == 'table' then + for ind,item in ipairs(inv) do + if type(item) == 'table' and item.id and item.id ~= 0 then + name_to_id_map[gearswap.res.items[item.id][gearswap.language]:lower()] = item.id + name_to_id_map[gearswap.res.items[item.id][gearswap.language..'_log']:lower()] = item.id + end + end + end + end + local trans = T{} + for i,v in pairs(tab) do + local item = name_to_id_map[i:lower()] and table.reassign({},gearswap.res.items[name_to_id_map[i:lower()]]) --and org.identify_unpacked_name(i,name_to_id_map) + if item then + local n = gearswap.res.items[item.id][gearswap.language]:lower() + local ln = gearswap.res.items[item.id][gearswap.language..'_log']:lower() + if not trans[n] then + trans[n] = T{id=item.id, + name=n, + log_name=ln, + } + end + trans[n]:extend(v) + end + end + return trans +end + +function org.unpack_names(ret_tab,up,tab_level,unpacked_table) + for i,v in pairs(tab_level) do + local flag = false + if type(v)=='table' and i ~= 'augments' and not ret_tab[tostring(tab_level[i])] then + ret_tab[tostring(tab_level[i])] = true + unpacked_table, ret_tab = org.unpack_names(ret_tab,i,v,unpacked_table) + elseif i=='name' then + -- v is supposed to be a name, then. + flag = true + elseif type(v) == 'string' and v~='augment' and v~= 'augments' and v~= 'priority' then + -- v is a string that's not any known option of gearswap, so treat it as an item name. + -- I really need to make a set of the known advanced table options and use that instead. + flag = true + end + if flag then + local n = tostring(v):lower() + if not unpacked_table[n] then unpacked_table[n] = {} end + local ind = #unpacked_table[n] + 1 + if i == 'name' and gearswap.slot_map[tostring(up):lower()] then -- Advanced Table + unpacked_table[n][ind] = tab_level + unpacked_table[n][ind].count = unpacked_table[n][ind].count or 1 + unpacked_table[n][ind].slot = gearswap.slot_map[up:lower()] + elseif gearswap.slot_map[tostring(i):lower()] then + unpacked_table[n][ind] = {slot=gearswap.slot_map[i:lower()],count=1} + end + end + end + return unpacked_table, ret_tab +end + +function org.string_augments(tab) + local aug_str = '' + if tab.augments then + for aug_ind,augment in pairs(tab.augments) do + if augment ~= 'none' then aug_str = aug_str..'['..aug_ind..'] = '..'"'..augment..'",\n' end + end + end + if tab.augment then + if tab.augment ~= 'none' then aug_str = aug_str.."'"..augment.."'," end + end + if aug_str ~= '' then return '{\n'..aug_str..'}' end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Modes.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Modes.lua new file mode 100644 index 0000000..35036b3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Modes.lua @@ -0,0 +1,358 @@ +------------------------------------------------------------------------------------------------------------------- +-- This include library allows use of specially-designed tables for tracking +-- certain types of modes and state. +-- +-- Usage: include('Modes.lua') +-- +-- Construction syntax: +-- +-- 1) Create a new list of mode values. The first value will always be the default. +-- MeleeMode = M{'Normal', 'Acc', 'Att'} -- Construct as a basic table, using braces. +-- MeleeMode = M('Normal', 'Acc', 'Att') -- Pass in a list of strings, using parentheses. +-- +-- Optional: If constructed as a basic table, and the table contains a key value +-- of 'description', that description will be saved for future reference. +-- If a simple list of strings is passed in, no description will be set. +-- +-- 2) Create a boolean mode with a specified default value (note parentheses): +-- UseLuzafRing = M(true) +-- UseLuzafRing = M(false) +-- +-- Optional: A string may be provided that will be used as the mode description: +-- UseLuzafRing = M(false, 'description') +-- UseLuzafRing = M(true, 'description') +-- +-- +-- Public information fields (all are case-insensitive): +-- +-- 1) m.description -- Get a text description of the mode table, if it's been set. +-- 2) m.current or m.value -- Gets the current mode value. Booleans will return the strings "on" or "off". +-- 3) m.index -- Gets the current index value, or true/false for booleans. +-- +-- +-- Public class functions: +-- +-- 1) m:describe(str) -- Sets the description for the mode table to the provided string value. +-- 2) m:options(options) -- Redefine the options for a list mode table. Cannot be used on a boolean table. +-- +-- +-- Public mode manipulation functions: +-- +-- 1) m:cycle() -- Cycles through the list going forwards. Acts as a toggle on boolean mode vars. +-- 2) m:cycleback() -- Cycles through the list going backwards. Acts as a toggle on boolean mode vars. +-- 3) m:toggle() -- Toggles a boolean Mode between true and false. +-- 4) m:set(n) -- Sets the current mode value to n. +-- Note: If m is boolean, n can be boolean true/false, or string of on/off/true/false. +-- Note: If m is boolean and no n is given, this forces m to true. +-- 5) m:unset() -- Sets a boolean mode var to false. +-- 6) m:reset() -- Returns the mode var to its default state. +-- 7) m:default() -- Same as reset() +-- +-- All public functions return the current value after completion. +-- +-- +-- Example usage: +-- +-- sets.MeleeMode.Normal = {} +-- sets.MeleeMode.Acc = {} +-- sets.MeleeMode.Att = {} +-- +-- MeleeMode = M{'Normal', 'Acc', 'Att', ['description']='Melee Mode'} +-- MeleeMode:cycle() +-- equip(sets.engaged[MeleeMode.current]) +-- MeleeMode:options('Normal', 'LowAcc', 'HighAcc') +-- >> Changes the option list, but the description stays 'Melee Mode' +-- +-- +-- sets.LuzafRing.on = {ring2="Luzaf's Ring"} +-- sets.LuzafRing.off = {} +-- +-- UseLuzafRing = M(false) +-- UseLuzafRing:toggle() +-- equip(sets.precast['Phantom Roll'], sets.LuzafRing[UseLuzafRing.value]) +------------------------------------------------------------------------------------------------------------------- + + +_meta = _meta or {} +_meta.M = {} +_meta.M.__class = 'mode' +_meta.M.__methods = {} + + +-- Default constructor for mode tables +-- M{'a', 'b', etc, ['description']='a'} -- defines a mode list, description 'a' +-- M('a', 'b', etc) -- defines a mode list, no description +-- M('a') -- defines a mode list, default 'a' +-- M{['description']='a'} -- defines a mode list, default 'Normal', description 'a' +-- M{} -- defines a mode list, default 'Normal', no description +-- M(false) -- defines a mode boolean, default false, no description +-- M(true) -- defines a mode boolean, default true, no description +-- M(false, 'a') -- defines a mode boolean, default false, description 'a' +-- M(true, 'a') -- defines a mode boolean, default true, description 'a' +-- M() -- defines a mode boolean, default false, no description +function M(t, ...) + local m = {} + m._track = {} + + -- If we're passed a list of strings, convert them to a table + local args = {...} + if type(t) == 'string' then + t = {[1] = t} + + for ind, val in ipairs(args) do + t[ind+1] = val + end + end + + -- Construct the table that we'll be added the metadata to + if type(t) == 'table' then + m._track._type = 'list' + m._track._invert = {} + m._track._count = 0 + + if t['description'] then + m._track._description = t['description'] + end + + -- Only copy numerically indexed values + for ind, val in ipairs(t) do + m[ind] = val + m._track._invert[val] = ind + m._track._count = ind + end + + if m._track._count == 0 then + m[1] = 'Normal' + m._track._invert['Normal'] = 1 + m._track._count = 1 + end + + m._track._default = 1 + elseif type(t) == 'boolean' or t == nil then + m._track._type = 'boolean' + m._track._default = t or false + m._track._description = args[1] + m._track._count = 2 + -- Text lookups for bool values + m[true] = 'on' + m[false] = 'off' + else + -- Construction failure + error("Unable to construct a mode table with the provided parameters.", 2) + end + + m._track._current = m._track._default + + return setmetatable(m, _meta.M) +end + +-------------------------------------------------------------------------- +-- Metamethods +-- Functions that will be used as metamethods for the class +-------------------------------------------------------------------------- + +-- Handler for __index when trying to access the current mode value. +-- Handles indexing 'current' or 'value' keys. +_meta.M.__index = function(m, k) + if type(k) == 'string' then + local lk = k:lower() + if lk == 'current' or lk == 'value' then + return m[m._track._current] + elseif lk == 'index' then + return m._track._current + elseif m._track['_'..lk] then + return m._track['_'..lk] + else + return _meta.M.__methods[lk] + end + end +end + +-- Tostring handler for printing out the table and its current state. +_meta.M.__tostring = function(m) + local res = '' + if m._track._description then + res = res .. m._track._description .. ': ' + end + + if m._track._type == 'list' then + res = res .. '{' + for k,v in ipairs(m) do + res = res..tostring(v) + if m[k+1] ~= nil then + res = res..', ' + end + end + res = res..'}' + else + res = res .. 'Boolean' + end + + res = res .. ' ('..tostring(m.Current).. ')' + + -- Debug addition + --res = res .. ' [' .. m._track._type .. '/' .. tostring(m._track._current) .. ']' + + return res +end + +-- Length handler for the # value lookup. +_meta.M.__len = function(m) + return m._track._count +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that can be used as public methods for manipulating the class. +-------------------------------------------------------------------------- + +-- Function to set the table's description. +_meta.M.__methods['describe'] = function(m, str) + if type(str) == 'string' then + m._track._description = str + else + error("Invalid argument type: " .. type(str), 2) + end +end + +-- Function to change the list of options available. +-- Leaves the description intact. +-- Cannot be used on boolean classes. +_meta.M.__methods['options'] = function(m, ...) + if m._track._type == 'boolean' then + error("Cannot revise the options list for a boolean mode class.", 2) + end + + local options = {...} + -- Always include a default option if nothing else is given. + if #options == 0 then + options = {'Normal'} + end + + -- Zero-out existing values and clear the tracked inverted list + -- and member count. + for key,val in ipairs(m) do + m[key] = nil + end + m._track._invert = {} + m._track._count = 0 + + -- Insert in new data. + for key,val in ipairs(options) do + m[key] = val + m._track._invert[val] = key + m._track._count = key + end + + m._track._current = m._track._default +end + + +-------------------------------------------------------------------------- +-- Public methods +-- Functions that will be used as public methods for manipulating state. +-------------------------------------------------------------------------- + +-- Cycle forwards through the list +_meta.M.__methods['cycle'] = function(m) + if m._track._type == 'list' then + m._track._current = (m._track._current % m._track._count) + 1 + else + m:toggle() + end + + return m.Current +end + +-- Cycle backwards through the list +_meta.M.__methods['cycleback'] = function(m) + if m._track._type == 'list' then + m._track._current = m._track._current - 1 + if m._track._current < 1 then + m._track._current = m._track._count + end + else + m:toggle() + end + + return m.Current +end + +-- Toggle a boolean value +_meta.M.__methods['toggle'] = function(m) + if m._track._type == 'boolean' then + m._track._current = not m._track._current + else + error("Cannot toggle a list mode.", 2) + end + + return m.Current +end + + +-- Set the current value +_meta.M.__methods['set'] = function(m, val) + if m._track._type == 'boolean' then + if val == nil then + m._track._current = true + elseif type(val) == 'boolean' then + m._track._current = val + elseif type(val) == 'string' then + val = val:lower() + if val == 'on' or val == 'true' then + m._track._current = true + elseif val == 'off' or val == 'false' then + m._track._current = false + else + error("Unrecognized value: "..tostring(val), 2) + end + else + error("Unrecognized value type: "..type(val), 2) + end + else + if m._track._invert[val] then + m._track._current = m._track._invert[val] + else + local found = false + for v, ind in pairs(m._track._invert) do + if val:lower() == v:lower() then + m._track._current = ind + found = true + break + end + end + + if not found then + error("Unknown mode value: " .. tostring(val), 2) + end + end + end + + return m.Current +end + +-- Reset to the default value +_meta.M.__methods['default'] = function(m) + m._track._current = m._track._default + + return m.Current +end + +-- Reset to the default value +_meta.M.__methods['reset'] = function(m) + m._track._current = m._track._default + + return m.Current +end + +-- Forces a boolean mode to false +_meta.M.__methods['unset'] = function(m) + if m._track._type == 'boolean' then + m._track._current = false + else + error("Cannot unset a list mode class.", 2) + end + + return m.Current +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Globals.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Globals.lua new file mode 100644 index 0000000..8546c39 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Globals.lua @@ -0,0 +1,112 @@ +------------------------------------------------------------------------------------------------------------------- +-- Tables and functions for commonly-referenced gear that job files may need, but +-- doesn't belong in the global Mote-Include file since they'd get clobbered on each +-- update. +-- Creates the 'gear' table for reference in other files. +-- +-- Note: Function and table definitions should be added to user, but references to +-- the contained tables via functions (such as for the obi function, below) use only +-- the 'gear' table. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Modify the sets table. Any gear sets that are added to the sets table need to +-- be defined within this function, because sets isn't available until after the +-- include is complete. It is called at the end of basic initialization in Mote-Include. +------------------------------------------------------------------------------------------------------------------- + +function define_global_sets() + -- Special gear info that may be useful across jobs. + + -- Staffs + gear.Staff = {} + gear.Staff.HMP = 'Chatoyant Staff' + gear.Staff.PDT = 'Earth Staff' + + -- Dark Rings + gear.DarkRing = {} + gear.DarkRing.physical = {name="Dark Ring",augments={'Magic dmg. taken -3%','Spell interruption rate down -5%','Phys. dmg. taken -6%'}} + gear.DarkRing.magical = {name="Dark Ring", augments={'Magic dmg. taken -6%','Breath dmg. taken -5%'}} + + -- Default items for utility gear values. + gear.default.weaponskill_neck = "Asperity Necklace" + gear.default.weaponskill_waist = "Caudata Belt" + gear.default.obi_waist = "Cognition Belt" + gear.default.obi_back = "Toro Cape" + gear.default.obi_ring = "Strendu Ring" + gear.default.fastcast_staff = "" + gear.default.recast_staff = "" +end + +------------------------------------------------------------------------------------------------------------------- +-- Functions to set user-specified binds, generally on load and unload. +-- Kept separate from the main include so as to not get clobbered when the main is updated. +------------------------------------------------------------------------------------------------------------------- + +-- Function to bind GearSwap binds when loading a GS script. +function global_on_load() + send_command('bind f9 gs c cycle OffenseMode') + send_command('bind ^f9 gs c cycle DefenseMode') + send_command('bind !f9 gs c cycle WeaponskillMode') + send_command('bind f10 gs c activate PhysicalDefense') + send_command('bind ^f10 gs c cycle PhysicalDefenseMode') + send_command('bind !f10 gs c toggle kiting') + send_command('bind f11 gs c activate MagicalDefense') + send_command('bind ^f11 gs c cycle CastingMode') + send_command('bind f12 gs c update user') + send_command('bind ^f12 gs c cycle IdleMode') + send_command('bind !f12 gs c reset defense') + + send_command('bind ^- gs c toggle selectnpctargets') + send_command('bind ^= gs c cycle pctargetmode') +end + +-- Function to revert binds when unloading. +function global_on_unload() + send_command('unbind f9') + send_command('unbind ^f9') + send_command('unbind !f9') + send_command('unbind f10') + send_command('unbind ^f10') + send_command('unbind !f10') + send_command('unbind f11') + send_command('unbind ^f11') + send_command('unbind !f11') + send_command('unbind f12') + send_command('unbind ^f12') + send_command('unbind !f12') + + send_command('unbind ^-') + send_command('unbind ^=') +end + +------------------------------------------------------------------------------------------------------------------- +-- Global event-handling functions. +------------------------------------------------------------------------------------------------------------------- + +-- Global intercept on precast. +function user_precast(spell, action, spellMap, eventArgs) + cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + refine_waltz(spell, action, spellMap, eventArgs) +end + +-- Global intercept on midcast. +function user_midcast(spell, action, spellMap, eventArgs) + -- Default base equipment layer of fast recast. + if spell.action_type == 'Magic' and sets.midcast and sets.midcast.FastRecast then + equip(sets.midcast.FastRecast) + end +end + +-- Global intercept on buff change. +function user_buff_change(buff, gain, eventArgs) + -- Create a timer when we gain weakness. Remove it when weakness is gone. + if buff:lower() == 'weakness' then + if gain then + send_command('timers create "Weakness" 300 up abilities/00255.png') + else + send_command('timers delete "Weakness"') + end + end +end + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Include.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Include.lua new file mode 100644 index 0000000..6ab1614 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Include.lua @@ -0,0 +1,1125 @@ +------------------------------------------------------------------------------------------------------------------- +-- Common variables and functions to be included in job scripts, for general default handling. +-- +-- Include this file in the get_sets() function with the command: +-- include('Mote-Include.lua') +-- +-- It will then automatically run its own init_include() function. +-- +-- IMPORTANT: This include requires supporting include files: +-- Mote-Utility +-- Mote-Mappings +-- Mote-SelfCommands +-- Mote-Globals +-- +-- Place the include() directive at the start of a job's get_sets() function. +-- +-- Included variables and functions are considered to be at the same scope level as +-- the job script itself, and can be used as such. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Initialization function that defines variables to be used. +-- These are accessible at the including job lua script's scope. +-- +-- Auto-initialize after defining this function. +------------------------------------------------------------------------------------------------------------------- + + +function init_include() + -- Used to define various types of data mappings. These may be used in the initialization, + -- so load it up front. + include('Mote-Mappings') + + -- Var for tracking misc info + info = {} + + -- Var for tracking state values + state = {} + + -- General melee offense/defense modes, allowing for hybrid set builds, as well as idle/resting/weaponskill. + state.OffenseMode = 'Normal' + state.DefenseMode = 'Normal' + state.RangedMode = 'Normal' + state.WeaponskillMode = 'Normal' + state.CastingMode = 'Normal' + state.IdleMode = 'Normal' + state.RestingMode = 'Normal' + + -- All-out defense state, either physical or magical + state.Defense = {} + state.Defense.Active = false + state.Defense.Type = 'Physical' + state.Defense.PhysicalMode = 'PDT' + state.Defense.MagicalMode = 'MDT' + + state.Kiting = false + state.MaxWeaponskillDistance = 0 + + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + + state.CombatWeapon = nil + state.CombatForm = nil + + state.Buff = {} + + + -- Vars for specifying valid mode values. + -- Defaults here are just for example. Set them properly in each job file. + options = {} + options.OffenseModes = {'Normal'} + options.DefenseModes = {'Normal'} + options.RangedModes = {'Normal'} + options.WeaponskillModes = {'Normal'} + options.CastingModes = {'Normal'} + options.IdleModes = {'Normal'} + options.RestingModes = {'Normal'} + options.PhysicalDefenseModes = {'PDT'} + options.MagicalDefenseModes = {'MDT'} + + options.TargetModes = {'default', 'stpc', 'stpt', 'stal'} + + + -- Spell mappings to describe a 'type' of spell. Used when searching for valid sets. + classes = {} + -- Basic spell mappings are based on common spell series. + -- EG: 'Cure' for Cure, Cure II, Cure III, Cure IV, Cure V, or Cure VI. + classes.SpellMaps = spell_maps + -- List of spells and spell maps that don't benefit from greater skill (though + -- they may benefit from spell-specific augments, such as improved regen or refresh). + -- Spells that fall under this category will be skipped when searching for + -- spell.skill sets. + classes.NoSkillSpells = no_skill_spells_list + classes.SkipSkillCheck = false + -- Custom, job-defined class, like the generic spell mappings. + -- Takes precedence over default spell maps. + -- Is reset at the end of each spell casting cycle (ie: at the end of aftercast). + classes.CustomClass = nil + classes.JAMode = nil + -- Custom groups used for defining melee and idle sets. Persists long-term. + classes.CustomMeleeGroups = L{} + classes.CustomRangedGroups = L{} + classes.CustomIdleGroups = L{} + classes.CustomDefenseGroups = L{} + + -- Class variables for time-based flags + classes.Daytime = false + classes.DuskToDawn = false + + + -- Special control flags. + mote_vars = {} + mote_vars.show_set = nil + mote_vars.set_breadcrumbs = L{} + + -- Display text mapping. + on_off_names = {[true] = 'on', [false] = 'off'} + on_off_values = T{'on', 'off', 'true', 'false'} + true_values = T{'on', 'true'} + + + -- Subtables within the sets table that we expect to exist, and are annoying to have to + -- define within each individual job file. We can define them here to make sure we don't + -- have to check for existence. The job file should be including this before defining + -- any sets, so any changes it makes will override these anyway. + sets.precast = {} + sets.precast.FC = {} + sets.precast.JA = {} + sets.precast.WS = {} + sets.precast.RA = {} + sets.midcast = {} + sets.midcast.RA = {} + sets.midcast.Pet = {} + sets.idle = {} + sets.resting = {} + sets.engaged = {} + sets.defense = {} + sets.buff = {} + + gear = {} + gear.default = {} + + gear.ElementalGorget = {name=""} + gear.ElementalBelt = {name=""} + gear.ElementalObi = {name=""} + gear.ElementalCape = {name=""} + gear.ElementalRing = {name=""} + gear.FastcastStaff = {name=""} + gear.RecastStaff = {name=""} + + + -- Load externally-defined information (info that we don't want to change every time this file is updated). + + -- Used to define misc utility functions that may be useful for this include or any job files. + include('Mote-Utility') + + -- Used for all self-command handling. + include('Mote-SelfCommands') + + -- Include general user globals, such as custom binds or gear tables. + -- Load Mote-Globals first, followed by User-Globals, followed by <character>-Globals. + -- Any functions re-defined in the later includes will overwrite the earlier versions. + include('Mote-Globals') + optional_include({'user-globals.lua'}) + optional_include({player.name..'-globals.lua'}) + + -- *-globals.lua may define additional sets to be added to the local ones. + if define_global_sets then + define_global_sets() + end + + -- Global default binds (either from Mote-Globals or user-globals) + (binds_on_load or global_on_load)() + + -- Load a sidecar file for the job (if it exists) that may re-define init_gear_sets and file_unload. + load_sidecar(player.main_job) + + -- General var initialization and setup. + if job_setup then + job_setup() + end + + -- User-specific var initialization and setup. + if user_setup then + user_setup() + end + + -- Load up all the gear sets. + init_gear_sets() +end + +-- Auto-initialize the include +init_include() + +-- Called when this job file is unloaded (eg: job change) +-- Conditional definition so that it doesn't overwrite explicit user +-- versions of this function. +if not file_unload then + file_unload = function() + if user_unload then + user_unload() + elseif job_file_unload then + job_file_unload() + end + _G[(binds_on_unload and 'binds_on_unload') or 'global_on_unload']() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Generalized functions for handling precast/midcast/aftercast for player-initiated actions. +-- This depends on proper set naming. +-- Global hooks can be written as user_xxx() to override functions at a global level. +-- Each job can override any of these general functions using job_xxx() hooks. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------ +-- Generic function to map a set processing order to all action events. +------------------------------------------------------------------------ + + +-- Process actions in a specific order of events: +-- Filter - filter_xxx() functions determine whether to run any of the code for this action. +-- Global - user_xxx() functions get called first. Define in Mote-Globals or User-Globals. +-- Local - job_xxx() functions get called next. Define in JOB.lua file. +-- Default - default_xxx() functions get called next. Defined in this file. +-- Cleanup - cleanup_xxx() functions always get called before exiting. +-- +-- Parameters: +-- spell - standard spell table passed in by GearSwap +-- action - string defining the function mapping to use (precast, midcast, etc) +function handle_actions(spell, action) + -- Init an eventArgs that allows cancelling. + local eventArgs = {handled = false, cancel = false} + + mote_vars.set_breadcrumbs:clear() + + -- Get the spell mapping, since we'll be passing it to various functions and checks. + local spellMap = get_spell_map(spell) + + -- General filter checks to see whether this function should be run. + -- If eventArgs.cancel is set, cancels this function, not the spell. + if _G['filter_'..action] then + _G['filter_'..action](spell, spellMap, eventArgs) + end + + -- If filter didn't cancel it, process user and default actions. + if not eventArgs.cancel then + -- Global user handling of this action + if _G['user_'..action] then + _G['user_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Job-specific handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['job_'..action] then + _G['job_'..action](spell, action, spellMap, eventArgs) + + if eventArgs.cancel then + cancel_spell() + end + end + + -- Default handling of this action + if not eventArgs.cancel and not eventArgs.handled and _G['default_'..action] then + _G['default_'..action](spell, spellMap) + display_breadcrumbs(spell, spellMap, action) + end + + -- Job-specific post-handling of this action + if not eventArgs.cancel and _G['job_post_'..action] then + _G['job_post_'..action](spell, action, spellMap, eventArgs) + end + end + + -- Cleanup once this action is done + if _G['cleanup_'..action] then + _G['cleanup_'..action](spell, spellMap, eventArgs) + end +end + + +-------------------------------------- +-- Action hooks called by GearSwap. +-------------------------------------- + +function pretarget(spell) + handle_actions(spell, 'pretarget') +end + +function precast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = true + end + handle_actions(spell, 'precast') +end + +function midcast(spell) + handle_actions(spell, 'midcast') +end + +function aftercast(spell) + if state.Buff[spell.english] ~= nil then + state.Buff[spell.english] = not spell.interrupted or buffactive[spell.english] or false + end + handle_actions(spell, 'aftercast') +end + +function pet_midcast(spell) + handle_actions(spell, 'pet_midcast') +end + +function pet_aftercast(spell) + handle_actions(spell, 'pet_aftercast') +end + +-------------------------------------- +-- Default code for each action. +-------------------------------------- + +function default_pretarget(spell, spellMap) + auto_change_target(spell, spellMap) +end + +function default_precast(spell, spellMap) + equip(get_precast_set(spell, spellMap)) +end + +function default_midcast(spell, spellMap) + equip(get_midcast_set(spell, spellMap)) +end + +function default_aftercast(spell, spellMap) + if not pet_midaction() then + handle_equipping_gear(player.status) + end +end + +function default_pet_midcast(spell, spellMap) + equip(get_pet_midcast_set(spell, spellMap)) +end + +function default_pet_aftercast(spell, spellMap) + handle_equipping_gear(player.status) +end + +-------------------------------------- +-- Filters for each action. +-- Set eventArgs.cancel to true to stop further processing. +-- May show notification messages, but should not do any processing here. +-------------------------------------- + +function filter_midcast(spell, spellMap, eventArgs) + if mote_vars.show_set == 'precast' then + eventArgs.cancel = true + end +end + +function filter_aftercast(spell, spellMap, eventArgs) + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' or mote_vars.show_set == 'pet_midcast' then + eventArgs.cancel = true + elseif spell.name == 'Unknown Interrupt' then + eventArgs.cancel = true + end +end + +function filter_pet_midcast(spell, spellMap, eventArgs) + -- If we have show_set active for precast or midcast, don't try to equip pet midcast gear. + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' then + add_to_chat(104, 'Show Sets: Pet midcast not equipped.') + eventArgs.cancel = true + end +end + +function filter_pet_aftercast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast or midcast, don't try to equip aftercast gear. + if mote_vars.show_set == 'precast' or mote_vars.show_set == 'midcast' or mote_vars.show_set == 'pet_midcast' then + eventArgs.cancel = true + end +end + +-------------------------------------- +-- Cleanup code for each action. +-------------------------------------- + +function cleanup_precast(spell, spellMap, eventArgs) + -- If show_set is flagged for precast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'precast' then + add_to_chat(104, 'Show Sets: Stopping at precast.') + end +end + +function cleanup_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for midcast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'midcast' then + add_to_chat(104, 'Show Sets: Stopping at midcast.') + end +end + +function cleanup_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + -- If we're in the middle of a pet action, pet_aftercast will handle clearing it. + if not pet_midaction() then + reset_transitory_classes() + end +end + +function cleanup_pet_midcast(spell, spellMap, eventArgs) + -- If show_set is flagged for pet midcast, notify that we won't try to equip later gear. + if mote_vars.show_set == 'pet_midcast' then + add_to_chat(104, 'Show Sets: Stopping at pet midcast.') + end +end + +function cleanup_pet_aftercast(spell, spellMap, eventArgs) + -- Reset custom classes after all possible precast/midcast/aftercast/job-specific usage of the value. + reset_transitory_classes() +end + + +-- Clears the values from classes that only exist til the action is complete. +function reset_transitory_classes() + classes.CustomClass = nil + classes.JAMode = nil +end + + + +------------------------------------------------------------------------------------------------------------------- +-- High-level functions for selecting and equipping gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Central point to call to equip gear based on status. +-- Status - Player status that we're using to define what gear to equip. +function handle_equipping_gear(playerStatus, petStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_handle_equipping_gear then + job_handle_equipping_gear(playerStatus, eventArgs) + end + + -- Equip default gear if job didn't handle it. + if not eventArgs.handled then + equip_gear_by_status(playerStatus, petStatus) + end +end + + +-- Function to wrap logic for equipping gear on aftercast, status change, or user update. +-- @param status : The current or new player status that determines what sort of gear to equip. +function equip_gear_by_status(playerStatus, petStatus) + if _global.debug_mode then add_to_chat(123,'Debug: Equip gear for status ['..tostring(status)..'], HP='..tostring(player.hp)) end + + playerStatus = playerStatus or player.status or 'Idle' + + -- If status not defined, treat as idle. + -- Be sure to check for positive HP to make sure they're not dead. + if (playerStatus == 'Idle' or playerStatus == '') and player.hp > 0 then + equip(get_idle_set(petStatus)) + elseif playerStatus == 'Engaged' then + equip(get_melee_set(petStatus)) + elseif playerStatus == 'Resting' then + equip(get_resting_set(petStatus)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on status. +------------------------------------------------------------------------------------------------------------------- + +-- Returns the appropriate idle set based on current state values and location. +-- Set construction order (all of which are optional): +-- sets.idle[idleScope][state.IdleMode][Pet[Engaged]][CustomIdleGroups] +-- +-- Params: +-- petStatus - Optional explicit definition of pet status. +function get_idle_set(petStatus) + local idleSet = sets.idle + + if not idleSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('idle') + + local idleScope + + if buffactive.weakness then + idleScope = 'Weak' + elseif areas.Cities:contains(world.area) then + idleScope = 'Town' + else + idleScope = 'Field' + end + + if idleSet[idleScope] then + idleSet = idleSet[idleScope] + mote_vars.set_breadcrumbs:append(idleScope) + end + + if idleSet[state.IdleMode] then + idleSet = idleSet[state.IdleMode] + mote_vars.set_breadcrumbs:append(state.IdleMode) + end + + if (pet.isvalid or state.Buff.Pet) and idleSet.Pet then + idleSet = idleSet.Pet + petStatus = petStatus or pet.status + mote_vars.set_breadcrumbs:append('Pet') + + if petStatus == 'Engaged' and idleSet.Engaged then + idleSet = idleSet.Engaged + mote_vars.set_breadcrumbs:append('Engaged') + end + end + + for _,group in ipairs(classes.CustomIdleGroups) do + if idleSet[group] then + idleSet = idleSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + idleSet = apply_defense(idleSet) + idleSet = apply_kiting(idleSet) + + if user_customize_idle_set then + idleSet = user_customize_idle_set(idleSet) + end + + if customize_idle_set then + idleSet = customize_idle_set(idleSet) + end + + return idleSet +end + + +-- Returns the appropriate melee set based on current state values. +-- Set construction order (all sets after sets.engaged are optional): +-- sets.engaged[state.CombatForm][state.CombatWeapon][state.OffenseMode][state.DefenseMode][classes.CustomMeleeGroups (any number)] +function get_melee_set() + local meleeSet = sets.engaged + + if not meleeSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('engaged') + + if state.CombatForm and meleeSet[state.CombatForm] then + meleeSet = meleeSet[state.CombatForm] + mote_vars.set_breadcrumbs:append(state.CombatForm) + end + + if state.CombatWeapon and meleeSet[state.CombatWeapon] then + meleeSet = meleeSet[state.CombatWeapon] + mote_vars.set_breadcrumbs:append(state.CombatWeapon) + end + + if meleeSet[state.OffenseMode] then + meleeSet = meleeSet[state.OffenseMode] + mote_vars.set_breadcrumbs:append(state.OffenseMode) + end + + if meleeSet[state.DefenseMode] then + meleeSet = meleeSet[state.DefenseMode] + mote_vars.set_breadcrumbs:append(state.DefenseMode) + end + + for _,group in ipairs(classes.CustomMeleeGroups) do + if meleeSet[group] then + meleeSet = meleeSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + meleeSet = apply_defense(meleeSet) + meleeSet = apply_kiting(meleeSet) + + if customize_melee_set then + meleeSet = customize_melee_set(meleeSet) + end + + if user_customize_melee_set then + meleeSet = user_customize_melee_set(meleeSet) + end + + return meleeSet +end + + +-- Returns the appropriate resting set based on current state values. +-- Set construction order: +-- sets.resting[state.RestingMode] +function get_resting_set() + local restingSet = sets.resting + + if not restingSet then + return {} + end + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('resting') + + if restingSet[state.RestingMode] then + restingSet = restingSet[state.RestingMode] + mote_vars.set_breadcrumbs:append(state.RestingMode) + end + + return restingSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for constructing default gear sets based on action. +------------------------------------------------------------------------------------------------------------------- + +-- Get the default precast gear set. +function get_precast_set(spell, spellMap) + -- If there are no precast sets defined, bail out. + if not sets.precast then + return {} + end + + local equipSet = sets.precast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('precast') + + -- Determine base sub-table from type of action being performed. + + local cat + + if spell.action_type == 'Magic' then + cat = 'FC' + elseif spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Ability' then + if spell.type == 'WeaponSkill' then + cat = 'WS' + elseif spell.type == 'JobAbility' then + cat = 'JA' + else + -- Allow fallback to .JA table if spell.type isn't found, for all non-weaponskill abilities. + cat = (sets.precast[spell.type] and spell.type) or 'JA' + end + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = false + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + + -- Once we have a named base set, do checks for specialized modes (casting mode, weaponskill mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.type == 'WeaponSkill' then + equipSet = get_weaponskill_set(equipSet, spell, spellMap) + elseif spell.action_type == 'Ability' then + if classes.JAMode and equipSet[classes.JAMode] then + equipSet = equipSet[classes.JAMode] + mote_vars.set_breadcrumbs:append(classes.JAMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Update defintions for element-specific gear that may be used. + set_elemental_gear(spell) + + -- Return whatever we've constructed. + return equipSet +end + + + +-- Get the default midcast gear set. +-- This builds on sets.midcast. +function get_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast then + return {} + end + + local equipSet = sets.midcast + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + + -- Determine base sub-table from type of action being performed. + -- Only ranged attacks and items get specific sub-categories here. + + local cat + + if spell.action_type == 'Ranged Attack' then + cat = (sets.precast.RangedAttack and 'RangedAttack') or 'RA' + elseif spell.action_type == 'Item' then + cat = 'Item' + end + + -- If no proper sub-category is defined in the job file, bail out. + if cat then + if equipSet[cat] then + equipSet = equipSet[cat] + mote_vars.set_breadcrumbs:append(cat) + else + mote_vars.set_breadcrumbs:clear() + return {} + end + end + + classes.SkipSkillCheck = classes.NoSkillSpells:contains(spell.english) + -- Handle automatic selection of set based on spell class/name/map/skill/type. + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- After the default checks, do checks for specialized modes (casting mode, etc). + + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.action_type == 'Ranged Attack' then + equipSet = get_ranged_set(equipSet, spell, spellMap) + end + + -- Return whatever we've constructed. + return equipSet +end + + +-- Get the default pet midcast gear set. +-- This is built in sets.midcast.Pet. +function get_pet_midcast_set(spell, spellMap) + -- If there are no midcast sets defined, bail out. + if not sets.midcast or not sets.midcast.Pet then + return {} + end + + local equipSet = sets.midcast.Pet + + mote_vars.set_breadcrumbs:append('sets') + mote_vars.set_breadcrumbs:append('midcast') + mote_vars.set_breadcrumbs:append('Pet') + + if sets.midcast and sets.midcast.Pet then + classes.SkipSkillCheck = false + equipSet = select_specific_set(equipSet, spell, spellMap) + + -- We can only generally be certain about whether the pet's action is + -- Magic (ie: it cast a spell of its own volition) or Ability (it performed + -- an action at the request of the player). Allow CastinMode and + -- OffenseMode to refine whatever set was selected above. + if spell.action_type == 'Magic' then + if equipSet[state.CastingMode] then + equipSet = equipSet[state.CastingMode] + mote_vars.set_breadcrumbs:append(state.CastingMode) + end + elseif spell.action_type == 'Ability' then + if equipSet[state.OffenseMode] then + equipSet = equipSet[state.OffenseMode] + mote_vars.set_breadcrumbs:append(state.OffenseMode) + end + end + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper weaponskill set. +function get_weaponskill_set(equipSet, spell, spellMap) + -- Custom handling for weaponskills + local ws_mode = state.WeaponskillMode + + if ws_mode == 'Normal' then + -- If a particular weaponskill mode isn't specified, see if we have a weaponskill mode + -- corresponding to the current offense mode. If so, use that. + if spell.skill == 'Archery' or spell.skill == 'Marksmanship' then + if state.RangedMode ~= 'Normal' and S(options.WeaponskillModes):contains(state.RangedMode) then + ws_mode = state.RangedMode + end + else + if state.OffenseMode ~= 'Normal' and S(options.WeaponskillModes):contains(state.OffenseMode) then + ws_mode = state.OffenseMode + end + end + end + + local custom_wsmode + + -- Allow the job file to specify a preferred weaponskill mode + if get_custom_wsmode then + custom_wsmode = get_custom_wsmode(spell, spellMap, ws_mode) + end + + -- If the job file returned a weaponskill mode, use that. + if custom_wsmode then + ws_mode = custom_wsmode + end + + if equipSet[ws_mode] then + equipSet = equipSet[ws_mode] + mote_vars.set_breadcrumbs:append(ws_mode) + end + + return equipSet +end + + +-- Function to handle the logic of selecting the proper ranged set. +function get_ranged_set(equipSet, spell, spellMap) + -- Attach Combat Form and Combat Weapon to set checks + if state.CombatForm and equipSet[state.CombatForm] then + equipSet = equipSet[state.CombatForm] + mote_vars.set_breadcrumbs:append(state.CombatForm) + end + + if state.CombatWeapon and equipSet[state.CombatWeapon] then + equipSet = equipSet[state.CombatWeapon] + mote_vars.set_breadcrumbs:append(state.CombatWeapon) + end + + -- Check for specific mode for ranged attacks (eg: Acc, Att, etc) + if equipSet[state.RangedMode] then + equipSet = equipSet[state.RangedMode] + mote_vars.set_breadcrumbs:append(state.RangedMode) + end + + -- Tack on any additionally specified custom groups, if the sets are defined. + for _,group in ipairs(classes.CustomRangedGroups) do + if equipSet[group] then + equipSet = equipSet[group] + mote_vars.set_breadcrumbs:append(group) + end + end + + return equipSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Functions for optional supplemental gear overriding the default sets defined above. +------------------------------------------------------------------------------------------------------------------- + +-- Function to apply any active defense set on top of the supplied set +-- @param baseSet : The set that any currently active defense set will be applied on top of. (gear set table) +function apply_defense(baseSet) + if state.Defense.Active then + local defenseSet = sets.defense + + if state.Defense.Type == 'Physical' then + defenseSet = sets.defense[state.Defense.PhysicalMode] or defenseSet + else + defenseSet = sets.defense[state.Defense.MagicalMode] or defenseSet + end + + for _,group in ipairs(classes.CustomDefenseGroups) do + defenseSet = defenseSet[group] or defenseSet + end + + baseSet = set_combine(baseSet, defenseSet) + end + + return baseSet +end + + +-- Function to add kiting gear on top of the base set if kiting state is true. +-- @param baseSet : The gear set that the kiting gear will be applied on top of. +function apply_kiting(baseSet) + if state.Kiting then + if sets.Kiting then + baseSet = set_combine(baseSet, sets.Kiting) + end + end + + return baseSet +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for constructing default gear sets. +------------------------------------------------------------------------------------------------------------------- + +-- Get a spell mapping for the spell. +function get_spell_map(spell) + local defaultSpellMap = classes.SpellMaps[spell.english] + local jobSpellMap + + if job_get_spell_map then + jobSpellMap = job_get_spell_map(spell, defaultSpellMap) + end + + return jobSpellMap or defaultSpellMap +end + + +-- Select the equipment set to equip from a given starting table, based on standard +-- selection order: custom class, spell name, spell map, spell skill, and spell type. +-- Spell skill and spell type may further refine their selections based on +-- custom class, spell name and spell map. +function select_specific_set(equipSet, spell, spellMap) + -- Take the determined base equipment set and try to get the simple naming extensions that + -- may apply to it (class, spell name, spell map). + local namedSet = get_named_set(equipSet, spell, spellMap) + + -- If no simple naming sub-tables were found, and we simply got back the original equip set, + -- check for spell.skill and spell.type, then check the simple naming extensions again. + if namedSet == equipSet then + if spell.skill and equipSet[spell.skill] and not classes.SkipSkillCheck then + namedSet = equipSet[spell.skill] + mote_vars.set_breadcrumbs:append(spell.skill) + elseif spell.type and equipSet[spell.type] then + namedSet = equipSet[spell.type] + mote_vars.set_breadcrumbs:append(spell.type) + else + return equipSet + end + + namedSet = get_named_set(namedSet, spell, spellMap) + end + + return namedSet or equipSet +end + + +-- Simple utility function to handle a portion of the equipment set determination. +-- It attempts to select a sub-table of the provided equipment set based on the +-- standard search order of custom class, spell name, and spell map. +-- If no such set is found, it returns the original base set (equipSet) provided. +function get_named_set(equipSet, spell, spellMap) + if equipSet then + if classes.CustomClass and equipSet[classes.CustomClass] then + mote_vars.set_breadcrumbs:append(classes.CustomClass) + return equipSet[classes.CustomClass] + elseif equipSet[spell.english] then + mote_vars.set_breadcrumbs:append(spell.english) + return equipSet[spell.english] + elseif spellMap and equipSet[spellMap] then + mote_vars.set_breadcrumbs:append(spellMap) + return equipSet[spellMap] + else + return equipSet + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Hooks for other events. +------------------------------------------------------------------------------------------------------------------- + +-- Called when the player's subjob changes. +function sub_job_change(newSubjob, oldSubjob) + if user_setup then + user_setup() + end + + if job_sub_job_change then + job_sub_job_change(newSubjob, oldSubjob) + end + + send_command('gs c update') +end + + +-- Called when the player's status changes. +function status_change(newStatus, oldStatus) + -- init a new eventArgs + local eventArgs = {handled = false} + mote_vars.set_breadcrumbs:clear() + + -- Allow a global function to be called on status change. + if user_status_change then + user_status_change(newStatus, oldStatus, eventArgs) + end + + -- Then call individual jobs to handle status change events. + if not eventArgs.handled then + if job_status_change then + job_status_change(newStatus, oldStatus, eventArgs) + end + end + + -- Handle equipping default gear if the job didn't mark this as handled. + if not eventArgs.handled then + handle_equipping_gear(newStatus) + display_breadcrumbs() + end +end + + +-- Called when a player gains or loses a buff. +-- buff == buff gained or lost +-- gain == true if the buff was gained, false if it was lost. +function buff_change(buff, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + if state.Buff[buff] ~= nil then + state.Buff[buff] = gain + end + + -- Allow a global function to be called on buff change. + if user_buff_change then + user_buff_change(buff, gain, eventArgs) + end + + -- Allow jobs to handle buff change events. + if not eventArgs.handled then + if job_buff_change then + job_buff_change(buff, gain, eventArgs) + end + end +end + + +-- Called when a player gains or loses a pet. +-- pet == pet gained or lost +-- gain == true if the pet was gained, false if it was lost. +function pet_change(pet, gain) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to handle pet change events. + if job_pet_change then + job_pet_change(pet, gain, eventArgs) + end + + -- Equip default gear if not handled by the job. + if not eventArgs.handled then + handle_equipping_gear(player.status) + end +end + + +-- Called when the player's pet's status changes. +-- Note that this is also called after pet_change when the pet is released. +-- As such, don't automatically handle gear equips. Only do so if directed +-- to do so by the job. +function pet_status_change(newStatus, oldStatus) + -- Init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_pet_status_change then + job_pet_status_change(newStatus, oldStatus, eventArgs) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Debugging functions. +------------------------------------------------------------------------------------------------------------------- + +-- This is a debugging function that will print the accumulated set selection +-- breadcrumbs for the default selected set for any given action stage. +function display_breadcrumbs(spell, spellMap, action) + if not _settings.debug_mode then + return + end + + local msg = 'Default ' + + if action and spell then + msg = msg .. action .. ' set selection for ' .. spell.name + end + + if spellMap then + msg = msg .. ' (' .. spellMap .. ')' + end + msg = msg .. ' : ' + + local cons + + for _,name in ipairs(mote_vars.set_breadcrumbs) do + if not cons then + cons = name + else + if name:contains(' ') or name:contains("'") then + cons = cons .. '["' .. name .. '"]' + else + cons = cons .. '.' .. name + end + end + end + + if cons then + if action and cons == ('sets.' .. action) then + msg = msg .. "None" + else + msg = msg .. tostring(cons) + end + add_to_chat(123, msg) + end +end + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua new file mode 100644 index 0000000..76297a3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Mappings.lua @@ -0,0 +1,273 @@ +------------------------------------------------------------------------------------------------------------------- +-- Mappings, lists and sets to describe game relationships that aren't easily determinable otherwise. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Elemental mappings for element relationships and certain types of spells and gear. +------------------------------------------------------------------------------------------------------------------- + +-- Basic elements +elements = {} + +elements.list = S{'Light','Dark','Fire','Ice','Wind','Earth','Lightning','Water'} + +elements.weak_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Ice', ['Ice']='Wind', ['Wind']='Earth', ['Earth']='Lightning', + ['Lightning']='Water', ['Water']='Fire'} + +elements.strong_to = {['Light']='Dark', ['Dark']='Light', ['Fire']='Water', ['Ice']='Fire', ['Wind']='Ice', ['Earth']='Wind', + ['Lightning']='Earth', ['Water']='Lightning'} + + +storms = S{"Aurorastorm", "Voidstorm", "Firestorm", "Sandstorm", "Rainstorm", "Windstorm", "Hailstorm", "Thunderstorm"} +elements.storm_of = {['Light']="Aurorastorm", ['Dark']="Voidstorm", ['Fire']="Firestorm", ['Earth']="Sandstorm", + ['Water']="Rainstorm", ['Wind']="Windstorm", ['Ice']="Hailstorm", ['Lightning']="Thunderstorm"} + +spirits = S{"LightSpirit", "DarkSpirit", "FireSpirit", "EarthSpirit", "WaterSpirit", "AirSpirit", "IceSpirit", "ThunderSpirit"} +elements.spirit_of = {['Light']="Light Spirit", ['Dark']="Dark Spirit", ['Fire']="Fire Spirit", ['Earth']="Earth Spirit", + ['Water']="Water Spirit", ['Wind']="Air Spirit", ['Ice']="Ice Spirit", ['Lightning']="Thunder Spirit"} + +runes = S{'Lux', 'Tenebrae', 'Ignis', 'Gelus', 'Flabra', 'Tellus', 'Sulpor', 'Unda'} +elements.rune_of = {['Light']='Lux', ['Dark']='Tenebrae', ['Fire']='Ignis', ['Ice']='Gelus', ['Wind']='Flabra', + ['Earth']='Tellus', ['Lightning']='Sulpor', ['Water']='Unda'} + +elements.obi_of = {['Light']='Hachirin-no-obi', ['Dark']='Hachirin-no-obi', ['Fire']='Hachirin-no-obi', ['Ice']='Hachirin-no-obi', ['Wind']='Hachirin-no-obi', + ['Earth']='Hachirin-no-obi', ['Lightning']='Hachirin-no-obi', ['Water']='Hachirin-no-obi'} + +elements.gorget_of = {['Light']='Fotia Gorget', ['Dark']='Fotia Gorget', ['Fire']='Fotia Gorget', ['Ice']='Fotia Gorget', + ['Wind']='Fotia Gorget', ['Earth']='Fotia Gorget', ['Lightning']='Fotia Gorget', ['Water']='Fotia Gorget'} + +elements.belt_of = {['Light']='Fotia Belt', ['Dark']='Fotia Belt', ['Fire']='Fotia Belt', ['Ice']='Fotia Belt', + ['Wind']='Fotia Belt', ['Earth']='Fotia Belt', ['Lightning']='Fotia Belt', ['Water']='Fotia Belt'} + +elements.fastcast_staff_of = {['Light']='Arka I', ['Dark']='Xsaeta I', ['Fire']='Atar I', ['Ice']='Vourukasha I', + ['Wind']='Vayuvata I', ['Earth']='Vishrava I', ['Lightning']='Apamajas I', ['Water']='Haoma I', ['Thunder']='Apamajas I'} + +elements.recast_staff_of = {['Light']='Arka II', ['Dark']='Xsaeta II', ['Fire']='Atar II', ['Ice']='Vourukasha II', + ['Wind']='Vayuvata II', ['Earth']='Vishrava II', ['Lightning']='Apamajas II', ['Water']='Haoma II', ['Thunder']='Apamajas II'} + +elements.perpetuance_staff_of = {['Light']='Arka III', ['Dark']='Xsaeta III', ['Fire']='Atar III', ['Ice']='Vourukasha III', + ['Wind']='Vayuvata III', ['Earth']='Vishrava III', ['Lightning']='Apamajas III', ['Water']='Haoma III', ['Thunder']='Apamajas III'} + + +-- Elements for skillchain names +skillchain_elements = {} +skillchain_elements.Light = S{'Light','Fire','Wind','Lightning'} +skillchain_elements.Darkness = S{'Dark','Ice','Earth','Water'} +skillchain_elements.Fusion = S{'Light','Fire'} +skillchain_elements.Fragmentation = S{'Wind','Lightning'} +skillchain_elements.Distortion = S{'Ice','Water'} +skillchain_elements.Gravitation = S{'Dark','Earth'} +skillchain_elements.Transfixion = S{'Light'} +skillchain_elements.Compression = S{'Dark'} +skillchain_elements.Liquification = S{'Fire'} +skillchain_elements.Induration = S{'Ice'} +skillchain_elements.Detonation = S{'Wind'} +skillchain_elements.Scission = S{'Earth'} +skillchain_elements.Impaction = S{'Lightning'} +skillchain_elements.Reverberation = S{'Water'} + + +------------------------------------------------------------------------------------------------------------------- +-- Mappings for weaponskills +------------------------------------------------------------------------------------------------------------------- + +-- REM weapons and their corresponding weaponskills +data = {} +data.weaponskills = {} +data.weaponskills.relic = { + ["Spharai"] = "Final Heaven", + ["Mandau"] = "Mercy Stroke", + ["Excalibur"] = "Knights of Round", + ["Ragnarok"] = "Scourge", + ["Guttler"] = "Onslaught", + ["Bravura"] = "Metatron Torment", + ["Apocalypse"] = "Catastrophe", + ["Gungnir"] = "Gierskogul", + ["Kikoku"] = "Blade: Metsu", + ["Amanomurakumo"] = "Tachi: Kaiten", + ["Mjollnir"] = "Randgrith", + ["Claustrum"] = "Gates of Tartarus", + ["Annihilator"] = "Coronach", + ["Yoichinoyumi"] = "Namas Arrow"} +data.weaponskills.mythic = { + ["Conqueror"] = "King's Justice", + ["Glanzfaust"] = "Ascetic's Fury", + ["Yagrush"] = "Mystic Boon", + ["Laevateinn"] = "Vidohunir", + ["Murgleis"] = "Death Blossom", + ["Vajra"] = "Mandalic Stab", + ["Burtgang"] = "Atonement", + ["Liberator"] = "Insurgency", + ["Aymur"] = "Primal Rend", + ["Carnwenhan"] = "Mordant Rime", + ["Gastraphetes"] = "Trueflight", + ["Kogarasumaru"] = "Tachi: Rana", + ["Nagi"] = "Blade: Kamu", + ["Ryunohige"] = "Drakesbane", + ["Nirvana"] = "Garland of Bliss", + ["Tizona"] = "Expiacion", + ["Death Penalty"] = "Leaden Salute", + ["Kenkonken"] = "Stringing Pummel", + ["Terpsichore"] = "Pyrrhic Kleos", + ["Tupsimati"] = "Omniscience", + ["Idris"] = "Exudation", + ["Epeolatry"] = "Dimidiation"} +data.weaponskills.empyrean = { + ["Verethragna"] = "Victory Smite", + ["Twashtar"] = "Rudra's Storm", + ["Almace"] = "Chant du Cygne", + ["Caladbolg"] = "Torcleaver", + ["Farsha"] = "Cloudsplitter", + ["Ukonvasara"] = "Ukko's Fury", + ["Redemption"] = "Quietus", + ["Rhongomiant"] = "Camlann's Torment", + ["Kannagi"] = "Blade: Hi", + ["Masamune"] = "Tachi: Fudo", + ["Gambanteinn"] = "Dagann", + ["Hvergelmir"] = "Myrkr", + ["Gandiva"] = "Jishnu's Radiance", + ["Armageddon"] = "Wildfire"} + +-- Weaponskills that can be used at range. +data.weaponskills.ranged = S{"Flaming Arrow", "Piercing Arrow", "Dulling Arrow", "Sidewinder", "Arching Arrow", + "Empyreal Arrow", "Refulgent Arrow", "Apex Arrow", "Namas Arrow", "Jishnu's Radiance", + "Hot Shot", "Split Shot", "Sniper Shot", "Slug Shot", "Heavy Shot", "Detonator", "Last Stand", + "Coronach", "Trueflight", "Leaden Salute", "Wildfire", + "Myrkr"} + +ranged_weaponskills = data.weaponskills.ranged + +------------------------------------------------------------------------------------------------------------------- +-- Spell mappings allow defining a general category or description that each of sets of related +-- spells all fall under. +------------------------------------------------------------------------------------------------------------------- + +spell_maps = { + ['Cure']='Cure',['Cure II']='Cure',['Cure III']='Cure',['Cure IV']='Cure',['Cure V']='Cure',['Cure VI']='Cure', + ['Cura']='Curaga',['Cura II']='Curaga',['Cura III']='Curaga', + ['Curaga']='Curaga',['Curaga II']='Curaga',['Curaga III']='Curaga',['Curaga IV']='Curaga',['Curaga V']='Curaga', + -- Status Removal doesn't include Esuna or Sacrifice, since they work differently than the rest + ['Poisona']='StatusRemoval',['Paralyna']='StatusRemoval',['Silena']='StatusRemoval',['Blindna']='StatusRemoval',['Cursna']='StatusRemoval', + ['Stona']='StatusRemoval',['Viruna']='StatusRemoval',['Erase']='StatusRemoval', + ['Barfire']='BarElement',['Barstone']='BarElement',['Barwater']='BarElement',['Baraero']='BarElement',['Barblizzard']='BarElement',['Barthunder']='BarElement', + ['Barfira']='BarElement',['Barstonra']='BarElement',['Barwatera']='BarElement',['Baraera']='BarElement',['Barblizzara']='BarElement',['Barthundra']='BarElement', + ['Raise']='Raise',['Raise II']='Raise',['Raise III']='Raise',['Arise']='Raise', + ['Reraise']='Reraise',['Reraise II']='Reraise',['Reraise III']='Reraise', + ['Protect']='Protect',['Protect II']='Protect',['Protect III']='Protect',['Protect IV']='Protect',['Protect V']='Protect', + ['Shell']='Shell',['Shell II']='Shell',['Shell III']='Shell',['Shell IV']='Shell',['Shell V']='Shell', + ['Protectra']='Protectra',['Protectra II']='Protectra',['Protectra III']='Protectra',['Protectra IV']='Protectra',['Protectra V']='Protectra', + ['Shellra']='Shellra',['Shellra II']='Shellra',['Shellra III']='Shellra',['Shellra IV']='Shellra',['Shellra V']='Shellra', + ['Regen']='Regen',['Regen II']='Regen',['Regen III']='Regen',['Regen IV']='Regen',['Regen V']='Regen', + ['Refresh']='Refresh',['Refresh II']='Refresh', + ['Teleport-Holla']='Teleport',['Teleport-Dem']='Teleport',['Teleport-Mea']='Teleport',['Teleport-Altep']='Teleport',['Teleport-Yhoat']='Teleport', + ['Teleport-Vahzl']='Teleport',['Recall-Pashh']='Teleport',['Recall-Meriph']='Teleport',['Recall-Jugner']='Teleport', + ['Valor Minuet']='Minuet',['Valor Minuet II']='Minuet',['Valor Minuet III']='Minuet',['Valor Minuet IV']='Minuet',['Valor Minuet V']='Minuet', + ["Knight's Minne"]='Minne',["Knight's Minne II"]='Minne',["Knight's Minne III"]='Minne',["Knight's Minne IV"]='Minne',["Knight's Minne V"]='Minne', + ['Advancing March']='March',['Victory March']='March', + ['Sword Madrigal']='Madrigal',['Blade Madrigal']='Madrigal', + ["Hunter's Prelude"]='Prelude',["Archer's Prelude"]='Prelude', + ['Sheepfoe Mambo']='Mambo',['Dragonfoe Mambo']='Mambo', + ['Raptor Mazurka']='Mazurka',['Chocobo Mazurka']='Mazurka', + ['Sinewy Etude']='Etude',['Dextrous Etude']='Etude',['Vivacious Etude']='Etude',['Quick Etude']='Etude',['Learned Etude']='Etude',['Spirited Etude']='Etude',['Enchanting Etude']='Etude', + ['Herculean Etude']='Etude',['Uncanny Etude']='Etude',['Vital Etude']='Etude',['Swift Etude']='Etude',['Sage Etude']='Etude',['Logical Etude']='Etude',['Bewitching Etude']='Etude', + ["Mage's Ballad"]='Ballad',["Mage's Ballad II"]='Ballad',["Mage's Ballad III"]='Ballad', + ["Army's Paeon"]='Paeon',["Army's Paeon II"]='Paeon',["Army's Paeon III"]='Paeon',["Army's Paeon IV"]='Paeon',["Army's Paeon V"]='Paeon',["Army's Paeon VI"]='Paeon', + ['Fire Carol']='Carol',['Ice Carol']='Carol',['Wind Carol']='Carol',['Earth Carol']='Carol',['Lightning Carol']='Carol',['Water Carol']='Carol',['Light Carol']='Carol',['Dark Carol']='Carol', + ['Fire Carol II']='Carol',['Ice Carol II']='Carol',['Wind Carol II']='Carol',['Earth Carol II']='Carol',['Lightning Carol II']='Carol',['Water Carol II']='Carol',['Light Carol II']='Carol',['Dark Carol II']='Carol', + ['Foe Lullaby']='Lullaby',['Foe Lullaby II']='Lullaby',['Horde Lullaby']='Lullaby',['Horde Lullaby II']='Lullaby', + ['Fire Threnody']='Threnody',['Ice Threnody']='Threnody',['Wind Threnody']='Threnody',['Earth Threnody']='Threnody',['Lightning Threnody']='Threnody',['Water Threnody']='Threnody',['Light Threnody']='Threnody',['Dark Threnody']='Threnody', + ['Battlefield Elegy']='Elegy',['Carnage Elegy']='Elegy', + ['Foe Requiem']='Requiem',['Foe Requiem II']='Requiem',['Foe Requiem III']='Requiem',['Foe Requiem IV']='Requiem',['Foe Requiem V']='Requiem',['Foe Requiem VI']='Requiem',['Foe Requiem VII']='Requiem', + ['Utsusemi: Ichi']='Utsusemi',['Utsusemi: Ni']='Utsusemi', + ['Katon: Ichi'] = 'ElementalNinjutsu',['Suiton: Ichi'] = 'ElementalNinjutsu',['Raiton: Ichi'] = 'ElementalNinjutsu', + ['Doton: Ichi'] = 'ElementalNinjutsu',['Huton: Ichi'] = 'ElementalNinjutsu',['Hyoton: Ichi'] = 'ElementalNinjutsu', + ['Katon: Ni'] = 'ElementalNinjutsu',['Suiton: Ni'] = 'ElementalNinjutsu',['Raiton: Ni'] = 'ElementalNinjutsu', + ['Doton: Ni'] = 'ElementalNinjutsu',['Huton: Ni'] = 'ElementalNinjutsu',['Hyoton: Ni'] = 'ElementalNinjutsu', + ['Katon: San'] = 'ElementalNinjutsu',['Suiton: San'] = 'ElementalNinjutsu',['Raiton: San'] = 'ElementalNinjutsu', + ['Doton: San'] = 'ElementalNinjutsu',['Huton: San'] = 'ElementalNinjutsu',['Hyoton: San'] = 'ElementalNinjutsu', + ['Banish']='Banish',['Banish II']='Banish',['Banish III']='Banish',['Banishga']='Banish',['Banishga II']='Banish', + ['Holy']='Holy',['Holy II']='Holy',['Drain']='Drain',['Drain II']='Drain',['Aspir']='Aspir',['Aspir II']='Aspir', + ['Absorb-Str']='Absorb',['Absorb-Dex']='Absorb',['Absorb-Vit']='Absorb',['Absorb-Agi']='Absorb',['Absorb-Int']='Absorb',['Absorb-Mnd']='Absorb',['Absorb-Chr']='Absorb', + ['Absorb-Acc']='Absorb',['Absorb-TP']='Absorb',['Absorb-Attri']='Absorb', + ['Burn']='ElementalEnfeeble',['Frost']='ElementalEnfeeble',['Choke']='ElementalEnfeeble',['Rasp']='ElementalEnfeeble',['Shock']='ElementalEnfeeble',['Drown']='ElementalEnfeeble', + ['Pyrohelix']='Helix',['Cryohelix']='Helix',['Anemohelix']='Helix',['Geohelix']='Helix',['Ionohelix']='Helix',['Hydrohelix']='Helix',['Luminohelix']='Helix',['Noctohelix']='Helix', + ['Firestorm']='Storm',['Hailstorm']='Storm',['Windstorm']='Storm',['Sandstorm']='Storm',['Thunderstorm']='Storm',['Rainstorm']='Storm',['Aurorastorm']='Storm',['Voidstorm']='Storm', + ['Fire Maneuver']='Maneuver',['Ice Maneuver']='Maneuver',['Wind Maneuver']='Maneuver',['Earth Maneuver']='Maneuver',['Thunder Maneuver']='Maneuver', + ['Water Maneuver']='Maneuver',['Light Maneuver']='Maneuver',['Dark Maneuver']='Maneuver', +} + +no_skill_spells_list = S{'Haste', 'Refresh', 'Regen', 'Protect', 'Protectra', 'Shell', 'Shellra', + 'Raise', 'Reraise', 'Sneak', 'Invisible', 'Deodorize'} + + +------------------------------------------------------------------------------------------------------------------- +-- Tables to specify general area groupings. Creates the 'areas' table to be referenced in job files. +-- Zone names provided by world.area/world.zone are currently in all-caps, so defining the same way here. +------------------------------------------------------------------------------------------------------------------- + +areas = {} + +-- City areas for town gear and behavior. +areas.Cities = S{ + "Ru'Lude Gardens", + "Upper Jeuno", + "Lower Jeuno", + "Port Jeuno", + "Port Windurst", + "Windurst Waters", + "Windurst Woods", + "Windurst Walls", + "Heavens Tower", + "Port San d'Oria", + "Northern San d'Oria", + "Southern San d'Oria", + "Port Bastok", + "Bastok Markets", + "Bastok Mines", + "Metalworks", + "Aht Urhgan Whitegate", + "Tavanazian Safehold", + "Nashmau", + "Selbina", + "Mhaura", + "Norg", + "Eastern Adoulin", + "Western Adoulin", + "Kazham" +} +-- Adoulin areas, where Ionis will grant special stat bonuses. +areas.Adoulin = S{ + "Yahse Hunting Grounds", + "Ceizak Battlegrounds", + "Foret de Hennetiel", + "Morimar Basalt Fields", + "Yorcia Weald", + "Yorcia Weald [U]", + "Cirdas Caverns", + "Cirdas Caverns [U]", + "Marjami Ravine", + "Kamihr Drifts", + "Sih Gates", + "Moh Gates", + "Dho Gates", + "Woh Gates", + "Rala Waterways", + "Rala Waterways [U]", + "Outer Ra'Kaznar", + "Outer Ra'Kaznar [U]" +} + + +------------------------------------------------------------------------------------------------------------------- +-- Lists of certain NPCs. +------------------------------------------------------------------------------------------------------------------- + +npcs = {} +npcs.Trust = S{'Ajido-Marujido','Aldo','Ayame','Cherukiki','Curilla','D.Shantotto','Elivira','Excenmille', + 'Fablinix','FerreousCoffin','Gadalar','Gessho','Ingrid','IronEater','Joachim','Klara','Kupipi', + 'LehkoHabhoka','LhuMhakaracca','Lion','Luzaf','Maat','MihliAliapoh','Mnejing','Moogle','Mumor', + 'NajaSalaheem','Najelith','Naji','NanaaMihgo','Nashmeira','Noillurie','Ovjang','Prishe','Rainemard', + 'RomaaMihgo','Sakura','Shantotto','StarSibyl','Tenzen','Trion','UkaTotlihn','Ulmia','Valaineral', + 'Volker','Zazarg','Zeid'} + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua new file mode 100644 index 0000000..b15a70a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-SelfCommands.lua @@ -0,0 +1,715 @@ +------------------------------------------------------------------------------------------------------------------- +-- General functions for manipulating state values via self-commands. +-- Only handles certain specific states that we've defined, though it +-- allows the user to hook into the cycle command. +------------------------------------------------------------------------------------------------------------------- + +-- Routing function for general known self_commands. +-- Handles splitting the provided command line up into discrete words, for the other functions to use. +function self_command(commandArgs) + local commandArgs = commandArgs + if type(commandArgs) == 'string' then + commandArgs = T(commandArgs:split(' ')) + if #commandArgs == 0 then + return + end + end + + -- init a new eventArgs + local eventArgs = {handled = false} + + -- Allow jobs to override this code + if job_self_command then + job_self_command(commandArgs, eventArgs) + end + + if not eventArgs.handled then + -- Of the original command message passed in, remove the first word from + -- the list (it will be used to determine which function to call), and + -- send the remaining words as parameters for the function. + local handleCmd = table.remove(commandArgs, 1) + + if selfCommandMaps[handleCmd] then + selfCommandMaps[handleCmd](commandArgs) + end + end +end + + +-- Individual handling of self-commands + + +-- Handle toggling specific vars that we know of. +-- Valid toggles: Defense, Kiting +-- Returns true if a known toggle was handled. Returns false if not. +-- User command format: gs c toggle [field] +function handle_toggle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Toggle parameter failure: field not specified.') + return + end + + local toggleField = cmdParams[1]:lower() + local reportDescription + local notifyDescription + local oldVal + local newVal + + -- Known global states + if toggleField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = not state.Defense.Active + newVal = state.Defense.Active + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif toggleField == 'kite' or toggleField == 'kiting' then + oldVal = state.Kiting + state.Kiting = not state.Kiting + newVal = state.Kiting + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif toggleField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = not state.SelectNPCTargets + newVal = state.SelectNPCTargets + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[cmdParams[1]]) == 'boolean' then + oldVal = state[cmdParams[1]] + state[cmdParams[1]] = not state[cmdParams[1]] + newVal = state[cmdParams[1]] + notifyDescription = cmdParams[1] + reportDescription = cmdParams[1] + elseif job_toggle_state then + reportDescription, newVal = job_toggle_state(cmdParams[1]) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-GearSwap: Toggle: Unknown field ['..toggleField..']') + end +end + + +-- Function to handle turning on particular states, while possibly also setting a mode value. +-- User command format: gs c activate [field] +function handle_activate(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Activate parameter failure: field not specified.') + return + end + + local activateField = cmdParams[1]:lower() + local reportDescription + local notifyDescription + local oldVal + local newVal = true + + -- Known global states + if activateField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = true + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif activateField == 'physicaldefense' then + oldVal = state.Defense.Active + state.Defense.Active = true + state.Defense.Type = 'Physical' + notifyDescription = state.Defense.Type .. ' Defense' + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + elseif activateField == 'magicaldefense' then + oldVal = state.Defense.Active + state.Defense.Active = true + state.Defense.Type = 'Magical' + notifyDescription = state.Defense.Type .. ' Defense' + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + elseif activateField == 'kite' or toggleField == 'kiting' then + oldVal = state.Kiting + state.Kiting = true + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif activateField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = true + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[cmdParams[1]]) == 'boolean' then + oldVal = state[cmdParams[1]] + state[cmdParams[1]] = true + notifyDescription = cmdParams[1] + reportDescription = cmdParams[1] + elseif job_activate_state then + reportDescription, newVal = job_activate_state(cmdParams[1]) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + handle_update({'auto'}) + else + add_to_chat(123,'Mote-GearSwap: Activate: Unknown field ['..activateField..']') + end +end + + +-- Handle cycling through the options for specific vars that we know of. +-- Valid fields: OffenseMode, DefenseMode, WeaponskillMode, IdleMode, RestingMode, CastingMode, PhysicalDefenseMode, MagicalDefenseMode +-- All fields must end in 'Mode' +-- Returns true if a known toggle was handled. Returns false if not. +-- User command format: gs c cycle [field] +function handle_cycle(cmdParams) + if #cmdParams == 0 then + add_to_chat(123,'Mote-GearSwap: Cycle parameter failure: field not specified.') + return + end + + -- identifier for the field we're changing + local paramField = cmdParams[1] + local modeField = paramField + local order = (cmdParams[2] and S{'reverse', 'backwards', 'r'}:contains(cmdParams[2]:lower()) and 'backwards') or 'forward' + + if paramField:endswith('mode') or paramField:endswith('Mode') then + -- Remove 'mode' from the end of the string + modeField = paramField:sub(1,#paramField-4) + end + + -- Convert WS to Weaponskill + if modeField == "ws" then + modeField = "weaponskill" + end + + -- Capitalize the field (for use on output display) + modeField = modeField:gsub("%f[%a]%a", string.upper) + + -- Get the options.XXXModes table, and the current state mode for the mode field. + local modeList, currentValue = get_mode_list(modeField) + + if not modeList then + if _global.debug_mode then add_to_chat(123,'Unknown mode : '..modeField..'.') end + return + end + + -- Get the index of the current mode. Index starts at 0 for 'undefined', so that it can increment to 1. + local invertedTable = invert_table(modeList) + local index = 0 + if invertedTable[currentValue] then + index = invertedTable[currentValue] + end + + -- Increment to the next index in the available modes. + if order == 'forward' then + index = index + 1 + if index > #modeList then + index = 1 + end + else + index = index - 1 + if index < 1 then + index = #modeList + end + end + + -- Determine the new mode value based on the index. + local newModeValue = '' + if index and modeList[index] then + newModeValue = modeList[index] + else + newModeValue = 'Normal' + end + + -- And save that to the appropriate state field. + set_option_mode(modeField, newModeValue) + + if job_state_change and newModeValue ~= currentValue then + job_state_change(modeField..'Mode', newModeValue, currentValue) + end + + -- Display what got changed to the user. + add_to_chat(122,modeField..' mode is now '..newModeValue..'.') + handle_update({'auto'}) +end + +-- Function to set various states to specific values directly. +-- User command format: gs c set [field] [value] +function handle_set(cmdParams) + if #cmdParams > 1 then + -- identifier for the field we're setting + local field = cmdParams[1] + local lowerField = field:lower() + local capField = lowerField:gsub("%a", string.upper, 1) + local setField = cmdParams[2] + local reportDescription + local notifyDescription + local fieldDesc + local oldVal + local newVal + + + -- Check if we're dealing with a boolean + if on_off_values:contains(setField:lower()) then + newVal = true_values:contains(setField:lower()) + + if lowerField == 'defense' then + oldVal = state.Defense.Active + state.Defense.Active = newVal + notifyDescription = state.Defense.Type .. ' Defense' + if state.Defense.Type == 'Physical' then + reportDescription = 'Physical defense ('..state.Defense.PhysicalMode..')' + else + reportDescription = 'Magical defense ('..state.Defense.MagicalMode..')' + end + elseif lowerField == 'kite' or lowerField == 'kiting' then + oldVal = state.Kiting + state.Kiting = newVal + notifyDescription = 'Kiting' + reportDescription = 'Kiting' + elseif lowerField == 'selectnpctargets' then + oldVal = state.SelectNPCTargets + state.SelectNPCTargets = newVal + notifyDescription = 'NPC Target Selection' + reportDescription = 'NPC Target Selection' + elseif type(state[field]) == 'boolean' then + oldVal = state[field] + state[field] = newVal + notifyDescription = field + reportDescription = field + elseif job_set_state then + reportDescription, newVal = job_set_state(field, newVal) + end + + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..on_off_names[newVal]..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + -- Check if we're dealing with some sort of cycle field (ends with 'mode'). + elseif lowerField:endswith('mode') or type(state[capField..'Mode']) == 'string' then + local modeField = lowerField + + -- Remove 'mode' from the end of the string + if modeField:endswith('mode') then + modeField = lowerField:sub(1,#lowerField-4) + end + + -- Convert WS to Weaponskill + if modeField == "ws" then + modeField = "weaponskill" + end + + -- Capitalize the field (for use on output display) + modeField = modeField:gsub("%a", string.upper, 1) + + -- Get the options.XXXModes table, and the current state mode for the mode field. + local modeList + modeList, oldVal = get_mode_list(modeField) + + if not modeList or not table.contains(modeList, setField) then + add_to_chat(123,'Unknown mode value: '..setField..' for '..modeField..' mode.') + return + end + + -- And save that to the appropriate state field. + set_option_mode(modeField, setField) + + -- Notify the job script of the change. + if job_state_change and setField ~= oldVal then + job_state_change(modeField, setField, oldVal) + end + + -- Display what got changed to the user. + add_to_chat(122,modeField..' mode is now '..setField..'.') + -- Or distance (where we may need to get game state info) + elseif lowerField == 'distance' then + if setField then + newVal = tonumber(setField) + if newVal ~= nil then + oldVal = state.MaxWeaponskillDistance + state.MaxWeaponskillDistance = newVal + else + add_to_chat(123,'Invalid distance value: '..tostring(setField)) + return + end + + -- Notify the job script of the change. + if job_state_change and newVal ~= oldVal then + job_state_change('MaxWeaponskillDistance', newVal, oldVal) + end + + add_to_chat(122,'Max weaponskill distance is now '..tostring(newVal)..'.') + else + -- set max weaponskill distance to the current distance the player is from the mob. + -- Get current player distance and use that + add_to_chat(123,'TODO: get player distance.') + end + -- If trying to set a number + elseif tonumber(setField) then + if state[field] and type(state[field]) == 'number' then + oldVal = state[field] + newVal = tonumber(setField) + state[field] = newVal + reportDescription = field + + -- Notify the job script of the change. + if job_state_change and newVal ~= oldVal then + job_state_change(field, newVal, oldVal) + end + elseif state[field] then + add_to_chat(123,'Mote-GearSwap: Set: Attempting to set a numeric value ['..setField..'] in a non-numeric field ['..field..'].') + return + elseif job_set_state then + reportDescription, newVal = job_set_state(field, setField) + end + + if reportDescription then + add_to_chat(122,field..' is now '..tostring(newVal)..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + -- otherwise assume trying to set a text field + else + if lowerField == 'combatform' then + oldVal = state.CombatForm + state.CombatForm = setField + newVal = setField + notifyDescription = 'Combat Form' + reportDescription = 'Combat Form' + elseif lowerField == 'combatweapon' then + oldVal = state.CombatWeapon + state.CombatWeapon = setField + newVal = setField + notifyDescription = 'Combat Weapon' + reportDescription = 'Combat Weapon' + elseif state[field] and type(state[field]) == 'string' then + oldVal = state[field] + state[field] = setField + newVal = setField + notifyDescription = field + reportDescription = field + elseif job_set_state then + reportDescription, newVal = job_set_state(field, setField) + end + + -- Notify user file of changes to global states. + if oldVal ~= nil then + if job_state_change and newVal ~= oldVal then + job_state_change(notifyDescription, newVal, oldVal) + end + end + + if reportDescription then + add_to_chat(122,reportDescription..' is now '..newVal..'.') + else + add_to_chat(123,'Mote-GearSwap: Set: Unknown field ['..field..']') + end + end + else + if _global.debug_mode then add_to_chat(123,'--handle_set parameter failure: insufficient fields') end + return false + end + + handle_update({'auto'}) + return true +end + + +-- Function to turn off togglable features, or reset values to their defaults. +-- User command format: gs c reset [field] +function handle_reset(cmdParams) + if #cmdParams == 0 then + if _global.debug_mode then add_to_chat(123,'handle_reset: parameter failure: reset type not specified') end + return + end + + resetState = cmdParams[1]:lower() + + if resetState == 'defense' then + state.Defense.Active = false + add_to_chat(122,state.Defense.Type..' defense is now off.') + elseif resetState == 'kite' or resetState == 'kiting' then + state.Kiting = false + add_to_chat(122,'Kiting is now off.') + elseif resetState == 'melee' then + state.OffenseMode = options.OffenseModes[1] + state.DefenseMode = options.DefenseModes[1] + add_to_chat(122,'Melee has been reset to defaults.') + elseif resetState == 'casting' then + state.CastingMode = options.CastingModes[1] + add_to_chat(122,'Casting has been reset to default.') + elseif resetState == 'distance' then + state.MaxWeaponskillDistance = 0 + add_to_chat(122,'Max weaponskill distance limitations have been removed.') + elseif resetState == 'target' then + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + add_to_chat(122,'Adjusting target selection has been turned off.') + elseif resetState == 'all' then + state.Defense.Active = false + state.Defense.PhysicalMode = options.PhysicalDefenseModes[1] + state.Defense.MagicalMode = options.MagicalDefenseModes[1] + state.Kiting = false + state.OffenseMode = options.OffenseModes[1] + state.DefenseMode = options.DefenseModes[1] + state.CastingMode = options.CastingModes[1] + state.IdleMode = options.IdleModes[1] + state.RestingMode = options.RestingModes[1] + state.MaxWeaponskillDistance = 0 + state.SelectNPCTargets = false + state.PCTargetMode = 'default' + mote_vars.show_set = nil + if job_reset then + job_reset(resetState) + end + add_to_chat(122,'Everything has been reset to defaults.') + elseif job_reset then + job_reset(resetState) + else + add_to_chat(123,'handle_reset: unknown state to reset: '..resetState) + return + end + + if job_state_change then + job_state_change('Reset', resetState) + end + + handle_update({'auto'}) +end + + +-- User command format: gs c update [option] +-- Where [option] can be 'user' to display current state. +-- Otherwise, generally refreshes current gear used. +function handle_update(cmdParams) + -- init a new eventArgs + local eventArgs = {handled = false} + + reset_buff_states() + + -- Allow jobs to override this code + if job_update then + job_update(cmdParams, eventArgs) + end + + if not eventArgs.handled then + if handle_equipping_gear then + handle_equipping_gear(player.status) + end + end + + if cmdParams[1] == 'user' then + display_current_state() + end +end + + +-- showset: equip the current TP set for examination. +function handle_show_set(cmdParams) + local showset_type + if cmdParams[1] then + showset_type = cmdParams[1]:lower() + end + + -- If no extra parameters, or 'tp' as a parameter, show the current TP set. + if not showset_type or showset_type == 'tp' then + local meleeGroups = '' + if #classes.CustomMeleeGroups > 0 then + meleeGroups = ' [' + for i = 1,#classes.CustomMeleeGroups do + meleeGroups = meleeGroups..classes.CustomMeleeGroups[i] + end + meleeGroups = meleeGroups..']' + end + + add_to_chat(122,'Showing current TP set: ['..state.OffenseMode..'/'..state.DefenseMode..']'..meleeGroups) + equip(get_melee_set()) + -- If given a param of 'precast', block equipping midcast/aftercast sets + elseif showset_type == 'precast' then + mote_vars.show_set = 'precast' + add_to_chat(122,'GearSwap will now only equip up to precast gear for spells/actions.') + -- If given a param of 'midcast', block equipping aftercast sets + elseif showset_type == 'midcast' then + mote_vars.show_set = 'midcast' + add_to_chat(122,'GearSwap will now only equip up to midcast gear for spells.') + -- If given a param of 'midcast', block equipping aftercast sets + elseif showset_type == 'petmidcast' or showset_type == 'pet_midcast' then + mote_vars.show_set = 'pet_midcast' + add_to_chat(122,'GearSwap will now only equip up to pet midcast gear for spells.') + -- With a parameter of 'off', turn off showset functionality. + elseif showset_type == 'off' then + mote_vars.show_set = nil + add_to_chat(122,'Show Sets is turned off.') + end +end + +-- Minor variation on the GearSwap "gs equip naked" command, that ensures that +-- all slots are enabled before removing gear. +-- Command: "gs c naked" +function handle_naked(cmdParams) + enable('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') + equip(sets.naked) +end + + +------ Utility functions to support self commands. ------ + +-- Function to get the options.XXXModes list and the corresponding state value for the requested field. +function get_mode_list(field) + local modeList = {} + local currentValue = '' + local lowerField = field:lower() + + if type(state[field..'Mode']) == 'string' and type(options[field..'Modes']) == 'table' then + -- Handles: Offense, Defense, Ranged, Casting, Weaponskill, Idle, Resting modes + modeList = options[field..'Modes'] + currentValue = state[field..'Mode'] + elseif lowerField == 'physicaldefense' then + modeList = options.PhysicalDefenseModes + currentValue = state.Defense.PhysicalMode + elseif lowerField == 'magicaldefense' then + modeList = options.MagicalDefenseModes + currentValue = state.Defense.MagicalMode + elseif lowerField == 'pctarget' then + modeList = options.TargetModes + currentValue = state.PCTargetMode + elseif type(state[field..'Mode']) == 'string' and type(options[field..'Modes']) ~= 'table' then + -- naming conflict + add_to_chat(123,'No valid options table for field: '..field) + elseif type(state[field..'Mode']) ~= 'string' and type(options[field..'Modes']) == 'table' then + -- naming conflict + add_to_chat(123,'No valid state string for field: '..field) + elseif job_get_option_modes then + -- Allow job scripts to expand the mode table lists + modeList, currentValue = job_get_option_modes(field) + if not modeList then + add_to_chat(123,'Attempt to acquire options list for unknown state field: '..field) + return nil + end + else + add_to_chat(123,'Attempt to acquire options list for unknown state field: '..field) + return nil + end + + return modeList, currentValue +end + +-- Function to set the appropriate state value for the specified field. +function set_option_mode(field, val) + local lowerField = field:lower() + + if type(state[field..'Mode']) == 'string' then + -- Handles: Offense, Defense, Ranged, Casting, Weaponskill, Idle, Resting modes + state[field..'Mode'] = val + elseif lowerField == 'physicaldefense' then + state.Defense.PhysicalMode = val + elseif lowerField == 'magicaldefense' then + state.Defense.MagicalMode = val + elseif lowerField == 'pctarget' then + state.PCTargetMode = val + elseif job_set_option_mode then + -- Allow job scripts to expand the mode table lists + if not job_set_option_mode(field, val) then + add_to_chat(123,'Attempt to set unknown option field: '..field) + end + else + add_to_chat(123,'Attempt to set unknown option field: '..field) + end +end + + +-- Function to display the current relevant user state when doing an update. +-- Uses display_current_job_state instead if that is defined in the job lua. +function display_current_state() + local eventArgs = {handled = false} + if display_current_job_state then + display_current_job_state(eventArgs) + end + + if not eventArgs.handled then + local defenseString = '' + if state.Defense.Active then + local defMode = state.Defense.PhysicalMode + if state.Defense.Type == 'Magical' then + defMode = state.Defense.MagicalMode + end + + defenseString = 'Defense: '..state.Defense.Type..' '..defMode..', ' + end + + local pcTarget = '' + if state.PCTargetMode ~= 'default' then + pcTarget = ', Target PC: '..state.PCTargetMode + end + + local npcTarget = '' + if state.SelectNPCTargets then + pcTarget = ', Target NPCs' + end + + + add_to_chat(122,'Melee: '..state.OffenseMode..'/'..state.DefenseMode..', WS: '..state.WeaponskillMode..', '..defenseString.. + 'Kiting: '..on_off_names[state.Kiting]..pcTarget..npcTarget) + end + + if mote_vars.show_set then + add_to_chat(122,'Show Sets it currently showing ['..mote_vars.show_set..'] sets. Use "//gs c showset off" to turn it off.') + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Test functions. +------------------------------------------------------------------------------------------------------------------- + +-- A function for testing lua code. Called via "gs c test". +function handle_test(cmdParams) + if user_test then + user_test(cmdParams) + end +end + + + +------------------------------------------------------------------------------------------------------------------- +-- The below table maps text commands to the above handler functions. +------------------------------------------------------------------------------------------------------------------- + +selfCommandMaps = { + ['toggle'] = handle_toggle, + ['activate'] = handle_activate, + ['cycle'] = handle_cycle, + ['set'] = handle_set, + ['reset'] = handle_reset, + ['update'] = handle_update, + ['showset'] = handle_show_set, + ['naked'] = handle_naked, + ['test'] = handle_test} + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua new file mode 100644 index 0000000..7367e85 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-TreasureHunter.lua @@ -0,0 +1,297 @@ +------------------------------------------------------------------------------------------------------------------- +-- Utility include for applying and tracking Treasure Hunter effects. +-- +-- Include this if you want a means of applying TH on the first contact +-- with a mob, then resume using normal gear. +-- Thf also has modes to use TH gear for SATA and for keeping it on fulltime. +-- +-- Command: include('Mote-TreasureHunter') +-- Place this in your job_setup() function, or user_setup() function if using +-- a sidecar file, or get_sets() function if your job file isn't based +-- on my includes. +-- Be sure to define your own sets.TreasureHunter gear set after the include. +-- If using a job file based on my includes, simply place it in the +-- standard init_gear_sets() function. +-- +-- If you define TH gear sets for common actions (eg: Provoke, Box Step, etc), +-- then make sure they are accounted for in a th_action_check function +-- (signature: th_action_check(category, param)) in the job file. It's passed +-- category and param value for actions the user takes, and if it returns true, +-- that means that it's considered a valid tagging action. +-- +-- If using this in a job file that isn't based on my includes, you must +-- handle cycling the options values on your own, unless you also include +-- Mote-SelfCommands. +-- +-- The job file must handle the 'update' self-command (gs c update auto). +-- This is automatically handled if using my includes, but must be ensured +-- if being used with a user-built job file. +-- When called, it merely needs to equip standard melee gear for the current +-- configuration. +-- +-- Create a macro or keybind to cycle the Treasure Mode value: +-- gs c cycle TreasureMode +------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------- +-- Setup vars and events when first running the include. +------------------------------------------------------------------------------------------------------------------- + +-- Ensure base tables are defined +options = options or {} +state = state or {} +info = info or {} + +-- TH mode handling +if player.main_job == 'THF' then + options.TreasureModes = {'None','Tag','SATA','Fulltime'} + state.TreasureMode = 'Tag' +else + options.TreasureModes = {'None','Tag'} + state.TreasureMode = 'None' +end + +-- Tracking vars for TH. +info.tagged_mobs = T{} +info.last_player_target_index = 0 +state.th_gear_is_locked = false + +-- Required gear set. Expand this in the job file when defining sets. +sets.TreasureHunter = {} + +-- Event registration is done at the bottom of this file. + + +------------------------------------------------------------------------------------------------------------------- +-- User-callable functions for TH handling utility. +------------------------------------------------------------------------------------------------------------------- + +-- Can call to force a status refresh. +-- Also displays the current tagged mob table if in debug mode. +function th_update(cmdParams, eventArgs) + if (cmdParams and cmdParams[1] == 'user') or not cmdParams then + TH_for_first_hit() + + if _settings.debug_mode then + print_set(info.tagged_mobs, 'Tagged mobs') + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Local functions to support TH handling. +------------------------------------------------------------------------------------------------------------------- + +-- Set locked TH flag to true, and disable relevant gear slots. +function lock_TH() + state.th_gear_is_locked = true + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + disable(slots) +end + + +-- Set locked TH flag to false, and enable relevant gear slots. +function unlock_TH() + state.th_gear_is_locked = false + local slots = T{} + for slot,item in pairs(sets.TreasureHunter) do + slots:append(slot) + end + enable(slots) + send_command('gs c update auto') +end + + +-- For any active TH mode, if we haven't already tagged this target, equip TH gear and lock slots until we manage to hit it. +function TH_for_first_hit() + if player.status == 'Engaged' and state.TreasureMode ~= 'None' then + if not info.tagged_mobs[player.target.id] then + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..tostring(player.target.id)..'.') end + equip(sets.TreasureHunter) + lock_TH() + elseif state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'Target '..player.target.id..' has been tagged. Unlocking.') end + unlock_TH() + else + if _settings.debug_mode then add_to_chat(123,'Prepping for first hit on '..player.target.id..'. Target has already been tagged.') end + end + else + unlock_TH() + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event handlers to allow tracking TH application. +------------------------------------------------------------------------------------------------------------------- + +-- On engaging a mob, attempt to add TH gear. For any other status change, unlock TH gear slots. +function on_status_change_for_th(new_status_id, old_status_id) + if gearswap.gearswap_disabled or T{2,3,4}:contains(old_status_id) or T{2,3,4}:contains(new_status_id) then return end + + local new_status = gearswap.res.statuses[new_status_id].english + local old_status = gearswap.res.statuses[old_status_id].english + + if new_status == 'Engaged' then + if _settings.debug_mode then add_to_chat(123,'Engaging '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + elseif old_status == 'Engaged' then + if _settings.debug_mode and state.th_gear_is_locked then add_to_chat(123,'Disengaging. Unlocking TH.') end + info.last_player_target_index = 0 + unlock_TH() + end +end + + +-- On changing targets, attempt to add TH gear. +function on_target_change_for_th(new_index, old_index) + -- Only care about changing targets while we're engaged, either manually or via current target death. + if player.status == 'Engaged' then + -- If the current player.target is the same as the new mob then we're actually + -- engaged with it. + -- If it's different than the last known mob, then we've actually changed targets. + if player.target.index == new_index and new_index ~= info.last_player_target_index then + if _settings.debug_mode then add_to_chat(123,'Changing target to '..player.target.id..'.') end + info.last_player_target_index = player.target.index + TH_for_first_hit() + end + end +end + + +-- On any action event, mark mobs that we tag with TH. Also, update the last time tagged mobs were acted on. +function on_action_for_th(action) + --add_to_chat(123,'cat='..action.category..',param='..action.param) + -- If player takes action, adjust TH tagging information + if state.TreasureMode ~= 'None' then + if action.actor_id == player.id then + -- category == 1=melee, 2=ranged, 3=weaponskill, 4=spell, 6=job ability, 14=unblinkable JA + if state.TreasureMode == 'Fulltime' or + (state.TreasureMode == 'SATA' and (action.category == 1 or ((state.Buff['Sneak Attack'] or state.Buff['Trick Attack']) and action.category == 3))) or + (state.TreasureMode == 'Tag' and action.category == 1 and state.th_gear_is_locked) or -- Tagging with a melee hit + (th_action_check and th_action_check(action.category, action.param)) -- Any user-specified tagging actions + then + for index,target in pairs(action.targets) do + if not info.tagged_mobs[target.id] and _settings.debug_mode then + add_to_chat(123,'Mob '..target.id..' hit. Adding to tagged mobs table.') + end + info.tagged_mobs[target.id] = os.time() + end + + if state.th_gear_is_locked then + unlock_TH() + end + end + elseif info.tagged_mobs[action.actor_id] then + -- If mob acts, keep an update of last action time for TH bookkeeping + info.tagged_mobs[action.actor_id] = os.time() + else + -- If anyone else acts, check if any of the targets are our tagged mobs + for index,target in pairs(action.targets) do + if info.tagged_mobs[target.id] then + info.tagged_mobs[target.id] = os.time() + end + end + end + end + + cleanup_tagged_mobs() +end + + +-- Need to use this event handler to listen for deaths in case Battlemod is loaded, +-- because Battlemod blocks the 'action message' event. +-- +-- This function removes mobs from our tracking table when they die. +function on_incoming_chunk_for_th(id, data, modified, injected, blocked) + if id == 0x29 then + local target_id = data:unpack('I',0x09) + local message_id = data:unpack('H',0x19)%32768 + + -- Remove mobs that die from our tagged mobs list. + if info.tagged_mobs[target_id] then + -- 6 == actor defeats target + -- 20 == target falls to the ground + if message_id == 6 or message_id == 20 then + if _settings.debug_mode then add_to_chat(123,'Mob '..target_id..' died. Removing from tagged mobs table.') end + info.tagged_mobs[target_id] = nil + end + end + end +end + + +-- Clear out the entire tagged mobs table when zoning. +function on_zone_change_for_th(new_zone, old_zone) + if _settings.debug_mode then add_to_chat(123,'Zoning. Clearing tagged mobs table.') end + info.tagged_mobs:clear() +end + + +-- Save the existing function, if it exists, and call it after our own handling. +if job_state_change then + job_state_change_via_th = job_state_change +end + + +-- Called if we change any user state fields. +function job_state_change(stateField, newValue, oldValue) + if stateField == 'TreasureMode' then + if newValue == 'None' and state.th_gear_is_locked then + if _settings.debug_mode then add_to_chat(123,'TH Mode set to None. Unlocking gear.') end + unlock_TH() + elseif oldValue == 'None' then + TH_for_first_hit() + end + end + + if job_state_change_via_th then + job_state_change_via_th(stateField, newValue, oldValue) + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Extra utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Remove mobs that we've marked as tagged with TH if we haven't seen any activity from or on them +-- for over 3 minutes. This is to handle deagros, player deaths, or other random stuff where the +-- mob is lost, but doesn't die. +function cleanup_tagged_mobs() + -- If it's been more than 3 minutes since an action on or by a tagged mob, + -- remove them from the tagged mobs list. + local current_time = os.time() + local remove_mobs = S{} + -- Search list and flag old entries. + for target_id,action_time in pairs(info.tagged_mobs) do + local time_since_last_action = current_time - action_time + if time_since_last_action > 180 then + remove_mobs:add(target_id) + if _settings.debug_mode then add_to_chat(123,'Over 3 minutes since last action on mob '..target_id..'. Removing from tagged mobs list.') end + end + end + -- Clean out mobs flagged for removal. + for mob_id,_ in pairs(remove_mobs) do + info.tagged_mobs[mob_id] = nil + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Event function registration calls. +-- Can call these now that the above functions have been defined. +------------------------------------------------------------------------------------------------------------------- + +-- Register events to allow us to manage TH application. +windower.register_event('status change', on_status_change_for_th) +windower.register_event('target change', on_target_change_for_th) +windower.raw_register_event('action', on_action_for_th) +windower.raw_register_event('incoming chunk', on_incoming_chunk_for_th) +windower.raw_register_event('zone change', on_zone_change_for_th) + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Utility.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Utility.lua new file mode 100644 index 0000000..8cf3c49 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-Utility.lua @@ -0,0 +1,672 @@ +------------------------------------------------------------------------------------------------------------------- +-- General utility functions that can be used by any job files. +-- Outside the scope of what the main include file deals with. +------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------- +-- Buff utility functions. +------------------------------------------------------------------------------------------------------------------- + +local cancel_spells_to_check = S{'Sneak', 'Stoneskin', 'Spectral Jig', 'Trance', 'Monomi: Ichi', 'Utsusemi: Ichi'} +local cancel_types_to_check = S{'Waltz', 'Samba'} + +-- Function to cancel buffs if they'd conflict with using the spell you're attempting. +-- Requirement: Must have Cancel addon installed and loaded for this to work. +function cancel_conflicting_buffs(spell, action, spellMap, eventArgs) + if cancel_spells_to_check:contains(spell.english) or cancel_types_to_check:contains(spell.type) then + if spell.action_type == 'Ability' then + local abil_recasts = windower.ffxi.get_ability_recasts() + if abil_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Ability waiting on recast.') + eventArgs.cancel = true + return + end + elseif spell.action_type == 'Magic' then + local spell_recasts = windower.ffxi.get_spell_recasts() + if spell_recasts[spell.recast_id] > 0 then + add_to_chat(123,'Abort: Spell waiting on recast.') + eventArgs.cancel = true + return + end + end + + if spell.english == 'Spectral Jig' and buffactive.sneak then + cast_delay(0.2) + send_command('cancel sneak') + elseif spell.english == 'Sneak' and spell.target.type == 'SELF' and buffactive.sneak then + send_command('cancel sneak') + elseif spell.english == ('Stoneskin') then + send_command('@wait 1.0;cancel stoneskin') + elseif spell.english:startswith('Monomi') then + send_command('@wait 1.7;cancel sneak') + elseif spell.english == 'Utsusemi: Ichi' then + send_command('@wait 1.7;cancel copy image*') + elseif (spell.english == 'Trance' or spell.type=='Waltz') and buffactive['saber dance'] then + cast_delay(0.2) + send_command('cancel saber dance') + elseif spell.type=='Samba' and buffactive['fan dance'] then + cast_delay(0.2) + send_command('cancel fan dance') + end + end +end + + +-- Some mythics have special durations for level 1 and 2 aftermaths +local special_aftermath_mythics = S{'Tizona', 'Kenkonken', 'Murgleis', 'Yagrush', 'Carnwenhan', 'Nirvana', 'Tupsimati', 'Idris'} + +-- Call from job_precast() to setup aftermath information for custom timers. +function custom_aftermath_timers_precast(spell) + if spell.type == 'WeaponSkill' then + info.aftermath = {} + + local relic_ws = data.weaponskills.relic[player.equipment.main] or data.weaponskills.relic[player.equipment.range] + local mythic_ws = data.weaponskills.mythic[player.equipment.main] or data.weaponskills.mythic[player.equipment.range] + local empy_ws = data.weaponskills.empyrean[player.equipment.main] or data.weaponskills.empyrean[player.equipment.range] + + if not relic_ws and not mythic_ws and not empy_ws then + return + end + + info.aftermath.weaponskill = spell.english + info.aftermath.duration = 0 + + info.aftermath.level = math.floor(player.tp / 1000) + if info.aftermath.level == 0 then + info.aftermath.level = 1 + end + + if spell.english == relic_ws then + info.aftermath.duration = math.floor(0.2 * player.tp) + if info.aftermath.duration < 20 then + info.aftermath.duration = 20 + end + elseif spell.english == empy_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- duration is based on aftermath level + info.aftermath.duration = 30 * info.aftermath.level + elseif spell.english == mythic_ws then + -- nothing can overwrite lvl 3 + if buffactive['Aftermath: Lv.3'] then + return + end + -- only lvl 3 can overwrite lvl 2 + if info.aftermath.level ~= 3 and buffactive['Aftermath: Lv.2'] then + return + end + + -- Assume mythic is lvl 80 or higher, for duration + + if info.aftermath.level == 1 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 90 + elseif info.aftermath.level == 2 then + info.aftermath.duration = (special_aftermath_mythics:contains(player.equipment.main) and 270) or 120 + else + info.aftermath.duration = 180 + end + end + end +end + + +-- Call from job_aftercast() to create the custom aftermath timer. +function custom_aftermath_timers_aftercast(spell) + if not spell.interrupted and spell.type == 'WeaponSkill' and + info.aftermath and info.aftermath.weaponskill == spell.english and info.aftermath.duration > 0 then + + local aftermath_name = 'Aftermath: Lv.'..tostring(info.aftermath.level) + send_command('timers d "Aftermath: Lv.1"') + send_command('timers d "Aftermath: Lv.2"') + send_command('timers d "Aftermath: Lv.3"') + send_command('timers c "'..aftermath_name..'" '..tostring(info.aftermath.duration)..' down abilities/00027.png') + + info.aftermath = {} + end +end + + +-- Function to reset state.Buff values. +function reset_buff_states() + if state.Buff then + for buff,present in pairs(state.Buff) do + state.Buff[buff] = buffactive[buff] or false + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for changing spells and target types in an automatic manner. +------------------------------------------------------------------------------------------------------------------- + +local waltz_tp_cost = {['Curing Waltz'] = 200, ['Curing Waltz II'] = 350, ['Curing Waltz III'] = 500, ['Curing Waltz IV'] = 650, ['Curing Waltz V'] = 800} + +-- Utility function for automatically adjusting the waltz spell being used to match HP needs and TP limits. +-- Handle spell changes before attempting any precast stuff. +function refine_waltz(spell, action, spellMap, eventArgs) + if spell.type ~= 'Waltz' then + return + end + + -- Don't modify anything for Healing Waltz or Divine Waltzes + if spell.english == "Healing Waltz" or spell.english == "Divine Waltz" or spell.english == "Divine Waltz II" then + return + end + + local newWaltz = spell.english + local waltzID + + local missingHP + + -- If curing ourself, get our exact missing HP + if spell.target.type == "SELF" then + missingHP = player.max_hp - player.hp + -- If curing someone in our alliance, we can estimate their missing HP + elseif spell.target.isallymember then + local target = find_player_in_alliance(spell.target.name) + local est_max_hp = target.hp / (target.hpp/100) + missingHP = math.floor(est_max_hp - target.hp) + end + + -- If we have an estimated missing HP value, we can adjust the preferred tier used. + if missingHP ~= nil then + if player.main_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 200 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 600 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + elseif missingHP < 1100 then + newWaltz = 'Curing Waltz III' + waltzID = 192 + elseif missingHP < 1500 then + newWaltz = 'Curing Waltz IV' + waltzID = 193 + else + newWaltz = 'Curing Waltz V' + waltzID = 311 + end + elseif player.sub_job == 'DNC' then + if missingHP < 40 and spell.target.name == player.name then + -- Not worth curing yourself for so little. + -- Don't block when curing others to allow for waking them up. + add_to_chat(122,'Full HP!') + eventArgs.cancel = true + return + elseif missingHP < 150 then + newWaltz = 'Curing Waltz' + waltzID = 190 + elseif missingHP < 300 then + newWaltz = 'Curing Waltz II' + waltzID = 191 + else + newWaltz = 'Curing Waltz III' + waltzID = 192 + end + else + -- Not dnc main or sub; bail out + return + end + end + + local tpCost = waltz_tp_cost[newWaltz] + + local downgrade + + -- Downgrade the spell to what we can afford + if player.tp < tpCost and not buffactive.trance then + --[[ Costs: + Curing Waltz: 200 TP + Curing Waltz II: 350 TP + Curing Waltz III: 500 TP + Curing Waltz IV: 650 TP + Curing Waltz V: 800 TP + Divine Waltz: 400 TP + Divine Waltz II: 800 TP + --]] + + if player.tp < 200 then + add_to_chat(122, 'Insufficient TP ['..tostring(player.tp)..']. Cancelling.') + eventArgs.cancel = true + return + elseif player.tp < 350 then + newWaltz = 'Curing Waltz' + elseif player.tp < 500 then + newWaltz = 'Curing Waltz II' + elseif player.tp < 650 then + newWaltz = 'Curing Waltz III' + elseif player.tp < 800 then + newWaltz = 'Curing Waltz IV' + end + + downgrade = 'Insufficient TP ['..tostring(player.tp)..']. Downgrading to '..newWaltz..'.' + end + + + if newWaltz ~= spell.english then + send_command('@input /ja "'..newWaltz..'" '..tostring(spell.target.raw)) + if downgrade then + add_to_chat(122, downgrade) + end + eventArgs.cancel = true + return + end + + if missingHP and missingHP > 0 then + add_to_chat(122,'Trying to cure '..tostring(missingHP)..' HP using '..newWaltz..'.') + end +end + + +-- Function to allow for automatic adjustment of the spell target type based on preferences. +function auto_change_target(spell, spellMap) + -- Don't adjust targetting for explicitly named targets + if not spell.target.raw:startswith('<') then + return + end + + -- Do not modify target for spells where we get <lastst> or <me>. + if spell.target.raw == ('<lastst>') or spell.target.raw == ('<me>') then + return + end + + -- init a new eventArgs with current values + local eventArgs = {handled = false, PCTargetMode = state.PCTargetMode, SelectNPCTargets = state.SelectNPCTargets} + + -- Allow the job to do custom handling, or override the default values. + -- They can completely handle it, or set one of the secondary eventArgs vars to selectively + -- override the default state vars. + if job_auto_change_target then + job_auto_change_target(spell, action, spellMap, eventArgs) + end + + -- If the job handled it, we're done. + if eventArgs.handled then + return + end + + local pcTargetMode = eventArgs.PCTargetMode + local selectNPCTargets = eventArgs.SelectNPCTargets + + + local validPlayers = S{'Self', 'Player', 'Party', 'Ally', 'NPC'} + + local intersection = spell.targets * validPlayers + local canUseOnPlayer = not intersection:empty() + + local newTarget + + -- For spells that we can cast on players: + if canUseOnPlayer and pcTargetMode ~= 'default' then + -- Do not adjust targetting for player-targettable spells where the target was <t> + if spell.target.raw ~= ('<t>') then + if pcTargetMode == 'stal' then + -- Use <stal> if possible, otherwise fall back to <stpt>. + if spell.targets.Ally then + newTarget = '<stal>' + elseif spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpt' then + -- Even ally-possible spells are limited to the current party. + if spell.targets.Ally or spell.targets.Party then + newTarget = '<stpt>' + end + elseif pcTargetMode == 'stpc' then + -- If it's anything other than a self-only spell, can change to <stpc>. + if spell.targets.Player or spell.targets.Party or spell.targets.Ally or spell.targets.NPC then + newTarget = '<stpc>' + end + end + end + -- For spells that can be used on enemies: + elseif spell.targets and spell.targets.Enemy and selectNPCTargets then + -- Note: this means macros should be written for <t>, and it will change to <stnpc> + -- if the flag is set. It won't change <stnpc> back to <t>. + newTarget = '<stnpc>' + end + + -- If a new target was selected and is different from the original, call the change function. + if newTarget and newTarget ~= spell.target.raw then + change_target(newTarget) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Environment utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- Function to get the current weather intensity: 0 for none, 1 for single weather, 2 for double weather. +function get_weather_intensity() + return gearswap.res.weather[world.weather_id].intensity +end + + +-- Returns true if you're in a party solely comprised of Trust NPCs. +-- TODO: Do we need a check to see if we're in a party partly comprised of Trust NPCs? +function is_trust_party() + -- Check if we're solo + if party.count == 1 then + return false + end + + -- Can call a max of 3 Trust NPCs, so parties larger than that are out. + if party.count > 4 then + return false + end + + -- If we're in an alliance, can't be a Trust party. + if alliance[2].count > 0 or alliance[3].count > 0 then + return false + end + + -- Check that, for each party position aside from our own, the party + -- member has one of the Trust NPC names, and that those party members + -- are flagged is_npc. + for i = 2,4 do + if party[i] then + if not npcs.Trust:contains(party[i].name) then + return false + end + if party[i].mob and party[i].mob.is_npc == false then + return false + end + end + end + + -- If it didn't fail any of the above checks, return true. + return true +end + + +-- Call these function with a list of equipment slots to check ('head', 'neck', 'body', etc) +-- Returns true if any of the specified slots are currently encumbered. +-- Returns false if all specified slots are unencumbered. +function is_encumbered(...) + local check_list = {...} + -- Compensate for people passing a table instead of a series of strings. + if type(check_list[1]) == 'table' then + check_list = check_list[1] + end + local check_set = S(check_list) + + for slot_id,slot_name in pairs(gearswap.default_slot_map) do + if check_set:contains(slot_name) then + if gearswap.encumbrance_table[slot_id] then + return true + end + end + end + + return false +end + +------------------------------------------------------------------------------------------------------------------- +-- Elemental gear utility functions. +------------------------------------------------------------------------------------------------------------------- + +-- General handler function to set all the elemental gear for an action. +function set_elemental_gear(spell) + set_elemental_gorget_belt(spell) + set_elemental_obi_cape_ring(spell) + set_elemental_staff(spell) +end + + +-- Set the name field of the predefined gear vars for gorgets and belts, for the specified weaponskill. +function set_elemental_gorget_belt(spell) + if spell.type ~= 'WeaponSkill' then + return + end + + -- Get the union of all the skillchain elements for the weaponskill + local weaponskill_elements = S{}: + union(skillchain_elements[spell.skillchain_a]): + union(skillchain_elements[spell.skillchain_b]): + union(skillchain_elements[spell.skillchain_c]) + + gear.ElementalGorget.name = get_elemental_item_name("gorget", weaponskill_elements) or gear.default.weaponskill_neck or "" + gear.ElementalBelt.name = get_elemental_item_name("belt", weaponskill_elements) or gear.default.weaponskill_waist or "" +end + + +-- Function to get an appropriate obi/cape/ring for the current action. +function set_elemental_obi_cape_ring(spell) + if spell.element == 'None' then + return + end + + local world_elements = S{world.day_element} + if world.weather_element ~= 'None' then + world_elements:add(world.weather_element) + end + + local obi_name = get_elemental_item_name("obi", S{spell.element}, world_elements) + gear.ElementalObi.name = obi_name or gear.default.obi_waist or "" + + if obi_name then + if player.inventory['Twilight Cape'] or player.wardrobe['Twilight Cape'] then + gear.ElementalCape.name = "Twilight Cape" + end + if (player.inventory['Zodiac Ring'] or player.wardrobe['Zodiac Ring']) and spell.english ~= 'Impact' and + not S{'Divine Magic','Dark Magic','Healing Magic'}:contains(spell.skill) then + gear.ElementalRing.name = "Zodiac Ring" + end + else + gear.ElementalCape.name = gear.default.obi_back + gear.ElementalRing.name = gear.default.obi_ring + end +end + + +-- Function to get the appropriate fast cast and/or recast staves for the current spell. +function set_elemental_staff(spell) + if spell.action_type ~= 'Magic' then + return + end + + gear.FastcastStaff.name = get_elemental_item_name("fastcast_staff", S{spell.element}) or gear.default.fastcast_staff or "" + gear.RecastStaff.name = get_elemental_item_name("recast_staff", S{spell.element}) or gear.default.recast_staff or "" +end + + +-- Gets the name of an elementally-aligned piece of gear within the player's +-- inventory that matches the conditions set in the parameters. +-- +-- item_type: Type of item as specified in the elemental_map mappings. +-- EG: gorget, belt, obi, fastcast_staff, recast_staff +-- +-- valid_elements: Elements that are valid for the action being taken. +-- IE: Weaponskill skillchain properties, or spell element. +-- +-- restricted_to_elements: Secondary elemental restriction that limits +-- whether the item check can be considered valid. +-- EG: Day or weather elements that have to match the spell element being queried. +-- +-- Returns: Nil if no match was found (either due to elemental restrictions, +-- or the gear isn't in the player inventory), or the name of the piece of +-- gear that matches the query. +function get_elemental_item_name(item_type, valid_elements, restricted_to_elements) + local potential_elements = restricted_to_elements or elements.list + local item_map = elements[item_type:lower()..'_of'] + + for element in (potential_elements.it or it)(potential_elements) do + if valid_elements:contains(element) and (player.inventory[item_map[element]] or player.wardrobe[item_map[element]]) then + return item_map[element] + end + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Function to easily change to a given macro set or book. Book value is optional. +------------------------------------------------------------------------------------------------------------------- + +function set_macro_page(set,book) + if not tonumber(set) then + add_to_chat(123,'Error setting macro page: Set is not a valid number ('..tostring(set)..').') + return + end + if set < 1 or set > 10 then + add_to_chat(123,'Error setting macro page: Macro set ('..tostring(set)..') must be between 1 and 10.') + return + end + + if book then + if not tonumber(book) then + add_to_chat(123,'Error setting macro page: book is not a valid number ('..tostring(book)..').') + return + end + if book < 1 or book > 20 then + add_to_chat(123,'Error setting macro page: Macro book ('..tostring(book)..') must be between 1 and 20.') + return + end + send_command('@input /macro book '..tostring(book)..';wait .1;input /macro set '..tostring(set)) + else + send_command('@input /macro set '..tostring(set)) + end +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for including local user files. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to load user gear files in place of default gear sets. +-- Return true if one exists and was loaded. +function load_sidecar(job) + if not job then return false end + + -- filename format example for user-local files: whm_gear.lua, or playername_whm_gear.lua + local filenames = {player.name..'_'..job..'_gear.lua', job..'_gear.lua', + 'gear/'..player.name..'_'..job..'_gear.lua', 'gear/'..job..'_gear.lua', + 'gear/'..player.name..'_'..job..'.lua', 'gear/'..job..'.lua'} + return optional_include(filenames) +end + +-- Attempt to include user-globals. Return true if it exists and was loaded. +function load_user_globals() + local filenames = {player.name..'-globals.lua', 'user-globals.lua'} + return optional_include(filenames) +end + +-- Optional version of include(). If file does not exist, does not +-- attempt to load, and does not throw an error. +-- filenames takes an array of possible file names to include and checks +-- each one. +function optional_include(filenames) + for _,v in pairs(filenames) do + local path = gearswap.pathsearch({v}) + if path then + include(v) + return true + end + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for vars or other data manipulation. +------------------------------------------------------------------------------------------------------------------- + +-- Attempt to locate a specified name within the current alliance. +function find_player_in_alliance(name) + for party_index,ally_party in ipairs(alliance) do + for player_index,_player in ipairs(ally_party) do + if _player.name == name then + return _player + end + end + end +end + + +-- buff_set is a set of buffs in a library table (any of S{}, T{} or L{}). +-- This function checks if any of those buffs are present on the player. +function has_any_buff_of(buff_set) + return buff_set:any( + -- Returns true if any buff from buff set that is sent to this function returns true: + function (b) return buffactive[b] end + ) +end + + +-- Invert a table such that the keys are values and the values are keys. +-- Use this to look up the index value of a given entry. +function invert_table(t) + if t == nil then error('Attempting to invert table, received nil.', 2) end + + local i={} + for k,v in pairs(t) do + i[v] = k + end + return i +end + + +-- Gets sub-tables based on baseSet from the string str that may be in dot form +-- (eg: baseSet=sets, str='precast.FC', this returns the table sets.precast.FC). +function get_expanded_set(baseSet, str) + local cur = baseSet + for i in str:gmatch("[^.]+") do + if cur then + cur = cur[i] + end + end + + return cur +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions data and event tracking. +------------------------------------------------------------------------------------------------------------------- + +-- This is a function that can be attached to a registered event for 'time change'. +-- It will send a call to the update() function if the time period changes. +-- It will also call job_time_change when any of the specific time class values have changed. +-- To activate this in your job lua, add this line to your user_setup function: +-- windower.register_event('time change', time_change) +-- +-- Variables it sets: classes.Daytime, and classes.DuskToDawn. They are set to true +-- if their respective descriptors are true, or false otherwise. +function time_change(new_time, old_time) + local was_daytime = classes.Daytime + local was_dusktime = classes.DuskToDawn + + if new_time >= 6*60 and new_time < 18*60 then + classes.Daytime = true + else + classes.Daytime = false + end + + if new_time >= 17*60 or new_time < 7*60 then + classes.DuskToDawn = true + else + classes.DuskToDawn = false + end + + if was_daytime ~= classes.Daytime or was_dusktime ~= classes.DuskToDawn then + if job_time_change then + job_time_change(new_time, old_time) + end + + handle_update({'auto'}) + end +end + + diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-documentation.txt b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-documentation.txt new file mode 100644 index 0000000..8f17241 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/Mote-documentation.txt @@ -0,0 +1 @@ +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation on the usage of these include files.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/README.md b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/README.md new file mode 100644 index 0000000..9fd1351 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/libs/rev1/README.md @@ -0,0 +1,6 @@ +Mote-libs +========= + +These are the Mote-Include library files for GearSwap. + +Please see https://github.com/Kinematics/GearSwap-Jobs/wiki for documentation. diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/packet_parsing.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/packet_parsing.lua new file mode 100644 index 0000000..f8cabf3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/packet_parsing.lua @@ -0,0 +1,716 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +parse = { + i={}, -- Incoming packets + o={} -- Outgoing packets, currently none are really parsed for information + } + +parse.i[0x00A] = function (data) + windower.debug('zone change') + command_registry = Command_Registry.new() + table.clear(not_sent_out_equip) + + player.id = data:unpack('I',0x05) + player.index = data:unpack('H',0x09) + if player.main_job_id and player.main_job_id ~= data:byte(0xB5) and player.name and player.name == data:unpack('z',0x85) and not gearswap_disabled then + windower.debug('job change on zone') + load_user_files(data:byte(0xB5)) + else + player.name = data:unpack('z',0x85) + end + player.main_job_id = data:byte(0xB5) + player.sub_job_id = data:byte(0xB8) + player.vitals.max_hp = data:unpack('I',0xE9) + player.vitals.max_mp = data:unpack('I',0xED) + player.max_hp = data:unpack('I',0xE9) + player.max_mp = data:unpack('I',0xED) + update_job_names() + + world.zone_id = data:unpack('H',0x31) + _ExtraData.world.conquest = false + for i,v in pairs(region_to_zone_map) do + if v:contains(world.zone_id) then + _ExtraData.world.conquest = { + region_id = i, + region_name = res.regions[i][language], + } + break + end + end + weather_update(data:byte(0x69)) + world.logged_in = true + + _ExtraData.world.in_mog_house = data:byte(0x81) == 1 + + _ExtraData.player.base_str = data:unpack('H',0xCD) + _ExtraData.player.base_dex = data:unpack('H',0xCF) + _ExtraData.player.base_vit = data:unpack('H',0xD1) + _ExtraData.player.base_agi = data:unpack('H',0xD3) + _ExtraData.player.base_int = data:unpack('H',0xD5) + _ExtraData.player.base_mnd = data:unpack('H',0xD7) + _ExtraData.player.base_chr = data:unpack('H',0xD9) + _ExtraData.player.add_str = data:unpack('h',0xDB) + _ExtraData.player.add_dex = data:unpack('h',0xDD) + _ExtraData.player.add_vit = data:unpack('h',0xDF) + _ExtraData.player.add_agi = data:unpack('h',0xE1) + _ExtraData.player.add_int = data:unpack('h',0xE3) + _ExtraData.player.add_mnd = data:unpack('h',0xE5) + _ExtraData.player.add_chr = data:unpack('h',0xE7) + + _ExtraData.player.str = _ExtraData.player.base_str + _ExtraData.player.add_str + _ExtraData.player.dex = _ExtraData.player.base_dex + _ExtraData.player.add_dex + _ExtraData.player.vit = _ExtraData.player.base_vit + _ExtraData.player.add_vit + _ExtraData.player.agi = _ExtraData.player.base_agi + _ExtraData.player.add_agi + _ExtraData.player.int = _ExtraData.player.base_int + _ExtraData.player.add_int + _ExtraData.player.mnd = _ExtraData.player.base_mnd + _ExtraData.player.add_mnd + _ExtraData.player.chr = _ExtraData.player.base_chr + _ExtraData.player.add_chr + refresh_ffxi_info() + + blank_0x063_v9_inc = true +end + +parse.i[0x00B] = function(data) + -- Blank temporary items when zoning. + items.temporary = make_inventory_table() +end + +parse.i[0x00E] = function (data) + if pet.index and pet.index == data:unpack('H',9) and math.floor((data:byte(11)%8)/4)== 1 then + local status_id = data:byte(32) + -- Filter all statuses aside from Idle/Engaged/Dead/Engaged dead. + if pet.status_id ~= status_id and (status_id < 4 or status_id == 33 or status_id == 47) then + if not next_packet_events then next_packet_events = {sequence_id = data:unpack('H',3)} end + next_packet_events.pet_status_change = {newstatus=res.statuses[status_id][language],oldstatus=pet.status} + pet.status = res.statuses[status_id][language] + pet.status_id = status_id + end + end +end + +parse.i[0x01B] = function (data) + for job_id = 1,23 do + player.jobs[to_windower_api(res.jobs[job_id].english)] = data:byte(job_id + 72) + end + + local enc = data:unpack('H',0x61) + local tab = {} + for slot_id,slot_name in pairs(default_slot_map) do + local tf = (((enc%(2^(slot_id+1))) / 2^slot_id) >= 1) + if encumbrance_table[slot_id] and not tf and not_sent_out_equip[slot_name] and not disable_table[i] then + tab[slot_name] = not_sent_out_equip[slot_name] + not_sent_out_equip[slot_name] = nil + end + if encumbrance_table[slot_id] and not tf then + msg.debugging("Your "..slot_name.." slot is now unlocked.") + end + encumbrance_table[slot_id] = tf + end + if table.length(tab) > 0 and not gearswap_disabled then + refresh_globals() + equip_sets('equip_command',nil,tab) + end +end + +parse.i[0x01E] = function (data) + local bag = to_windower_compact(res.bags[data:byte(0x09)].english) + local slot = data:byte(0x0A) + local count = data:unpack('I',5) + local status = data:byte(0x0B) + if not items[bag][slot] then items[bag][slot] = make_empty_item_table(slot) end + items[bag][slot].count = count + items[bag][slot].status = status + if count == 0 then + items[bag][slot].id = 0 + items[bag][slot].bazaar = 0 + items[bag][slot].status = 0 + end +end + +parse.i[0x01F] = function (data) + local bag = to_windower_compact(res.bags[data:byte(0x0B)].english) + local slot = data:byte(0x0C) + if not items[bag][slot] then items[bag][slot] = make_empty_item_table(slot) end + items[bag][slot].id = data:unpack('H',9) + items[bag][slot].count = data:unpack('I',5) + items[bag][slot].status = data:byte(0x0D) +end + +parse.i[0x020] = function (data) + local bag = to_windower_compact(res.bags[data:byte(0x0F)].english) + local slot = data:byte(0x10) + if not items[bag][slot] then items[bag][slot] = make_empty_item_table(slot) end + items[bag][slot].id = data:unpack('H',0x0D) + items[bag][slot].count = data:unpack('I',5) + items[bag][slot].bazaar = data:unpack('I',9) + items[bag][slot].status = data:byte(0x11) + items[bag][slot].extdata = data:sub(0x12,0x29) + -- Did not mess with linkshell stuff +end + +parse.i[0x037] = function (data) + player.status_id = data:byte(0x31) + --[[local bitmask = data:sub(0x4D,0x54) + for i = 1,32 do + local bitmask_position = 2*((i-1)%4) + local id = data:byte(4+i) + 256*math.floor(bitmask:byte(1+math.floor((i-1)/4))%(2^(bitmask_position+2))/(2^bitmask_position)) + if player.buffs[i] ~= id then + if id == 255 and player.buffs[i] then + player.buffs[i] = nil + elseif id ~= 255 then + player.buffs[i] = id + end + end + end]] + + local indi_byte = data:byte(0x59) + if indi_byte%128/64 >= 1 then + local temp_indi = _ExtraData.player.indi + _ExtraData.player.indi = { + element = res.elements[indi_byte%8][language], + element_id = indi_byte%8, + size = math.floor((indi_byte%64)/16) + 1, -- Size range of 1~4 + } + if (indi_byte%16)/8 >= 1 then + _ExtraData.player.indi.target = 'Enemy' + else + _ExtraData.player.indi.target = 'Ally' + end + if not gearswap_disabled then + if not temp_indi then + -- There was not an indi spell up + refresh_globals() + equip_sets('indi_change',nil,_ExtraData.player.indi,true) + elseif temp_indi.element_id ~= _ExtraData.player.indi.element_id or temp_indi.target ~= _ExtraData.player.indi.target or temp_indi.size ~= _ExtraData.player.indi.size then + -- There was already an indi spell up, so check if it changed + refresh_globals() + equip_sets('indi_change',nil,temp_indi,false) + equip_sets('indi_change',nil,_ExtraData.player.indi,true) + end + end + elseif _ExtraData.player.indi then + -- An indi effect has been lost. + local temp_indi = _ExtraData.player.indi + _ExtraData.player.indi = nil + if not gearswap_disabled then + refresh_globals() + equip_sets('indi_change',nil,temp_indi,false) + end + end + + local subj_ind = data:unpack('H', 0x35) / 8 + if subj_ind == 0 and pet.isvalid then + if not next_packet_events then next_packet_events = {sequence_id = data:unpack('H',3)} end + refresh_globals() + pet.isvalid = false + _ExtraData.pet = {} + next_packet_events.pet_change = {pet = table.reassign({},pet)} + elseif subj_ind ~= 0 and not pet.isvalid then + if not next_packet_events then next_packet_events = {sequence_id = data:unpack('H',3)} end + _ExtraData.pet.tp = 0 + next_packet_events.pet_change = {subj_ind = subj_ind} + end +end + +function parse_equip_chunk(chunk) + local inv_slot = chunk:byte(1) -- 0 indicates unequipping the item + local equip_slot = toslotname(chunk:byte(2)) + if inv_slot == 0 then -- Unequipping + local bag_id = items.equipment[equip_slot].bag_id + inv_slot = items.equipment[equip_slot].slot + + if inv_slot == empty then return end -- unequipping something that was already unequipped? + + local inv = items[to_windower_compact(res.bags[bag_id].english)] + if not inv[inv_slot] then inv[inv_slot] = make_empty_item_table(inv_slot) end + + inv[inv_slot].status = 0 -- Set the status to "unequipped" + items.equipment[equip_slot] = {slot=empty,bag_id=0} + else + local bag_id = chunk:byte(3) + local inv = items[to_windower_compact(res.bags[bag_id].english)] + items.equipment[equip_slot] = {slot=inv_slot,bag_id = bag_id} + if not inv[inv_slot] then inv[inv_slot] = make_empty_item_table(inv_slot) end + inv[inv_slot].status = 5 -- Set the status to "equipped" + end +end + +parse.o[0x050] = function (data,injected) --equip + if injected then return end + -- Because of the way windower works, uninjected chunks will appear after + -- injected chunks in the chunk events but will hit the server before them. + -- Thus, I use insert here instead of append + injected_equipment_registry[data:byte(6)]:insert(1,data:sub(5,7)) +end + +parse.o[0x051] = function (data,injected) --equipset + if injected then return end + for i=9,9+4*(data:byte(5)-1),4 do + injected_equipment_registry[data:byte(i+1)]:insert(1,data:sub(i,i+2)) + end +end + +parse.i[0x050] = function (data) + -- should simplify this code using return when I gain confidence in it + parse_equip_chunk(data:sub(5,7)) + local slot = data:byte(6) + for chunk,ind in injected_equipment_registry[slot]:it() do + if chunk == data:sub(5,7) then + -- Matched + injected_equipment_registry[slot] = injected_equipment_registry[slot]:slice(ind+1) -- Eliminate current and all preceding packets if we get a match + matched = true + return + end + --[[for i=9,9+4*(chunk:byte(5)-1),4 do -- The server replies to equipset packets with both single equip packets and equipset packets. + if chunk:sub(i,i+2) == data:sub(5,7) then + matched = true + break + end + end]] + end + -- Unexpected packet found! +end + +function update_equipment() + local tab = {} + for i,v in pairs(items.equipment) do + tab[i] = {bag_id = v.bag_id,slot=v.slot} + end + for i,v in pairs(injected_equipment_registry) do + local last = v:last() + if last then + tab[default_slot_map[i]] = { + bag_id = last:byte(3), + slot = last:byte(1) == 0 and empty or last:byte(1), + } + end + end + return tab +end + +-- The server always sends both equip responses and equipset responses following an equipset command +-- Equip responses are sent for pieces the successfully equip +-- The equipset response contains a copy of the original equipset and a new list of all your currently worn gear +-- It always comes after the equip commands, so one way to address this is to simply read the new list of currently worn gear +-- and act as if it's all unexpected. +parse.i[0x117] = function (data) + -- should simplify this code using return when I gain confidence in it + for i=9,9+4*(data:byte(5)-1),4 do -- Byte position for the start of the current chunk + local slot = data:byte(i+1) + for chunk,ind in injected_equipment_registry[slot]:it() do + if chunk == data:sub(i,i+2) then + -- This is the response to an injected equipset packet, so remove those packets from the registry even if it actually failed to swap. + injected_equipment_registry[slot] = injected_equipment_registry[slot]:slice(ind+1) + end + end + end + for i=0x49,0x85,4 do + -- Update items.equipment + parse_equip_chunk(data:sub(i,i+2)) + end +end + +parse.i[0x053] = function (data) + local message = data:unpack('H',0xD) + if (message == 0x12D or message == 0x12A or message == 0x12B or message == 0x12C) and player then + -- You're unable to use trust magic if you're not the party leader, solo, pt full or trying to summon an already summoned trust + local ts,tab = command_registry:find_by_time() + if tab and tab.spell and tab.spell.prefix ~= '/pet' and not gearswap_disabled then + tab.spell.action_type = 'Interruption' + tab.spell.interrupted = true + equip_sets('aftercast',nil,tab.spell) + end + end +end + +parse.i[0x05E] = function (data) + -- Conquest ID + if _ExtraData.world.conquest then + local offset = _ExtraData.world.conquest.region_id*4 + 11 + if offset == 99 then + offset = 95 + elseif offset == 107 then + offset = 99 + end + local strength_map = {[0]='Minimal',[1]='Minor',[2]='Major',[3]='Dominant'} + local nation_map = {[0]={english='Neutral',japanese='Neutral'},[1]=res.regions[0],[2]=res.regions[1], + [3]=res.regions[2],[4]={english='Beastman',japanese='Beastman'},[0xFF]=res.regions[3]} + _ExtraData.world.conquest.strengths = { + sandoria=strength_map[data:byte(offset+2)%4], + bastok=strength_map[math.floor(data:byte(offset+2)%16/4)], + windurst=strength_map[math.floor(data:byte(offset+2)%64/16)], + beastmen=strength_map[math.floor(data:byte(offset+2)/64)],} + _ExtraData.world.conquest.nation = nation_map[data:byte(offset+3)][language] + _ExtraData.world.conquest.sandoria = data:byte(0x87) + _ExtraData.world.conquest.bastok = data:byte(0x88) + _ExtraData.world.conquest.windurst = data:byte(0x89) + _ExtraData.world.conquest.beastmen = 100-data:byte(0x87)-data:byte(0x88)-data:byte(0x89) + end +end + +parse.i[0x061] = function (data) + player.vitals.max_hp = data:unpack('I',5) + player.vitals.max_mp = data:unpack('I',9) + player.max_hp = data:unpack('I',5) + player.max_mp = data:unpack('I',9) + player.main_job_id = data:byte(13) + player.main_job_level = data:byte(14) + + _ExtraData.player.nation_id = data:byte(0x51) + _ExtraData.player.nation = res.regions[_ExtraData.player.nation_id][language] or 'None' + _ExtraData.player.base_str = data:unpack('H',0x15) + _ExtraData.player.base_dex = data:unpack('H',0x17) + _ExtraData.player.base_vit = data:unpack('H',0x19) + _ExtraData.player.base_agi = data:unpack('H',0x1B) + _ExtraData.player.base_int = data:unpack('H',0x1D) + _ExtraData.player.base_mnd = data:unpack('H',0x1F) + _ExtraData.player.base_chr = data:unpack('H',0x21) + _ExtraData.player.add_str = data:unpack('h',0x23) + _ExtraData.player.add_dex = data:unpack('h',0x25) + _ExtraData.player.add_vit = data:unpack('h',0x27) + _ExtraData.player.add_agi = data:unpack('h',0x29) + _ExtraData.player.add_int = data:unpack('h',0x2B) + _ExtraData.player.add_mnd = data:unpack('h',0x2D) + _ExtraData.player.add_chr = data:unpack('h',0x2F) + _ExtraData.player.attack = data:unpack('H',0x31) + _ExtraData.player.defense = data:unpack('H',0x33) + _ExtraData.player.fire_resistance = data:unpack('h',0x35) + _ExtraData.player.wind_resistance = data:unpack('h',0x37) + _ExtraData.player.lightning_resistance = data:unpack('h',0x39) + _ExtraData.player.light_resistance = data:unpack('h',0x3B) + _ExtraData.player.ice_resistance = data:unpack('h',0x3D) + _ExtraData.player.earth_resistance = data:unpack('h',0x3F) + _ExtraData.player.water_resistance = data:unpack('h',0x41) + _ExtraData.player.dark_resistance = data:unpack('h',0x43) + + _ExtraData.player.str = _ExtraData.player.base_str + _ExtraData.player.add_str + _ExtraData.player.dex = _ExtraData.player.base_dex + _ExtraData.player.add_dex + _ExtraData.player.vit = _ExtraData.player.base_vit + _ExtraData.player.add_vit + _ExtraData.player.agi = _ExtraData.player.base_agi + _ExtraData.player.add_agi + _ExtraData.player.int = _ExtraData.player.base_int + _ExtraData.player.add_int + _ExtraData.player.mnd = _ExtraData.player.base_mnd + _ExtraData.player.add_mnd + _ExtraData.player.chr = _ExtraData.player.base_chr + _ExtraData.player.add_chr + + if player.sub_job_id ~= data:byte(15) then + -- Subjob change event + local temp_sub = player.sub_job + player.sub_job_id = data:byte(15) + player.sub_job_level = data:byte(16) + update_job_names() + if not gearswap_disabled then + refresh_globals() + equip_sets('sub_job_change',nil,player.sub_job,temp_sub) + end + end + update_job_names() +end + +parse.i[0x062] = function (data) + for i = 1,0x71,2 do + local skill = data:unpack('H',i + 0x82)%32768 + local current_skill = res.skills[math.floor(i/2)+1] + if current_skill then + player.skills[to_windower_api(current_skill.english)] = skill + end + end +end + +parse.i[0x063] = function (data) + if data:byte(0x05) == 0x09 and blank_0x063_v9_inc then + -- After zoning, players receive a blank 0x063 v9 packet + -- (because their buff line is temporarily empty) + -- So this flag is set in 0x00A + blank_0x063_v9_inc = false + -- However, players can also reload gearswap and fail to get a 0x063 v9 packet from + -- windower.packets.last_incoming, which leaves them without buff information but with a + -- informative 0x063 v9 packet coming next. So this step checks confirms the packet is + -- empty before returning + if data:sub(0x49,0xC8) == string.char(0):rep(128) then + return + end + end + if data:byte(0x05) == 0x09 then + local newbuffs = {} + for i=1,32 do + local buff_id = data:unpack('H',i*2+7) + if buff_id ~= 255 and buff_id ~= 0 then -- 255 is used for "no buff" + local t = data:unpack('I',i*4+0x45)/60+572662306+1009810800 + newbuffs[i] = setmetatable({ + name=res.buffs[buff_id].name, + buff=copy_entry(res.buffs[buff_id]), + id = buff_id, + time=t, + date=os.date('*t',t), + }, + {__index=function(t,k) + if k and k=='duration' then + return rawget(t,'time')-os.time() + else + return rawget(t,k) + end + end}) + end + end + if seen_0x063_type9 then + + -- Look for exact matches + for n,new in pairs(newbuffs) do + newbuffs[n].matched_exactly = nil + for i,old in pairs(_ExtraData.player.buff_details) do + -- Find unchanged buffs + if old.id == new.id and math.abs(old.time-new.time) < 1 and not old.matched_exactly then + newbuffs[n].matched_exactly = true + _ExtraData.player.buff_details[i].matched_exactly = true + break + end + end + end + + -- Look for time-independent matches, which are assumedly a spell overwriting itself + for n,new in pairs(newbuffs) do + newbuffs[n].matched_imprecisely = nil + if not new.matched_exactly then + for i,old in pairs(_ExtraData.player.buff_details) do + -- Buffs can be overwritten + if old.id == new.id and not (old.matched_exactly or old.matched_imprecisely) then + newbuffs[n].matched_imprecisely = true + _ExtraData.player.buff_details[i].matched_imprecisely = true + break + end + end + end + end + + for n,new in pairs(newbuffs) do + if new.matched_exactly then + newbuffs[n].matched_exactly = nil + elseif new.matched_imprecisely then + newbuffs[n].matched_imprecisely = nil + -- Matched a previous buff, but the time didn't jive so it's assumed + -- that it was overwritten with the same status effect + if not res.buffs[new.id] then + error('GearSwap: No known status for buff id #'..tostring(new.id)) + end + local buff_name = res.buffs[new.id][language] + windower.debug('refresh buff '..buff_name..' ('..tostring(new.id)..')') + if not gearswap_disabled then + refresh_globals() + equip_sets('buff_refresh',nil,buff_name,new) + end + else + -- Not matched, so it's assumed the buff is new + if not res.buffs[new.id] then + error('GearSwap: No known status for buff id #'..tostring(new.id)) + end + local buff_name = res.buffs[new.id][language] + windower.debug('gain buff '..buff_name..' ('..tostring(new.id)..')') + -- Need to figure out what I'm going to do with this: + if T{'terror','sleep','stun','petrification','charm','weakness'}:contains(buff_name:lower()) then + for ts,v in pairs(command_registry) do + if v.midaction then + command_registry:delete_entry(ts) + end + end + end + if not gearswap_disabled then + refresh_globals() + equip_sets('buff_change',nil,buff_name,true,new) + end + end + end + for i,old in pairs(_ExtraData.player.buff_details) do + if not (old.matched_exactly or old.matched_imprecisely) then + -- Old status was not matched to any new status, so it's assumed it was lost + if not res.buffs[old.id] then + error('GearSwap: No known status for buff id #'..tostring(old.id)) + end + local buff_name = res.buffs[old.id][language] + windower.debug('lose buff '..buff_name..' ('..tostring(old.id)..')') + if not gearswap_disabled then + refresh_globals() + equip_sets('buff_change',nil,buff_name,false,old) + end + end + end + end + + table.reassign(_ExtraData.player.buff_details,newbuffs) + for i=1,32 do + player.buffs[i] = (newbuffs[i] and newbuffs[i].id) or nil + end + -- Cannot reliably recall this packet using last_incoming on load because there + -- are 9 version of it and you only get the last one. Hence, this flag: + seen_0x063_type9 = true + end +end + +parse.i[0x067] = function (data) + if player.index == data:unpack('H',0x0D) then -- You are the owner + _ExtraData.pet.tp = data:unpack('H',0x11) + end +end + +parse.i[0x068] = function (data) + + if player.index == data:unpack('H',0x07) then -- You are the owner + _ExtraData.pet.tp = data:unpack('H',0x11) + end +end + +parse.i[0x076] = function (data) + partybuffs = {} + for i = 0,4 do + if data:unpack('I',i*48+5) == 0 then + break + else + local index = data:unpack('H',i*48+5+4) + partybuffs[index] = { + id = data:unpack('I',i*48+5+0), + index = data:unpack('H',i*48+5+4), + buffs = {} + } + for n=1,32 do + partybuffs[index].buffs[n] = data:byte(i*48+5+16+n-1) + 256*( math.floor( data:byte(i*48+5+8+ math.floor((n-1)/4)) / 4^((n-1)%4) )%4) + end + + + if alliance[1] then + local cur_player + for n,m in pairs(alliance[1]) do + if type(m) == 'table' and m.mob and m.mob.index == index then + cur_player = m + break + end + end + local new_buffs = convert_buff_list(partybuffs[index].buffs) + if cur_player and cur_player.buffactive and not gearswap_disabled then + local old_buffs = cur_player.buffactive + -- Make sure the character existed before (with a buffactive list) - Avoids zoning. + for n,m in pairs(new_buffs) do + if type(n) == 'number' and m ~= old_buffs[n] then + if not old_buffs[n] or m > old_buffs[n] then -- gaining buff + equip_sets('party_buff_change',nil,cur_player,res.buffs[n][language],true,copy_entry(res.buffs[n])) + old_buffs[n] = nil + else -- losing buff + equip_sets('party_buff_change',nil,cur_player,res.buffs[n][language],false,copy_entry(res.buffs[n])) + old_buffs[n] = nil + end + elseif type(n) ~= 'number' then + -- Clear out the string entries so we don't have to iterate over them in the second loop + old_buffs[n] = nil + end + end + + for n,m in pairs(old_buffs) do + if type(n) == 'number' and m ~= new_buffs[n] then-- losing buff + equip_sets('party_buff_change',nil,cur_player,res.buffs[n][language],false,copy_entry(res.buffs[n])) + end + end + end + if cur_player then + cur_player.buffactive = new_buffs + end + end + + end + end +end + +parse.i[0x0DF] = function (data) + if data:unpack('I',5) == player.id then + player.vitals.hp = data:unpack('I',9) + player.vitals.mp = data:unpack('I',13) + player.vitals.tp = data:unpack('I',0x11) + player.vitals.hpp = data:byte(0x17) + player.vitals.mpp = data:byte(0x18) + + player.hp = data:unpack('I',9) + player.mp = data:unpack('I',13) + player.tp = data:unpack('I',0x11) + player.hpp = data:byte(0x17) + player.mpp = data:byte(0x18) + end +end + +parse.i[0x0E2] = function (data) + if data:unpack('I',5)==player.id then + player.vitals.hp = data:unpack('I',9) + player.vitals.mp = data:unpack('I',0xB) + player.vitals.tp = data:unpack('I',0x11) + player.vitals.hpp = data:byte(0x1E) + player.vitals.mpp = data:byte(0x1F) + + player.hp = data:unpack('I',9) + player.mp = data:unpack('I',0xB) + player.tp = data:unpack('I',0x11) + player.hpp = data:byte(0x1E) + player.mpp = data:byte(0x1F) + end +end + +parse.o[0x100] = function(data) + -- Scrub the equipment array if a valid outgoing job change packet is sent. + local newmain = data:byte(5) + if res.jobs[newmain] and newmain ~= 0 and newmain ~= player.main_job_id then + windower.debug('job change') + + command_registry = Command_Registry.new() + + table.clear(not_sent_out_equip) + table.clear(equip_list_history) + table.clear(equip_list) + player.main_job_id = newmain + update_job_names() + for i=0,15 do + injected_equipment_registry[i]:clear() + items.equipment[default_slot_map[i]] = {bag_id=0,slot=empty} + end + windower.send_command('lua i '.._addon.name..' load_user_files '..newmain) + end + + + if gearswap_disabled then return end + + local newmain = data:byte(5) + if res.jobs[newmain] and newmain ~= player.main_job_id then + command_enable('main','sub','range','ammo','head','neck','lear','rear','body','hands','lring','rring','back','waist','legs','feet') -- enable all slots + end +end + +function initialize_packet_parsing() + for i,v in pairs(parse.i) do + if i ~= 0x028 then + local lastpacket = windower.packets.last_incoming(i) + if lastpacket then + v(lastpacket) + end + if i == 0x63 and lastpacket and lastpacket:byte(5) ~= 9 then + -- Not receiving an accurate buff line on load because the wrong 0x063 packet was sent last + + end + end + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/refresh.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/refresh.lua new file mode 100644 index 0000000..9fb5e95 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/refresh.lua @@ -0,0 +1,727 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +-- Deals with refreshing player information and loading user settings -- + + + +----------------------------------------------------------------------------------- +--Name: refresh_globals() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None +---- Updates all global variables to reflect the player's status. Generally run +---- before calling a player function. +----------------------------------------------------------------------------------- +function refresh_globals(user_event_flag) + local current = os.clock() + local dt = current - last_refresh + if not user_event_flag or dt > 0.05 then + refresh_player(dt,user_event_flag) + refresh_ffxi_info(dt,user_event_flag) + refresh_group_info(dt,user_event_flag) + last_refresh = current + end +end + +----------------------------------------------------------------------------------- +--Name: load_user_files() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- user_env, a table of all of the player defined functions and their current +---- variables. +----------------------------------------------------------------------------------- +function load_user_files(job_id,user_file) + job_id = tonumber(job_id) + + if current_file then + user_pcall('file_unload',current_file) + end + + for i in pairs(registered_user_events) do + unregister_event_user(i) + end + + for i in pairs(__raw.text.registry) do + windower.text.delete(i) + end + + for i in pairs(__raw.prim.registry) do + windower.prim.delete(i) + end + + current_file = nil + sets = nil + user_env = nil + unhandled_command_events = {} + --registered_user_events = {} + include_user_path = nil + + language = 'english' -- Reset language to english when changing job files. + refresh_globals() + + if job_id and res.jobs[job_id] then + player.main_job_id = job_id + update_job_names() + end + + + local path,base_dir,filename + path,base_dir,filename = pathsearch({user_file}) + if not path then + local long_job = res.jobs[job_id].english + local short_job = res.jobs[job_id].english_short + local tab = {player.name..'_'..short_job..'.lua',player.name..'-'..short_job..'.lua', + player.name..'_'..long_job..'.lua',player.name..'-'..long_job..'.lua', + player.name..'.lua',short_job..'.lua',long_job..'.lua','default.lua'} + path,base_dir,filename = pathsearch(tab) + end + + if not path then + current_file = nil + sets = nil + return + end + + user_env = {gearswap = _G, _global = _global, _settings = _settings,_addon=_addon, + -- Player functions + equip = equip, cancel_spell=cancel_spell, change_target=change_target, cast_delay=cast_delay, + print_set=print_set,set_combine=set_combine,disable=disable,enable=user_enable, + send_command=send_cmd_user,windower=user_windower,include=include_user, + midaction=user_midaction,pet_midaction=user_pet_midaction,set_language=set_language, + show_swaps = show_swaps,debug_mode=debug_mode,include_path=user_include_path, + register_unhandled_command=user_unhandled_command,move_spell_target=move_spell_target, + language=language, + + -- Library functions + string=string,math=math,table=table,set=set,list=list,T=T,S=S,L=L,pack=pack,functions=functions, + os=os,texts=texts,bit=bit,type=type,tostring=tostring,tonumber=tonumber,pairs=pairs, + ipairs=ipairs, print=print, add_to_chat=add_to_chat_user,unpack=unpack,next=next, + select=select,lua_base_path=windower.addon_path,empty=empty,file=file, + loadstring=loadstring,assert=assert,error=error,pcall=pcall,io=io,dofile=dofile, + + debug=debug,coroutine=coroutine,setmetatable=setmetatable,getmetatable=getmetatable, + rawset=rawset,rawget=rawget,require=include_user, + _libs=_libs, + + -- Player environment things + buffactive=buffactive, + player=player, + world=world, + pet=pet, + fellow=fellow, + alliance=alliance, + party=alliance[1], + sets={naked = {main=empty,sub=empty,range=empty,ammo=empty, + head=empty,neck=empty,ear1=empty,ear2=empty, + body=empty,hands=empty,ring1=empty,ring2=empty, + back=empty,waist=empty,legs=empty,feet=empty}} + } + + user_env['_G'] = user_env + + -- Try to load data/<name>_<main job>.lua + local funct, err = loadfile(path) + + -- If the file cannot be loaded, print the error and load the default. + if funct == nil then + print('User file problem: '..err) + current_file = nil + sets = nil + return + else + current_file = filename + print('GearSwap: Loaded your '..current_file..' file!') + end + + setfenv(funct, user_env) + + -- Verify that funct contains functions. + local status, plugin = pcall(funct) + + if not status then + error('GearSwap: File failed to load: \n'..plugin) + sets = nil + return nil + end + + _global.pretarget_cast_delay = 0 + _global.precast_cast_delay = 0 + _global.cancel_spell = false + _global.current_event = 'get_sets' + user_pcall('get_sets') + + gearswap_disabled = false + sets = user_env.sets +end + + +----------------------------------------------------------------------------------- +--Name: refresh_player() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None +---- +---- Loads player from windower.ffxi.get_player(). +---- Adds in a "job", "race", "equipment", "target", and "subtarget" field +---- Also updates "pet" and assigns isvalid and element fields. +---- Further converts player.buffs to buffactive. +-------- Indexes buffs by their buff name and assigns a value equal to the number +-------- of buffs with that name active. +----------------------------------------------------------------------------------- +function refresh_player(dt,user_event_flag) + local pl, player_mob_table + if not user_event_flag or dt > 0.5 then + pl = windower.ffxi.get_player() + if not pl or not pl.vitals then return end + + player_mob_table = windower.ffxi.get_mob_by_index(pl.index) + if not player_mob_table then return end + + table.reassign(player,pl) + for i,v in pairs(player.vitals) do + player[i]=v + end + update_job_names() + player.status_id = player.status + if res.statuses[player.status] then + player.status = res.statuses[player.status].english + else + print(player.status_id) + end + player.nation_id = player.nation + player.nation = res.regions[player.nation_id][language] or 'None' + + for i,v in pairs(player_mob_table) do + if i == 'name' then + player.mob_name = v + elseif i~= 'is_npc' and i~='tp' and i~='mpp' and i~='claim_id' and i~='status' then + player[i] = v + end + end + + if player_mob_table.race ~= nil then + player.race_id = player.race + player.race = res.races[player.race][language] + end + + -- If we have a pet, create or update the table info. + if player_mob_table and player_mob_table.pet_index then + local player_pet_table = windower.ffxi.get_mob_by_index(player_mob_table.pet_index) + if player_pet_table then + table.reassign(pet, target_complete(player_pet_table)) + pet.claim_id = nil + pet.is_npc = nil + pet.isvalid = true + if pet.tp then pet.tp = pet.tp/10 end + + if avatar_element[pet.name] then + pet.element = res.elements[avatar_element[pet.name]][language] + else + pet.element = res.elements[-1][language] -- Physical + end + else + table.reassign(pet, {isvalid=false}) + end + else + table.reassign(pet, {isvalid=false}) + end + + if player.main_job_id == 18 or player.sub_job_id == 18 then + local auto_tab + if player.main_job_id == 18 then auto_tab = windower.ffxi.get_mjob_data() + else auto_tab = windower.ffxi.get_sjob_data() end + + if auto_tab.name then + for i,v in pairs(auto_tab) do + if not T{'available_heads','attachments','available_frames','available_attachments','frame','head'}:contains(i) then + pet[i] = v + end + end + pet.available_heads = make_user_table() + pet.attachments = make_user_table() + pet.available_frames = make_user_table() + pet.available_attachments = make_user_table() + + -- available parts + for i,id in pairs(auto_tab.available_heads) do + if res.items[id] and type(res.items[id]) == 'table' then + pet.available_heads[res.items[id][language]] = true + end + end + for i,id in pairs(auto_tab.available_frames) do + if res.items[id] and type(res.items[id]) == 'table' then + pet.available_frames[res.items[id][language]] = true + end + end + for i,id in pairs(auto_tab.available_attachments) do + if res.items[id] and type(res.items[id]) == 'table' then + pet.available_attachments[res.items[id][language]] = true + end + end + + -- actual parts + pet.head = res.items[auto_tab.head][language] + pet.frame = res.items[auto_tab.frame][language] + for i,id in pairs(auto_tab.attachments) do + if res.items[id] and type(res.items[id]) == 'table' then + pet.attachments[res.items[id][language]] = true + end + end + + if pet.max_mp ~= 0 then + pet.mpp = math.floor(pet.mp/pet.max_mp*100) + else + pet.mpp = 0 + end + end + elseif player.main_job_id == 23 then + local species_id = windower.ffxi.get_mjob_data().species + -- Should add instincts when they become available + + if species_id then + player.species = {} + for i,v in pairs(res.monstrosity[species_id]) do + player.species[i] = v + end + player.species.name = player.species[language] + player.species.tp_moves = copy_entry(res.monstrosity[species_id].tp_moves) + for i,v in pairs(player.species.tp_moves) do + if v > player.main_job_level then + player.species.tp_moves[i] = nil + end + end + end + else + player.species = nil + end + end + + -- This being nil does not cause a return, but items should not really be changing when zoning. + local cur_equip = table.reassign({},items.equipment) + + -- Assign player.equipment to be the gear that has been sent out and the server currently thinks + -- you are wearing. (the sent_out_equip for loop above). + player.equipment = make_user_table() + table.reassign(player.equipment,to_names_set(cur_equip)) + + -- Assign player.inventory to be keyed to item.inventory[i][language] and to have a value of count, similar to buffactive + for i,bag in pairs(res.bags) do + local bag_name = to_windower_bag_api(bag.en) + if items[bag_name] then player[bag_name] = refresh_item_list(items[bag_name]) end + end + + -- Monster tables for the target and subtarget. + player.target = target_complete(windower.ffxi.get_mob_by_target('t')) + player.subtarget = target_complete(windower.ffxi.get_mob_by_target('st')) + player.last_subtarget = target_complete(windower.ffxi.get_mob_by_target('lastst')) + + + + table.reassign(fellow,target_complete(windower.ffxi.get_mob_by_target('<ft>'))) + if fellow.name then + fellow.isvalid = true + else + fellow.isvalid=false + end + + table.reassign(buffactive,convert_buff_list(player.buffs)) + + for global_variable_name,extradatatable in pairs(_ExtraData) do + if _G[global_variable_name] then + for sub_variable_name,value in pairs(extradatatable) do + _G[global_variable_name][sub_variable_name] = value + end + end + end +end + +----------------------------------------------------------------------------------- +--Name: refresh_ffxi_info() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None +---- +---- Updates the global "world" with windower.ffxi.get_info (ignores the target field). +---- Also sets windower.ffxi.get_info()['zone'] to be world.area for consistency with spellcast +----------------------------------------------------------------------------------- +function refresh_ffxi_info(dt,user_event_flag) + local info = windower.ffxi.get_info() + for i,v in pairs(info) do + if i == 'zone' and res.zones[v] then + world.zone = res.zones[v][language] + world.area = world.zone + elseif i == 'weather' and res.weather[v] then + weather_update(v) + elseif i == 'day' and res.days[v] then + world.day = res.days[v][language] + world.day_element = res.elements[res.days[v].element][language] + elseif i == 'moon' then + world.moon_pct = v + elseif i == 'moon_phase' and res.moon_phases[v] then + world.moon = res.moon_phases[v][language] + elseif i ~= 'target' then + world[i] = v + end + end + + for global_variable_name,extradatatable in pairs(_ExtraData) do + if _G[global_variable_name] then + for sub_variable_name,value in pairs(extradatatable) do + _G[global_variable_name][sub_variable_name] = value + end + end + end +end + + +----------------------------------------------------------------------------------- +--Name: weather_update(id) +--Args: +---- id Current weather ID +----------------------------------------------------------------------------------- +--Returns: +---- None, updates the table. +----------------------------------------------------------------------------------- +function weather_update(id) + world.weather_id = id + world.real_weather_id = id + world.real_weather = res.weather[id][language] + world.real_weather_element = res.elements[res.weather[id].element][language] + world.real_weather_intensity = res.weather[world.real_weather_id].intensity + if buffactive[178] then + world.weather_id = 4 + elseif buffactive[179] then + world.weather_id = 12 + elseif buffactive[180] then + world.weather_id = 10 + elseif buffactive[181] then + world.weather_id = 8 + elseif buffactive[182] then + world.weather_id = 14 + elseif buffactive[183] then + world.weather_id = 6 + elseif buffactive[184] then + world.weather_id = 16 + elseif buffactive[185] then + world.weather_id = 18 + elseif buffactive[589] then + world.weather_id = 5 + elseif buffactive[590] then + world.weather_id = 13 + elseif buffactive[591] then + world.weather_id = 11 + elseif buffactive[592] then + world.weather_id = 9 + elseif buffactive[593] then + world.weather_id = 15 + elseif buffactive[594] then + world.weather_id = 7 + elseif buffactive[595] then + world.weather_id = 17 + elseif buffactive[596] then + world.weather_id = 19 + end + world.weather = res.weather[world.weather_id][language] + world.weather_element = res.elements[res.weather[world.weather_id].element][language] + world.weather_intensity = res.weather[world.weather_id].intensity +end + + +----------------------------------------------------------------------------------- +--Name: refresh_group_info() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None +---- +---- Takes the mob arrays from windower.ffxi.get_party() and splits them from p0~5, a10~15, a20~25 +---- into alliance[1][1~6], alliance[2][1~6], alliance[3][1~6], respectively. +---- Also adds a "count" field to alliance (total number of people in alliance) and +---- to the individual subtables (total number of people in each party. +----------------------------------------------------------------------------------- +function refresh_group_info(dt,user_event_flag) + if not alliance or #alliance == 0 then + alliance = make_alliance() + end + + local c_alliance = make_alliance() + + local j = windower.ffxi.get_party() or {} + + c_alliance.leader = j.alliance_leader -- Test whether this works + c_alliance[1].leader = j.party1_leader + c_alliance[2].leader = j.party2_leader + c_alliance[3].leader = j.party3_leader + + for i,v in pairs(j) do + if type(v) == 'table' and v.mob and v.mob.race then + v.mob.race_id = v.mob.race + v.mob.race = res.races[v.mob.race][language] + end + + local allyIndex + local partyIndex + + -- For 'p#', ally index is 1, party index is the second char + if i:sub(1,1) == 'p' and tonumber(i:sub(2)) then + allyIndex = 1 + partyIndex = tonumber(i:sub(2))+1 + -- For 'a##', ally index is the second char, party index is the third char + elseif tonumber(i:sub(2,2)) and tonumber(i:sub(3)) then + allyIndex = tonumber(i:sub(2,2))+1 + partyIndex = tonumber(i:sub(3))+1 + end + + if allyIndex and partyIndex then + if v.mob and partybuffs[v.mob.index] then + v.buffactive = convert_buff_list(partybuffs[v.mob.index].buffs) + elseif v.mob and v.mob.index == player.index then + v.buffactive = buffactive + end + c_alliance[allyIndex][partyIndex] = v + c_alliance[allyIndex].count = c_alliance[allyIndex].count + 1 + c_alliance.count = c_alliance.count + 1 + + if v.mob then + if v.mob.id == c_alliance[1].leader then + c_alliance[1].leader = v + elseif v.mob.id == c_alliance[2].leader then + c_alliance[2].leader = v + elseif v.mob.id == c_alliance[3].leader then + c_alliance[3].leader = v + end + + if v.mob.id == c_alliance.leader then + c_alliance.leader = v + end + end + end + end + + + -- Clear the old structure while maintaining the party references: + for ally_party = 1,3 do + for i,v in pairs(alliance[ally_party]) do + alliance[ally_party][i] = nil + end + alliance[ally_party].count = 0 + end + alliance.count = 0 + alliance.leader = nil + + -- Reassign to the new structure + table.reassign(alliance[1],c_alliance[1]) + table.reassign(alliance[2],c_alliance[2]) + table.reassign(alliance[3],c_alliance[3]) + alliance.count = c_alliance.count + alliance.leader = c_alliance.leader +end + +----------------------------------------------------------------------------------- +--Name: make_alliance() +--Args: +---- none +----------------------------------------------------------------------------------- +--Returns: +---- one blank alliance structure +----------------------------------------------------------------------------------- +function make_alliance() + local all = make_user_table() + all[1]={count=0,leader=nil} + all[2]={count=0,leader=nil} + all[3]={count=0,leader=nil} + all.count=0 + all.leader=nil + return all +end + +----------------------------------------------------------------------------------- +--Name: convert_buff_list(bufflist) +--Args: +---- bufflist (table): List of buffs from windower.ffxi.get_player()['buffs'] +----------------------------------------------------------------------------------- +--Returns: +---- buffarr (table) +---- buffarr is indexed by the string buff name and has a value equal to the number +---- of that string present in the buff array. So two marches would give +---- buffarr.march==2. +----------------------------------------------------------------------------------- +function convert_buff_list(bufflist) + local buffarr = {} + for i,id in pairs(bufflist) do + if res.buffs[id] then -- For some reason we always have buff 255 active, which doesn't have an entry. + local buff = res.buffs[id][language]:lower() + if buffarr[buff] then + buffarr[buff] = buffarr[buff] +1 + else + buffarr[buff] = 1 + end + + if buffarr[id] then + buffarr[id] = buffarr[id] +1 + else + buffarr[id] = 1 + end + end + end + return buffarr +end + +----------------------------------------------------------------------------------- +--Name: refresh_item_list(itemlist) +--Args: +---- itemlist (table): List of items from windower.ffxi.get_items().something +----------------------------------------------------------------------------------- +--Returns: +---- retarr (table) +---- retarr is indexed by the item name, and contains a table with the +---- item id, count, and short name. If the long name for the item is +---- different than the short name, an additional entry is added for +---- that. +---- +---- Overall, this allows doing simple existance checks for both short +---- and long name (eg: player.inventory["Theo. Cap +1"] and +---- player.inventory["Theopany Cap +1"] both return the same table, +---- and both would be 'true' in a conditional check)), and get the item +---- count (player.inventory["Orichalc. Bullet"].count) +---- It also allows one to check for the alternate spelling of an item. +----------------------------------------------------------------------------------- +function refresh_item_list(itemlist) + retarr = make_user_table() + for i,v in pairs(itemlist) do + if type(v) == 'table' and v.id and v.id ~= 0 then + -- If we don't already have the primary item name in the table, add it. + if res.items[v.id] and res.items[v.id][language] and not retarr[res.items[v.id][language]] then + retarr[res.items[v.id][language]] = table.copy(v) + retarr[res.items[v.id][language]].shortname=res.items[v.id][language]:lower() + -- If a long version of the name exists, and is different from the short version, + -- add the long name to the info table and point the long name's key at that table. + if res.items[v.id][language..'_log'] and res.items[v.id][language..'_log']:lower() ~= res.items[v.id][language]:lower() then + retarr[res.items[v.id][language]].longname = res.items[v.id][language..'_log']:lower() + retarr[res.items[v.id][language..'_log']] = retarr[res.items[v.id][language]] + end + elseif res.items[v.id] and res.items[v.id][language] then + -- If there's already an entry for this item, all the hard work has already + -- been done. Just update the count on the subtable of the main item, and + -- everything else will link together. + retarr[res.items[v.id][language]].count = retarr[res.items[v.id][language]].count + v.count + end + end + end + return retarr +end + +----------------------------------------------------------------------------------- +--Name: refresh_user_env() +--Args: +---- none +----------------------------------------------------------------------------------- +--Returns: +---- none, but loads user files if they exist. +----------------------------------------------------------------------------------- +function refresh_user_env(job_id) + refresh_globals() + if not job_id then job_id = windower.ffxi.get_player().main_job_id end + + if not job_id then + windower.send_command('@wait 1;lua i '.._addon.name..' refresh_user_env') + else + load_user_files(job_id) + end +end + + +----------------------------------------------------------------------------------- +--Name: pathsearch() +--Args: +---- files_list - table of strings of the file name to search. +----------------------------------------------------------------------------------- +--Returns: +---- path of a valid file, if it exists. False if it doesn't. +----------------------------------------------------------------------------------- +function pathsearch(files_list) + + -- base directory search order: + -- windower + -- %appdata%/Windower/GearSwap + + -- sub directory search order: + -- libs-dev (only in windower addon path) + -- libs (only in windower addon path) + -- data/player.name + -- data/common + -- data + + local gearswap_data = windower.addon_path .. 'data/' + local gearswap_appdata = (os.getenv('APPDATA') or '') .. '/Windower/GearSwap/' + + local search_path = { + [1] = windower.addon_path .. 'libs-dev/', + [2] = windower.addon_path .. 'libs/', + [3] = gearswap_data .. player.name .. '/', + [4] = gearswap_data .. 'common/', + [5] = gearswap_data, + [6] = gearswap_appdata .. player.name .. '/', + [7] = gearswap_appdata .. 'common/', + [8] = gearswap_appdata, + [9] = windower.windower_path .. 'addons/libs/' + } + + local user_path + local normal_path + + for _,basepath in ipairs(search_path) do + if windower.dir_exists(basepath) then + for i,v in ipairs(files_list) do + if v ~= '' then + if include_user_path then + user_path = basepath .. include_user_path .. '/' .. v + end + normal_path = basepath .. v + + if user_path and windower.file_exists(user_path) then + return user_path,basepath,v + elseif normal_path and windower.file_exists(normal_path) then + return normal_path,basepath,v + end + end + end + end + end + + return false +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/statics.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/statics.lua new file mode 100644 index 0000000..2e9e46d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/statics.lua @@ -0,0 +1,447 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +-- Convert the spells and job abilities into a referenceable list of aliases -- + +unify_prefix = {['/ma'] = '/ma', ['/magic']='/ma',['/jobability'] = '/ja',['/ja']='/ja',['/item']='/item',['/song']='/ma', + ['/so']='/ma',['/ninjutsu']='/ma',['/weaponskill']='/ws',['/ws']='/ws',['/ra']='/ra',['/rangedattack']='/ra',['/nin']='/ma', + ['/throw']='/ra',['/range']='/ra',['/shoot']='/ra',['/monsterskill']='/ms',['/ms']='/ms',['/pet']='/ja',['Monster']='Monster',['/bstpet']='/ja'} + +action_type_map = {['/ja']='Ability',['/jobability']='Ability',['/so']='Magic',['/song']='Magic',['/ma']='Magic',['/magic']='Magic',['/nin']='Magic',['/ninjutsu']='Magic', + ['/ra']='Ranged Attack',['/range']='Ranged Attack',['/throw']='Ranged Attack',['/shoot']='Ranged Attack',['/ms']='Ability',['/monsterskill']='Ability', + ['/ws']='Ability',['/weaponskill']='Ability',['/item']='Item',['/pet']='Ability',['/bstpet']='Ability',['Monster']='Monster Move'} + +usable_item_bags = { + res.bags[3], -- Temporary Items + res.bags[0], -- Inventory + res.bags[8], -- Wardrobe 1 + res.bags[10], -- Wardrobe 2 + res.bags[11], -- Wardrobe 3 + res.bags[12]} -- Wardrobe 4 + +equippable_item_bags = { + res.bags[0], -- Inventory + res.bags[8], -- Wardrobe 1 + res.bags[10], -- Wardrobe 2 + res.bags[11], -- Wardrobe 3 + res.bags[12]} -- Wardrobe 4 + +bag_string_lookup = {} +for i,v in pairs(res.bags) do + bag_string_lookup[to_windower_bag_api(v.en)]=i +end + +bstpet_range = {min=672,max=798} -- Range of the JA resource devoted to BST jugpet abilities + +delay_map_to_action_type = {['Ability']=3,['Magic']=20,['Ranged Attack']=10,['Item']=10,['Monster Move']=10,['Interruption']=3} + +validabils = {} +validabils['english'] = {['/ma'] = {}, ['/ja'] = {}, ['/ws'] = {}, ['/item'] = {}, ['/ra'] = {}, ['/ms'] = {}, ['/pet'] = {}, ['/trig'] = {}, ['/echo'] = {}} +validabils['french'] = {['/ma'] = {}, ['/ja'] = {}, ['/ws'] = {}, ['/item'] = {}, ['/ra'] = {}, ['/ms'] = {}, ['/pet'] = {}, ['/trig'] = {}, ['/echo'] = {}} +validabils['german'] = {['/ma'] = {}, ['/ja'] = {}, ['/ws'] = {}, ['/item'] = {}, ['/ra'] = {}, ['/ms'] = {}, ['/pet'] = {}, ['/trig'] = {}, ['/echo'] = {}} +validabils['japanese'] = {['/ma'] = {}, ['/ja'] = {}, ['/ws'] = {}, ['/item'] = {}, ['/ra'] = {}, ['/ms'] = {}, ['/pet'] = {}, ['/trig'] = {}, ['/echo'] = {}} + +function make_abil(abil,lang,i) + if not abil[lang] or not abil.prefix then return end + local sp,pref = abil[lang]:lower(), unify_prefix[abil.prefix:lower()] + validabils[lang][pref][sp] = i +end + +function make_entry(v,i) + make_abil(v,'english',i) + make_abil(v,'german',i) + make_abil(v,'french',i) + make_abil(v,'japanese',i) +end + +for i,v in pairs(res.spells) do + if not T{363,364}:contains(i) then + make_entry(v,i) + end +end + +for i,v in pairs(res.job_abilities) do + make_entry(v,i) +end + +for i,v in pairs(res.weapon_skills) do + v.type = 'WeaponSkill' + make_entry(v,i) +end + +for i,v in pairs(res.monster_skills) do + v.type = 'MonsterSkill' + make_entry(v,i) +end + +for i,v in pairs(res.items) do + v.prefix = '/item' + if not validabils['english'][v.prefix][v.english:lower()] or v.cast_delay then + make_entry(v,i) + end +end + + -- Should transition these slot maps to be based off res.slots, but it's very unlikely to change. +default_slot_map = T{'sub','range','ammo','head','body','hands','legs','feet','neck','waist', + 'left_ear', 'right_ear', 'left_ring', 'right_ring','back'} +default_slot_map[0]= 'main' + +jas = {false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,false}-- {6,14,15} +readies = {false,false,false,false,false,false,true,true,true,false,false,true,false,false,false,false} -- {7,8,9,12} +uses = {false,true,true,true,true,false,false,false,false,false,true,false,true,false,false,false}--{2,3,4,5,11,13} +unable_to_use = T{17,18,55,56,87,88,89,90,104,191,308,313,325,410,428,561,574,579,580,581,661,665, + 12,16,34,35,40,47,48,49,71,72,76,78,84,91,92,95,96,106,111,128,154,155,190,192,193,198, + 199,215,216,217,218,219,220,233,246,247,307,315,316,328,337,338,346,347,348,349,356,411,443,444, + 445,446,514,516,517,518,523,524,525,547,568,569,575,649,660,662,666,700,701,62,717} -- Probably don't need some of these (event action) + -- 94 removed - "You must wait longer to perform that action." -- I think this is only sent in response to engage packets. + +-- 192 : param_1 = Ability ID +-- 17 : no information +-- 34 : param_1 = Spell index +pass_through_targs = {['<t>']=true,['<me>']=true,['<ft>']=true,['<scan>']=true,['<bt>']=true,['<lastst>']=true, + ['<r>']=true,['<pet>']=true,['<p0>']=true,['<p1>']=true,['<p2>']=true,['<p3>']=true,['<p4>']=true, + ['<p5>']=true,['<a10>']=true,['<a11>']=true,['<a12>']=true,['<a13>']=true,['<a14>']=true,['<a15>']=true, + ['<a20>']=true,['<a21>']=true,['<a22>']=true,['<a23>']=true,['<a24>']=true,['<a25>']=true,['<st>']=true, + ['<stnpc>']=true,['<stal>']=true,['<stpc>']=true,['<stpt>']=true} + +avatar_element = {Ifrit=0,Titan=3,Leviathan=5,Garuda=2,Shiva=1,Ramuh=4,Carbuncle=6, + Diabolos=7,Fenrir=7,['Cait Sith']=6,FireSpirit=0,EarthSpirit=3,WaterSpirit=5, + AirSpirit=2,IceSpirit=1,ThunderSpirit=4,LightSpirit=6, + DarkSpirit=7} + +encumbrance_map = {0x79,0x7F,0x7F,0x7A,0x7B,0x7C,0x7D,0x7D,0x7A,0x7E,0x80,0x80,0x80,0x80,0x7E} +encumbrance_map[0] = 0x79 -- Slots mapped onto encumbrance byte values. + +addendum_white = {[14]="Poisona",[15]="Paralyna",[16]="Blindna",[17]="Silena",[18]="Stona",[19]="Viruna",[20]="Cursna", + [143]="Erase",[13]="Raise II",[140]="Raise III",[141]="Reraise II",[142]="Reraise III",[135]="Reraise"} + +addendum_black = {[253]="Sleep",[259]="Sleep II",[260]="Dispel",[162]="Stone IV",[163]="Stone V",[167]="Thunder IV", + [168]="Thunder V",[157]="Aero IV",[158]="Aero V",[152]="Blizzard IV",[153]="Blizzard V",[147]="Fire IV",[148]="Fire V", + [172]="Water IV",[173]="Water V",[255]="Break"} + +resources_ranged_attack = {id="0",index="0",prefix="/range",english="Ranged",german="Fernwaffe",french="Attaque à dist.",japanese="飛び道具",type="Misc",element="None",targets=S{"Enemy"}} + + +-- _globals -- +user_data_table = { + __newindex = function(tab, key, val) + rawset(tab, user_key_filter(key), val) + end, + + __index = function(tab, key) + return rawget(tab, user_key_filter(key)) + end + } + +--[[eq_data_table = { + __newindex = function(tab, key, val) + rawset(tab, slot_map[user_key_filter(key)], newtab) + end, + + __index = function(tab, key) + return rawget(tab, slot_map[user_key_filter(key)]) + end + }]] + +slot_map = make_user_table() + +slot_map.main = 0 +slot_map.sub = 1 +slot_map.range = 2 +slot_map.ranged = 2 +slot_map.ammo = 3 +slot_map.head = 4 +slot_map.body = 5 +slot_map.hands = 6 +slot_map.legs = 7 +slot_map.feet = 8 +slot_map.neck = 9 +slot_map.waist = 10 +slot_map.ear1 = 11 +slot_map.ear2 = 12 +slot_map.left_ear = 11 +slot_map.right_ear = 12 +slot_map.learring = 11 +slot_map.rearring = 12 +slot_map.lear = 11 +slot_map.rear = 12 +slot_map.left_ring = 13 +slot_map.right_ring = 14 +slot_map.lring = 13 +slot_map.rring = 14 +slot_map.ring1 = 13 +slot_map.ring2 = 14 +slot_map.back = 15 + + + +gearswap_disabled = false +seen_0x063_type9 = false +delay_0x063_v9 = false +not_sent_out_equip = {} +command_registry = Command_Registry.new() +equip_list = {} +equip_list_history = {} +world = make_user_table() +buffactive = make_user_table() +alliance = make_user_table() +st_targs = {['<st>']=true,['<stpc>']=true,['<stal>']=true,['<stnpc>']=true,['<stpt>']=true} +current_file = nil +disable_table = {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false} +disable_table[0] = false +outgoing_action_category_table = {['/ma']=3,['/ws']=7,['/ja']=9,['/ra']=16,['/ms']=25} +encumbrance_table = table.reassign({},disable_table) +registered_user_events = {} +unhandled_command_events = {} +empty = {name="empty"} +--outgoing_packet_table = {} +last_refresh = 0 + +injected_equipment_registry = {} +for i=0,15 do + injected_equipment_registry[i] = L{} +end + + +_global = make_user_table() +_global.pretarget_cast_delay = 0 +_global.precast_cast_delay = 0 +_global.cancel_spell = false +_global.current_event = 'None' + +_settings = {debug_mode = false, demo_mode = false, show_swaps = false} + +-- _ExtraData is persistent information that isn't included in the windower API. +-- Because player, pet, and so forth are regularly regenerated from the windower API, +-- this table is necessary to maintain information that goes beyond the windower API. +_ExtraData = { + player = {buff_details = {}}, + pet = {}, + world = {in_mog_house = false,conquest=false}, + } + +unbridled_learning_set = {['Thunderbolt']=true,['Harden Shell']=true,['Absolute Terror']=true, + ['Gates of Hades']=true,['Tourbillion']=true,['Pyric Bulwark']=true,['Bilgestorm']=true, + ['Bloodrake']=true,['Droning Whirlwind']=true,['Carcharian Verve']=true,['Blistering Roar']=true, + ['Uproot']=true,['Crashing Thunder']=true,['Polar Roar']=true,['Mighty Guard']=true,['Cruel Joke']=true, + ['Cesspool']=true,['Tearing Gust']=true} + +tool_map = { + ['Katon: Ichi'] = res.items[1161], + ['Katon: Ni'] = res.items[1161], + ['Katon: San'] = res.items[1161], + ['Hyoton: Ichi'] = res.items[1164], + ['Hyoton: Ni'] = res.items[1164], + ['Hyoton: San'] = res.items[1164], + ['Huton: Ichi'] = res.items[1167], + ['Huton: Ni'] = res.items[1167], + ['Huton: San'] = res.items[1167], + ['Doton: Ichi'] = res.items[1170], + ['Doton: Ni'] = res.items[1170], + ['Doton: San'] = res.items[1170], + ['Raiton: Ichi'] = res.items[1173], + ['Raiton: Ni'] = res.items[1173], + ['Raiton: San'] = res.items[1173], + ['Suiton: Ichi'] = res.items[1176], + ['Suiton: Ni'] = res.items[1176], + ['Suiton: San'] = res.items[1176], + ['Utsusemi: Ichi'] = res.items[1179], + ['Utsusemi: Ni'] = res.items[1179], + ['Utsusemi: San'] = res.items[1179], + ['Jubaku: Ichi'] = res.items[1182], + ['Jubaku: Ni'] = res.items[1182], + ['Jubaku: San'] = res.items[1182], + ['Hojo: Ichi'] = res.items[1185], + ['Hojo: Ni'] = res.items[1185], + ['Hojo: San'] = res.items[1185], + ['Kurayami: Ichi'] = res.items[1188], + ['Kurayami: Ni'] = res.items[1188], + ['Kurayami: San'] = res.items[1188], + ['Dokumori: Ichi'] = res.items[1191], + ['Dokumori: Ni'] = res.items[1191], + ['Dokumori: San'] = res.items[1191], + ['Tonko: Ichi'] = res.items[1194], + ['Tonko: Ni'] = res.items[1194], + ['Tonko: San'] = res.items[1194], + ['Monomi: Ichi'] = res.items[2553], + ['Monomi: Ni'] = res.items[2553], + ['Aisha: Ichi'] = res.items[2555], + ['Myoshu: Ichi'] = res.items[2642], + ['Yurin: Ichi'] = res.items[2643], + ['Migawari: Ichi'] = res.items[2970], + ['Kakka: Ichi'] = res.items[2644], + ['Gekka: Ichi'] = res.items[8803], + ['Yain: Ichi'] = res.items[8804], + } + +universal_tool_map = { + ['Katon: Ichi'] = res.items[2971], + ['Katon: Ni'] = res.items[2971], + ['Katon: San'] = res.items[2971], + ['Hyoton: Ichi'] = res.items[2971], + ['Hyoton: Ni'] = res.items[2971], + ['Hyoton: San'] = res.items[2971], + ['Huton: Ichi'] = res.items[2971], + ['Huton: Ni'] = res.items[2971], + ['Huton: San'] = res.items[2971], + ['Doton: Ichi'] = res.items[2971], + ['Doton: Ni'] = res.items[2971], + ['Doton: San'] = res.items[2971], + ['Raiton: Ichi'] = res.items[2971], + ['Raiton: Ni'] = res.items[2971], + ['Raiton: San'] = res.items[2971], + ['Suiton: Ichi'] = res.items[2971], + ['Suiton: Ni'] = res.items[2971], + ['Suiton: San'] = res.items[2971], + ['Utsusemi: Ichi'] = res.items[2972], + ['Utsusemi: Ni'] = res.items[2972], + ['Utsusemi: San'] = res.items[2972], + ['Jubaku: Ichi'] = res.items[2973], + ['Jubaku: Ni'] = res.items[2973], + ['Jubaku: San'] = res.items[2973], + ['Hojo: Ichi'] = res.items[2973], + ['Hojo: Ni'] = res.items[2973], + ['Hojo: San'] = res.items[2973], + ['Kurayami: Ichi'] = res.items[2973], + ['Kurayami: Ni'] = res.items[2973], + ['Kurayami: San'] = res.items[2973], + ['Dokumori: Ichi'] = res.items[2973], + ['Dokumori: Ni'] = res.items[2973], + ['Dokumori: San'] = res.items[2973], + ['Tonko: Ichi'] = res.items[2972], + ['Tonko: Ni'] = res.items[2972], + ['Tonko: San'] = res.items[2972], + ['Monomi: Ichi'] = res.items[2972], + ['Aisha: Ichi'] = res.items[2973], + ['Myoshu: Ichi'] = res.items[2972], + ['Yurin: Ichi'] = res.items[2973], + ['Migawari: Ichi'] = res.items[2972], + ['Kakka: Ichi'] = res.items[2972], + ['Gekka: Ichi'] = res.items[2972], + ['Yain: Ichi'] = res.items[2972], + } + +region_to_zone_map = { + [4] = S{100,101,139,140,141,142,167,190}, + [5] = S{102,103,108,193,196,248}, + [6] = S{1,2,104,105,149,150,195}, + [7] = S{106,107,143,144,172,173,191}, + [8] = S{109,110,147,148,197}, + [9] = S{115,116,145,146,169,170,192,194}, + [10] = S{3,4,117,118,198,213,249}, + [11] = S{7,8,119,120,151,152,200}, + [12] = S{9,10,111,166,203,204,206}, + [13] = S{5,6,112,161,162,165}, + [14] = S{126,127,157,158,179,184}, + [15] = S{121,122,153,154,202,251}, + [16] = S{114,125,168,208,209,247}, + [17] = S{113,128.174,201,212}, + [18] = S{123,176,250,252}, + [19] = S{124,159,160,163,205,207,211}, + [20] = S{130,177,178,180,181}, + [22] = S{11,12,13}, + [24] = S{24,25,26,27,28,29,30,31,32}, + } + + +function initialize_globals() + local pl = windower.ffxi.get_player() + if not pl then + player = make_user_table() + player.vitals = {} + player.buffs = {} + player.skills = {} + player.jobs = {} + player.merits = {} + else + player = make_user_table() + table.reassign(player,pl) + if not player.vitals then player.vitals = {} end + if not player.buffs then player.buffs = {} end + if not player.skills then player.skills = {} end + if not player.jobs then player.jobs = {} end + if not player.merits then player.merits = {} end + end + + player.equipment = make_user_table() + pet = make_user_table() + pet.isvalid = false + fellow = make_user_table() + fellow.isvalid = false + partybuffs = {} + + -- GearSwap effectively needs to maintain two inventory structures: + -- one is the proposed current inventory based on equip packets sent to the server, + -- the other is the currently reported inventory based on packets sent from the server. + -- The problem with proposed_inv is that it doesn't know when actions force items to unequip or prevent them from equipping. + -- The problem with reported_inv is that packets can be dropped, so it doesn't always report everything accurately. + -- In an ideal world, gearswap would maintain a registry of expected changes for each slot, + -- and would advance along the registry as changes are reported by the server. + items = windower.ffxi.get_items() + if not items then + items = { + equipment = {}, + } + for id,name in pairs(default_slot_map) do + items.equipment[name] = {slot = empty,bag_id=0} + end + else + if not items.equipment then + items.equipment = {} + for id,name in pairs(default_slot_map) do + items.equipment[name] = {slot = empty,bag_id=0} + end + else + for id,name in pairs(default_slot_map) do + items.equipment[name] = { + slot = items.equipment[name], + bag_id = items.equipment[name..'_bag'] + } + items.equipment[name..'_bag'] = nil + if items.equipment[name].slot == 0 then items.equipment[name].slot = empty end + end + end + end + for i in pairs(windower.ffxi.get_bag_info()) do + if not items[i] then items[i] = make_inventory_table() + else items[i][0] = make_empty_item_table(0) end + end + + local wo = windower.ffxi.get_info() + if wo then + for i,v in pairs(region_to_zone_map) do + if v:contains(wo.zone) then + _ExtraData.world.conquest = { + region_id = i, + region_name = res.regions[i][language], + } + break + end + end + end +end + +initialize_globals() diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/targets.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/targets.lua new file mode 100644 index 0000000..3e47b74 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/targets.lua @@ -0,0 +1,149 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +-- Target Processing -- + +function valid_target(targ) + local spelltarget = {} + + local spell_targ + if pass_through_targs[targ] then + local j = windower.ffxi.get_mob_by_target(targ) + + if j then spelltarget = target_complete(j) end + + spelltarget.raw = targ + return targ, spelltarget + elseif targ and tonumber(targ) and tonumber(targ) > 255 then + local j = windower.ffxi.get_mob_by_id(tonumber(targ)) + + if j then spelltarget = target_complete(j) end + + spelltarget.raw = targ + return targ, spelltarget + elseif targ and not tonumber(targ) and targ ~= '' then + local mob_array = windower.ffxi.get_mob_array() + for i,v in pairs(mob_array) do + if v.name:lower()==targ:lower() and (not v.is_npc or v.spawn_type == 14) then + spelltarget = target_complete(v) + spelltarget.raw = targ + return targ, spelltarget + end + end + end + return false, false +end + +function target_complete(mob_table) + if mob_table == nil then return {type = 'NONE'} end + + ------------------------------- Should consider moving the partycount part of this code to refresh_player() ---------------------------------- + mob_table.isallymember = false + if not mob_table.id then + mob_table.type = 'NONE' + else + local j = windower.ffxi.get_party() + + for i,v in pairs(j) do + if type(v) == 'table' and v.mob then + if v.mob.id == mob_table.id then + mob_table.isallymember = true + if i:sub(1,1) == 'p' then + mob_table.ispartymember = true + end + end + end + end + ------------------------------------------------------------------------------------------------------------------------------------ + + if player.id == mob_table.id then + mob_table.type = 'SELF' + elseif mob_table.is_npc then + if mob_table.id%4096>2047 then + mob_table.type = 'NPC' + else + mob_table.type = 'MONSTER' + end + else + mob_table.type = 'PLAYER' + end + end + + if mob_table.race then + mob_table.race_id = mob_table.race + if res.races[mob_table.race] then + mob_table.race = res.races[mob_table.race][language] + else + mob_table.race = 'Unknown' + end + end + if mob_table.status then + mob_table.status_id = mob_table.status + if res.statuses[mob_table.status] then + mob_table.status = res.statuses[mob_table.status].english + else + mob_table.status = 'Unknown' + end + end + if mob_table.distance then + mob_table.distance = math.sqrt(mob_table.distance) + end + return mob_table +end + +function target_type_check(spell) + --[[ Spawn type mapping: + 1 = Other players + 2 = Town NPCs, AH counters, Logging Points, etc. + Bit 1 = 1 PC + Bit 2 = 2 NPC (not attackable) + Bit 3 = 4 Party Member + Bit 4 = 8 Ally + Bit 5 = 16 Enemy + Bit 6 = 32 Door (Environment) + 13 = Self + 14 = Trust NPC in party + 16 = Monsters + 34 = Some doors + ]] + + local temptype = spell.target.type + if temptype ~= 'NPC' then + temptype = temptype:lower():ucfirst() + end + + if temptype == 'Player' and spell.target.hpp == 0 then + temptype = 'Corpse' + elseif temptype == 'Player' and spell.target.ispartymember then + temptype = 'Party' + elseif temptype == 'Player' and spell.target.isallymember then + temptype = 'Ally' + end + + if spell.targets[temptype] then return true end + return false +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/triggers.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/triggers.lua new file mode 100644 index 0000000..8df14f1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/triggers.lua @@ -0,0 +1,343 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + + + +----------------------------------------------------------------------------------- +--Name: outgoing_text(original,modified,blocked,ffxi) +--Desc: Searches the client's outgoing text for GearSwap handled commands and +-- returns '' if it finds one. Otherwise returns the command unaltered. +--Args: +---- original - String entered by the user +---- modified - String after being modified by upstream addons/plugins +---- blocked - Boolean indicating whether the outgoing text is blocked upstream +---- ffxi - Boolean indicating whether the outgoing text is generated by FFXI +----------------------------------------------------------------------------------- +--Returns: +---- none or '' +----------------------------------------------------------------------------------- +windower.register_event('outgoing text',function(original,modified,blocked,ffxi,extra_stuff,extra2) + windower.debug('outgoing text') + if gearswap_disabled then return modified end + + local splitline = windower.from_shift_jis(windower.convert_auto_trans(modified)):gsub(' <wait %d+>',''):gsub('"(.-)"',function(str) + return str:gsub(' ',string.char(7)) + end):split(' '):filter(-'') + + if splitline.n == 0 then return end + + local command = splitline[1] + local bstpet = command == '/bstpet' + local unified_prefix = unify_prefix[command] + local abil, temptarg, temp_mob_arr + if splitline[2] and not bstpet then + abil = splitline[2]:gsub(string.char(7),' '):lower() -- Why am I removing \x7? + elseif splitline[2] and bstpet and tonumber(splitline[2]) then + local pet_abilities = {} + for _,v in ipairs(windower.ffxi.get_abilities().job_abilities) do + if v >= bstpet_range.min and v <= bstpet_range.max then + pet_abilities[#pet_abilities+1] = v + end + end + if pet_abilities[tonumber(splitline[2])] then + abil = res.job_abilities[pet_abilities[tonumber(splitline[2])]].name:gsub(string.char(7),' '):lower() -- .name, or .english? + end + end + + if validabils[language][unified_prefix] and validabils[language][unified_prefix][abil] then + temptarg, temp_mob_arr = valid_target(splitline[3]) + elseif validabils[language][unified_prefix] then + temptarg, temp_mob_arr = valid_target(splitline[2]) + end + + if unified_prefix and temptarg and (validabils[language][unified_prefix][abil] or unified_prefix=='/ra') then + if st_flag then + st_flag = nil + return modified + elseif temp_mob_arr then + refresh_globals() + + local r_line, find_monster_skill + + function find_monster_skill(abil) + local line = false + if player.species and player.species.tp_moves then + -- Iterates over currently available monster TP moves instead of using validabils + for i,v in pairs(player.species.tp_moves) do + if res.monster_skills[i][language]:lower() == abil then + line = copy_entry(res.monster_skills[i]) + break + end + end + end + return line + end + + if unified_prefix == '/ma' then + r_line = copy_entry(res.spells[validabils[language][unified_prefix][abil]]) + storedcommand = command..' "'..windower.to_shift_jis(r_line[language])..'" ' + elseif unified_prefix == '/ms' and find_monster_skill(abil) then + r_line = find_monster_skill(abil) + storedcommand = command..' "'..windower.to_shift_jis(r_line[language])..'" ' + elseif unified_prefix == '/ws' then + r_line = copy_entry(res.weapon_skills[validabils[language][unified_prefix][abil]]) + storedcommand = command..' "'..windower.to_shift_jis(r_line[language])..'" ' + elseif unified_prefix == '/ja' then + r_line = copy_entry(res.job_abilities[validabils[language][unified_prefix][abil]]) + if bstpet then + storedcommand = command..' '..splitline[2] + else + storedcommand = command..' "'..windower.to_shift_jis(r_line[language])..'" ' + end + elseif unified_prefix == '/item' then + r_line = copy_entry(res.items[validabils[language][unified_prefix][abil]]) + r_line.prefix = '/item' + r_line.type = 'Item' + storedcommand = command..' "'..windower.to_shift_jis(r_line[language])..'" ' + elseif unified_prefix == '/ra' then + r_line = copy_entry(resources_ranged_attack) + storedcommand = command..' ' + end + + r_line.name = r_line[language] + local spell = spell_complete(r_line) + spell.target = temp_mob_arr + spell.action_type = action_type_map[command] + + if filter_pretarget(spell) then + if tonumber(splitline[splitline.n]) then + -- If the target is a number + local ts = command_registry:new_entry(spell) + + if spell.prefix == '/item' then + -- Item use packet handling here + if bit.band(spell.target.spawn_type, 2) == 2 and find_inventory_item(spell.id) then + --0x36 packet + if spell.target.distance <= 6 then + command_registry[ts].proposed_packet = assemble_menu_item_packet(spell.target.id,spell.target.index,spell.id) + else + windower.add_to_chat(67, "Target out of range.") + return true + end + elseif find_usable_item(spell.id) then + --0x37 packet + command_registry[ts].proposed_packet = assemble_use_item_packet(spell.target.id,spell.target.index,spell.id) + end + else + command_registry[ts].proposed_packet = assemble_action_packet(spell.target.id,spell.target.index,outgoing_action_category_table[unify_prefix[spell.prefix]],spell.id,initialize_arrow_offset(spell.target)) + end + -- The packets created above should not be used. + if command_registry[ts].proposed_packet then + equip_sets('precast',ts,spell) + return true + end + else + return equip_sets('pretarget',-1,spell) + end + else + return equip_sets('filtered_action',-1,spell) + end + end + end + return modified +end) + + + +----------------------------------------------------------------------------------- +--Name: parse.i[0x028](act) +--Desc: Calls midcast or aftercast functions as appropriate in response to incoming +-- action packets. +--Args: +---- act - Action packet array (described on the dev wiki) +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +parse.i[0x028] = function (data) + local act = windower.packets.parse_action(data) + if gearswap_disabled or act.category == 1 then return end + +-- local spell_res = ActionPacket.new(act):get_spell() + + --print(((res[unpackedaction.resource] or {})[unpackedaction.spell_id] or {}).english,unpackedaction.type,unpackedaction.value,unpackedaction.interruption) + local temp_player_mob_table,temp_pet,pet_id = windower.ffxi.get_mob_by_index(player.index) + if temp_player_mob_table and temp_player_mob_table.pet_index then + temp_pet = windower.ffxi.get_mob_by_index(temp_player_mob_table.pet_index) + if temp_pet then + pet_id = temp_pet.id + end + end + + if act.actor_id ~= player.id and act.actor_id ~= pet_id then + return -- If the action is not being used by the player, the pet, or is a melee attack then abort processing. + end + + local prefix = '' + + if act.actor_id == pet_id then + prefix = 'pet_' + end + + local spell = get_spell(act) +-- if not spell_res or (spell.english ~= spell_res.english) then print('Did not match.',spell.english,spell_res) end + + if spell then logit('\n\n'..tostring(os.clock)..'(178) Event Action: '..tostring(spell[language])..' '..tostring(act.category)) + else logit('\n\nNil spell detected') end + + if spell and spell[language] then + spell.target = target_complete(windower.ffxi.get_mob_by_id(act.targets[1].id)) + spell.action_type = action_type_map[unify_prefix[spell.prefix or 'Monster']] + elseif S{84,78}:contains(act.targets[1].actions[1].message) then -- "Paralyzed" and "too far away" respectively + local ts,tab = command_registry:delete_by_id(act.targets[1].id) + if tab and tab.spell and tab.spell.prefix == '/pet' then + tab.spell.interrupted = true + equip_sets('pet_aftercast',nil,tab.spell) + elseif tab and tab.spell then + tab.spell.interrupted = true + equip_sets('aftercast',nil,tab.spell) + end + return + else + if debugging.general then windower.send_command('input /echo Incoming Action packet did not generate a spell/aftercast.')end + return + end + + --[[4 (action message) = "out of range" when attempting to melee something that's too far away + 78 (action message) = "too far away" when attempting to engage or cast magic on something that's too far away + 78 (action) = "too far away" when attempting to WS something that's too far away + 154 (action message) - "out of range" when attempting to use a JA on something that's too far away. param_1 is the JA ID]] + + -- Paralysis of JAs/spells/etc. and Out of Range messages for avatars both send two action packets when they occur. + -- The first packet is a paralysis packet that contains the message and spell-appropriate information. + -- The second packet contains the interruption code and no useful information as far as I can see. + -- The same occurs for items, except that they are both category 9 messages. + + -- For some reason avatar Out of Range messages send two packets (Category 4 and Category 7) + -- Category 4 contains real information, while Category 7 does not. + -- I do not know if this will affect automatons being interrupted. + local ts = command_registry:find_by_spell(spell) + if (jas[act.category] or uses[act.category]) then + if uses[act.category] and act.param == 28787 then + spell.action_type = 'Interruption' + spell.interrupted = true + else + spell.value = act.targets[1].actions[1].param + end + if ts then --or spell.prefix == '/item' then + -- Only aftercast things that were precasted. + -- Also, there are some actions (like being paralyzed while casting Ninjutsu) that sends two result action packets. Block the second packet. + refresh_globals() + command_registry[ts].midaction = false + equip_sets(prefix..'aftercast',ts,spell) + elseif debugging.command_registry then + msg.debugging('Hitting Aftercast without detecting an entry in command_registry') + end + elseif (readies[act.category] and act.param == 28787) then -- and not (act.category == 9 or (act.category == 7 and prefix == 'pet_'))) then + spell.action_type = 'Interruption' + spell.interrupted = true + if ts or spell.prefix == '/item' then + -- Only aftercast things that were precasted. + -- Also, there are some actions (like being paralyzed while casting Ninjutsu) that sends two result action packets. Block the second packet. + refresh_globals() + if command_registry[ts] then command_registry[ts].midaction = false end + equip_sets(prefix..'aftercast',ts,spell) + elseif debugging.command_registry then + msg.debugging('Hitting Aftercast without detecting an entry in command_registry') + end + elseif readies[act.category] and prefix == 'pet_' and act.targets[1].actions[1].message ~= 0 then + -- Entry for pet midcast. Excludes the second packet of "Out of range" BPs. + ts = command_registry:new_entry(spell) + refresh_globals() + command_registry[ts].pet_midaction = true + equip_sets('pet_midcast',ts,spell) + end +end + + + +----------------------------------------------------------------------------------- +--Name: parse.i[0x029](data) +--Desc: Responds to incoming action message packets. +--Args: +---- arr - Action message packet arguments (described on the dev wiki): + -- actor_id,target_id,param_1,param_2,param_3,actor_index,target_index,message_id) +----------------------------------------------------------------------------------- +--Returns: +---- none +----------------------------------------------------------------------------------- +parse.i[0x029] = function (data) + if gearswap_disabled then return end + local arr = {} + arr.actor_id = data:unpack('I',0x05) + arr.target_id = data:unpack('I',0x09) + arr.param_1 = data:unpack('I',0x0D) + arr.param_2 = data:unpack('I',0x11)%64 -- First 6 bits + arr.param_3 = math.floor(data:unpack('I',0x11)/64) -- Rest + arr.actor_index = data:unpack('H',0x15) + arr.target_index = data:unpack('H',0x17) + arr.message_id = data:unpack('H',0x19)%32768 + + + windower.debug('action message') + if T{6,20,113,406,605,646}:contains(arr.message_id) then -- death messages + local ts,tab = command_registry:delete_by_id(arr.target_id) + if tab and tab.spell and tab.spell.prefix == '/pet' then + equip_sets('pet_aftercast',nil,tab.spell) + elseif tab and tab.spell then + equip_sets('aftercast',nil,tab.spell) + end + return + end + + local tempplay = windower.ffxi.get_player() + local prefix = '' + if arr.actor_id ~= tempplay.id then + if tempplay.pet_index then + if arr.actor_id ~= windower.ffxi.get_mob_by_index(tempplay.pet_index).id then + return + else + prefix = 'pet_' + end + else + return + end + end + + if unable_to_use:contains(arr.message_id) then + logit('\n\n'..tostring(os.clock)..'(195) Event Action Message: '..tostring(message_id)..' Interrupt') + local ts,tab = command_registry:find_by_time() + + if tab and tab.spell then + tab.spell.interrupted = true + tab.spell.action_type = 'Interruption' + command_registry[ts].midaction = false + refresh_globals() + equip_sets(prefix..'aftercast',ts,tab.spell) + end + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/user_functions.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/user_functions.lua new file mode 100644 index 0000000..afdcf84 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/user_functions.lua @@ -0,0 +1,423 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +-- Functions that are directly exposed to users -- + + +function set_language(lang) + if _global.current_event ~= 'get_sets' then + error('\nGearSwap: set_language() is only valid in the get_sets function', 2) + return + end + if lang and type(lang) == 'string' and (lang == 'english' or lang == 'japanese') then + rawset(_G,'language',lang) + refresh_globals() + else + error('\nGearSwap: set_language() was passed an invalid value ('..tostring(lang)..'). (must be a string)', 2) + end +end + +function debug_mode(boolean) + if type(boolean) == "boolean" then _settings.debug_mode = boolean + elseif boolean == nil then + _settings.debug_mode = true + else + error('\nGearSwap: show_swaps() was passed an invalid value ('..tostring(boolean)..'). (true/no value/nil=on, false=off)', 2) + end +end + +function show_swaps(boolean) + if type(boolean) == "boolean" then _settings.show_swaps = boolean + elseif boolean == nil then + _settings.show_swaps = true + else + error('\nGearSwap: show_swaps() was passed an invalid value ('..tostring(boolean)..'). (true/no value/nil=on, false=off)', 2) + end +end + +function cancel_spell(boolean) + if _global.current_event ~= 'precast' and _global.current_event ~= 'pretarget' and _global.current_event ~= 'filtered_action' then + error('\nGearSwap: cancel_spell() is only valid in the precast, pretarget, or filtered_action functions', 2) + return + end + if type(boolean) == "boolean" then _global.cancel_spell = boolean + elseif boolean == nil then + _global.cancel_spell = true + else + error('\nGearSwap: cancel_spell() was passed an invalid value ('..tostring(boolean)..'). (true/no value/nil=Cancel the spell, false=do not cancel the spell)', 2) + end +end + +function move_spell_target(position_table) + if _global.current_event ~= 'precast' then + error('\nGearSwap: move_spell_target() is only valid in the precast function', 2) + return + end + + if type(position_table) == 'table' and type(position_table.x or position_table.X) == 'number' and + type(position_table.y or position_table.Y) == 'number' and + type(position_table.z or positino_table.Z) == 'number' then + _global.target_arrow.x = position_table.x or position_table.X + _global.target_arrow.y = position_table.y or position_table.Y + _global.target_arrow.z = position_table.z or position_table.Z + print_set(_global.target_arrow) + else + error('\nGearSwap: move_spell_target() was passed an invalid value ('..tostring(position_table)..'). Should be a table with x, y, and z keys (offset from target)', 2) + end +end + +function change_target(name) + if _global.current_event ~= 'pretarget' then + error('\nGearSwap: change_target() is only valid in the pretarget function', 2) + return + end + if name and type(name)=='string' then + if valid_target(name) then + _,_global.new_target = valid_target(name) + else + error('\nGearSwap: change_target() was passed an invalid value ('..tostring(name)..'). (must be a valid target)', 2) + end + else + error('\nGearSwap: change_target() was passed an invalid value ('..tostring(name)..'). (must be a string)', 2) + end +end + +function cast_delay(delay) + if _global.current_event ~= 'precast' and _global.current_event ~= 'pretarget' then + error('\nGearSwap: cast_delay() is only valid in the precast and pretarget functions', 2) + return + end + if tonumber(delay) then + _global[_global.current_event.."_cast_delay"] = tonumber(delay) + else + error('\nGearSwap: cast_delay() was passed an invalid value ('..tostring(delay)..'). (cast delay must be a number of seconds)', 2) + end +end + +-- Combines the provided gear sets into a new set. Returns the result. +function set_combine(...) + return set_merge(false,{}, ...) +end + +-- Combines the provided gear sets into the equip_list set. +function equip(...) + set_merge(true,equip_list, ...) +end + +function disable(...) + local disable_tab = {...} + if type(disable_tab[1]) == 'table' then + disable_tab = disable_tab[1] -- Compensates for people passing a table instead of a series of strings. + end + for i,v in pairs(disable_tab) do + if slot_map[v] then + rawset(disable_table,slot_map[v],true) + else + error('\nGearSwap: disable error, passed an unrecognized slot name. ('..tostring(v)..')',2) + end + end +end + +function enable(...) + local enable_tab = {...} + if type(enable_tab[1]) == 'table' then + enable_tab = enable_tab[1] -- Compensates for people passing a table instead of a series of strings. + end + local sending_table = {} + for i,v in pairs(enable_tab) do + local local_slot = get_default_slot(v) + if local_slot then + disable_table[toslotid(v)] = false + if not_sent_out_equip[local_slot] then + sending_table[local_slot] = not_sent_out_equip[local_slot] + not_sent_out_equip[local_slot] = nil + end + else + error('\nGearSwap: enable error, passed an unrecognized slot name. ('..tostring(v)..')',2) + end + end + + return sending_table +end + +function user_enable(...) + local sending_table = enable(...) + + if table.length(sending_table) > 0 then + equip(sending_table) + end + return sending_table +end + +function command_enable(...) + local sending_table = enable(...) + + if table.length(sending_table) > 0 then + refresh_globals() + equip_sets('equip_command',nil,sending_table) + end +end + +function print_set(set,title) + if not set then + if title then + error('\nGearSwap: print_set error, '..windower.to_shift_jis(tostring(title))..' set is nil.', 2) + else + error('\nGearSwap: print_set error, set is nil.', 2) + end + return + elseif type(set) ~= 'table' then + if title then + error('\nGearSwap: print_set error, '..windower.to_shift_jis(tostring(title))..' set is not a table.', 2) + else + error('\nGearSwap: print_set error, set is not a table.', 2) + end + end + if table.length(set) == 0 then + if title then + msg.add_to_chat(1,'------------------'.. windower.to_shift_jis(tostring(title))..' -- Empty Table -----------------') + else + msg.add_to_chat(1,'-------------------------- Empty Table -------------------------') + end + return + elseif title then + msg.add_to_chat(1,'------------------------- '..windower.to_shift_jis(tostring(title))..' -------------------------') + else + msg.add_to_chat(1,'----------------------------------------------------------------') + end + local function print_element(key,value) + if type(value) == 'table' and value.name then + msg.add_to_chat(8,windower.to_shift_jis(tostring(key))..' '..windower.to_shift_jis(tostring(value.name))..' (Adv.)') + else + msg.add_to_chat(8,windower.to_shift_jis(tostring(key))..' '..windower.to_shift_jis(tostring(value))) + end + end + local function cmp_key(key,tab) + for k in pairs(tab) do + if k:lower() == key:lower() then + return k + end + end + end + + if #set == table.length(set) then -- If it is a list (keyed by continuous whole number starting at 1), then print it out in order + for key,value in ipairs(set) do + print_element(key,value) + end + else -- Otherwise, try to print out the gear in order and then everything else. + for _,key in ipairs({'main','sub','ranged','range','ammo','head','neck','lear','ear1','learring','left_ear','rear','ear2','rearring','right_ear','body','hands','lring','ring1','left_ring','rring','ring2','right_ring','back','waist','legs','feet'}) do + local k = cmp_key(key,set) + if k then + print_element(k,set[k]) + end + end + for key,value in pairs(set) do + if not slot_map[key] then + print_element(key,set[key]) + end + end + end + msg.add_to_chat(1,'----------------------------------------------------------------') +end + +function send_cmd_user(command) + if string.byte(1) ~= 0x40 then + command='@'..command + end + windower.send_command(command) +end + +function register_event_user(str,func) + if type(func)~='function' then + error('\nGearSwap: windower.register_event() was passed an invalid value ('..tostring(func)..'). (must be a function)', 2) + elseif type(str) ~= 'string' then + error('\nGearSwap: windower.register_event() was passed an invalid value ('..tostring(str)..'). (must be a string)', 2) + end + local id = windower.register_event(str,user_equip_sets(func)) + registered_user_events[id] = true + return id +end + +function raw_register_event_user(str,func) + if type(func)~='function' then + error('\nGearSwap: windower.register_event() was passed an invalid value ('..tostring(func)..'). (must be a function)', 2) + elseif type(str) ~= 'string' then + error('\nGearSwap: windower.register_event() was passed an invalid value ('..tostring(str)..'). (must be a string)', 2) + end + local id = windower.register_event(str,setfenv(func,user_env)) + registered_user_events[id] = true + return id +end + +function unregister_event_user(id) + if type(id)~='number' then + error('\nGearSwap: windower.unregister_event() was passed an invalid value ('..tostring(id)..'). (must be a number)', 2) + end + windower.unregister_event(id) + registered_user_events[id] = nil +end + +function user_equip_sets(func) + return setfenv(function(...) + if not gearswap.gearswap_disabled then + gearswap.refresh_globals(true) + return gearswap.equip_sets(func,nil,...) + end + end,user_env) +end + +function user_unhandled_command(func) + if type(func) ~= 'function' then + error('\nGearSwap: unhandled_command was passed an invalid value ('..tostring(func)..'). (must be a function)', 2) + end + unhandled_command_events[#unhandled_command_events+1] = setfenv(func,user_env) +end + +function include_user(str, load_include_in_this_table) + if not (type(str) == 'string') then + error('\nGearSwap: include() was passed an invalid value ('..tostring(str)..'). (must be a string)', 2) + end + + str = str:lower() + if type(package.loaded[str]) == 'table' then + return package.loaded[str] + elseif T{'pack'}:contains(str) then + return + end + + if str:sub(-4)~='.lua' then str = str..'.lua' end + local path, loaded_values = pathsearch({str}) + + if not path then + error('\nGearSwap: Cannot find the include file ('..tostring(str)..').', 2) + end + + local f, err = loadfile(path) + if f and not err then + if load_include_in_this_table and type(load_include_in_this_table) == 'table' then + setmetatable(load_include_in_this_table, {__index=user_env._G}) + setfenv(f, load_include_in_this_table) + pcall(f, load_include_in_this_table) + return load_include_in_this_table + else + setfenv(f,user_env) + return f() + end + else + error('\nGearSwap: Error loading file ('..tostring(str)..'): '..err, 2) + end +end + +-- Allow the user to set a path subdirectory to check when searching for included files. +-- This path is checked as a subdirectory to each fixed path, before the fixed path itself is checked. +-- Path argument can only be a string; otherwise this is set to nil. +function user_include_path(path) + if type(path) == 'string' then + include_user_path = path + else + include_user_path = nil + end +end + + +function user_midaction(bool) + if bool == false then + for i,v in pairs(command_registry) do + if v.midaction then + command_registry[i].midaction = false + end + end + end + + for i,v in pairs(command_registry) do + if type(v) == 'table' and v.midaction then + return true, v.spell + end + end + + return false +end + +function user_pet_midaction(bool) + if bool == false then + for i,v in pairs(command_registry) do + if v.pet_midaction then + command_registry.pet_midaction = false + end + end + end + + for i,v in pairs(command_registry) do + if v.pet_midaction then + return true, v.spell + end + end + + return false +end + +function add_to_chat_user(num,str) + local backup_str + if type(num) == 'string' then + -- It was passed a string as the first argument. + str = not tonumber(str) and str or num + num = 8 + elseif not num and str and type(str) == 'string' then + -- It only needs the number. + num=8 + end + + if language == 'japanese' then + msg.add_to_chat(num,windower.to_shift_jis(str)) + else + msg.add_to_chat(num,str) + end +end + + +function user_sleep(delay) + if not delay then + error('\nGearSwap: coroutine.sleep() not passed a delay value', 2) + elseif type(delay) ~= 'number' or delay < 0 then + error('\nGearSwap: coroutine.sleep() was passed an invalid value ('..tostring(delay)..'). (must be a number >= 0)', 2) + else + coroutine.yield('sleep',delay) + end +end + +function user_yield() + coroutine.yield('yield') +end + + +-- Define the user windower functions. +user_windower = {register_event = register_event_user, raw_register_event = raw_register_event_user, + unregister_event = unregister_event_user, send_command = send_cmd_user,add_to_chat=add_to_chat_user} +user_coroutine = coroutine +user_coroutine.sleep = user_sleep +user_coroutine.yield = user_yield +setmetatable(user_windower,{__index=windower})
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/validate.lua b/Data/DefaultContent/Libraries/addons/addons/GearSwap/validate.lua new file mode 100644 index 0000000..5969878 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/validate.lua @@ -0,0 +1,319 @@ +--Copyright (c) 2013~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +------------------------------------------------------------------------------------------------------------------- +-- Primary entry point. +------------------------------------------------------------------------------------------------------------------- + +-- Validate either gear sets or inventory. +-- gs validate [inv|set] [filterlist] +-- Where inv == i or inv or inventory +-- Where set == s or set or sets +-- Where filterlist can use - to negate a value (eg: -charis for everything except charis, instead of only charis) +function validate(options) + local validateType = 'sets' + if options and #options > 0 then + if S{'sets','set','s'}:contains(options[1]:lower()) then + table.remove(options,1) + elseif S{'inventory','inv','i'}:contains(options[1]:lower()) then + validateType = 'inv' + table.remove(options,1) + end + end + + if validateType == 'inv' then + validate_inventory(options) + else + validate_sets(options) + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Functions to handle the primary logic separation. +------------------------------------------------------------------------------------------------------------------- + +-- Function for determining and displaying which items from a player's inventory are not in their gear sets. +function validate_inventory(filter) + msg.addon_msg(123,'Checking for items in inventory that are not used in your gear sets.') + + local extra_items = search_sets_for_items_in_bag(items.inventory, filter) + + local display_list = get_item_names(extra_items):sort(insensitive_sort) + display_list:map(function(item) msg.add_to_chat(120, windower.to_shift_jis((string.gsub(item, "^%l", string.upper))) ) end) + msg.addon_msg(123,'Final count = '..tostring(display_list:length())) +end + +-- Function for determining and displaying which items of a player's gear sets are not in their inventory. +function validate_sets(filter) + msg.addon_msg(123,'Checking for items in gear sets that are not in your inventory.') + + local missing_items = search_bags_for_items_in_set(sets, filter) + + local display_list = get_item_names(missing_items):sort(insensitive_sort) + display_list:map(function(item) msg.add_to_chat(120, windower.to_shift_jis((string.gsub(item, "^%l", string.upper))) ) end) + msg.addon_msg(123,'Final count = '..tostring(display_list:length())) +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for output and id>name conversion. +------------------------------------------------------------------------------------------------------------------- + +-- Given a set of item IDs, create a set of item names. +function get_item_names(item_set) + return item_set:map(get_item_name) +end + +-- Get the name of an item. Handle the various types of items that can be passed to this function. +function get_item_name(item) + local name = '' + local aug = '' + + if type(item) == 'string' then + name = item + elseif type(item) == 'table' then + if item.id then + name = get_formal_name_by_item_id(item.id) + elseif item.name then + name = item.name + end + + + local aug = item.aug and table.concat(item.aug,', ') or get_augment_string(item) + if aug then + name = name .. ' {' .. aug .. '}' + end + end + + return name +end + +-- Get the (preferably) capitalized version of an item's name, or the +-- log version if the short version is abbreviated. +function get_formal_name_by_item_id(id) + local shortname = get_short_name_by_item_id(id) + local logname = get_log_name_by_item_id(id) + + return (#logname > #shortname) and logname or shortname +end + +-- Given an item id, get the log item name. +function get_log_name_by_item_id(id) + return res.items[id][language..'_log'] +end + +-- Given an item id, get the short item name. +function get_short_name_by_item_id(id) + return res.items[id][language] +end + +-- If the provided item has augments on it, return a string containing the list of augments. +function get_augment_string(item) + local augments + if item.extdata then + augments = extdata.decode(item).augments or {} + else + augments = item.augment or item.augments + end + + local started = false + if augments and #augments > 0 then + local aug_str = '' + for aug_ind,augment in pairs(augments) do + if augment ~= 'none' then + if started then + aug_str = aug_str .. ',' + end + + aug_str = aug_str.."'"..augment.."'" + started = true + end + end + + return aug_str + end +end + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for searching. +------------------------------------------------------------------------------------------------------------------- + +-- General search to find what 'extra' items are in inventory +function search_sets_for_items_in_bag(bag, filter) + local extra_bag_items = S{} + for _,item in ipairs(bag) do + if item.id ~= 0 and tryfilter(lowercase_name(get_log_name_by_item_id(item.id)), filter) then + if not find_in_sets(item, sets) then + extra_bag_items:add(item) + end + end + end + + return extra_bag_items +end + +-- General search to find what 'extra' items are in the job's gear sets +function search_bags_for_items_in_set(gear_table, filter, missing_items, stack) + if stack and stack:contains(gear_table) then return end + if type(gear_table) ~= 'table' then return end + if missing_items == nil then missing_items = S{} end + + for i,v in pairs(gear_table) do + local name = (type(v) == 'table' and v.name) or v + local aug = (type (v) == 'table' and (v.augments or v.augment)) + + if type(aug) == 'string' then aug = {aug} end + if type(name) == 'string' and name ~= 'empty' and name ~= '' and type(i) == 'string' then + if not slot_map[i] then + msg.addon_msg(123,windower.to_shift_jis(tostring(i))..' contains a "name" element but is not a valid slot.') + elseif tryfilter(lowercase_name(name), filter) and not find_in_equippable_inventories(name, aug) then + -- This is one spot where inventory names will be left hardcoded until an equippable bool is added to the resources + missing_items:add({name=lowercase_name(name),aug=aug}) + end + elseif type(name) == 'table' and name ~= empty then + if not stack then stack = S{} end + + stack:add(gear_table) + search_bags_for_items_in_set(v, filter, missing_items, stack) + stack:remove(gear_table) + end + end + + return missing_items +end + +-- Utility function to search equippable inventories +function find_in_equippable_inventories(name,aug) + for _,bag in pairs(equippable_item_bags) do + if find_in_inv(items[to_windower_bag_api(bag.en)], name, aug) then + return true + end + end +end + +-- Utility function to help search sets +function find_in_sets(item, tab, stack) + if stack and stack:contains(tab) then + return false + end + + local item_short_name = lowercase_name(get_short_name_by_item_id(item.id)) + local item_log_name = lowercase_name(get_log_name_by_item_id(item.id)) + + for _,v in pairs(tab) do + local name = (type(v) == 'table' and v.name) or v + local aug = (type(v) == 'table' and (v.augments or v.augment)) + if type(aug) == 'string' then aug = {aug} end + if type(name) == 'string' then + if compare_item(item, name, aug, item_short_name, item_log_name) then + return true + end + elseif type(v) == 'table' then + if not stack then stack = S{} end + + stack:add(tab) + local try = find_in_sets(item, v, stack) + stack:remove(tab) + + if try then + return true + end + end + end + + return false +end + +-- Utility function to help search inventory +function find_in_inv(bag, name, aug) + for _,item in ipairs(bag) do + if compare_item(item, name, aug) then + return true + end + end + return false +end + +-- Utility function to compare items that may possibly be augmented. +function compare_item(item, name, aug, item_short_name, item_log_name) + if item.id == 0 or not res.items[item.id] then + return false + end + + name = lowercase_name(name) + item_short_name = lowercase_name(item_short_name or get_short_name_by_item_id(item.id)) + item_log_name = lowercase_name(item_log_name or get_log_name_by_item_id(item.id)) + + if item_short_name == name or item_log_name == name then + if not aug or extdata.compare_augments(aug, extdata.decode(item).augments) then + return true + end + end + + return false +end + + +------------------------------------------------------------------------------------------------------------------- +-- Utility functions for filtering. +------------------------------------------------------------------------------------------------------------------- + +function tryfilter(itemname, filter) + if not filter or #filter == 0 then + return true + end + + local pass = true + for _,v in pairs(filter) do + if v[1] == '-' then + pass = false + v = v:sub(2) + end + if not v or type(v) ~= 'string' then + print_set(filter,'filter with bad v') + end + if itemname:contains(lowercase_name(v)) then + return pass + end + end + return not pass +end + + +function lowercase_name(name) + if type(name) == 'string' then + return name:lower() + else + return name + end +end + +function insensitive_sort(item1, item2) + if type(item1) == 'string' and type(item2) == 'string' then + return item1:lower() < item2:lower() + else + return item1 < item2 + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/GearSwap/version_history.txt b/Data/DefaultContent/Libraries/addons/addons/GearSwap/version_history.txt new file mode 100644 index 0000000..3d4e29e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/GearSwap/version_history.txt @@ -0,0 +1,236 @@ +------------------------------------------------- +GearSwap 0.935 - Filter enchanted items with duplicate names. +------------------------------------------------- +GearSwap 0.934 - Fixed /item command handling. +------------------------------------------------- +GearSwap 0.933 - Adjusted how item use packets are chosen. +------------------------------------------------- +GearSwap 0.932 - Fixed bug that prevented gearswap from identifying items by their bag when worn. +------------------------------------------------- +GearSwap 0.931 - Added support to exporting with custom file names. +------------------------------------------------- +GearSwap 0.930 - Fixed a problem with player[bag][item_name].count in the user environment. +------------------------------------------------- +GearSwap 0.929 - Adjusted the print_set function to give preference and order to slot name keys. +------------------------------------------------- +GearSwap 0.928 - Changed how the outgoing equipment chunks were parsed due to a changing understanding of how the windower packet events work. +------------------------------------------------- +GearSwap 0.927 - Sped up equipment handling and made Rare items ignore augments. +------------------------------------------------- +GearSwap 0.926 - Simplified the equipment handling and used 0x117 incoming. +------------------------------------------------- +GearSwap 0.925 - Fixed minor problem that arose with the previous version's implementation of the equip history. +------------------------------------------------- +GearSwap 0.924 - Fixed problems that arose when attempting to equip two of the same item in two different slots. Changed how player equipment is handled. +------------------------------------------------- +GearSwap 0.923 - Added augments to //gs validate sets. +------------------------------------------------- +GearSwap 0.922 - Changed how the reload event works so it no longer causes stack problems. +------------------------------------------------- +GearSwap 0.921 - Split a buff overwriting itself off into a separate event, buff_refresh, and adjusted documentation. +------------------------------------------------- +GearSwap 0.920 - Made file_unload fire on load commands and changed its argument to be the previous file name. +------------------------------------------------- +GearSwap 0.919 - Switched handling of buff_change to rely on 0x063 incoming v9, which includes the durations. +------------------------------------------------- +GearSwap 0.918 - Added player.buff_details and made it so buff_change triggers when buffs are overwritten. +------------------------------------------------- +GearSwap 0.917 - Added code to ignore already-equipped items when searching for items to equip. +------------------------------------------------- +GearSwap 0.916 - Added wardrobes 3 and 4. +------------------------------------------------- +GearSwap 0.915 - Added wardrobe2. +------------------------------------------------- +GearSwap 0.914 - Added /bstpet handling. +------------------------------------------------- +GearSwap 0.913 - Fixed japanese job point filter in filter_pretarget +- Fixed command handling so filtered_action will be called for spells you haven't learned +- Fixed pet_status_change so it will always be called (including in cases where the pet is dead) +- Re-implemented pet.tp +- Added player.base_str, player.add_str, and player.str, which is your total strength stat. Did this for all base stats as well as resistances and attack/defense. May not be reliable, but was requested. +- Added spell.value, but there is no filter determining when it's meaningful or not. Mostly useful for COR rolls, which people should be able to filter out on their own. +------------------------------------------------- +GearSwap 0.912 - Added Geo- spell positioning. +------------------------------------------------- +GearSwap 0.911 - Removed dependence of Unbridled Wisdom spells on the respective buff. +------------------------------------------------- +GearSwap 0.910 - Fixed set_combine so it does not respect the disable_table and updated copyright statements. +------------------------------------------------- +GearSwap 0.908 - Fixed party.count failing to update in user_env. Added README.md file. +------------------------------------------------- +GearSwap 0.907 - Fixed a spell completion issue in aftercast caused by action packets that had no message. +------------------------------------------------- +GearSwap 0.906 - Added BLU magic spells and handled one aftercast that uses the 0x053 incoming packet. +------------------------------------------------- +GearSwap 0.904 - Adjusted party buff information to run off indices instead of names. +------------------------------------------------- +GearSwap 0.903 - Added buff information to the alliance/party structure. +------------------------------------------------- +GearSwap 0.902 - Changed action packet length. +------------------------------------------------- +GearSwap 0.901 - Modified add_to_chat handling throughout GearSwap so it would show up in a consistent log (the same one as mode 1) +------------------------------------------------- +GearSwap 0.900 - Adjusted GearSwap to be compatible with the new library behavior. +------------------------------------------------- +GearSwap 0.899 - Adjusted GearSwap to be compatible with the new library behavior. +------------------------------------------------- +GearSwap 0.898 - Fixed a bug with using cast_delay in the pretarget function. +------------------------------------------------- +GearSwap 0.897 - Superior system support and filtering. +------------------------------------------------- +GearSwap 0.896 - Command_registry class addition. +------------------------------------------------- +GearSwap 0.894 - Various bugfixes (including Monstrosity loading). +------------------------------------------------- +GearSwap 0.892 - Proxied primitive and text commands. +------------------------------------------------- +GearSwap 0.891 - Fixed a few bugs with /item commands. Exposed the _addon table. +------------------------------------------------- +GearSwap 0.890 - Added support for enchanted items in Wardrobe. Added a filter for enchanted items/armors. Made resources tables static. +------------------------------------------------- +GearSwap 0.880 - Accommodated booleans and numbers in equip commands. Fixed world.conquest.strengths. +------------------------------------------------- +GearSwap 0.875 - Changed command_registry handling slightly +------------------------------------------------- +GearSwap 0.874 - Added indi_change event +------------------------------------------------- +GearSwap 0.873 - Adjusted midaction and pet_midaction. +------------------------------------------------- +GearSwap 0.872 - Fixed Encumbrance, Job Change issues, and a nil error on login. +------------------------------------------------- +GearSwap 0.871 - Debugging and Logging changes. +------------------------------------------------- +GearSwap 0.870 - Rewrite of "order" as "priority" for advanced tables. +------------------------------------------------- +GearSwap 0.868 - Ironed out some bugs. Added .in_mog_house. Made pet_change work for gaining charmed pets. +------------------------------------------------- +GearSwap 0.865 - Replicated part of the LuaCore API in GearSwap and set limits on global variable refresh rates. +------------------------------------------------- +GearSwap 0.860 - Reworked the augment system to use the extdata library. Consequently enabled evolith support. +------------------------------------------------- +GearSwap 0.851 - Added Load command and file path support for a libs folder. +------------------------------------------------- +GearSwap 0.850 - Japanese support. +------------------------------------------------- +GearSwap 0.840 - Overhaul for Wardrobe. Preparations for new augment library. +------------------------------------------------- +GearSwap 0.837 - Created infrastructure for GearSwap served information and added Indi spell aura information to player. +------------------------------------------------- +GearSwap 0.836 - Added augments to the export feature for currently equipped sets and inventory. +------------------------------------------------- +GearSwap 0.835 - Adaptation for new resources. +------------------------------------------------- +GearSwap 0.834 - Filter now handles Scholar spells and Blue Magic. +------------------------------------------------- +GearSwap 0.833 - Added the first version of a more authentic filter. +------------------------------------------------- +GearSwap 0.832 - Adapted GearSwap for the .lua version of resources lib. +------------------------------------------------- +GearSwap 0.830 - Fixed various bugs. Differentiated pretarget and precast delays. Augments work again. +------------------------------------------------- +GearSwap 0.823 - Changed GearSwap to run off the resources library. Fixed Double-Up's aftercast. +------------------------------------------------- +GearSwap 0.822 - Added demomode and filters to prevent impossible actions. +------------------------------------------------- +GearSwap 0.821 - Finalized register_event(). Protected user files to prevent error propagation. +------------------------------------------------- +GearSwap 0.820 - Added 0x02, 0x35 extdata augment handling. Added sub_job_change(new,old) event. +------------------------------------------------- +GearSwap 0.819 - API adjustments. Massive refactoring of the scheduler, which improved midaction() and pet_midaction(). Fixed " <wait X>" macros and the buff_change() event. +------------------------------------------------- +GearSwap 0.818 - Added pet_change() user event. Refined event registration. +------------------------------------------------- +GearSwap 0.817 - Added filtering to //gs validate. Added pet_midcast() user function. +------------------------------------------------- +GearSwap 0.816 - Automaton midcast fixed. //gs fixed. //gs export no longer requires a user file (except for the sets option). +------------------------------------------------- +GearSwap 0.815 - Automaton information added. +------------------------------------------------- +GearSwap 0.814 - Fixed the equipment table, which was being unreliable. +------------------------------------------------- +GearSwap 0.813 - Fixed issues with /pet. Meddled with triggering interruption again. Potentially re-created the Paralysis issue in the process. +------------------------------------------------- +GearSwap 0.812 - Fixed Interruption handling for action messages that show the player as the target regardless the real target. +------------------------------------------------- +GearSwap 0.811 - Added a check for whether or not a given spell/ability can be used. Made player.equipment update between pretarget/precast/midcast. Deleted the second argument to pretarget, precast, midcast, aftercast, pet_midcast, and pet_aftercast. +------------------------------------------------- +GearSwap 0.810 - Changed how precast and midcast are defined for players. Removed verify_equip and force_send. Refactored a great deal. +------------------------------------------------- +GearSwap 0.802 - Introduced the debugging "clocking" setting. Changed how actions are injected. +------------------------------------------------- +GearSwap 0.801 - Combated double precasts, fixed a variety of nil errors, and made Paralysis work (hopefully). +------------------------------------------------- +GearSwap 0.800 - Added pretarget and moved precast to outgoing_chunk. Changed "buff_change()" to pass a boolean instead of a string indicating whether the buff was gained or lost. +------------------------------------------------- +GearSwap 0.723 - Removed a debug message, updated documentation, and refactored equip_processing.lua extensively. +------------------------------------------------- +GearSwap 0.722 - Changed the ranged attack spell table to reflect the resources. Added spell.interrupted boolean. Added a persistent spell for ambiguous interruption message cases. Changed interrupted action.type to "Interruption"... Many things. +------------------------------------------------- +GearSwap 0.721 - Adjusted for API changes and related documentation. +------------------------------------------------- +GearSwap 0.720 - Fixed Pet TP magnitude, exposed OS library, updated documentation, improved file name handling (a bit), and minor changes to the validation algorithm. +------------------------------------------------- +GearSwap 0.719 - Removed dependence on sets library. Fixed error that occured when there was no subjob. +------------------------------------------------- +GearSwap 0.718 - Fixed player.status. Added the validate command. Improved export sets' efficiency. Updated documentation. +------------------------------------------------- +GearSwap 0.717 - Fixed equipment removal. Added sets.naked. Handled category 11 outgoing actions (homepointing). Updated documentation. +------------------------------------------------- +GearSwap 0.716 - Export now looks up the proper equipment slot. Changed syntax for equipping an empty slot to be less ambiguous. Fixed unequipping on zone (I think). +------------------------------------------------- +GearSwap 0.715 - Converted resource strings to numbers if appropriate. Added Pianissimo cast_time handling. +------------------------------------------------- +GearSwap 0.714 - Minor changes to make export more user friendly. +------------------------------------------------- +GearSwap 0.713 - Added equipment order support. Made export sets more safe. Added equip "empty" support. +------------------------------------------------- +GearSwap 0.712 - Added adventuring fellow handling. +------------------------------------------------- +GearSwap 0.711 - Refined category exclusions for the outgoing action packet. +------------------------------------------------- +GearSwap 0.710 - Added windower.debug statements. +------------------------------------------------- +GearSwap 0.709 - Looped midact back through console with a 1.5 second delay. Tested for 2 days without extra DC issues. +------------------------------------------------- +GearSwap 0.708 - Added Encumbrance support. Added a message for Exporting files. Added another _global.midaction fix. Altered how Disable works, slightly. +------------------------------------------------- +GearSwap 0.707 - Forced the "sets" export to convert all equipment to inventory names. Started to add encumbrance code. Added a check for debuffs that prevent ability/spell usage in outgoing_chunk to reset midact. +------------------------------------------------- +GearSwap 0.706 - Added a "sets" option to the export command. +------------------------------------------------- +GearSwap 0.705 - Likely eliminated the Line 71 and 324 errors. Changed the "disable" console command so that it can be used for individual slots or all of gearSwap. +------------------------------------------------- +GearSwap 0.704 - Changed require() to include() and reworked its back end. Confirmed that register_event() works for users now and are successfully deleted. +------------------------------------------------- +GearSwap 0.703 - Added an export command and the appropriate documentation. +------------------------------------------------- +GearSwap 0.702 - Made keys to player data tables all case insensitive. Fixed the user windower structure. Set it so it should reset _global.midaction if the spelltarget dies while casting. +------------------------------------------------- +GearSwap 0.701 - Made silent interrupts reset _global.midaction. Fixed aftercast triggering on event_action_messages other than your own/your pets. +------------------------------------------------- +GearSwap 0.700 - Added preliminary augmented equipment support. Improved register_event. Finished conversion of GearSwap to new API. +------------------------------------------------- +GearSwap 0.605 - Fixed pet_mid/aftercast. Added/documented disable/enable. Verified that register_event will work in user documents. +------------------------------------------------- +GearSwap 0.604 - Added more handling to deal with monstrosity. Dramatical reduced memory requirements. +------------------------------------------------- +GearSwap 0.603 - Added handling to deal with monstrosity. About a week worth of misc. changes that I can't remember. +------------------------------------------------- +GearSwap 0.602 - Minor refactoring, moved things around, added a little more protection to windower.send_command() +------------------------------------------------- +GearSwap 0.601 - Improved the equip command to handle multiple set layers +------------------------------------------------- +GearSwap 0.600 - Added item handling and add/documented require +------------------------------------------------- +GearSwap 0.505 - Added and documented set_combine() +------------------------------------------------- +GearSwap 0.504 - "Fixed" reraise crash error. Likely indicates an underlying problem in LuaCore that should be addressed +------------------------------------------------- +GearSwap 0.503 - Event_status_change() handling changed so people don't swap gear when disengaging from NPCs or crafting. +------------------------------------------------- +GearSwap 0.502 - Require unmasked and documented. +------------------------------------------------- +GearSwap 0.501 - File_unload user function added. +------------------------------------------------- +GearSwap 0.500 - Initial beta release +------------------------------------------------- diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoBar/InfoBar.lua b/Data/DefaultContent/Libraries/addons/addons/InfoBar/InfoBar.lua new file mode 100644 index 0000000..8b03d0e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoBar/InfoBar.lua @@ -0,0 +1,276 @@ +--[[Copyright © 2018, Kenshi +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of InfoBar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL KENSHI BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'Infobar' +_addon.author = 'Kenshi' +_addon.version = '1.0' +_addon.commands = {'ib', 'infobar'} + +config = require('config') +texts = require('texts') +require('vectors') +res = require('resources') +require('sqlite3') + +defaults = {} +defaults.NoTarget = "${name} (${main_job}${main_job_level}/${sub_job}${sub_job_level}) (${x},${y},${z})" +defaults.TargetPC = "${name}" +defaults.TargetNPC = "${name}" +defaults.TargetMOB = "${name}" +defaults.display = {} +defaults.display.pos = {} +defaults.display.pos.x = 0 +defaults.display.pos.y = 0 +defaults.display.bg = {} +defaults.display.bg.red = 0 +defaults.display.bg.green = 0 +defaults.display.bg.blue = 0 +defaults.display.bg.alpha = 102 +defaults.display.text = {} +defaults.display.text.font = 'Consolas' +defaults.display.text.red = 255 +defaults.display.text.green = 255 +defaults.display.text.blue = 255 +defaults.display.text.alpha = 255 +defaults.display.text.size = 12 + +settings = config.load(defaults) + +box = texts.new("", settings.display, settings) + +local infobar = {} +infobar.new_line = '\n' + +windower.register_event('load',function() + db = sqlite3.open(windower.addon_path..'/database.db') + notesdb = sqlite3.open(windower.addon_path..'/notes.db') + notesdb:exec('CREATE TABLE IF NOT EXISTS notes(name TEXT primary key, note TEXT)') + if not windower.ffxi.get_info().logged_in then return end + local target = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') or windower.ffxi.get_player() + get_target(target.index) +end) + +windower.register_event('unload',function() + db:close() + notesdb:close() +end) + +function getDegrees(value) + return math.round(360 / math.tau * value) +end + +local dir_sets = L{'W', 'WNW', 'NW', 'NNW', 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W'} +function DegreesToDirection(val) + return dir_sets[math.round((val + math.pi) / math.pi * 8) + 1] +end + +function get_db(target, zones, level) + local query = 'SELECT * FROM "monster" WHERE name = "'..target..'" AND zone = "'..zones..'"' + local MOB_infobar = {} + box:bold(false) + + if db:isopen() and query then + for id,name,family,job,zone,isaggressive,islinking,isnm,isfishing,levelmin,levelmax,sight,sound,magic,lowhp,healing,ts,th,scent,weakness,resistances,immunities,drops,stolen,spawn,spawntime in db:urows(query) do + if name == target and zone == zones then + MOB_infobar.family = family or '' + MOB_infobar.job = job or '' + MOB_infobar.levelrange = levelmin and levelmax and levelmin.."-"..levelmax or '' + MOB_infobar.weakness = weakness or '' + MOB_infobar.resistances = resistances or '' + MOB_infobar.immunities = immunities or '' + MOB_infobar.drops = drops or '' + MOB_infobar.stolen = stolen or '' + MOB_infobar.spawns = spawn or '' + MOB_infobar.spawntime = spawntime or '' + if isaggressive == 1 then + MOB_infobar.isagressive = 'A' + if type(levelmax) == 'number' and (level - levelmax) <= 10 then + box:bold(true) + end + else + MOB_infobar.isagressive = 'NA' + end + MOB_infobar.islinking = islinking == 1 and 'L' or 'NL' + MOB_infobar.isnm = isnm == 1 and 'NM' or 'No NM' + MOB_infobar.isfishing = isfishing == 1 and 'F' or 'NF' + local detect = L{ + sight == 1 and 'S' or '', + sound == 1 and 'H' or '', + magic == 1 and 'M' or '', + lowhp == 1 and 'HP' or '', + healing == 1 and 'R' or '', + ts == 1 and 'TS' or '', + th == 1 and 'TH' or '', + scent == 1 and 'Sc' or '', + } + MOB_infobar.detect = detect:filter(-''):concat(',') + end + end + end + box:update(MOB_infobar) +end + +function get_notes(target) + local statement = notesdb:prepare('SELECT * FROM "notes" WHERE name = ?;') + if notesdb:isopen() and statement then + statement:bind(1, target) + for name, note in statement:urows(query, { target }) do + if name == target then + return note or nil + end + end + end +end + +function get_target(index) + local player = windower.ffxi.get_player() + local target = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') or player + infobar.name = target.name + infobar.id = target.id + infobar.index = target.index + infobar.notes = get_notes(target.name) + if index == 0 or index == player.index then + infobar.main_job = player.main_job + infobar.main_job_level = player.main_job_level + infobar.sub_job = player.sub_job + infobar.sub_job_level = player.sub_job_level + box:color(255,255,255) + box:bold(false) + box:text(settings.NoTarget) + else + if target.spawn_type == 13 or target.spawn_type == 14 or target.spawn_type == 9 or target.spawn_type == 1 then + box:bold(false) + if target.spawn_type == 1 then + box:color(255,255,255) + else + box:color(128,255,255) + end + box:text(settings.TargetPC) + elseif target.spawn_type == 2 or target.spawn_type == 34 then + box:color(128,255,128) + box:text(settings.TargetNPC) + box:bold(false) + elseif target.spawn_type == 16 then + local zone = res.zones[windower.ffxi.get_info().zone].name + box:color(255,255,128) + box:text(settings.TargetMOB) + get_db(target.name, zone, player.main_job_level) + end + end + box:update(infobar) +end + +windower.register_event('incoming chunk',function(id,org,modi,is_injected,is_blocked) + if id == 0xB then + zoning_bool = true + elseif id == 0xA then + zoning_bool = false + end +end) + +windower.register_event('prerender', function() + local info = windower.ffxi.get_info() + + if not info.logged_in or not windower.ffxi.get_player() or zoning_bool then + box:hide() + return + end + + infobar.game_moon = res.moon_phases[info.moon_phase].name + infobar.game_moon_pct = info.moon..'%' + infobar.zone_name = res.zones[info.zone].name + + local pos = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') or windower.ffxi.get_mob_by_target('me') + if not pos then return end + infobar.x = string.format('%0.3f', pos.x) + infobar.y = string.format('%0.3f', pos.y) + infobar.z = string.format('%0.3f', pos.z) + infobar.facing = tostring(getDegrees(pos.facing))..'°' + infobar.facing_dir = DegreesToDirection(pos.facing) + + box:update(infobar) + box:show() +end) + +windower.register_event('target change', get_target) +windower.register_event('job change', function() + get_target(windower.ffxi.get_player().index) +end) + +windower.register_event('time change', function(new, old) + local alchemy = new >= 8*60 and new <= 23*60 and 'Open' or 'Closed' + infobar.alchemy = alchemy == "Closed" and '\\cs(255,0,0)'..alchemy..'\\cr' or '\\cs(0,255,0)'..alchemy..'\\cr' + local bonecraft = new >= 8*60 and new <= 23*60 and 'Open' or 'Closed' + infobar.bonecraft = bonecraft == "Closed" and '\\cs(255,0,0)'..bonecraft..'\\cr' or '\\cs(0,255,0)'..bonecraft..'\\cr' + local clothcraft = new >= 6*60 and new <= 21*60 and 'Open' or 'Closed' + infobar.clothcraft = clothcraft == "Closed" and '\\cs(255,0,0)'..clothcraft..'\\cr' or '\\cs(0,255,0)'..clothcraft..'\\cr' + local cooking = new >= 5*60 and new <= 20*60 and 'Open' or 'Closed' + infobar.cooking = cooking == "Closed" and '\\cs(255,0,0)'..cooking..'\\cr' or '\\cs(0,255,0)'..cooking..'\\cr' + local fishing = new >= 3*60 and new <= 18*60 and 'Open' or 'Closed' + infobar.fishing = fishing == "Closed" and '\\cs(255,0,0)'..fishing..'\\cr' or '\\cs(0,255,0)'..fishing..'\\cr' + local goldsmithing = new >= 8*60 and new <= 23*60 and 'Open' or 'Closed' + infobar.goldsmithing = goldsmithing == "Closed" and '\\cs(255,0,0)'..goldsmithing..'\\cr' or '\\cs(0,255,0)'..goldsmithing..'\\cr' + local leathercraft = new >= 3*60 and new <= 18*60 and 'Open' or 'Closed' + infobar.leathercraft = leathercraft == "Closed" and '\\cs(255,0,0)'..leathercraft..'\\cr' or '\\cs(0,255,0)'..leathercraft..'\\cr' + local smithing = new >= 8*60 and new <= 23*60 and 'Open' or 'Closed' + infobar.smithing = smithing == "Closed" and '\\cs(255,0,0)'..smithing..'\\cr' or '\\cs(0,255,0)'..smithing..'\\cr' + local woodworking = new >= 6*60 and new <= 21*60 and 'Open' or 'Closed' + infobar.woodworking = woodworking == "Closed" and '\\cs(255,0,0)'..woodworking..'\\cr' or '\\cs(0,255,0)'..woodworking..'\\cr' + box:update(infobar) +end) + +windower.register_event('addon command', function(...) + local args = T{...} + if args[1] then + if args[1]:lower() == 'help' then + windower.add_to_chat(207,"Infobar Commands:") + windower.add_to_chat(207,"//ib|infobar notes add 'string'") + windower.add_to_chat(207,"//ib|infobar notes delete") + elseif args[1]:lower() == 'notes' then + local target = windower.ffxi.get_mob_by_target('t') + local tname = string.gsub(target.name, ' ', '_') + if not args[2] then + windower.add_to_chat(207,"Second argument not specified, use '//ib|infobar help' for info.") + elseif args[2]:lower() == 'add' then + if not target then windower.add_to_chat(207,"No target selected") return end + for i,v in pairs(args) do args[i]=windower.convert_auto_trans(args[i]) end + local str = table.concat(args," ",3) + notesdb:exec('INSERT OR REPLACE INTO notes VALUES ("'..target.name..'","'..str..'")') + get_target(target.index) + elseif args[2]:lower() == 'delete' then + if not target then windower.add_to_chat(207,"No target selected") return end + notesdb:exec('DELETE FROM notes WHERE name = "'..target.name..'"') + get_target(target.index) + else + windower.add_to_chat(207,"Second argument wrong, use '//ib|infobar help' for info.") + end + else + windower.add_to_chat(207,"First argument wrong, use '//ib|infobar help' for info.") + end + else + windower.add_to_chat(207,"First argument not specified, use '//ib|infobar help' for info.") + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoBar/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/InfoBar/ReadMe.md new file mode 100644 index 0000000..c43f8b0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoBar/ReadMe.md @@ -0,0 +1,46 @@ +# InfoBar # + +Displays a configurable bar showing information on your targets. + +List of variables: +${name}, ${id}, ${index}, ${x}, ${y}, ${z}, ${facing}, ${facing_dir}, ${game_moon}, ${game_moon_pct}, ${zone_name}, ${notes} +${alchemy}, ${bonecraft}, ${clothcraft}, ${cooking}, ${fishing}, ${goldsmithing}, ${leathercraft}, ${smithing}, +${woodworking} (this will show if the guild shops are closed or open) +player only variables: ${main_job}, ${main_job_level}, ${sub_job}, ${sub_job_level} +mob only variables: ${family}, ${job}, ${levelrange}, ${weakness}, ${resistances}, +${immunities}, ${drops}, ${stolen}, ${spawns}, ${spawntime}, ${isagressive}, +${islinking}, ${isnm}, ${isfishing}, ${detect} + +Adding variables: +To add variables open the settings.xml in the data folder with an editor and add the variables as you wish +to the NoTarget (when you have no target or target yourself), TargetPC (you target another player), +TargetNPC (you target a npc) , TargetMob (you target a mob) tags. +You can also add normal strings to them, for example Name: ${name} + +---- + +### Commands: ### + +#### help #### + +``` +//ib|infobar help +``` + +Shows a list of commands. + +#### notes add #### + +``` +//ib|infobar notes add <string> +``` + +Defines a note to the current target. + +#### notes delete #### + +``` +//ib|infobar notes delete +``` + +Delete a note to the current target that was defined previously. diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoBar/database.db b/Data/DefaultContent/Libraries/addons/addons/InfoBar/database.db Binary files differnew file mode 100644 index 0000000..2127d6e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoBar/database.db diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/InfoReplacer.lua b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/InfoReplacer.lua new file mode 100644 index 0000000..7948f62 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/InfoReplacer.lua @@ -0,0 +1,105 @@ +-- Copyright 2014-2015, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of InfoReplacer nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn BE LIABLE FOR ANY +-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.author = 'Cairthenn' +_addon.name = 'InfoReplacer' +_addon.version = '2.0' +_addon.command = 'inforeplacer' + +require('tables') +require('strings') +require('logger') + +replace = require('reps') +raw = {} +custom = T{} +fns = {} + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + local args = {...} + + if command == 'help' or command == 'h' then + print(_addon.name .. 'v.' .. _addon.version) + print(' \\cs(51, 153, 255)s\\cs(153, 204, 255)et <name> <value>\\cr - Defines a custom replacement') + print(' \\cs(153, 204, 255)set\\cs(51, 153, 255)e\\cs(153, 204, 255)val <name> <code>\\cr - Defines a custom replacement as Lua code') + print(' \\cs(51, 153, 255)u\\cs(153, 204, 255)nset <name>\\cr - Removes a previously defined custom replacement') + print(' \\cs(51, 153, 255)l\\cs(153, 204, 255)ist\\cr - Lists all custom replacements') + + elseif command == 'list' or command == 'l' then + if not custom:empty() then + log('Available custom replacement variable list:') + for key in custom:keyset():sort():it() do + local value = raw[key] + if fns[key] then + value = '%s ? %s':format(value, custom[key]()) + end + log(' #' .. key, value) + end + else + log('No custom replacements defined. For default replacements view ' .. windower.addon_path .. 'reps.lua') + end + + elseif command == 'set' or command == 's' then + if #args < 2 then + error('Incorrect syntax. The "set" syntax is as follows: //inforeplacer set <name> <value>') + return + end + + custom[args[1]] = args[2]:fn() + raw[args[1]] = args[2] + fns[args[1]] = nil + + elseif command == 'unset' or command == 'u' then + if #args < 1 then + error('Incorrect syntax. The "unset" syntax is as follows: //inforeplacer unset <name>') + return + end + + custom[args[1]] = nil + raw[args[1]] = nil + fns[args[1]] = nil + + elseif command == 'seteval' or command == 'e' then + if #args < 2 then + error('Incorrect syntax. The "seteval" syntax is as follows: //inforeplacer seteval <name> <value>') + return + end + + custom[args[1]] = assert(loadstring('return ' .. args[2])) + raw[args[1]] = args[2] + fns[args[1]] = true + + end +end) + +windower.register_event('outgoing text', function(original,modified,blocked) + return modified:gsub('%%[^ ]+', function(match) + local token = match:sub(2) + local fn = custom[token] or replace[token] + return fn and fn() or match + end) +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/ReadMe.md new file mode 100644 index 0000000..6772625 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/ReadMe.md @@ -0,0 +1,39 @@ +# InfoReplacer # + +Replaces outgoing text prefixed by % with respective information. For a complete list of replacements, view [`reps.lua`](https://github.com/Windower/Lua/blob/4.1/addons/InfoReplacer/reps.lua). + +---- + +### Commands: ### + +#### Show replacements #### + +``` +//inforeplacer list +``` + +Shows all (custom) replacement names. + +#### Set custom replacement #### + +``` +//inforeplacer set <name> <value> +``` + +Defines a custom replacement variable as the provided value. + +#### Set custom code replacement #### + +``` +//inforeplacer seteval <name> <code> +``` + +Defines a custom replacement variable as the provided Lua expression. This expression will be evaluated whenever it appears in the chat, so this can be used to print dynamic values. + +#### Remove custom replacement #### + +``` +//inforeplacer unset <name> +``` + +Deletes a previously defined custom replacement variable. diff --git a/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/reps.lua b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/reps.lua new file mode 100644 index 0000000..685a17c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/InfoReplacer/reps.lua @@ -0,0 +1,203 @@ +-- Copyright 2014-2015, Cairthenn +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of InfoReplacer nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cairthenn 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. + +require('tables') +local res = require('resources') + +return T{ + + name = function() return windower.ffxi.get_player().name end, + linkshell = function() return windower.ffxi.get_player().linkshell end, + linkshell_rank = function() return windower.ffxi.get_player().linkshell_rank end, + linkshell_slot = function() return windower.ffxi.get_player().linkshell_slot end, + main_job = function() return res.jobs[windower.ffxi.get_player().main_job].short end, + main_job_level = function() return windower.ffxi.get_player().main_job_level end, + main_job_full = function() return res.jobs[windower.ffxi.get_player().main_job].english end, + main_job_id = function() return windower.ffxi.get_player().main_job_id end, + status = function() return res.statuses[windower.ffxi.get_player().status].english end, + status_id = function() return windower.ffxi.get_player().status end, + index = function() return windower.ffxi.get_player().index end, + sub_job = function() return res.jobs[windower.ffxi.get_player().sub_job].short end, + sub_job_level = function() return windower.ffxi.get_player().sub_job_level end, + sub_job_full = function() return res.jobs[windower.ffxi.get_player().sub_job].english end, + sub_job_id = function() return windower.ffxi.get_player().sub_job_id end, + target_index = function() return windower.ffxi.get_player().target_index end, + target_locked = function() return windower.ffxi.get_player().target_locked and "True" or "False" end, + autorun = function() return windower.ffxi.get_player().autorun and "True" or "False" end, + follow_index = function() return windower.ffxi.get_player().follow_index end, + in_combat = function() return windower.ffxi.get_player().in_combat and "True" or "False" end, + id = function() return windower.ffxi.get_mob_by_target('me').id end, + + + hp = function() return windower.ffxi.get_player().vitals.hp end, + max_hp = function() return windower.ffxi.get_player().vitals.max_hp end, + hpp = function() return windower.ffxi.get_player().vitals.hpp end, + mp = function() return windower.ffxi.get_player().vitals.mp end, + max_mp = function() return windower.ffxi.get_player().vitals.max_mp end, + mpp = function() return windower.ffxi.get_player().vitals.mpp end, + tp = function() return windower.ffxi.get_player().vitals.tp end, + + + hand_to_hand = function() return windower.ffxi.get_player().skills.hand_to_hand end, + dagger = function() return windower.ffxi.get_player().skills.dagger end, + sword = function() return windower.ffxi.get_player().skills.sword end, + great_sword = function() return windower.ffxi.get_player().skills.great_sword end, + axe = function() return windower.ffxi.get_player().skills.axe end, + great_axe = function() return windower.ffxi.get_player().skills.great_axe end, + scythe = function() return windower.ffxi.get_player().skills.scythe end, + polearm = function() return windower.ffxi.get_player().skills.polearm end, + katana = function() return windower.ffxi.get_player().skills.katana end, + great_katana = function() return windower.ffxi.get_player().skills.great_katana end, + club = function() return windower.ffxi.get_player().skills.club end, + staff = function() return windower.ffxi.get_player().skills.staff end, + archery = function() return windower.ffxi.get_player().skills.archery end, + marksmanship = function() return windower.ffxi.get_player().skills.marksmanship end, + throwing = function() return windower.ffxi.get_player().skills.throwing end, + guarding = function() return windower.ffxi.get_player().skills.guarding end, + evasion = function() return windower.ffxi.get_player().skills.evasion end, + shield = function() return windower.ffxi.get_player().skills.shield end, + parrying = function() return windower.ffxi.get_player().skills.parrying end, + divine_magic = function() return windower.ffxi.get_player().skills.divine_magic end, + healing_magic = function() return windower.ffxi.get_player().skills.healing_magic end, + enhancing_magic = function() return windower.ffxi.get_player().skills.enhancing_magic end, + enfeebling_magic = function() return windower.ffxi.get_player().skills.enfeebling_magic end, + elemental_magic = function() return windower.ffxi.get_player().skills.elemental_magic end, + dark_magic = function() return windower.ffxi.get_player().skills.dark_magic end, + summoning_magic = function() return windower.ffxi.get_player().skills.summoning_magic end, + ninjitsu = function() return windower.ffxi.get_player().skills.ninjitsu end, + singing = function() return windower.ffxi.get_player().skills.singing end, + stringed_instrument = function() return windower.ffxi.get_player().skills.string end, + wind_instrument = function() return windower.ffxi.get_player().skills.wind end, + blue_magic = function() return windower.ffxi.get_player().skills.blue_magic end, + alchemy = function() return windower.ffxi.get_player().skills.alchemy end, + bonecraft = function() return windower.ffxi.get_player().skills.bonecraft end, + clothcraft = function() return windower.ffxi.get_player().skills.clothcraft end, + cooking = function() return windower.ffxi.get_player().skills.cooking end, + fishing = function() return windower.ffxi.get_player().skills.fishing end, + goldsmithing = function() return windower.ffxi.get_player().skills.goldsmithing end, + leathercraft = function() return windower.ffxi.get_player().skills.leathercraft end, + smithing = function() return windower.ffxi.get_player().skills.smithing end, + woodworking = function() return windower.ffxi.get_player().skills.woodworking end, + synergy = function() return windower.ffxi.get_player().skills.synergy end, + + + camera_x = function() return windower.ffxi.get_camera().camera_x end, + camera_y = function() return windower.ffxi.get_camera().camera_y end, + camera_z = function() return windower.ffxi.get_camera().camera_z end, + + + windower_x = function() return windower.get_windower_settings().windower_x end, + windower_y = function() return windower.get_windower_settings().windower_y end, + windower_x_pos = function() return windower.get_windower_settings().windower_x_pos end, + windower_y_pos = function() return windower.get_windower_settings().windower_y_pos end, + ui_x = function() return windower.get_windower_settings().ui_x end, + ui_y = function() return windower.get_windower_settings().ui_y end, + launcher_version = function() return windower.get_windower_settings().launcher_version end, + + + day = function() return res.days[windower.ffxi.get_info().day].english end, + day_element = function() return res.elements[res.days[windower.ffxi.get_info().day].element].english end, + moon = function() return res.moon_phases[windower.ffxi.get_info().moon_phase].english end, + moon_pct = function() return windower.ffxi.get_info().moon end, + time = function() time = windower.ffxi.get_info().time return (time / 60):floor() .. ':' .. (time % 60) end, + zone = function() return res.zones[windower.ffxi.get_info().zone].english end, + zone_id = function() return windower.ffxi.get_info().zone end, + logged_in = function() return windower.ffxi.get_info().logged_in end, + weather = function() return res.weather[windower.ffxi.get_info().weather].english end, + weather_id = function() return windower.ffxi.get_info().weather end, + weather_element = function() return res.elements[res.weathers[windower.ffxi.get_info().weather].element].english end, + language = function() return windower.ffxi.get_info().language end, + + + target_name = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').name end, + target_claim_id = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').claim_id end, + target_distance = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').distance end, + target_facing = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').facing end, + target_hpp = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').hpp end, + target_id = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').id end, + target_is_npc = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').is_npc and "True" or "False" end, + target_mob_type = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').mob_type end, + target_model_size = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t')._model_size end, + target_speed = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').speed end, + target_speed_base = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').speed_base end, + target_status = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').status end, + target_index = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').index end, + target_x = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').x end, + target_y = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').y end, + target_z = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').z end, + target_pet_index = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').pet_index end, + target_mpp = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').mpp end, + target_fellow_index = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').fellow_index end, + target_race = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').race end, + target_tp = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').tp end, + target_charmed = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').charmed and "True" or "False" end, + target_in_party = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').in_party and "True" or "False" end, + target_in_alliance = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').in_alliance and "True" or "False" end, + target_is_valid = function() return windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').is_valid and "True" or "False" end, + + pet_name = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').name end, + pet_claim_id = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').claim_id end, + pet_distance = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').distance end, + pet_facing = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').facing end, + pet_hpp = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').hpp end, + pet_id = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').id end, + pet_is_npc = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').is_npc end, + pet_mob_type = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').mob_type end, + pet_model_size = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').model_size end, + pet_speed = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').speed end, + pet_speed_base = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').speed_base end, + pet_status = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').status end, + pet_index = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').index end, + pet_x = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').x end, + pet_y = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').y end, + pet_z = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').z end, + pet_race = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').race end, + pet_tp = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').tp end, + pet_charmed = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').charmed and "True" or "False" end, + pet_in_party = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').in_party and "True" or "False" end, + pet_in_alliance = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').in_alliance and "True" or "False" end, + pet_is_valid = function() return windower.ffxi.get_mob_by_target('pet') and windower.ffxi.get_mob_by_target('pet').is_valid and "True" or "False" end, + + + gil = function() return windower.ffxi.get_items().gil end, + + ammo = function() return windower.ffxi.get_items().equipment.ammo > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.ammo].id].english end, + back = function() return windower.ffxi.get_items().equipment.back > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.back].id].english end, + body = function() return windower.ffxi.get_items().equipment.body > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.body].id].english end, + feet = function() return windower.ffxi.get_items().equipment.feet > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.feet].id].english end, + hands = function() return windower.ffxi.get_items().equipment.hands > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.hands].id].english end, + head = function() return windower.ffxi.get_items().equipment.head > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.head].id].english end, + left_ear = function() return windower.ffxi.get_items().equipment.left_ear > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.left_ear].id].english end, + legs = function() return windower.ffxi.get_items().equipment.legs > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.legs].id].english end, + left_ring = function() return windower.ffxi.get_items().equipment.left_ring > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.left_ring].id].english end, + main = function() return windower.ffxi.get_items().equipment.main > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.main].id].english end, + neck = function() return windower.ffxi.get_items().equipment.neck > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.neck].id].english end, + range = function() return windower.ffxi.get_items().equipment.range > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.range].id].english end, + right_ear = function() return windower.ffxi.get_items().equipment.right_ear > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.right_ear].id].english end, + right_ring = function() return windower.ffxi.get_items().equipment.right_ring > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.right_ring].id].english end, + sub = function() return windower.ffxi.get_items().equipment.sub > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.sub].id].english end, + waist = function() return windower.ffxi.get_items().equipment.waist > 0 and res.items[windower.ffxi.get_items().inventory[windower.ffxi.get_items().equipment.waist].id].english end +} diff --git a/Data/DefaultContent/Libraries/addons/addons/JobChange/README.md b/Data/DefaultContent/Libraries/addons/addons/JobChange/README.md new file mode 100644 index 0000000..369c22a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/JobChange/README.md @@ -0,0 +1,15 @@ +Job Change AddOn. + +Allows command line job change as long as you're within 6 yalms of a Job Change NPC. + +Usage: +* //jc main job - change just main job +* //jc sub job - change just sub job +* //jc main/sub - change both jobs at the same time. +* //jc reset (Resets JA's by changing your sub job off and back) + + +If you are already the slot and job you want (IE you're already WAR and want to change MAIN to war) we'll temp-change to another job and change back to WAR. + +If your sub or main conflicts with the target job change (IE: You're main WAR and want to change to sub WAR) we'll temp-change MAIN to another job (random starter), then change sub to WAR. + diff --git a/Data/DefaultContent/Libraries/addons/addons/JobChange/jobchange.lua b/Data/DefaultContent/Libraries/addons/addons/JobChange/jobchange.lua new file mode 100644 index 0000000..e5068ba --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/JobChange/jobchange.lua @@ -0,0 +1,207 @@ +--[[ +Copyright © 2017, Sammeh of Quetzalcoatl +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of JobChange nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sammeh BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'Job Change' +_addon.author = 'Sammeh; Akaden' +_addon.version = '1.0.4' +_addon.command = 'jc' + +-- 1.0.1 first release +-- 1.0.2 added 'reset' command to simply reset to existing job. Changes sub job to a random starting job and back. +-- 1.0.3 Code clean-up +-- 1.0.4 Added /jc main/sub command and organized solve for fewest changes. + +require('tables') +packets = require('packets') +res = require ('resources') + +local temp_jobs = T { 'NIN', 'DNC', 'WAR', 'MNK', 'WHM', 'BLM', 'RDM', 'THF' } +local mog_zones = S { 'Selbina', 'Mhaura', 'Tavnazian Safehold', 'Nashmau', 'Rabao', 'Kazham', 'Norg', 'Walk of Echoes [P1]', 'Walk of Echoes [P2]' } +local moogles = S { 'Moogle', 'Nomad Moogle', 'Green Thumb Moogle', 'Pilgrim Moogle' } + +local log = function(msg) + windower.add_to_chat(4,'JobChange: '..msg) +end + +local jobchange = function(job, main) + local packet = packets.new('outgoing', 0x100, { + [(main and 'Main' or 'Sub')..' Job'] = job, + }) + packets.inject(packet) +end + +local find_conflict = function(job_name, p) + if p.main_job == job_name:upper() then + return 'main' + end + if p.sub_job == job_name:upper() then + return 'sub' + end +end + +local find_temp_job = function(p) + for _, job_name in ipairs(temp_jobs) do + if not find_conflict(job_name, p) and p.jobs[job_name:upper()] > 0 then + for index,value in pairs(res.jobs) do + if value.ens == job_name then + return index + end + end + end + end +end + +local find_job = function(job,p) + if job == nil then return nil end + local jobLevel = p.jobs[job:upper()] + for index,value in pairs(res.jobs) do + if value.ens:lower() == job and jobLevel > 0 then + return index + end + end +end + +local find_job_change_npc = function() + local info = windower.ffxi.get_info() + if not (info.mog_house or mog_zones:contains(res.zones[info.zone].english)) then + log('Not in a zone with a Change NPC') + return + end + + for _, v in pairs(windower.ffxi.get_mob_array()) do + if v.distance < 36 and v.valid_target and moogles:contains(v.name) then + return v + end + end +end + +windower.register_event('addon command', function(command, ...) + local p = windower.ffxi.get_player() + local args = L{...} + local job = '' + if args[1] then + job = args[1]:lower() + end + local main = nil + local sub = nil + if command:lower() == 'main' then + main = job + if main and main:upper() == p.main_job then main = nil end + elseif command:lower() == 'sub' then + sub = job + if sub and sub:upper() == p.sub_job then main = nil end + elseif command:lower() == 'reset' then + log('Resetting Job') + sub = p.sub_job:lower() + elseif command:contains('/') or command:contains('\\') then + command = command:gsub('\\','/') + local js = command:split('/') + main = (js[1] ~= '' and js[1] or nil) + sub = (js[2] ~= '' and js[2] or nil) + -- remove identicals. + if main and main:upper() == p.main_job then main = nil end + if sub and sub:upper() == p.sub_job then sub = nil end + elseif command ~= nil and command ~= '' then + main = command:lower() + if main and main:upper() == p.main_job then main = nil end + else + log('Syntax: //jc main|sub JOB -- Chnages main or sub to target JOB') + log('Syntax: //jc main/sub -- Changes main and sub') + log('Syntax: //jc reset -- Resets Current Job') + return + end + + local changes = T{} + + local main_id = find_job(main, p) + if main ~= nil and main_id == nil then + log('Could not change main job to to '..main:upper()..' ---Mistype|NotUnlocked') + return + end + local sub_id = find_job(sub, p) + if sub ~= nil and sub_id == nil then + log('Could not change sub job to to '..sub:upper()..' ---Mistype|NotUnlocked') + return + end + + if main_id == nil and sub_id == nil then + log('No change required.') + return + end + + if main_id ~= nil and main:upper() == p.sub_job then + if sub_id ~= nil and sub:upper() == p.main_job then + changes:append({job_id=find_temp_job(p), is_conflict=true, is_main=false}) + changes:append({job_id=main_id, is_main=true}) + changes:append({job_id=sub_id, is_main=false}) + else + if sub_id ~= nil then + changes:append({job_id=sub_id, is_main=false}) + else + changes:append({job_id=find_temp_job(p), is_conflict=true, is_main=false}) + end + changes:append({job_id=main_id, is_main=true}) + end + elseif sub_id ~= nil and sub:upper() == p.main_job then + if main_id ~= nil then + changes:append({job_id=main_id, is_main=true}) + else + changes:append({job_id=find_temp_job(p), is_conflict=true, is_main=true}) + end + changes:append({job_id=sub_id, is_main=false}) + else + if main_id ~= nil then + if main:upper() == p.main_job then + changes:append({job_id=find_temp_job(p), is_conflict=true, is_main=true}) + end + changes:append({job_id=main_id, is_main=true}) + end + if sub_id ~= nil then + if sub:upper() == p.sub_job then + changes:append({job_id=find_temp_job(p), is_conflict=true, is_main=false}) + end + changes:append({job_id=sub_id, is_main=false}) + end + end + + local npc = find_job_change_npc() + if npc then + for _, change in ipairs(changes) do + if change.is_conflict then + log('Conflict with '..(change.is_main and 'main' or 'sub')..' job. Changing to: '..res.jobs[change.job_id].ens) + else + log('Changing '..(change.is_main and 'main' or 'sub')..' job to: '..res.jobs[change.job_id].ens) + end + jobchange(change.job_id, change.is_main) + + coroutine.sleep(0.5) + end + else + log('Not close enough to a Moogle!') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Logger/Logger.lua b/Data/DefaultContent/Libraries/addons/addons/Logger/Logger.lua new file mode 100644 index 0000000..4cd2cf4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Logger/Logger.lua @@ -0,0 +1,47 @@ +_addon.name = 'Logger' +_addon.author = 'Aikar' +_addon.version = '1.0.1.1' + +require('chat') +files = require('files') +config = require('config') + +defaults = {} +defaults.AddTimestamp = false +defaults.TimestampFormat = '%H:%M:%S' + +settings = config.load(defaults) + +name = windower.ffxi.get_player() and windower.ffxi.get_player().name + +windower.register_event('login', function(new_name) + name = new_name +end) + +windower.register_event('incoming text', function(_, text, _, _, blocked) + if blocked or text == '' then + return + end + + local date = os.date('*t') + + local file = files.new('../../logs/%s_%.4u.%.2u.%.2u.log':format(name, date.year, date.month, date.day)) + if not file:exists() then + file:create() + end + + file:append('%s%s\n':format(settings.AddTimestamp and os.date(settings.TimestampFormat, os.time()) or '', text:strip_format())) +end) + +--[[ +Copyright © 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/Logger/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Logger/ReadMe.md new file mode 100644 index 0000000..765ebb4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Logger/ReadMe.md @@ -0,0 +1,3 @@ +# Logger + +Outputs all text that appears in the chat log into a text file under `Windower/logs/`. diff --git a/Data/DefaultContent/Libraries/addons/addons/Lookup/Lookup.lua b/Data/DefaultContent/Libraries/addons/addons/Lookup/Lookup.lua new file mode 100644 index 0000000..499f2a6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Lookup/Lookup.lua @@ -0,0 +1,248 @@ +--[[ + Copyright © 2018, Karuberu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Lookup nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR 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. +]] +_addon.name = 'Lookup' +_addon.author = 'Karuberu' +_addon.version = '1.0' +_addon.commands = {'lookup', 'lu'} + +config = require('config') +res = require('resources') + +settings = nil +ids = nil +last_item = nil + +function load_settings() + settings = config.load({ + default = 'ffxiclopedia'; + sites = { + ffxiclopedia = { + search = 'https://ffxiclopedia.fandom.com/wiki/Special:Search?query=${term}'; + }; + ['bg-wiki'] = { + search = 'https://www.bg-wiki.com/bg/Special:Search?go=Go&search=${term}'; + }; + ffxidb = { + item = 'http://www.ffxidb.com/items/${term}'; + zone = 'http://www.ffxidb.com/zones/${term}'; + search = 'http://www.ffxidb.com/search?q=${term}'; + }; + ffxiah = { + item = 'http://www.ffxiah.com/item/${term}'; + search = 'http://www.ffxiah.com/search/item?q=${term}'; + }; + ffxiahplayer = { + search = 'https://www.ffxiah.com/search/player?name=${term}'; + }; + google = { + search = 'https://www.google.com/search?q=${term}'; + }; + ffxi = { redirect = 'ffxiclopedia'; }; + wikia = { redirect = 'ffxiclopedia'; }; + bgwiki = { redirect = 'bg-wiki'; }; + bg = { redirect = 'bg-wiki'; }; + db = { redirect = 'ffxidb'; }; + ah = { redirect = 'ffxiah'; }; + ffxiahp = { redirect = 'ffxiahplayer'; }; + ahp = { redirect = 'ffxiahplayer'; }; + }; + }) +end + +-- Creates a list of item and zone ids by name for quicker lookup by name +function initialize_ids() + ids = { + items = {}; + zones = {}; + } + for item in res.items:it() do + ids.items[item.name] = item.id + ids.items[item.name_log] = item.id + end + for zone in res.zones:it() do + ids.zones[zone.name] = zone.id + end +end + +function get_id(name) + if id == nil then + return {} + end + return { + item = ids.items[name]; + zone = ids.zones[name]; + } +end + +function get_name(id, list) + if id == nil then + return nil + end + return (list[id] or {}).name +end + +-- Converts auto-translate strings to plain text. +-- If the string is not an auto-translate string, the original string is returned. +function translate(str) + return windower.convert_auto_trans(str) +end + +-- Checks to see if the string is a selector (enclosed by <>) and returns a replacement. +-- If the string is not a selector, the original string is returned. +function parse_selection(str) + local target = str:match('<(.+)>') + if target == nil then + return str + end + + -- custom selection handlers + if target == 'job' or target == 'mjob' then + return windower.ffxi.get_player().main_job_full + elseif target == 'sjob' then + return windower.ffxi.get_player().sub_job_full + elseif target == 'zone' then + if windower.ffxi.get_info().mog_house then + return 'Mog House' + else + return get_name(windower.ffxi.get_info().zone, res.zones) + end + elseif target == 'item' then + return get_name(last_item, res.items) + end + -- default to windower's selection handlers + return (windower.ffxi.get_mob_by_target(str) or {}).name +end + +function set_default_site(command_modifier, site) + settings.default = site + if command_modifier == 'player' or command_modifier == 'p' then + -- save only for the current character + settings:save() + else + -- save for all characters + settings:save('all') + end +end + +function modify_site_settings(site, type, url) + if url == 'remove' then + url = nil + end + settings.sites[site][type] = url +end + +function set_last_item(bag, index, id, count) + if bag == 0 then + last_item = id + end +end + +-- Replaces the named parameters in the url +function format_url(url, term) + if term == nil then + return term + end + return url:gsub('${term}', '%s':format(term)) +end + +function get_site(command) + local site = settings.sites[command] + if site ~= nil and site.redirect ~= nil then + site = settings.sites[site.redirect] + end + return site +end + +function get_url(site, term) + term = translate(term) + term = parse_selection(term) + local id = get_id(term) + if id.item ~= nil and site.item ~= nil then + url = format_url(site.item, id.item) + elseif id.zone ~= nil and site.zone ~= nil then + url = format_url(site.zone, id.zone) + else + url = format_url(site.search, term) + end + return url +end + +function process_command(...) + -- get the first argument and set it as the command for now + local command = ({...}[1] or ''):lower() + + if command == 'default' then + local command_modifier, default_site + if {...}[3] ~= nil then + -- if there are three or more arguments, the second one is the modifier + command_modifier = {...}[2] + default_site = {...}[3] + else + default_site = {...}[2] + end + set_default_site(command_modifier, default_site) + return + elseif command == 'site' then + local site = {...}[2] + local type = {...}[3] + local url = {...}[4] + modify_site_settings(site, type, url) + return + end + + local term; + if {...}[2] ~= nil then + -- if there are two arguments, the first is the command and the second the term + command = {...}[1] + term = {...}[2] + else + -- otherwise, just a term is provided, so use the default command + command = settings.default + term = {...}[1] + end + if term == nil then + return + end + + local site = get_site(command:lower()) + if site == nil then + return + end + + local url = get_url(site, term) + if url == nil then + return + end + windower.open_url(url) +end + +load_settings() +initialize_ids() + +windower.register_event('add item', set_last_item) +windower.register_event('addon command', process_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/Lookup/README.md b/Data/DefaultContent/Libraries/addons/addons/Lookup/README.md new file mode 100644 index 0000000..d091c0e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Lookup/README.md @@ -0,0 +1,147 @@ +# Lookup +A simple [Windower4](http://www.windower.net/) addon that looks up search terms through in-game commands. + +The default search is performed with the following command: +``` +//lookup "Search Term" +``` +Alternatively, the shorthand `lu` can be used: +``` +//lu "Search Term" +``` + +Running this command will open up the search in your default browser. + +The search term can be plain text, auto-translate text, or one of the available [selectors](#selectors). If the search term contains a space, it must be surrounded in quotes (this does not apply to selectors). + +The default search sites are [FFXIclopedia](http://ffxiclopedia.wikia.com/), [BGWiki](http://www.bg-wiki.com), [FFXIAH](http://www.ffxiah.com), [FFXIDB](http://www.ffxidb.com), and [Google](http://www.google.com). See the [site command](#site) for how to add additional sites. + +See the [commands](#commands) section for a list of all available commands. + +## Selectors +Selectors can be used in place of plain text search terms. They are very useful for quickly getting information about something in the environment or a recently obtained item. + +The following selectors are accepted by this addon: + +| Selector | Replacement | +|----------|-------------| +| `<job>`<br>`<mjob>` | The current player's main job. | +| `<sjob>` | The current player's subjob. | +| `<zone>` | The current area/zone. | +| `<item>` | The last item placed in the player's inventory, including items moved from other bags. | + +The selectors found in [Windower's documentation](https://github.com/Windower/Lua/wiki/FFXI-Functions#windowerffxiget_mob_by_targettarget) are also accepted. Some of the more useful selectors are listed below, for convenience: + +| Selector | Replacement | +|----------|-------------| +| `<t>` | The current target's name. | +| `<bt>` | The current battle target'name . | +| `<pet>` | The name of the current player's pet. | +| `<me>` | The current player's name. | +| `<r>` | The name of the player that last sent a tell to you. | + +## Commands +``` +//lookup "Search Term" +``` +Searches for the term on the default site. The default site is set to "ffxiclopedia" initially, but can be changed with the "default" command. + +Alternatively, the shorthand `lu` can be used: +``` +//lu "Search Term" +``` + +#### Default +``` +//lookup default "site" +``` +Sets the default site to search with. Saved in the global settings (not character-specific). + +``` +//lookup default player "site" +``` +``` +//lookup default p "site" +``` +Saves the default site only for the current player. + +#### Site +``` +//lookup site "site" search "http://www.example.com/search?q=${term}" +``` +Adds or modifies the site lookup capability. + +The second argument, `"site"` is the site that you're modifying. For example, specifying `"ffxiclopedia"` would modify the settings for `ffxiclopedia` searches. New sites can also be added this way. + +The third argument, `search`, can be substituted for `zone` or `item` if the site supports zone or item ids in its url. + +The last argument is the url of the search. The `${term}` in the url will be substituted for the search term when a lookup is performed. + +``` +//lookup site "site" remove +``` +Removes all lookup capability for the specified site (`"site"`). + +``` +//lookup site "site" search remove +``` +Removes the `search` lookup capability for the specified site (`"site"`). The `search` argument can also be substituted for `zone` or `item`. + +#### FFXIclopedia +``` +//lookup ffxiclopedia "Search Term" +``` +``` +//lookup ffxi "Search Term" +``` +``` +//lookup wikia "Search Term" +``` +Searches for the term on [FFXIclopedia](http://ffxiclopedia.wikia.com/). + +#### BGWiki +``` +//lookup bg-wiki "Search Term" +``` +``` +//lookup bgwiki "Search Term" +``` +``` +//lookup bg "Search Term" +``` +Searches for the term on [BGWiki](http://www.bg-wiki.com). + +#### FFXIAH +``` +//lookup ffxiah "Item" +``` +``` +//lookup ah "Item" +``` +Searches for the item on [FFXIAH](http://www.ffxiah.com). + +``` +//lookup ffxiahplayer "Player" +``` +``` +//lookup ffxiahp "Player" +``` +``` +//lookup ahp "Player" +``` +Searches for the player on [FFXIAH](http://www.ffxiah.com). + +#### FFXIDB +``` +//lookup ffxidb "Search Term" +``` +``` +//lookup db "Search Term" +``` +Searches for the term on [FFXIDB](http://www.ffxidb.com). + +#### Google +``` +//lookup google "Search Term" +``` +Searches for the term on [Google](http://www.google.com). diff --git a/Data/DefaultContent/Libraries/addons/addons/MobCompass/MobCompass.lua b/Data/DefaultContent/Libraries/addons/addons/MobCompass/MobCompass.lua new file mode 100644 index 0000000..7a833f2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MobCompass/MobCompass.lua @@ -0,0 +1,248 @@ +--[[ +Copyright (c) 2013, Sebastien Gomez +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of MobCompass nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sebastien Gomez BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'MobCompass' +_addon.version = '2.0.1' + +texts = require('texts') +config = require('config') + +do + local s_arrows={ + pos = {}, + bg = {visible=false}, + flags = {draggable=false}, + text = {size=33,font='Wingdings'} + } + + circle = texts.new('l',s_arrows) + circle2 = texts.new('l',s_arrows) + + n = texts.new('Ù',s_arrows) + s = texts.new('Ú',s_arrows) + w = texts.new('×',s_arrows) + e = texts.new('Ø',s_arrows) + + s_arrows.text.size = 20 + + ne = texts.new('w',s_arrows) + nw = texts.new('w',s_arrows) + sw = texts.new('w',s_arrows) + se = texts.new('w',s_arrows) + + _defaults = config.load({ + x_pos = 0, + y_pos = 0, + }) + x_pos = _defaults.x_pos + y_pos = _defaults.y_pos + + config.register(_defaults, function(settings_table) + local x_pos = settings_table.x_pos + local y_pos = settings_table.y_pos + + n:pos(x_pos+29,y_pos) + s:pos(x_pos+29,y_pos+58) + e:pos(x_pos+62,y_pos+29) + w:pos(x_pos,y_pos+29) + + circle:pos(x_pos+22,y_pos+14) + circle2:pos(x_pos-8,y_pos-27) + + sw:pos(x_pos+19,y_pos+60) + se:pos(x_pos+64,y_pos+60) + nw:pos(x_pos+19,y_pos+17) + ne:pos(x_pos+64,y_pos+17) + sas:pos(x_pos+32,y_pos+40) + labels:pos(x_pos-22,y_pos-18) + + end) + + sas = texts.new('360',{ + pos = {x=x_pos+32,y=y_pos+40}, + bg = {visible=false}, + flags = {draggable=false}, + text = {size=15,font='Consolas',} + }) + + labels = texts.new(' Crit\n\n\n\nMB Att\n\n\n\n Acc',{ + pos = {x=x_pos-22,y=y_pos-18}, + bg = {visible=false}, + flags = {draggable=false}, + text = {size=10,font='Consolas',} + }) + + n:pos(x_pos+29,y_pos) + s:pos(x_pos+29,y_pos+58) + e:pos(x_pos+62,y_pos+29) + w:pos(x_pos,y_pos+29) + + circle:pos(x_pos+22,y_pos+14) + circle2:pos(x_pos-8,y_pos-27) + + sw:pos(x_pos+19,y_pos+60) + se:pos(x_pos+64,y_pos+60) + nw:pos(x_pos+19,y_pos+17) + ne:pos(x_pos+64,y_pos+17) + + circle:size(53) + circle:alpha(100) + circle2:size(111) + circle2:color(0,0,165) + circle2:alpha(28) + + s_arrows.text.alpha = 255 +end + +do + local drag_and_drop + + windower.register_event('mouse', function(type, x, y, delta, blocked) + if blocked then return end + if type == 0 then + if drag_and_drop then + sas:pos(x-drag_and_drop[1]+32,y-drag_and_drop[2]+40) + n:pos(x-drag_and_drop[1]+29,y-drag_and_drop[2]) + s:pos(x-drag_and_drop[1]+29,y-drag_and_drop[2]+58) + e:pos(x-drag_and_drop[1]+62,y-drag_and_drop[2]+29) + w:pos(x-drag_and_drop[1],y-drag_and_drop[2]+29) + sw:pos(x-drag_and_drop[1]+19,y-drag_and_drop[2]+60) + se:pos(x-drag_and_drop[1]+64,y-drag_and_drop[2]+60) + nw:pos(x-drag_and_drop[1]+19,y-drag_and_drop[2]+17) + ne:pos(x-drag_and_drop[1]+64,y-drag_and_drop[2]+17) + circle:pos(x-drag_and_drop[1]+22,y-drag_and_drop[2]+14) + circle2:pos(x-drag_and_drop[1]-8,y-drag_and_drop[2]-27) + labels:pos(x-drag_and_drop[1]-22,y-drag_and_drop[2]-18) + return true + end + elseif type == 1 then + if (x-x_pos-45)^2 + (y-y_pos-45)^2 < 2025 then + drag_and_drop = {x-x_pos,y-y_pos} + return true + end + elseif type == 2 then + if drag_and_drop then + x_pos,y_pos = x-drag_and_drop[1],y-drag_and_drop[2] + _defaults.x_pos = x_pos + _defaults.y_pos = y_pos + config.save(_defaults) + drag_and_drop = nil + return true + end + end + end) +end + +do + local is_labels_visible = false + windower.register_event('job change', function(main_job_id,_,sub_job_id) + is_labels_visible = main_job_id == 21 or sub_job_id == 21 + labels:visible(is_labels_visible and w:visible()) + end) + + local player_index + if windower.ffxi.get_info().logged_in then + local player = windower.ffxi.get_player() + player_index = player.index + is_labels_visible = player.main_job_id == 21 or player.sub_job_id == 21 + end + + windower.register_event('zone change', function() + player_index = windower.ffxi.get_player().index + end) + + windower.register_event('login', function() + player_index = windower.ffxi.get_player().index + end) + + local target = 0 + + windower.register_event('target change', function(n) + target = n ~= player_index and n or 0 + if target == 0 then + for i=1,#windower.text.saved_texts do + windower.text.saved_texts[i]:hide() + end + elseif not w:visible() then + for i=1,#windower.text.saved_texts do + windower.text.saved_texts[i]:show() + end + labels:visible(is_labels_visible) + end + end) + + local atan = math.atan + local pi = math.pi + local last45angle = 10 + local last16angle + local direction = { + [0]=' N ', ' N ', 'NNE', 'N E', 'ENE', ' E ', 'ESE', 'S E', 'SSE', ' S ', 'SSW', 'S W', 'WSW', + ' W ', 'WNW', 'N W', 'NNW', [-7]='SSW', [-6]='S W', [-5]='WSW', [-4]=' W ', [-3]='WNW', [-2]='N W', [-1]='NNW', + } + local arrow_map = {[0]=e,e,ne,n,nw,w,sw,s,se,e,sas} + + windower.register_event('prerender', function() + if target ~= 0 then + local player = windower.ffxi.get_mob_by_index(player_index) + if not player then + target = 0 + return + end + local mob = windower.ffxi.get_mob_by_index(target) + local x,y = player.x-mob.x,player.y-mob.y + local angle = atan(y/x) + if x < 0 then + angle = angle+pi + elseif y < 0 then + angle = angle+2*pi + end + local next45angle = math.ceil((angle+pi/8)/(pi/4)) + if next45angle ~= last45angle then + if next45angle ~= nil and last45angle ~=nil then + arrow_map[last45angle]:color(255,255,255) + arrow_map[next45angle]:color(255,0,0) + last45angle = next45angle + end + end + local heading = mob.facing + if heading < 0 then + heading = -heading + else + heading = 2*pi-heading + end + + heading = heading - angle + + local next16angle = math.ceil((heading+pi/16)/(pi/8)) + if next16angle ~= last16angle then + sas:text(direction[next16angle]) + last16angle = next16angle + end + end + end) +end diff --git a/Data/DefaultContent/Libraries/addons/addons/MobCompass/Readme.txt b/Data/DefaultContent/Libraries/addons/addons/MobCompass/Readme.txt new file mode 100644 index 0000000..ffd9ca0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MobCompass/Readme.txt @@ -0,0 +1,8 @@ +Author: Sebastien Gomez +Version: 1.1 + +-------------------------- +Multiple compasses in one. +-------------------------- +Compass arrows indicate position relative to the targeted monster. +Compass readout displays position relative to the direction the targeted monster is facing.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/LICENSE b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/LICENSE new file mode 100644 index 0000000..1b2e964 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/LICENSE @@ -0,0 +1,25 @@ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Mount Muzzle nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/README.md b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/README.md new file mode 100644 index 0000000..6345213 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/README.md @@ -0,0 +1,238 @@ +**Author:** [Sjshovan (Apogee)](https://github.com/Ap0gee) +**Version:** v0.9.5 + + +# Mount Muzzle + +> A Windower 4 addon that allows the user to change or remove the default mount music in Final Fantasy 11 Online. + + +### Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Aliases](#aliases) +- [Usage](#usage) +- [Commands](#commands) +- [Support](#support) +- [Change Log](#change-log) +- [Known Issues](#known-issues) +- [TODOs](#todos) +- [License](#license) + +___ +### Prerequisites +1. [Final Fantasy 11 Online](http://www.playonline.com/ff11us/index.shtml) +2. [Windower 4](http://windower.net/) + +___ +### Installation + +**Windower:** +1. Navigate to the `Addons` section at the top of Windower. +2. Locate the `MountMuzzle` addon. +3. Click the download button. +4. Ensure the addon is switched on. + +**Manual:** +1. Navigate to <https://github.com/Ap0gee/MountMuzzle>. +2. Click on `Releases`. +3. Click on the `Source code (zip)` link within the latest release to download. +4. Extract the zipped folder to `Windower4/addons/`. +5. Rename the folder to remove the version tag (`-v0.9.5`). The folder should be named `MountMuzzle`. + +___ +### Aliases +The following aliases are available to Mount Muzzle commands: + +**mountmuzzle:** muzzle | mm +**list:** l +**set:** s +**get:** g +**default:** d +**unload:** u +**reload:** r +**about:** a +**silent:** s +**mount:** m +**chocobo:** c +**zone:** z +**help:** h + + ___ +### Usage + +Manually load the addon by using one of the following commands: + + //lua load mountmuzzle + //lua l mountmuzzle + +___ +### Commands + +**help** + +Displays available Mount Muzzle commands. Below are the equivalent ways of calling the command: + + //mountmuzzle help + //muzzle help + //mm help + //mountmuzzle h + //muzzle h + //mm h + +**list** + +Displays the available muzzle types. Below are the equivalent ways of calling the command: + + //mountmuzzle list + //muzzle list + //mm list + //mm l + +**set _\<muzzle>_** + +Sets the current muzzle to the given muzzle type. This command takes a single argument represented by `<muzzle>`. Below are the equivalent ways of calling the command: + + //mountmuzzle set <muzzle> + //muzzle set <muzzle> + //mm set <muzzle> + //mm s <muzzle> + +Here are some usage examples for the **set _\<muzzle>_** command: `mm set silent` and `muzzle set zone` etc... + +**get** + +Displays the current muzzle that is set. Below are the equivalent ways of calling the command: + + //mountmuzzle get + //muzzle get + //mm get + //mm g + +**default** + +Sets the current muzzle to the default muzzle type: `Silent`. Below are the equivalent ways of calling the command: + + //mountmuzzle default + //muzzle default + //mm default + //mm d + +**unload** + +Unloads the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + //mountmuzzle unload + //muzzle unload + //mm unload + //mm u + +**reload** + +Reloads the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + //mountmuzzle reload + //muzzle reload + //mm reload + //mm r + +**about** + +Displays information about the Mount Muzzle addon. Below are the equivalent ways of calling the command: + + //mountmuzzle about + //muzzle about + //mm about + //mm a + +___ +### Support +**Having Issues with this addon?** +* Please let me know [here](https://github.com/Ap0gee/MountMuzzle/issues/new). + +**Have something to say?** +* Send me some feedback here: <sjshovan@gmail.com> + +**Want to stay in the loop with my work?** +* You can follow me at: <https://twitter.com/Sjshovan> + +**Want to show your love and help me make more awesome stuff?** +* You can do so here: <https://www.paypal.me/Sjshovan> + +___ +### Change Log + +**v0.9.5** - 1/05/2019 +- **Fix:** Client sometimes crashes/locks when exiting game. +- **Update:** Silent muzzle song id changed to help prevent future game updates from overriding. +- **Update:** README Known Issues updated. +- **Update:** README TODOS updated. + +**v0.9.4** - 9/06/2018 +- **Fix:** Music wouldn't change if addon loaded while on mount. +- **Fix:** Music wouldn't change if addon unloaded while on mount. +- **Fix:** Muzzle type 'Silent' was playing incorrect track. +- **Update:** Licences now display correct addon name. +- **Update:** Muzzle type 'Normal' changed to 'Mount'. +- **Update:** Muzzle type 'Choco' changed to 'Chocobo'. +- **Update:** mountmuzzle.lua refactored and condensed. +- **Update:** README Commands updated. +- **Update:** README Installation updated. +- **Update:** README Table of Contents updated. +- **Update:** README Known Issues updated. +- **Update:** README TODOS updated. +- **Add:** New commands added (about, unload). +- **Add:** Shorthand aliases added to all commands. +- **Add:** Aliases added to README. + +**v0.9.3** - 5/31/2018 +- **Remove:** Removed /data/settings.xml file. +- **Update:** Licences now display correct author name. +- **Update:** helpers.lua now requires only colors from constants.lua. +- **Update:** constants.lua now returns table of globals for modular support. +- **Update:** mountmuzzle.lua refactored in attempt to meet merge criteria. +- **Update:** README refactored in attempt to meet merge criteria. + +**v0.9.2** - 5/24/2018 +- **Fix:** Zone music gets silenced if player enters reive on mount with zone muzzle selected. +- **Fix:** Player reaches error if no arguments are given upon invoking the addon. +- **Update:** Convert tab characters to spaces, simplify code. +- **Update:** README Usage Instructions updated. +- **Update:** README Known Issues updated. +- **Add:** Table of Contents added to README. +- **Add:** Prerequisites added to README. +- **Add:** Installation added to README. +- **Add:** Support added to README. +- **Add:** License added to README. + +**v0.9.1** - 5/22/2018 +- **Fix:** Chosen music does not start upon login if mounted. +- **Fix:** Chosen music does not persist upon changing zones. +- **Add:** Known Issues added to README. +- **Add:** TODOS added to README. + +**v0.9.0** - 5/21/2018 +- Initial release + +___ +### Known Issues + +- **Issue:** If Mount Muzzle is selected to automatically load and the player is mounted upon login, there is a significant delay before the chosen music will begin to play. +- **Issue:** Upon changing zones the default music can be heard for a moment before the chosen music begins to play. +- **Issue:** Unable to correctly set mount music to original if Mount Muzzle is unloaded while mounted. +- **Issue:** Unable to alter mount music upon addon unload through the Windower `exit` command, which causes client to crash. No known viable alternatives. +___ +### TODOs + +- **TODO:** Investigate alternative methods for music change as packet injection/swap allows the player to hear the default music upon zone change and login, regardless of chosen music. +- **TODO:** Investigate methods for determining which mount type the player is on when loading/unloading Mount Muzzle. +- **TODO:** Investigate ways to prevent packet injection client crash/lock within unload event if triggered through Windower `exit` command. +___ + +### License + +Copyright © 2018, [Sjshovan (Apogee)](https://github.com/Ap0gee). +Released under the [BSD License](LICENSE). + +***
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/constants.lua b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/constants.lua new file mode 100644 index 0000000..dfd66a7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/constants.lua @@ -0,0 +1,111 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Mount Muzzle nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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. +--]] + +packets = { + inbound = { + music_change = { + id = 0x05F + }, + zone_update = { + id = 0x00A + } + }, + outbound = { + action = { + id = 0x1A, + categories = { + mount = 0x1A, + unmount = 0x12 + }, + } + }, +} + +player = { + statuses = { + mounted = 85 + }, + buffs = { + reiveMark = 511, + mounted = 252 + } +} + +music = { + songs = { + silent = 9999, + mount = 84, + chocobo = 212, + zone = 0, + }, + types = { + mount = 4, + idle_day = 0, + idle_night = 1 + } +} + +colors = { + primary = 200, + secondary = 207, + info = 0, + warn = 140, + danger = 167, + success = 158 +} + +muzzles = { + silent = { + name = 'silent', + song = music.songs.silent, + description = 'No Music (Default)' + }, + mount = { + name = 'mount', + song = music.songs.mount, + description = 'Mount Music' + }, + chocobo = { + name = 'chocobo', + song = music.songs.chocobo, + description = 'Chocobo Music' + }, + zone = { + name = 'zone', + song = music.songs.zone, + description = 'Current Zone Music' + } +} + +return { + packets = packets, + player = player, + music = music, + colors = colors, + muzzles = muzzles +}
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/helpers.lua b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/helpers.lua new file mode 100644 index 0000000..000a65e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/helpers.lua @@ -0,0 +1,80 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Mount Muzzle nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) 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. +--]] + +local colors = require("constants").colors + +function buildHelpCommandEntry(command, description) + local short_name = "mm":color(colors.primary) + local command = command:color(colors.secondary) + local sep = "=>":color(colors.primary) + local description = description:color(colors.info) + + return "%s %s %s %s":format(short_name, command, sep, description) +end + +function buildHelpTypeEntry(name, description) + local name = name:color(colors.secondary) + local sep = "=>":color(colors.primary) + local description = description:color(colors.info) + + return "%s %s %s":format(name, sep, description) +end + +function buildHelpTitle(context) + local context = context:color(colors.danger) + + return "%s Help: %s":color(colors.primary):format(_addon.name, context) +end + +function buildHelpSeperator(character, count) + local sep = '' + + for i = 1, count do + sep = sep .. character + end + + return sep:color(colors.warn) +end + +function buildCommandResponse(message, success) + local response_color = colors.success + local response_type = 'Success' + + if not success then + response_type = 'Error' + response_color = colors.danger + end + + return "%s: %s":format(response_type:color(response_color), message) +end + +function displayResponse(response, color) + color = color or colors.info + windower.add_to_chat(color, response) + windower.console.write(response:strip_colors()) +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/mountmuzzle.lua b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/mountmuzzle.lua new file mode 100644 index 0000000..95ab080 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountMuzzle/mountmuzzle.lua @@ -0,0 +1,261 @@ +--[[ +Copyright © 2018, Sjshovan (Apogee) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Mount Muzzle nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Sjshovan (Apogee) BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--]] + +_addon.name = 'Mount Muzzle' +_addon.description = 'Change or remove the default mount music.' +_addon.author = 'Sjshovan (Apogee) sjshovan@gmail.com' +_addon.version = '0.9.5' +_addon.commands = {'mountmuzzle', 'muzzle', 'mm'} + +local _logger = require('logger') +local _config = require('config') +local _packets = require('packets') + +require('constants') +require('helpers') + +local needs_inject = false + +local defaults = { + muzzle = muzzles.silent.name +} + +local settings = _config.load(defaults) + +local help = { + commands = { + buildHelpSeperator('=', 26), + buildHelpTitle('Commands'), + buildHelpSeperator('=', 26), + buildHelpCommandEntry('list', 'Display the available muzzle types.'), + buildHelpCommandEntry('set <muzzle>', 'Set the current muzzle to the given muzzle type.'), + buildHelpCommandEntry('get', 'Display the current muzzle.'), + buildHelpCommandEntry('default', 'Set the current muzzle to the default (Silent).'), + buildHelpCommandEntry('unload', 'Unload Mount Muzzle.'), + buildHelpCommandEntry('reload', 'Reload Mount Muzzle.'), + buildHelpCommandEntry('about', 'Display information about Mount Muzzle.'), + buildHelpCommandEntry('help', 'Display Mount Muzzle commands.'), + buildHelpSeperator('=', 26), + }, + types = { + buildHelpSeperator('=', 23), + buildHelpTitle('Types'), + buildHelpSeperator('=', 23), + buildHelpTypeEntry(muzzles.silent.name:ucfirst(), muzzles.silent.description), + buildHelpTypeEntry(muzzles.mount.name:ucfirst(), muzzles.mount.description), + buildHelpTypeEntry(muzzles.chocobo.name:ucfirst(), muzzles.chocobo.description), + buildHelpTypeEntry(muzzles.zone.name:ucfirst(), muzzles.zone.description), + buildHelpSeperator('=', 23), + }, + about = { + buildHelpSeperator('=', 23), + buildHelpTitle('About'), + buildHelpSeperator('=', 23), + buildHelpTypeEntry('Name', _addon.name), + buildHelpTypeEntry('Description', _addon.description), + buildHelpTypeEntry('Author', _addon.author), + buildHelpTypeEntry('Version', _addon.version), + buildHelpSeperator('=', 23), + }, + aliases = { + muzzles = { + s = muzzles.silent.name, + m = muzzles.mount.name, + c = muzzles.chocobo.name, + z = muzzles.zone.name + } + } +} + +function display_help(table_help) + for index, command in pairs(table_help) do + displayResponse(command) + end +end + +function getMuzzle() + return settings.muzzle +end + +function getPlayerBuffs() + return T(windower.ffxi.get_player().buffs) +end + +function resolveCurrentMuzzle() + local current_muzzle = getMuzzle() + + if not muzzleValid(current_muzzle) then + current_muzzle = muzzles.silent.name + setMuzzle(current_muzzle) + displayResponse( + 'Note: Muzzle found in settings was not valid and is now set to the default (%s).':format('Silent':color(colors.secondary)), + colors.warn + ) + end + + return muzzles[current_muzzle] +end + +function setMuzzle(muzzle) + settings.muzzle = muzzle + settings:save() +end + +function playerInReive() + return getPlayerBuffs():contains(player.buffs.reiveMark) +end + +function playerIsMounted() + local _player = windower.ffxi.get_player() + + if _player then + return _player.status == player.statuses.mounted or getPlayerBuffs():contains(player.buffs.mounted) + end + + return false +end + +function muzzleValid(muzzle) + return muzzles[muzzle] ~= nil +end + +function injectMuzzleMusic() + injectMusic(music.types.mount, resolveCurrentMuzzle().song) +end + +function injectMusic(bgmType, songID) + _packets.inject(_packets.new('incoming', packets.inbound.music_change.id, { + ['BGM Type'] = bgmType, + ['Song ID'] = songID, + })) +end + +function requestInject() + needs_inject = true +end + +function handleInjectionNeeds() + if needs_inject and playerIsMounted() then + injectMuzzleMusic() + needs_inject = false; + end +end + +function tryInject() + requestInject() + handleInjectionNeeds() +end + +windower.register_event('login', 'load', 'zone change', function() + tryInject() +end) + +windower.register_event('addon command', function(command, ...) + if command then + command = command:lower() + else + return display_help(help.commands) + end + + local command_args = {...} + local respond = false + local response_message = '' + local success = true + + if command == 'list' or command == 'l' then + display_help(help.types) + + elseif command == 'set' or command == 's' then + respond = true + + local muzzle = tostring(command_args[1]):lower() + local from_alias = help.aliases.muzzles[muzzle] + + if (from_alias ~= nil) then + muzzle = from_alias + end + + if not muzzleValid(muzzle) then + success = false + response_message = 'Muzzle type not recognized.' + else + requestInject() + setMuzzle(muzzle) + response_message = 'Updated current muzzle to %s.':format(muzzle:ucfirst():color(colors.secondary)) + end + + elseif command == 'get' or command == 'g' then + respond = true + response_message = 'Current muzzle is %s.':format(getMuzzle():ucfirst():color(colors.secondary)) + + elseif command == 'default' or command == 'd' then + respond = true + requestInject() + + setMuzzle(muzzles.silent.name) + response_message = 'Updated current muzzle to the default (%s).':format('Silent':color(colors.secondary)) + + elseif command == 'reload' or command == 'r' then + windower.send_command('lua r mountmuzzle') + + elseif command == 'unload' or command == 'u' then + respond = true + response_message = 'Thank you for using Mount Muzzle. Goodbye.' + injectMusic(music.types.mount, muzzles.zone.song) + windower.send_command('lua unload mountmuzzle') + + elseif command == 'about' or command == 'a' then + display_help(help.about) + + elseif command == 'help' or command == 'h' then + display_help(help.commands) + else + display_help(help.commands) + end + + if respond then + displayResponse( + buildCommandResponse(response_message, success) + ) + end + + handleInjectionNeeds() +end) + +windower.register_event('incoming chunk', function(id, data) + if id == packets.inbound.music_change.id then + local packet = _packets.parse('incoming', data) + + if packet['BGM Type'] == music.types.mount then + packet['Song ID'] = resolveCurrentMuzzle().song + return _packets.build(packet) + end + + tryInject() + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/MountRoulette/MountRoulette.lua b/Data/DefaultContent/Libraries/addons/addons/MountRoulette/MountRoulette.lua new file mode 100644 index 0000000..0b272b7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountRoulette/MountRoulette.lua @@ -0,0 +1,89 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Mount Roulette nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'Mount Roulette' +_addon.author = 'Dean James (Xurion of Bismarck)' +_addon.version = '3.0.1' +_addon.commands = {'mountroulette', 'mr'} + +require('lists') +require('sets') +resources = require('resources') + +math.randomseed(os.time()) + +allowed_mounts = L{} +possible_mounts = L{} +for _, mount in pairs(resources.mounts) do + possible_mounts:append(mount.name:lower()) +end + +function update_allowed_mounts() + local allowed_mounts_set = S{} + local kis = windower.ffxi.get_key_items() + + for _, id in ipairs(kis) do + local ki = resources.key_items[id] + if ki.category == 'Mounts' and ki.name ~= "trainer's whistle" then -- Don't care about the quest KI + local mount_index = possible_mounts:find(function(possible_mount) + return windower.wc_match(ki.name:lower(), '♪' .. possible_mount .. '*') + end) + local mount = possible_mounts[mount_index] + + allowed_mounts_set:add(mount) + end + end + + allowed_mounts = L(allowed_mounts_set) +end + +update_allowed_mounts() + +windower.register_event('incoming chunk', function(id) + if id == 0x055 then --ki update + update_allowed_mounts() + end +end) + +windower.register_event('addon command', function() + local player = windower.ffxi.get_player() + + -- If the player is mounted, dismount now + for _, buff in pairs(player.buffs) do + if buff == 252 then --mounted buff + windower.send_command('input /dismount') + return + end + end + + if #allowed_mounts == 0 then return end + + -- Generate random number and use it to choose a mount + local mount_index = math.ceil(math.random() * #allowed_mounts) + windower.send_command('input /mount "' .. allowed_mounts[mount_index] .. '"') +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/MountRoulette/README.md b/Data/DefaultContent/Libraries/addons/addons/MountRoulette/README.md new file mode 100644 index 0000000..5072d4a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MountRoulette/README.md @@ -0,0 +1,17 @@ +# Final Fantasy XI Mount Roulette + +A Lua addon to summon a mount at random for Windower 4. Mimics the FFXIV Mount Roulette function. + +## Usage + +Get the addon from the addon section of the Windower launcher. + +### Summon a mount + +`//mr` + +This will also dismount you if you're currently mounted. + +## Mount music + +In an earlier version, this addon disabled mount music. This is now removed in favour of the MountMuzzle addon. diff --git a/Data/DefaultContent/Libraries/addons/addons/MyHome/MyHome.lua b/Data/DefaultContent/Libraries/addons/addons/MyHome/MyHome.lua new file mode 100644 index 0000000..3c4a668 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MyHome/MyHome.lua @@ -0,0 +1,123 @@ +--[[ +Copyright © 2018, from20020516 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of MyHome nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL from20020516 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'MyHome' +_addon.author = 'from20020516' +_addon.version = '1.1.1' +_addon.commands = {'myhome','mh','warp'} + +require('logger') +extdata = require('extdata') +res_bags = require('resources').bags + +log_flag = true + +lang = string.lower(windower.ffxi.get_info().language) +item_info = { + [1]={id=28540,japanese='デジョンリング',english='"Warp Ring"',slot=13}, + [2]={id=17040,japanese='デジョンカジェル',english='"Warp Cudgel"',slot=0}, + [3]={id=4181,japanese='呪符デジョン',english='"Instant Warp"'}} + +function search_item() + if windower.ffxi.get_player().status > 1 then + log('You cannot use items at this time.') + return + end + + local item_array = {} + local get_items = windower.ffxi.get_items + local set_equip = windower.ffxi.set_equip + + for bag_id in pairs(res_bags:equippable(true)) do + local bag = get_items(bag_id) + for _,item in ipairs(bag) do + if item.id > 0 then + item_array[item.id] = item + item_array[item.id].bag = bag_id + item_array[item.id].bag_enabled = bag.enabled + end + end + end + for index,stats in pairs(item_info) do + local item = item_array[stats.id] + if item and item.bag_enabled then + local ext = extdata.decode(item) + local enchant = ext.type == 'Enchanted Equipment' + local recast = enchant and ext.charges_remaining > 0 and math.max(ext.next_use_time+18000-os.time(),0) + local usable = recast and recast == 0 + log(stats[lang],usable and '' or recast and recast..' sec recast.') + if usable or ext.type == 'General' then + if enchant and item.status ~= 5 then --not equipped + set_equip(item.slot,stats.slot,item.bag) + repeat --waiting cast delay + coroutine.sleep(1) + local ext = extdata.decode(get_items(item.bag,item.slot)) + local delay = ext.activation_time+18000-os.time() + if delay > 0 then + log(stats[lang],delay) + elseif log_flag then + log_flag = false + log('Item use within 3 seconds..') + end + until ext.usable or delay > 30 + end + windower.chat.input('/item '..windower.to_shift_jis(stats[lang])..' <me>') + break; + end + elseif item and not item.bag_enabled then + log('You cannot access '..stats[lang]..' from ' .. res_bags[item.bag].name ..' at this time.') + else + log('You don\'t have '..stats[lang]..'.') + end + end +end + +windower.register_event('addon command',function(...) + local args = T{...} + local cmd = args[1] + if cmd == 'all' then + windower.chat.input('//myhome') + windower.send_ipc_message('myhome') + else + local player = windower.ffxi.get_player() + local get_spells = windower.ffxi.get_spells() + local spell = S{player.main_job_id,player.sub_job_id}[4] + and (get_spells[261] and player.vitals.mp >= 100 and {japanese='デジョン',english='"Warp"'} + or get_spells[262] and player.vitals.mp >= 150 and {japanese='デジョンII',english='"Warp II"'}) + if spell then + windower.chat.input('/ma '..windower.to_shift_jis(spell[lang])..' <me>') + else + search_item() + end + end +end) + +windower.register_event('ipc message',function (msg) + if msg == 'myhome' then + windower.chat.input('//myhome') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/MyHome/README.md b/Data/DefaultContent/Libraries/addons/addons/MyHome/README.md new file mode 100644 index 0000000..67d44fe --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/MyHome/README.md @@ -0,0 +1,30 @@ +# MyHome +## English +- Automatically choose and uses a warp spell or an item. + +### Command +- `//mh` OR `//warp` +- `//mh all` OR `//warp all` will warp all characters. + + +- Priorities are: + 1. Warp <me> - require learned and main job or sub job BLM. + 2. Warp II <me> + 3. Warp Ring - search inventory and wardrobes. + 4. Warp Cudgel + 5. Instant Warp - search inventory. + +## 日本語 +- デジョンやデジョン系アイテムをリキャストに応じて自動で選択、使用します。 + +### Command +- `//mh` または `//warp` +- `//mh all` または `//warp all` すべての文字をワープします。 +- 優先順位: + + + 1. デジョン <me> - 習得済かつメインまたはサポートジョブが黒魔道士 + 2. デジョンII <me> + 3. デジョンリング - マイバッグ、またはワードローブ(1~4)を検索 + 4. デジョンカジェル + 5. 呪符デジョン - マイバッグを検索 diff --git a/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/NoCampaignMusic.lua b/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/NoCampaignMusic.lua new file mode 100644 index 0000000..a45e9ff --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/NoCampaignMusic.lua @@ -0,0 +1,129 @@ +--[[ +Copyright © 2020, Dean James (Xurion of Bismarck) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of No Campaign Music nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Dean James (Xurion of Bismarck) BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'No Campaign Music' +_addon.author = 'Dean James (Xurion of Bismarck)' +_addon.version = '2.0.1' +_addon.commands = {'nocampaignmusic', 'ncm'} + +packets = require('packets') +config = require('config') + +defaults = { + Notifications = false, +} + +settings = config.load(defaults) + +campaign_id = 247 +solo_id = 101 +party_id = 215 +solo_dungeon_id = 115 +party_dungeon_id = 216 + +zone_music_map = {} +zone_music_map[80] = { 254, 254, solo_id, party_id } --Southern San d'Oria [S] +zone_music_map[81] = { 251, 251, solo_id, party_id } --East Ronfaure [S] +zone_music_map[82] = { 0, 0, solo_id, party_id } --Jugner Forest [S] +zone_music_map[83] = { 0, 0, solo_id, party_id } --Vunkerl Inlet [S] +zone_music_map[84] = { 252, 252, solo_id, party_id } --Batallia Downs [S] +zone_music_map[85] = { 44, 44, solo_dungeon_id, party_dungeon_id } --La Vaule [S] +zone_music_map[87] = { 180, 180, solo_id, party_id } --Bastok Markets [S] +zone_music_map[88] = { 253, 253, solo_id, party_id } --North Gustaberg [S] +zone_music_map[89] = { 0, 0, solo_id, party_id } --Grauberg [S] +zone_music_map[90] = { 0, 0, solo_id, party_id } --Pashhow Marshlands [S] +zone_music_map[91] = { 252, 252, solo_id, party_id } --Rolanberry Fields [S] +zone_music_map[92] = { 44, 44, solo_dungeon_id, party_dungeon_id } --Beadeaux [S] +zone_music_map[94] = { 182, 182, solo_id, party_id } --Windurst Waters [S] +zone_music_map[95] = { 141, 141, solo_id, party_id } --West Sarutabaruta [S] +zone_music_map[96] = { 0, 0, solo_id, party_id } --Fort Karugo-Narugo [S] +zone_music_map[97] = { 0, 0, solo_id, party_id } --Meriphataud Mountains [S] +zone_music_map[98] = { 252, 252, solo_id, party_id } --Sauromugue Champaign [S] +zone_music_map[99] = { 44, 44, solo_dungeon_id, party_dungeon_id } --Castle Oztroja [S] +zone_music_map[136] = { 0, 0, solo_id, party_id } --Beaucedine Glacier [S] +zone_music_map[137] = { 42, 42, solo_id, party_id } --Xarcabard [S] +zone_music_map[138] = { 43, 43, solo_dungeon_id, party_dungeon_id } --Castle Zvahl Baileys [S] +zone_music_map[155] = { 43, 43, solo_dungeon_id, party_dungeon_id } --Castle Zvahl Keep [S] +zone_music_map[164] = { 0, 0, solo_dungeon_id, party_dungeon_id } --Garlaige Citadel [S] +zone_music_map[171] = { 0, 0, solo_dungeon_id, party_dungeon_id } --Crawlers' Nest [S] +zone_music_map[175] = { 0, 0, solo_dungeon_id, party_dungeon_id } --The Eldieme Necropolis [S] + +windower.register_event('incoming chunk', function(id, data) + if id ~= 0x00A and id ~= 0x05F then return end + + local parsed = packets.parse('incoming', data) + local zone_music = zone_music_map[parsed['Zone'] or windower.ffxi.get_info().zone] + + if not zone_music then return end + + if id == 0x00A then --Zone update (zoned in) + if parsed['Day Music'] == campaign_id and zone_music then + parsed['Day Music'] = zone_music[1] + parsed['Night Music'] = zone_music[2] + parsed['Solo Combat Music'] = zone_music[3] + parsed['Party Combat Music'] = zone_music[4] + + return packets.build(parsed) + end + else --Music update (campaign possibly started/finished) + local info = windower.ffxi.get_info() + if parsed['Song ID'] == campaign_id then + + if settings.Notifications and parsed['BGM Type'] == 0 then --only log to the chat once + windower.add_to_chat(8, 'Prevented campaign music.') + end + + parsed['Song ID'] = zone_music[parsed['BGM Type'] + 1] + return packets.build(parsed) + end + end +end) + +commands = {} + +commands.notify = function() + settings.Notifications = not settings.Notifications + settings:save() + windower.add_to_chat(8, 'Campaign notifications: ' .. tostring(settings.Notifications)) +end + +commands.help = function() + windower.add_to_chat(8, 'No Campaign Music:') + windower.add_to_chat(8, ' //ncm notify - toggles campaign notifications (default false)') + windower.add_to_chat(8, ' //ncm help - shows this help') +end + +windower.register_event('addon command', function(command) + command = command and command:lower() or 'help' + + if commands[command] then + commands[command]() + else + commands.help() + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/README.md b/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/README.md new file mode 100644 index 0000000..63ec719 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/NoCampaignMusic/README.md @@ -0,0 +1,27 @@ +# FFXI - No Campaign Music + +Prevents all campaign battle music from playing in Final Fantasy XI, so you can listen to that sweet music that is normally obnoxiously interrupted. + +## Load + +``` +//lua load ncm +``` + +## Campaign notifications + +If you want to know when campaign is happening, you can toggle notifications: + +``` +//ncm notify +``` + +## Note + +When campaign music is prevented from playing (whether it's as you zone in, or campaign starts in the zone) a message will log to the chatlog stating "Prevented campaign music." This not only confirms the addon is working, but shows you there's a campaign active should you want to take part. + +## Contributing + +If you notice something not quite right, please [raise an issue](https://github.com/xurion/ffxi-no-campaign-music/issues). + +Or better yet, [pull requests](https://github.com/xurion/ffxi-no-campaign-music/pulls) are welcome! diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/Nostrum.lua b/Data/DefaultContent/Libraries/addons/addons/Nostrum/Nostrum.lua new file mode 100644 index 0000000..586abda --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/Nostrum.lua @@ -0,0 +1,769 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Nostrum nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.--]] + +_addon.name = 'Nostrum' +_addon.author = 'trv' +_addon.version = '2.2.0' +_addon.commands = {'Nostrum','nos',} + +packets=require('packets') +require('tables') +require('strings') +require('variables') +require('logger') +require('helperfunctions') +require('sets') +require('lists') +config = require('config') +prims = require('prims') + +defaults={ + text={ + buttons={ + color={a=255,r=255,g=255,b=255}, + }, + name={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + tp={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + hp={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + mp={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + hpp={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + na={ + color={a=255,r=255,g=255,b=255}, + visible=true + }, + buffs={ + color={a=255,r=255,g=255,b=255}, + visible=true} + }, + primitives={ + buttons={ + visible=false, + color={a=0, r=0, g=0, b=0}, + }, + highlight={ + color={a=100, r=255, g=255, b=255}, + }, + curaga_buttons={ + visible=false, + color={a=0, r=0,g=0, b=0}, + }, + background={ + visible=true, + color={a=100, r=0, g=0, b=0}, + }, + hp_bar={ + green={a=176, r=1, g=100, b=14}, + yellow={a=176, r=255,g=255,b=0}, + orange={a=176, r=255, g=100, b=1}, + red={a=176, r=255, g=0, b=0}, + }, + mp_bar={ + visible=true, + color={a=100, r=149, g=212, b=255}, + }, + hp_bar_background={ + visible=true, + color={a=200, r=0, g=0, b=0}, + }, + na_buttons={ + visible=false, + color={a=0,r=255,g=255,b=255}, + }, + buff_buttons={ + visible=false, + color={a=0, r=255, g=255, b=255}, + }, + }, + window={x_offset=0,y_offset=0,}, + profiles={ + default={ + ["cure"]=true, + ["cureii"]=true, + ["cureiii"]=true, + ["cureiv"]=true, + ["curev"]=true, + ["curevi"]=true, + ["curaga"]=true, + ["curagaii"]=true, + ["curagaiii"]=true, + ["curagaiv"]=true, + ["curagav"]=true, + ["sacrifice"]=true, + ["erase"]=true, + ["paralyna"]=true, + ["silena"]=true, + ["blindna"]=true, + ["poisona"]=true, + ["viruna"]=true, + ["stona"]=true, + ["cursna"]=true, + ["haste"]=true, + ["hasteii"]=false, + ["flurry"]=false, + ["flurryii"]=false, + ["protect"]=false, + ["shell"]=false, + ["protectii"]=false, + ["shellii"]=false, + ["protectiii"]=false, + ["shelliii"]=false, + ["protectiv"]=false, + ["shelliv"]=false, + ["protectv"]=true, + ["shellv"]=true, + ["refresh"]=false, + ["refreshii"]=false, + ["regen"]=false, + ["regenii"]=false, + ["regeniii"]=false, + ["regeniv"]=true, + ["regenv"]=false, + ["phalanxii"]=false, + ["adloquium"]=false, + ["animusaugeo"]=false, + ["animusminuo"]=false, + ["embrava"]=false, + ["curingwaltz"]=false, + ["curingwaltzii"]=false, + ["curingwaltziii"]=false, + ["curingwaltziv"]=false, + ["curingwaltzv"]=false, + ["divinewaltz"]=false, + ["divinewaltzii"]=false, + ["healingwaltz"]=false, + }, + }, +} +_defaults = config.load(defaults) + +_settings=merge_user_file_and_settings(_defaults,settings) +profile=_settings.profiles.default + +function build_macro() + local x_start=_settings.window.x_res-1-_defaults.window.x_offset + local y_start=_settings.window.y_res-h-1-_defaults.window.y_offset + local prim = _settings.primitives + local text = _settings.text + + for k=1,3 do + local pt = party[k] + if pt.n ~= 0 then + prim_simple("BG"..tostring(k),prim.background,x_start-(macro_order[k].n)*(w+1)-153,y_start-pt.n*(h+1)+h,macro_order[k].n*(w+1)+1,pt.n*(h+1)+1) + prim_simple("info"..tostring(k),prim.hp_bar_background,x_start-152,y_start-pt.n*(h+1)+h,152,pt.n*(h+1)+1) + macro[k]:add("BG"..tostring(k)) + end + for j=pt.n,1,-1 do + local stats = stat_table[pt[j]] + local n = position_lookup[pt[j]] + local s = tostring(n) + prim_simple("phpp" .. s,prim.hp_bar,x_start-151,y_start,150/100*stats.hpp,h) + local color = prim.hp_bar[choose_color(stats.hpp)] + windower.prim.set_color("phpp" .. s,color.a,color.r,color.g,color.b) + prim_simple("pmpp" .. s,prim.mp_bar,x_start-151,y_start+19,150/100*stats.mpp,5) + text_simple("tp" .. s, text.tp, x_start-151, y_start+11,stats.tp) + text_simple("name" .. s, text.name, x_start-151, y_start-3, prepare_names(stats.name)) + text_simple("hpp" .. s, text.hpp, x_start, y_start-4, stats.hpp) + text_simple("hp" .. s, text.hp, x_start-40, y_start-3, stats.hp) + text_simple("mp" .. s, text.mp, x_start-40, y_start+11,stats.mp) + prims_by_layer[n]:extend(L{"phpp" .. s,"pmpp" .. s}) + texts_by_layer[n]:extend(L{"tp" .. s,"name" .. s,"hpp" .. s,"hp" .. s,"mp" .. s}) + + prim_rose(macro_order[k],n,x_start,y_start,k) + y_start=y_start-(h+1) + + end + + y_start=y_start-(175-75*k) + + end + + prim_simple("target_background",prim.hp_bar_background,x_start-152,prim_coordinates.y['info1']-52,152,32) + text_simple("target_name", text.name, x_start-151, prim_coordinates.y['info1']-50,'') + windower.text.set_font_size("target_name11", 13) + prim_simple("target",prim.hp_bar,x_start-151,prim_coordinates.y['info1']-50,150,30) + text_simple("targethpp",text.tp, x_start-151, prim_coordinates.y['info1']-34, '0') + local color = prim.hp_bar[choose_color(100)] + windower.prim.set_color("target",color.a,color.r,color.g,color.b) + misc_hold_for_up.prims:append("target_background") + misc_hold_for_up.prims:append("target") + misc_hold_for_up.texts:append("target_name") + misc_hold_for_up.texts:append("targethpp") + prim_simple("pmenu",prim.hp_bar_background,x_start-152,prim_coordinates.y['info1']-20,152,20) + text_simple("menu",text.name, x_start-94, prim_coordinates.y['info1']-18, 'menu') + misc_hold_for_up.prims:append("pmenu") + misc_hold_for_up.texts:append("menu") + + y_start=prim_coordinates.y['BG1']-27 + if macro_order[4].n~=0 then + prim_simple("BGna",prim.background,x_start-33*macro_order[4].n-153,y_start,(macro_order[4].n)*(33)+1,27) + misc_hold_for_up.prims:append("BGna") + macro[1]:add("BGna") + image_row(macro_order[4],x_start,y_start+1) + y_start=y_start-27 + end + + if macro_order[5].n~=0 then + prim_simple("BGbuffs",prim.background,x_start-33*macro_order[5].n-153,y_start,(macro_order[5].n)*(33)+1,27) + misc_hold_for_up.prims:append("BGbuffs") + macro[1]:add("BGbuffs") + image_row(macro_order[5],x_start,y_start+1) + end + + prim_simple("hover24",table.set(_defaults.primitives.highlight,'visible',false),0,0,29,24) + prim_simple("hover32",table.set(_defaults.primitives.highlight,'visible',false),0,0,25,25) + misc_hold_for_up.prims:append("hover24") + misc_hold_for_up.prims:append("hover32") + + + toggle_macro_visibility(1) + toggle_macro_visibility(2) + toggle_macro_visibility(3) + +end + +do + local initialized = false + initialize = function(bool) + if bool ~= nil then initialized = bool return end + if initialized or not windower.ffxi.get_info().logged_in then return end + initialized = true + local alliance_keys = {'p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'a10', 'a11', 'a12', 'a13', 'a14', 'a15', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25'} + local party_from_memory = windower.ffxi.get_party() + local player = windower.ffxi.get_player() + + player_id = player.id + position_lookup = {} + stat_table = {} + party = {L{},L{},L{}} + count_cures(profile) + count_na(profile) + count_buffs(profile) + + for i=1,18 do + local party_member_from_memory = party_from_memory[alliance_keys[i]] + + if party_member_from_memory and party_member_from_memory.mob then + local id = party_member_from_memory.mob.id + local n = math.ceil(i/6) + + party[n]:append(id) + + local m = 6*n + 1 - party[n].n + + position_lookup[id] = m + + position[1][m] = party_member_from_memory.mob.x + position[2][m] = party_member_from_memory.mob.y + stat_table[id]={ + hp = party_member_from_memory.hp, + mp = party_member_from_memory.mp, + mpp = party_member_from_memory.mpp, + hpp = party_member_from_memory.hpp, + tp = party_member_from_memory.tp, + name = party_member_from_memory.name, + buffs = {{n=0},{n=0}} + } + end + end + + build_macro() + define_active_regions() + register_events(true) + stat_table[player_id].index = player.index + end +end + +windower.register_event('load', initialize) + +windower.register_event('login', function() + coroutine.sleep(6) + initialize() +end) + +windower.register_event('logout', function() + wrecking_ball() + initialize(false) + register_events(false) +end) + +windower.register_event('addon command', function(...) + local args={...} + local c = args[1] and args[1]:lower() or 'help' + if c == 'help' then + print(help_text) + elseif c == 'hide' or c == 'h' then + toggle_visibility() + elseif c == 'cut' or c == 'c' then + trim_macro() + elseif c == 'refresh' or c == 'r' then + compare_alliance_to_memory() + elseif c == 'send' or c == 's' then + if args[2] then + send_string = 'send ' .. tostring(args[2]) .. ' ' + print('Commands will be sent to: ' .. tostring(args[2])) + else + send_string = '' + print('Input contained no name. Send disabled.') + end + elseif c == 'profile' or c == 'p' then + if args[2] == 'reload' or args[2] == 'r' then + config.reload(defaults) + elseif _settings.profiles[args[2]] then + profile = _settings.profiles[args[2]] + switch_profiles() + else + print('Profile ' .. args[2] .. ' not found.') + end + end +end) + +do + local incoming_chunk_event + local outgoing_chunk_event + local zone_change_event + local keyboard_event + local mouse_event + local last_x,last_y = 0,0 + local last_x32,last_y32 = 0,0 + local prim_coordinates = prim_coordinates + local x_offset = _defaults.window.x_offset + local y_offset = _defaults.window.y_offset + local x_res = settings.window.x_res + local y_res = settings.window.y_res + +register_events = function(bool) + if bool then + keyboard_event = windower.register_event('keyboard', function(dik,down,flags,blocked) + if down and tab_keys[dik] and not bit.is_set(flags, 6) then + coroutine.sleep(.02) + local target = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') + if target then update_target(target) end + end + end) + + mouse_event = windower.register_event('mouse', function(type, x, y, delta, blocked) + if is_hidden then return end + if type == 0 then + local _x = math.ceil((x_res-x-x_offset-152)/30) + local _y = math.ceil((y_res-y-y_offset)/25) + if mouse_map[_y] and mouse_map[_y][_x] then + if not macro_visibility[position_to_region_map[_y]] then + toggle_macro_visibility(position_to_region_map[_y]) + end + if not prim_coordinates.visible['hover24'] then + windower.prim.set_visibility("hover24",true) + windower.prim.set_visibility("hover32",false) + prim_coordinates.visible['hover32'] = false + prim_coordinates.visible['hover24'] = true + end + if _x ~= last_x or _y ~= last_y then + prim_coordinates.x['hover24'] = x_res-153-x_offset-_x*30 + prim_coordinates.y['hover24'] = y_res-y_offset-25*_y + windower.prim.set_position("hover24",prim_coordinates.x['hover24'],prim_coordinates.y['hover24']) + last_x = _x + last_y = _y + end + return + end + _y = math.ceil((y_res-y-y_offset-25*(party[1].n+vacancies[1]))/28) + _x = math.ceil((x_res-x-x_offset-152)/26) + if mouse_map2[_y] and mouse_map2[_y][_x] then + if not macro_visibility[1] then + toggle_macro_visibility(1) + end + if not prim_coordinates.visible['hover32'] then + windower.prim.set_visibility("hover32",true) + windower.prim.set_visibility("hover24",false) + prim_coordinates.visible['hover32'] = true + prim_coordinates.visible['hover24'] = false + end + if _x ~= last_x32 or _y ~= last_y32 then + prim_coordinates.x['hover32'] = x_res-153-x_offset-_x*26 + prim_coordinates.y['hover32'] = y_res-y_offset-25*_y-25*(party[1].n+vacancies[1])-2*_y + windower.prim.set_position("hover32",prim_coordinates.x['hover32'],prim_coordinates.y['hover32']) + last_x32 = _x + last_y32 = _y + end + return + end + for i = 1,3 do + if macro_visibility[i] then + toggle_macro_visibility(i) + end + end + if prim_coordinates.visible['hover32'] then + windower.prim.set_visibility("hover32",false) + prim_coordinates.visible['hover32'] = false + end + if prim_coordinates.visible['hover24'] then + windower.prim.set_visibility("hover24",false) + prim_coordinates.visible['hover24'] = false + end + elseif type == 1 then + local _x = (x_res-x-x_offset) + local _y = math.ceil((y_res-y-y_offset)/25) + if _x < 153 then + if region_to_name_map[_y] then + windower.send_command('%sinput /target %s':format(send_string,region_to_name_map[_y])) + dragged = true + return true + end + else + _x = math.ceil((_x-152)/30) + if mouse_map[_y] and mouse_map[_y][_x] then + local spell = mouse_map[_y][_x] + windower.send_command('%sinput %s "%s" %s':format(send_string, prefix[spell], spell, region_to_name_map[_y])) + dragged = true + return true + end + _y = math.ceil((y_res-y-y_offset-25*(party[1].n+vacancies[1]))/28) + _x = math.ceil((x_res-x-x_offset-152)/26) + if mouse_map2[_y] and mouse_map2[_y][_x] then + local spell = mouse_map2[_y][_x] + windower.send_command('%sinput %s "%s" %s':format(send_string, prefix[spell], spell, '<t>')) + dragged = true + return true + end + end + elseif type == 2 then + if dragged then + dragged = false + return true + end + elseif type == 4 then + local _x = (x_res-x-x_offset) + local _y = math.ceil((y_res-y-y_offset)/25) + if _x < 153 then + if region_to_name_map[_y] then + windower.send_command('%sinput %s "%s" %s':format(send_string, prefix[spell_default] or '', spell_default, region_to_name_map[_y])) + dragged = true + return true + end + else + _y = math.ceil((y_res-y-y_offset-25*(party[1].n+vacancies[1]))/28) + _x = math.ceil((_x-152)/26) + if mouse_map2[_y] and mouse_map2[_y][_x] then + spell_default = mouse_map2[_y][_x] + windower.text.set_text('menu', spell_default) + text_coordinates.x['menu'] = prim_coordinates.x['pmenu'] + 1 + (150 - 7.55 * string.length(spell_default))/2 + windower.text.set_location('menu',text_coordinates.x['menu'],text_coordinates.y['menu']) + dragged = true + return true + end + end + elseif type == 5 then + if dragged then + dragged = false + return true + end + end + end) + + outgoing_chunk_event = windower.register_event('outgoing chunk', function(id,data) + if id == 0x015 then + local packet = packets.parse('outgoing', data) + + if packet['Target Index'] ~= last_index or last_index == stat_table[player_id].index then + update_target(windower.ffxi.get_mob_by_index(packet['Target Index'])) + end + + local position = position + + if position[1][6] ~= packet['X'] or position[2][6] ~= packet['Y'] then + position[1][6],position[2][6] = packet['X'],packet['Y'] + + local party = party + + for i = 5,7-party[1].n,-1 do + if not (out_of_zone[party[1][7 - i]] or out_of_view[i]) then + indicate_distance(false,i,position[1][i],position[2][i]) + end + end + + for j = 2,3 do + for i = j*6,j*6-party[j].n+1,-1 do + if not (out_of_zone[party[j][j*6-i+1]] or out_of_view[i]) then + indicate_distance(false,i,position[1][i],position[2][i]) + end + end + end + end + elseif id == 0x00D then + is_zoning = true + if not is_hidden then + for key in pairs(saved_prims) do + if prim_coordinates.visible[key] then + windower.prim.set_visibility(key,false) + end + end + for key in pairs(saved_texts) do + if text_coordinates.visible[key] then + windower.text.set_visibility(key,false) + end + end + + toggle_buff_visibility(false) + + for i = 1,party[1].n do + stat_table[party[1][i]].buffs={{n = 0},{n = 0}} -- wipe buffs on zone + end + end + end + end) + + incoming_chunk_event = windower.register_event('incoming chunk', function(id, data) + if id == 0x00D or id == 0x00E then -- kind of weird + local Mask = data:unpack('C',0x0B) + if data:unpack('H',9) == last_index and bit.band(Mask,4) > 0 then + update_target_hp(data:unpack('C',0x1F)) + end + local f = position_lookup[data:unpack('I',5)] + if f and f ~= 6 then + if bit.band(Mask,1) > 0 then --Mask + local X,Z,Y = data:unpack('fff',0x0D) --0b000001 position updated + position[1][f] = X --0b000100 hp updated + position[2][f] = Y --0b011111 model appear i.e. update all + indicate_distance(false,f,X,Y) --0b100000 model disappear + elseif bit.band(Mask,32) > 0 then + indicate_distance(true,f) + end + end + elseif id == 0x0DF then + local packet = packets.parse('incoming', data) + local id = packet['ID'] + if not position_lookup[id] then return end + local to_update = L{} + local stats = stat_table[id] + if stats.hp ~= packet['HP'] then + stats.hp = packet['HP'] + to_update:append('hp') + end + if stats.mp ~= packet['MP'] then + stats.mp = packet['MP'] + to_update:append('mp') + end + if stats.tp ~= packet['TP'] then + stats.tp = packet['TP'] + to_update:append('tp') + end + if stats.hpp ~= packet['HPP'] then + if math.floor(stats.hpp/25) ~= math.floor(packet['HPP']/25) then + local color=_settings.primitives.hp_bar[choose_color(packet['HPP'])] + windower.prim.set_color('phpp'..position_lookup[id],color.a,color.r,color.g,color.b) + end + stats.hpp = packet['HPP'] + to_update:append('hpp') + windower.prim.set_size('phpp'..position_lookup[id],150/100*stats['hpp'],h) + end + if stats.mpp ~= packet['MPP'] then + stats.mpp = packet['MPP'] + windower.prim.set_size('pmpp'..position_lookup[id],150/100*stats['mpp'],5) + end + + update_macro_data(id,to_update) + elseif id == 0x076 then + for i = 0,4 do + local id = data:unpack('I', i*48+5) + + if id == 0 then + break + elseif position_lookup[id] then + local packet_buffs = L{} + local packet_debuffs = L{} + local buff + + for j = 1,32 do + buff = data:byte(i*48+5+16+j-1) + 256*( math.floor( data:byte(i*48+5+8+ math.floor((j-1)/4)) / 4^((j-1)%4) )%4) -- Credit: Byrth, GearSwap + + if buff == 255 then + break + elseif tracked_buffs[1][buff] then + packet_buffs:append(buff) + elseif tracked_buffs[2][buff] then + packet_debuffs:append(buff) + end + end + + draw_buff_display(packet_buffs, id, 1) + draw_buff_display(packet_debuffs, id, 2) + + stat_table[id].buffs[1] = packet_buffs + stat_table[id].buffs[2] = packet_debuffs + end + end + elseif id == 0x0DD then + local packet = packets.parse('incoming',data) + local id = packet['ID'] + if not position_lookup[id] then return end + local pos_tostring = tostring(position_lookup[id]) + if packet['Zone'] ~= 0 then + if not out_of_zone[id] then + remove_macro_information(pos_tostring,false) + out_of_zone[id] = true + seeking_information[id] = true + end + if who_am_i[id] then + stat_table[id].name = packet['Name'] + windower.text.set_text("name"..pos_tostring,prepare_names(packet['Name'])) + who_am_i[id] = nil + update_name_map(id,packet['Name']) + end + elseif is_zoning or seeking_information[packet['ID']] then + local to_update = L{} + local stats = stat_table[id] + stats.hp = packet['HP'] + to_update:append('hp') + stats.mp = packet['MP'] + to_update:append('mp') + stats.tp = packet['TP'] + to_update:append('tp') + local color=_settings.primitives.hp_bar[choose_color(packet['HP%'])] + windower.prim.set_color('phpp'..pos_tostring,color.a,color.r,color.g,color.b) + stats.hpp = packet['HP%'] + to_update:append('hpp') + windower.prim.set_size('phpp'..pos_tostring,150/100*stats['hpp'],h) + stats.mpp = packet['MP%'] + windower.prim.set_size('pmpp'..pos_tostring,150/100*stats['mpp'],5) + update_macro_data(id,to_update) + if who_am_i[id] then + stats.name = packet['Name'] + windower.text.set_text("name"..pos_tostring,prepare_names(packet['Name'])) + who_am_i[id] = false + update_name_map(id,packet['Name']) + end + seeking_information[id] = false + out_of_zone[id] = false + end + elseif id == 0x0C8 then + local packet = packets.parse('incoming', data) + + local packet_id_struc = { + packet['ID 1'], + packet['ID 2'], + packet['ID 3'], + packet['ID 4'], + packet['ID 5'], + packet['ID 6'], + packet['ID 7'], + packet['ID 8'], + packet['ID 9'], + packet['ID 10'], + packet['ID 11'], + packet['ID 12'], + packet['ID 13'], + packet['ID 14'], + packet['ID 15'], + packet['ID 16'], + packet['ID 17'], + packet['ID 18'] + } + local packet_flag_struc = { + packet['Flags 1'], + packet['Flags 2'], + packet['Flags 3'], + packet['Flags 4'], + packet['Flags 5'], + packet['Flags 6'], + packet['Flags 7'], + packet['Flags 8'], + packet['Flags 9'], + packet['Flags 10'], + packet['Flags 11'], + packet['Flags 12'], + packet['Flags 13'], + packet['Flags 14'], + packet['Flags 15'], + packet['Flags 16'], + packet['Flags 17'], + packet['Flags 18'] + } + local packet_pt_struc = {S{},S{},S{}} + for i = 1,18 do + if packet_id_struc[i]~=0 then + if bit.band(packet_flag_struc[i],2) == 2 then + if bit.band(packet_flag_struc[i],1) == 1 then --Flags + packet_pt_struc[3]:add(packet_id_struc[i]) --0b0000 Solo (trusts) + else --0b0001 Party A + packet_pt_struc[1]:add(packet_id_struc[i]) --0b0010 Party B + end --0b0011 Party C + elseif bit.band(packet_flag_struc[i],1) == 1 then --The order of the parties + packet_pt_struc[2]:add(packet_id_struc[i]) --is not determined by which + else --party contains the player + packet_pt_struc[1]:add(packet_id_struc[i]) + end + end + end + + if packet_pt_struc[3]:contains(player_id) then + packet_pt_struc[1],packet_pt_struc[3] = packet_pt_struc[3],packet_pt_struc[1] + elseif packet_pt_struc[2]:contains(player_id) then + packet_pt_struc[1],packet_pt_struc[2] = packet_pt_struc[2],packet_pt_struc[1] + end + + if packet_pt_struc[2]:length() == 0 then + packet_pt_struc[2],packet_pt_struc[3] = packet_pt_struc[3],packet_pt_struc[2] + end + new_members(packet_pt_struc) + end + end) + zone_change_event = windower.register_event('zone change', function() + if not is_hidden then + for key in pairs(saved_prims - (macro[1] + macro[2] + macro[3])) do + windower.prim.set_visibility(key,prim_coordinates.visible[key]) + end + for key in pairs(saved_texts - (macro[1] + macro[2] + macro[3])) do + windower.text.set_visibility(key,text_coordinates.visible[key]) + end + end + is_zoning = false + if windower.ffxi.get_info().logged_in then + stat_table[player_id].index = windower.ffxi.get_player().index + end + end) + else + windower.unregister_event(keyboard_event) + windower.unregister_event(mouse_event) + windower.unregister_event(incoming_chunk_event) + windower.unregister_event(outgoing_chunk_event) + windower.unregister_event(zone_change_event) + end +end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Nostrum/ReadMe.md new file mode 100644 index 0000000..36fb810 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/ReadMe.md @@ -0,0 +1,51 @@ +#Nostrum + +Creates a click-able onscreen macro to help avoid targeting issues while curing. (MsJans clone) +MsJans was originally written by Janice (BG), was further developed by Ragns (BG), and was most recently maintained by Arcon. + +### Settings +The color, contents, and position of the macro can be modified by editing the Nostrum/data/settings.xml file. + +### Using the Macro +The macro consists of two basic units: a list of your current party members, and a palette of cure spells. These automatically resize when the party structure changes. However, in order to avoid constant resizing during bard rotations and other busy moments, the macro does not automatically shrink when members leave the party. It can be manually resized by using the 'cut' command. + +To use the macro, click on the appropriate region of the display. + + +####Left Click + +Region | Action +------ | ------ +Status Removal Icons | Casts the corresponding spell on `'<t>'` +Buff Icons | Casts the corresponding spell on `'<t>'` +Party Lists | Targets the corresponding party member +Cure Palette | Casts the corresponding cure on the party member + +####Right Click + +Region | Action +------ | ------ +Status Removal Icons | Saves the corresponding spell to the right-click button +Buff Icons | Saves the corresponding spell to the right-click button +Party Lists | Casts the saved spell on the corresponding party member + +### Commands + +command(shortcut) + +#####Abbreviation: //nos +1. help(h): + - Prints a list of these commands in the console. +2. refresh(r): + - Compares the macro's current party structures to the party structure in memory. Adds new members and removes any old members (trusts). Only people nearby you will be added to the macro, and current party members who are not nearby will be removed. +3. hide(h): + - Toggles the macro's visibility. +4. cut(c): + - Trims the macro down to size, removing blank spaces. +5. send(s) <name>: + - Requires send addon. Sends commands to the character whose name was provided. If no name was provided, send settings will reset and Nostrum will function normally. +6. profile(p) <name>: + - Loads a new profile from the settings file. + +###Issues +1.Loading profiles in a busy area may be a bad idea.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/helperfunctions.lua b/Data/DefaultContent/Libraries/addons/addons/Nostrum/helperfunctions.lua new file mode 100644 index 0000000..663d657 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/helperfunctions.lua @@ -0,0 +1,783 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Nostrum nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv 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.--]] + +function prim_simple(name,settings_table,pos_x,pos_y,width,height) + windower.prim.create(name) + windower.prim.set_position(name,pos_x,pos_y) + windower.prim.set_size(name,width,height) + windower.prim.set_color(name,settings_table.color.a,settings_table.color.r,settings_table.color.g,settings_table.color.b) + windower.prim.set_visibility(name, not is_hidden and settings_table.visible) + saved_prims:add(name) + prim_coordinates.x[name]=pos_x + prim_coordinates.y[name]=pos_y + prim_coordinates.visible[name]=settings_table.visible + prim_coordinates.a[name]=settings_table.color.a + prim_coordinates.r[name]=settings_table.color.r + prim_coordinates.g[name]=settings_table.color.g + prim_coordinates.b[name]=settings_table.color.b +end + +function img_simple(name,texture,pos_x,pos_y) + windower.prim.create(name) + windower.prim.set_position(name,pos_x,pos_y) + windower.prim.set_size(name,25,25) + windower.prim.set_visibility(name,not is_hidden) + windower.prim.set_fit_to_texture(name, false) + windower.prim.set_texture(name, texture) + saved_prims:add(name) + prim_coordinates.x[name]=pos_x + prim_coordinates.y[name]=pos_y + prim_coordinates.visible[name]=true +end + +function text_simple(name, settings_table, pos_x, pos_y, text) + windower.text.create(name) + windower.text.set_color(name, settings_table.color.a, settings_table.color.r, settings_table.color.g, settings_table.color.b) + windower.text.set_bold(name,settings_table.bold) + windower.text.set_font(name,settings_table.font) + windower.text.set_font_size(name,settings_table.font_size) + windower.text.set_location(name, pos_x, pos_y) + windower.text.set_right_justified(name,settings_table.right_justified) + windower.text.set_text(name,text) + windower.text.set_visibility(name, not is_hidden and settings_table.visible) + saved_texts:add(name) + text_coordinates.x[name]=pos_x + text_coordinates.y[name]=pos_y + text_coordinates.visible[name]=settings_table.visible +end + +function count_cures(t) + for i=options.cures.n,1,-1 do + if t[options.cures[i]] then + macro_order[2]:append(xml_to_lua[options.cures[i]]) + end + macro_order[3] = macro_order[2] + end + macro_order[1] = macro_order[2]:copy(false) + for i=options.curagas.n,1,-1 do + if t[options.curagas[i]] then + macro_order[1]:append(xml_to_lua[options.curagas[i]]) + end + end +end + +function count_na(t) + for i=1,options.na['n'] do + if t[options.na[i]] then + macro_order[4]:append(xml_to_lua[options.na[i]]) + end + end + if macro_order[4].n ~= 0 then + mouse_map2:append(T(macro_order[4])) + end +end + +function count_buffs(t) + for i=1,options.buffs['n'] do + if t[options.buffs[i]] then + macro_order[5]:append(xml_to_lua[options.buffs[i]]) + end + end + if macro_order[5].n ~= 0 then + mouse_map2:append(T(macro_order[5])) + end +end + +function choose_color(hpp) + if hpp >= 75 then + return 'green' + elseif hpp >= 50 + then return 'yellow' + elseif hpp >= 25 then + return 'orange' + else + return 'red' + end +end + +function prepare_names(s) + if string.len(s)>10 then + return(string.sub(s,1,10)..'.') + else + return(s) + end +end + +function merge_user_file_and_settings(t,u) + for k,v in pairs(t) do + if u[k] then + if type(u[k]) == 'table' then + u[k] = merge_user_file_and_settings(t[k],u[k]) + end + else + u[k] = v + end + end + return u +end + +function indicate_distance(bool,n,x,y) + if bool then --Invisible conflict? + out_of_view[n] = true + if not out_of_range[n] then + out_of_range[n] = true + windower.text.set_color('name'..tostring(n), 255, 175, 98, 177) + end + elseif ((position[1][6] - x)^2 + (position[2][6] - y)^2) > 441 then + out_of_view[n] = false + if not out_of_range[n] then + out_of_range[n] = true + windower.text.set_color('name'..tostring(n), 255, 175, 98, 177) + end + elseif out_of_range[n] then + out_of_view[n] = false + out_of_range[n] = false + windower.text.set_color('name'..tostring(n), 255, 255, 255, 255) + end +end + +function remove_macro_information(s,bool) + windower.text.set_text('hp'..s,'') + windower.text.set_text('mp'..s,'') + windower.text.set_text('tp'..s,'') + windower.text.set_text('hpp'..s,'') + windower.text.set_text('mpp'..s,'') + windower.prim.set_size('phpp'..s,0,h) + windower.prim.set_size('pmpp'..s,0,5) + if bool then + windower.text.set_text('name'..s,'') + end +end + +function wrecking_ball() + for i = 1,18 do + prims_by_layer[i]:clear() + texts_by_layer[i]:clear() + end + misc_hold_for_up.texts:clear() + misc_hold_for_up.prims:clear() + for i = 1,3 do + macro[i]:clear() + end + for key in pairs(saved_texts) do + windower.text.delete(key) + saved_texts:remove(key) + end + for key in pairs(saved_prims) do + windower.prim.delete(key) + saved_prims:remove(key) + end + for i=1,5 do + macro_order[i]:clear() + end + mouse_map2:clear() +end + +function toggle_visibility() + is_hidden = not is_hidden + if is_hidden then + for key in pairs(saved_prims) do + if prim_coordinates.visible[key] then + windower.prim.set_visibility(key,false) + end + end + for key in pairs(saved_texts) do + if text_coordinates.visible[key] then + windower.text.set_visibility(key,false) + end + end + else + for key in pairs(saved_prims) do + windower.prim.set_visibility(key,prim_coordinates.visible[key]) + end + for key in pairs(saved_texts) do + windower.text.set_visibility(key,text_coordinates.visible[key]) + end + end + toggle_buff_visibility(not is_hidden) +end + +function toggle_buff_visibility(b) + local party = party[1] + for i = 1, party.n do + local id = party[i] + local buffs = stat_table[id].buffs[1] + local debuffs = stat_table[id].buffs[2] + local prims = buff_map[position_lookup[id]] + + for j = 1, buffs.n do + prims[1][j]:visible(b) + end + for j = 1, debuffs.n do + prims[2][j]:visible(b) + end + end +end + +function toggle_macro_visibility(n) + macro_visibility[n] = not macro_visibility[n] + if macro_visibility[n] then + for key in pairs(macro[n]) do + if saved_prims:contains(key) then + windower.prim.set_visibility(key,prim_coordinates.visible[key]) + else + windower.text.set_visibility(key,text_coordinates.visible[key]) + end + end + else + for key in pairs(macro[n]) do + if saved_prims:contains(key) then + windower.prim.set_visibility(key,false) + else + windower.text.set_visibility(key,false) + end + end + end + if n == 1 then + toggle_buff_visibility(not macro_visibility[1]) + end +end + +function switch_profiles() + wrecking_ball() + count_cures(profile) + count_na(profile) + count_buffs(profile) + coroutine.sleep(1) + build_macro() + define_active_regions() + last_index=-1 +end + +function image_row(t,x_start,y_start) + local s + for i = 1,t.n do + s = t[i] + prim_simple('p' .. s,_settings.primitives.buff_buttons,x_start-26*i-152,y_start,26,26) + img_simple(s..'i',windower.windower_path.."/plugins/icons/"..options.images[s],x_start-26*i-152,y_start) + text_simple(s, _settings.text.buffs, x_start-26*i-152, y_start, options.aliases[s]) + misc_hold_for_up.texts:append(s) + misc_hold_for_up.prims:extend({s..'i','p' .. s}) + macro[1]:add(s) + macro[1]:add(s..'i') + macro[1]:add('p' .. s) + windower.text.set_stroke_color(s, 255, 0, 0, 0) + windower.text.set_stroke_width(s, 1) + end +end + +function prim_rose(t,pos,x_start,y_start,n) --Pun! + local spell + local s + local prim = _settings.primitives.buttons + local text = _settings.text.buttons + + for i = 1,t.n do + spell = t[i] + s = spell .. tostring(pos) + prim_simple('p' .. s,prim,x_start-(i)*(w+1)+1-153,y_start,w,h) + text_simple(s, text, x_start-(i)*(w+1)+1+((w-font_widths[options.aliases[spell]])/2)-153, y_start, options.aliases[spell]) + prims_by_layer[pos]:append('p' .. s) + texts_by_layer[pos]:append(s) + macro[n]:add('p' .. s) + macro[n]:add(s) + if not macro_visibility[n] then + windower.prim.set_visibility('p' .. s,false) + windower.text.set_visibility(s,false) + end + end +end + +last_hpp=0 +last_index=-1 +function update_target(mob) + if not mob or mob.index == 0 then + if prim_coordinates.visible['target'] then + windower.prim.set_visibility("target_background",false) + windower.prim.set_visibility("target",false) + windower.text.set_visibility("targethpp",false) + windower.text.set_visibility("target_name",false) + text_coordinates.visible["targethpp"]=false + text_coordinates.visible["target_name"]=false + prim_coordinates.visible["target_background"]=false + prim_coordinates.visible["target"]=false + end + last_index = 0 + else + if not prim_coordinates.visible['target'] and not is_hidden then + windower.prim.set_visibility("target_background",true) + windower.prim.set_visibility("target",true) + windower.text.set_visibility("targethpp",true) + windower.text.set_visibility("target_name",true) + text_coordinates.visible["targethpp"]=true + text_coordinates.visible["target_name"]=true + prim_coordinates.visible["target_background"]=_settings.primitives.hp_bar_background.visible + prim_coordinates.visible["target"]=true + end + if mob.index ~= last_index then + windower.text.set_text("target_name",string.sub(mob.name,1,20)) + last_index = mob.index + end + if mob.hpp ~= last_hpp then + update_target_hp(mob.hpp) + end + end +end + +function update_target_hp(hpp) + if hpp~=last_hpp then + windower.text.set_text("targethpp",tostring(hpp)) + windower.prim.set_size("target",150/100*hpp,30) + if math.floor(hpp/25) ~= math.floor(last_hpp/25) then + local color=_settings.primitives.hp_bar[choose_color(hpp)] + windower.prim.set_color("target",color.a,color.r,color.g,color.b) + end + end + last_hpp = hpp +end + +function compare_alliance_to_memory() + local alliance_keys = {'p5', 'p4', 'p3', 'p2', 'p1', 'p0', 'a15', 'a14', 'a13', 'a12', 'a11', 'a10', 'a25', 'a24', 'a23', 'a22', 'a21', 'a20'} + local alliance_clone = windower.ffxi.get_party() + local ally_id=(table.keyset(alliance_clone)) + local party1 = (party_keys * ally_id) + local party2 = (party_two_keys * ally_id) + local party3 = (party_three_keys * ally_id) + local names = {} + local packet_pt_struc = {S{},S{},S{}} + for i=1,18 do + if ally_id[alliance_keys[i]] and alliance_clone[alliance_keys[i]].mob then + names[alliance_clone[alliance_keys[i]].mob.id]=alliance_clone[alliance_keys[i]].mob.name + end + if party1[alliance_keys[i]] and alliance_clone[alliance_keys[i]].mob then + packet_pt_struc[1]:add(alliance_clone[alliance_keys[i]].mob.id) + elseif party2[alliance_keys[i]] and alliance_clone[alliance_keys[i]].mob then + packet_pt_struc[2]:add(alliance_clone[alliance_keys[i]].mob.id) + elseif party3[alliance_keys[i]] and alliance_clone[alliance_keys[i]].mob then + packet_pt_struc[3]:add(alliance_clone[alliance_keys[i]].mob.id) + end + end + new_members(packet_pt_struc) + for k,v in pairs(names) do + if who_am_i[k] then + windower.text.set_text('name'..position_lookup[k],prepare_names(v)) + stat_table[k].name = v + who_am_i[k] = nil + update_name_map(k,v) + end + end +end + +function new_members(packet_pt_struc) -- snippet from invite, reused in c_a_t_m + local p = {S(party[1]),S(party[2]),S(party[3])} + local to_kick = p[1] + p[2] + p[3] - (packet_pt_struc[3] + packet_pt_struc[2] + packet_pt_struc[1]) + for k in pairs(to_kick) do + kick(k,math.ceil(position_lookup[k]/6)) + end + for i=1,3 do + local to_invite = packet_pt_struc[i] - p[i] + for k in pairs(to_invite) do + invite(k,i) + end + end + define_active_regions() +end + +function invite(id,n) + local prim = _settings.primitives + local text = _settings.text + local x_start=_settings.window.x_res-1-_defaults.window.x_offset + party[n]:append(id) + local ptn = party[n].n + position_lookup[id] = 1 + 6 * n - ptn + local pos_id = position_lookup[id] + stat_table[id]={hp=0,mp=0,mpp=0,hpp=0,name='???',tp=0,buffs={{n = 0},{n = 0}}} + seeking_information[id] = true + who_am_i[id] = true + local m = tostring(n) + local pos_tostring = tostring(pos_id) + if vacancies[n] == 0 then + lift_macro(n) + if not saved_prims:contains('BG'..m) then + local y_start = prim_coordinates.y['BG'..tostring(n-1)]-(175-75*(n-1))-ptn*(h+1) + prim_simple('BG'..m,prim.background,x_start-macro_order[n].n*(w+1)-153,y_start,macro_order[n].n*(w+1)+1,ptn*(h+1)+1) + prim_simple("info"..m,prim.hp_bar_background,x_start-152,y_start,152,ptn*(h+1)+1) + macro[n]:add('BG'..m) + if not macro_visibility[n] then + windower.prim.set_visibility('BG'..m,false) + end + else + windower.prim.set_size('info'..m,152,ptn*(h+1)+1) + windower.prim.set_size('BG'..m,macro_order[n].n*(w+1)+1,ptn*(h+1)+1) + end + local y_start = prim_coordinates.y['BG'..m]+1+(h+1)*(ptn-1) + prim_rose(macro_order[n],pos_id,x_start,y_start,n) + prim_simple("phpp"..pos_tostring,prim.hp_bar,x_start-151,y_start,150,h) + prim_simple("pmpp"..pos_tostring,prim.mp_bar,x_start-151,y_start+19,150,5) + text_simple("tp"..pos_tostring, text.tp, x_start-151, y_start+11, '') + text_simple("name"..pos_tostring, text.name, x_start-151, y_start-3, stat_table[id].name) + text_simple("hpp"..pos_tostring, text.hpp, x_start, y_start-4, '') + text_simple("hp"..pos_tostring, text.hp, x_start-40, y_start-3, '') + text_simple("mp"..pos_tostring, text.mp, x_start-40, y_start+11,'') + prims_by_layer[pos_id]:extend(L{"phpp" ..pos_tostring,"pmpp" ..pos_tostring}) + texts_by_layer[pos_id]:extend(L{"tp" ..pos_tostring,"name" ..pos_tostring,"hpp" ..pos_tostring,"hp" ..pos_tostring,"mp" ..pos_tostring}) + else + vacancies[n] = vacancies[n]-1 + windower.text.set_text('name'..pos_id,stat_table[id].name) + end + local pos = windower.ffxi.get_mob_by_id(id) + if pos then + position[1][pos_id] = pos.x + position[2][pos_id] = pos.y + indicate_distance(false,pos_id,pos.x,pos.y) + elseif not out_of_view[pos_id] then + indicate_distance(true,pos_id) + end +end + +function lift_macro(n) + if n == 1 then + up(misc_hold_for_up.texts) + up(misc_hold_for_up.prims) + end + for k=n,3 do + if saved_prims:contains("BG" .. tostring(k)) then + up("BG" .. tostring(k)) + up("info" .. tostring(k)) + end + end + for j=6*n-party[n].n+1,18 do--+2? + up(prims_by_layer[j]) + up(texts_by_layer[j]) + end +end + +function up(t) + if not t then return end + if class(t) == 'List' then + for i=1,t.n do + if saved_prims:contains(t[i]) then + windower.prim.set_position(t[i],prim_coordinates.x[t[i]],prim_coordinates.y[t[i]]-25) + prim_coordinates.y[t[i]]=prim_coordinates.y[t[i]]-25 + elseif saved_texts:contains(t[i]) then + windower.text.set_location(t[i],text_coordinates.x[t[i]],text_coordinates.y[t[i]]-25) + text_coordinates.y[t[i]]=text_coordinates.y[t[i]]-25 + elseif class(t[i]) == 'Prim' then + t[i]:up(25) + end + end + elseif type(t) == 'table' then + for _,v in pairs(t) do + if saved_prims:contains(v) then + windower.prim.set_position(v,prim_coordinates.x[v],prim_coordinates.y[v]-25) + prim_coordinates.y[v]=prim_coordinates.y[v]-25 + elseif saved_texts:contains(v) then + windower.text.set_location(v,text_coordinates.x[v],text_coordinates.y[v]-25) + text_coordinates.y[v]=text_coordinates.y[v]-25 + elseif class(v) == 'Prim' then + v:up(25) + end + end + else + if saved_prims:contains(t) then + windower.prim.set_position(t,prim_coordinates.x[t],prim_coordinates.y[t]-25) + prim_coordinates.y[t]=prim_coordinates.y[t]-25 + elseif saved_texts:contains(t) then + windower.text.set_location(t,text_coordinates.x[t],text_coordinates.y[t]-25) + text_coordinates.y[t]=text_coordinates.y[t]-25 + end + end +end + +function lower_macro(n) + if n == 1 then + down(misc_hold_for_up.texts) + down(misc_hold_for_up.prims) + end + for k=n,3 do + if saved_prims:contains("BG" .. k) then + down("BG" .. tostring(k)) + down("info" .. tostring(k)) + end + end + for j=6*n-party[n].n+1,18 do + down(prims_by_layer[j]) + down(texts_by_layer[j]) + end +end + +function down(periscope) + if not periscope then return end + if class(periscope) == 'list' then + for i=1,periscope.n do + if saved_prims:contains(i) then + windower.prim.set_position(i,prim_coordinates.x[i],prim_coordinates.y[i]+25) + prim_coordinates.y[i]=prim_coordinates.y[i]+25 + elseif saved_texts:contains(i) then + windower.text.set_location(i,text_coordinates.x[i],text_coordinates.y[i]+25) + text_coordinates.y[i]=text_coordinates.y[i]+25 + elseif class(i) == 'Prim' then + i:down(25) + end + end + elseif type(periscope) == 'table' then + for _,v in pairs(periscope) do + if saved_prims:contains(v) then + windower.prim.set_position(v,prim_coordinates.x[v],prim_coordinates.y[v]+25) + prim_coordinates.y[v]=prim_coordinates.y[v]+25 + elseif saved_texts:contains(v) then + windower.text.set_location(v,text_coordinates.x[v],text_coordinates.y[v]+25) + text_coordinates.y[v]=text_coordinates.y[v]+25 + elseif class(v) == 'Prim' then + v:down(25) + end + end + else + if saved_prims:contains(periscope) then + windower.prim.set_position(periscope,prim_coordinates.x[periscope],prim_coordinates.y[periscope]+25) + prim_coordinates.y[periscope]=prim_coordinates.y[periscope]+25 + elseif saved_texts:contains(periscope) then + windower.text.set_location(periscope,text_coordinates.x[periscope],text_coordinates.y[periscope]+25) + text_coordinates.y[periscope]=text_coordinates.y[periscope]+25 + end + end +end + +function trim_macro() + for j = 1,3 do + for i = 6*j-party[j].n,1+6*(j-1),-1 do + local prim = prims_by_layer[i] + for k=1,prim.n do + if class(prim[k]) == 'Prim' then + prim[k]:destroy() + else + windower.prim.delete(prim[k]) + saved_prims:remove(prim[k]) + prim_coordinates.x[prim[k]]=nil + prim_coordinates.y[prim[k]]=nil + macro[j]:remove(prim[k]) + end + end + local text = texts_by_layer[i] + for k=1,text.n do + windower.text.delete(text[k]) + saved_texts:remove(text[k]) + text_coordinates.x[text[k]]=nil + text_coordinates.y[text[k]]=nil + macro[j]:remove(text[k]) + end + if prim.n ~= 0 or text.n ~= 0 then + lower_macro(j) + end + prims_by_layer[i]:clear() + texts_by_layer[i]:clear() + end + if saved_prims:contains('BG'..tostring(j)) then + local s1 = 'BG'..tostring(j) + local s2 = 'info'..tostring(j) + if party[j].n == 0 then + windower.prim.delete(s1) + windower.prim.delete(s2) + saved_prims:remove(s1) + saved_prims:remove(s2) + macro[j]:remove(s1) + misc_hold_for_up.prims:delete(s1) + misc_hold_for_up.prims:delete(s2) + else + windower.prim.set_size(s1,macro_order[j].n*(w+1)+1,party[j].n*(h+1)+1) + windower.prim.set_size(s2,152,party[j].n*(h+1)+1) + end + end + end + vacancies={0,0,0} + define_active_regions() +end + +function update_macro_data(id,t) + for i=1,t:length() do + windower.text.set_text(t[i]..tostring(position_lookup[id]),tostring(stat_table[id][t[i]])) + end +end + +function kick(id,n) + local i = position_lookup[id] + + if i < 6 then + local party = party[1] + local last = party[party.n] + + for k = 1, 2 do + local prim = buff_map[position_lookup[last]][k] + for j = 1, stat_table[last].buffs[k].n do + prim[j]:hide() + end + end + + for j = party.n, 8 - i, -1 do + draw_buff_display(stat_table[party[j]].buffs[1], party[j - 1], 1) + draw_buff_display(stat_table[party[j]].buffs[2], party[j - 1], 1) + end + end + + party[n]:remove(6*n+1-i) + local j = 6*(n)-party[n].n + vacancies[n] = vacancies[n] + 1 + + while i > j do + local m = 6*n+1-i + local m_id = party[n][m] + local pos_tostring = tostring(i) + + position_lookup[m_id] = position_lookup[m_id] + 1 + update_macro_data(m_id,L{'tp','hp','mp','hpp','mpp'}) + windower.text.set_text('name'..pos_tostring,prepare_names(stat_table[m_id]['name']))--shouldn't send name to update_macro... since it won't truncate + local color=_settings.primitives.hp_bar[choose_color(stat_table[m_id].hpp)] + windower.prim.set_color('phpp'..pos_tostring,color.a,color.r,color.g,color.b) + windower.prim.set_size('phpp'..pos_tostring,150/100*stat_table[m_id]['hpp'],h) + windower.prim.set_size('pmpp'..pos_tostring,150/100*stat_table[m_id]['mpp'],5) + if out_of_range[i] and not out_of_range[i-1] then + windower.text.set_color('name' .. tostring(i), 255, 255, 255, 255) + end + if out_of_range[i-1] and not out_of_range[i] then + windower.text.set_color('name' .. tostring(i), 255, 175, 98, 177) + end + out_of_range[i] = out_of_range[i-1] + out_of_view[i] = out_of_view[i-1] + position[1][i] = position[1][i-1] + position[2][i] = position[2][i-1] + + i = i - 1 + end + + remove_macro_information(tostring(i),true) + stat_table[id] = nil + out_of_zone[id] = nil + who_am_i[id] = nil + seeking_information[id] = nil + position_lookup[id] = nil +end + +function draw_buff_display(t, id, type) + local p_id = position_lookup[id] + local buffs = stat_table[id].buffs[type] + + for i = 1, t.n do + if t[i] ~= buffs[i] then + local prim = buff_map[p_id][type] + + if not prim[i] then + local hpp_string = 'phpp'..tostring(p_id) + prim[i] = prims.new({ + pos = {prim_coordinates.x[hpp_string] - 1 - 12 * i, prim_coordinates.y[hpp_string] + (type == 2 and 12 or 0)}, + w = 12, + color = color_over_texture[t[i]], + h = 12, + visible = not is_hidden and not macro_visibility[1], + set_texture = true, + texture = tracked_buffs[type][t[i]], + fit_texture = false, + }) + prims_by_layer[p_id]:append(prim[i]) + else + prim[i]:texture(tracked_buffs[type][t[i]]) + if color_over_texture[t[i]] then + prim[i]:argb(unpack(color_over_texture[t[i]])) + else + prim[i]:argb(255, 255, 255, 255) + end + prim[i]:visible(not macro_visibility[1]) + end + end + end + for i = t.n + 1, buffs.n do + buff_map[p_id][type][i]:hide() + end +end + +function update_name_map(id,name) + local pos_id = position_lookup[id] + if pos_id < 7 then + region_to_name_map[pos_id-(6-(party[1].n+vacancies[1]))] = name + elseif pos_id < 13 then + region_to_name_map[party[1].n+vacancies[1]+pos_id-6-(6-(party[2].n+vacancies[2]))+4] = name + elseif pos_id < 19 then + region_to_name_map[party[1].n+vacancies[1]+party[2].n+vacancies[2]+pos_id-12-(6-(party[3].n+vacancies[3]))+5] = name + end +end + +function define_active_regions() + mouse_map=T{} + region_to_name_map=T{} + position_to_region_map=T{} + local copy={{},{},{}} -- lists accept negative indices + for j = 1,3 do + for i = 1,macro_order[j].n do + copy[j][i] = macro_order[j][i] + end + end + for i = 1,vacancies[1] do + mouse_map:append(false) + region_to_name_map:append(false) + position_to_region_map:append(false) + end + for i = 1,party[1].n do + mouse_map:append(copy[1]) + region_to_name_map:append(stat_table[party[1][party[1].n-i+1]].name) + position_to_region_map:append(1) + end + for i = 1,4 do + mouse_map:append(false) + region_to_name_map:append(false) + position_to_region_map:append(false) + end + for i = 1,vacancies[2] do + mouse_map:append(false) + region_to_name_map:append(false) + position_to_region_map:append(false) + end + for i = 1,party[2].n do + mouse_map:append(copy[2]) + region_to_name_map:append(stat_table[party[2][party[2].n-i+1]].name) + position_to_region_map:append(2) + end + mouse_map:append(false) + region_to_name_map:append(false) + position_to_region_map:append(false) + for i = 1,vacancies[3] do + mouse_map:append(false) + region_to_name_map:append(false) + position_to_region_map:append(false) + end + for i = 1,party[3].n do + mouse_map:append(copy[3]) + region_to_name_map:append(stat_table[party[3][party[3].n-i+1]].name) + position_to_region_map:append(3) + end +end + +function bit.is_set(val, pos) -- Credit: Arcon + return bit.band(val, 2^(pos - 1)) > 0 +end diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/amnesia.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/amnesia.png Binary files differnew file mode 100644 index 0000000..a875559 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/amnesia.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/bind.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/bind.png Binary files differnew file mode 100644 index 0000000..0ab506b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/bind.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/charm.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/charm.png Binary files differnew file mode 100644 index 0000000..e2dbdd2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/charm.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/defdown.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/defdown.png Binary files differnew file mode 100644 index 0000000..4fc1bb2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/defdown.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/doom.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/doom.png Binary files differnew file mode 100644 index 0000000..846a570 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/doom.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/embrava.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/embrava.png Binary files differnew file mode 100644 index 0000000..d5b392a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/embrava.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/haste.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/haste.png Binary files differnew file mode 100644 index 0000000..2fe2109 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/haste.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/protect.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/protect.png Binary files differnew file mode 100644 index 0000000..db5a74b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/protect.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/regen.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/regen.png Binary files differnew file mode 100644 index 0000000..c281f40 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/regen.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/reraise.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/reraise.png Binary files differnew file mode 100644 index 0000000..19fa2b3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/reraise.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/roll.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/roll.png Binary files differnew file mode 100644 index 0000000..fc4244a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/roll.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/shell.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/shell.png Binary files differnew file mode 100644 index 0000000..6bc145f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/shell.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sleep.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sleep.png Binary files differnew file mode 100644 index 0000000..c866580 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sleep.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/slow.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/slow.png Binary files differnew file mode 100644 index 0000000..7db572c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/slow.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/song.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/song.png Binary files differnew file mode 100644 index 0000000..acdee62 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/song.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sublimation.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sublimation.png Binary files differnew file mode 100644 index 0000000..caf8ae3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/sublimation.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weakness.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weakness.png Binary files differnew file mode 100644 index 0000000..42005c8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weakness.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weight.png b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weight.png Binary files differnew file mode 100644 index 0000000..685e804 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/icons/weight.png diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/prims.lua b/Data/DefaultContent/Libraries/addons/addons/Nostrum/prims.lua new file mode 100644 index 0000000..292c4a6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/prims.lua @@ -0,0 +1,433 @@ +--[[ + A library to facilitate primitive creation and manipulation. +]] + +windower.prim.saved_prims = {} +local prims = {} +local meta = {} + +_libs = _libs or {} +_libs.prims = prims + +_meta = _meta or {} +_meta.Prim = _meta.Prim or {} +_meta.Prim.__class = 'Prim' +_meta.Prim.__index = prims + +--[[ + settings = { + pos = {x,y}, + w = number, + color = {a,r,g,b}, + h = number, + visible = boolean, + image = boolean, + texture = string, + fit = boolean, + tile = {x_rep,y_rep} + } +--]] + +local events = { + left_click = true, + right_click = true, + middle_click = true, + scroll_up = true, + scroll_down = true, + hover = true, + hover_begin = true, + hover_end = true, + drag = true, + right_drag = true, +} + +function prims.new(settings) + local t = {} + settings = settings or {} + settings.pos = settings.pos or {0, 0} + local m = { + color = settings.color and {unpack(settings.color)} or {255, 255, 255, 255}, + width = settings.w or 0, + height = settings.h or 0, + visible = settings.visible or false, + image = settings.set_texture or false, + texture = settings.texture, + fit = settings.fit_texture or false, + tile = settings.tile or {1, 1}, + events = {} + } + m.x1, m.x2 = m.width >= 0 and settings.pos[1], m.width + settings.pos[1] or m.width + settings.pos[1], settings.pos[1] + m.y1, m.y2 = m.height >= 0 and m.height + settings.pos[2], settings.pos[2] or settings.pos[2], m.height + settings.pos[2] + + meta[t] = m + m.name = (_addon and _addon.name or 'prim') .. '_gensym_' .. tostring(t):sub(8) .. '_%.8X':format(16^8 * math.random()):sub(3) + windower.prim.create(m.name) + --defaults for prims seem pointless, but these will prevent errors if people forget information. + + if settings.color then + windower.prim.set_color(m.name, unpack(m.color)) + end + + windower.prim.set_position(m.name, m.x1, m.y2) + windower.prim.set_size(m.name, m.width, m.height) + windower.prim.set_visibility(m.name, m.visible) + + if m.image then + windower.prim.set_fit_to_texture(m.name, m.fit) + windower.prim.set_texture(m.name, m.texture) + if settings.tile then + windower.prim.set_repeat(m.name, unpack(m.tile)) + end + end + + return setmetatable(t, _meta.Prim) +end + +-- Makes the primitive visible +function prims.show(t) + windower.prim.set_visibility(meta[t].name, true) + meta[t].visible = true +end + +-- Makes the primitive invisible +function prims.hide(t) + windower.prim.set_visibility(meta[t].name, false) + meta[t].visible = false +end + +--[[ + The following methods all either set the respective values or return them, if no arguments to set them are provided. +]] + +-- Returns whether or not the prim object is visible +function prims.visible(t, visible) + if visible == nil then + return meta[t].visible + end + windower.prim.set_visibility(meta[t].name, visible) + meta[t].visible = visible +end + +function prims.append_label(t, str) +end + +function prims.pos(t, x, y) + if not y then + return meta[t].width >= 0 and meta[t].x1 or meta[t].x2, + meta[t].height >= 0 and meta[t].y2 or meta[t].y1 + end + + local m = meta[t] + windower.prim.set_position(m.name, x, y) + + m.x1, m.x2 = m.width >= 0 and x, m.width + x or m.width + x, x + m.y1, m.y2 = m.height >= 0 and m.height + y, y or y, m.height + y +end + +function prims.pos_x(t, x) + if not x then + return meta[t].width >= 0 and meta[t].x1 or meta[t].x2 + end + + local m = meta[t] + windower.prim.set_position(m.name, x, meta[t].height >= 0 and m.y2 or m.y1) -- swapped these + + m.x1, m.x2 = m.width >= 0 and x, m.width + x or m.width + x, x +end + +function prims.pos_y(t, y) + if not y then + return meta[t].height >= 0 and meta[t].y2 or meta[t].y1 -- swapped these + end + + local m = meta[t] + windower.prim.set_position(m.name, m.width >= 0 and m.x1 or m.x2, y) + + m.y1, m.y2 = m.height >= 0 and m.height + y, y or y, m.height + y +end + +function prims.right(t, d) + local m = meta[t] + t:pos_x((m.width >= 0 and m.x1 or m.x2) + d) +end + +function prims.left(t, d) + local m = meta[t] + t:pos_x((m.width >= 0 and m.x1 or m.x2) - d) +end + +function prims.up(t, d) + local m = meta[t] + t:pos_y((m.height >= 0 and m.y2 or m.y1) - d) +end + +function prims.down(t, d) + local m = meta[t] + t:pos_y((m.height >= 0 and m.y2 or m.y1) + d) +end + +function prims.width(t, width) + if not width then + return meta[t].width + end + + local m = meta[t] + local w = m.width + + windower.prim.set_size(m.name, width, m.height) + m.width = width + if w * width >= 0 then + if w >= 0 then + m.x2 = m.x1 + width + else + m.x1 = m.x2 + width + end + else + if w >= 0 then + m.x1, m.x2 = m.x2 + width, m.x2 + else + m.x1, m.x2 = m.x1 + width, m.x1 + end + end +end + +local newanimator = function(n) + return function(fn) + while fn() do + coroutine.sleep(n) + end + end +end + +function prims.width_smooth(t, width, time_interval, dwidth) + local m = meta[t] + m.width_actual = width + + if not t.animator then + dwidth = (width - m.width) * dwidth > 0 and dwidth or -dwidth + t.animator = newanimator(time_interval) + t.animator(function() + local n = m.width + dwidth + if dwidth > 0 and n >= m.width_actual or dwidth < 0 and n <= m.width_actual then + t:width(m.width_actual) + t.animator = nil + return false + else + t:width(n) + return true + end + end) + end +end + +function prims.height(t, height) + if not height then + return meta[t].height + end + + local m = meta[t] + local h = m.height + + windower.prim.set_size(m.name, m.width, height) + m.height = height + if h * height >= 0 then + if h >= 0 then + m.y1 = m.y2 + height + else + m.y2 = m.y1 + height + end + else + if h >= 0 then + m.y1, m.y2 = m.y2, m.y2 + height + else + m.y1, m.y2 = m.y1 + height, m.y1 + end + end +end + +function prims.height_smooth(t, height, time_interval, dheight) + local m = meta[t] + m.height_actual = height + + if not t.animator then + dheight = (height - m.height) * dheight > 0 and dheight or -dheight + t.animator = newanimator(time_interval) + t.animator(function() + local n = m.height + dheight + if dheight > 0 and n >= m.height_actual or dheight < 0 and n <= m.height_actual then + t:height(m.height_actual) + t.animator = nil + return false + else + t:height(n) + return true + end + end) + end +end + +function prims.size(t, width, height) + if not height then + return meta[t].width, meta[t].height + end + + local m = meta[t] + local w = m.width + local h = m.height + + if w * width >= 0 then + if w >= 0 then + m.x2 = m.x1 + width + else + m.x1 = m.x2 + width + end + else + if w >= 0 then + m.x1, m.x2 = m.x2 + width, m.x2 + else + m.x1, m.x2 = m.x1 + width, m.x1 + end + end + + if h * height >= 0 then + if h >= 0 then + m.y1 = m.y2 + height + else + m.y2 = m.y1 + height + end + else + if h >= 0 then + m.y1, m.y2 = m.y2, m.y2 + height + else + m.y1, m.y2 = m.y1 + height, m.y1 + end + end + + windower.prim.set_size(m.name, width, height) + m.width, m.height = width, height +end + +function prims.extents(t) + return meta[t].width, meta[t].height +end + +function prims.color(t, red, green, blue) + if not blue then + return unpack(meta[t].color,2,4) + end + + local m = meta[t] + windower.prim.set_color(m.name, m.color[1], red, green, blue) + m.color[2], m.color[3], m.color[4] = red, green, blue +end + +function prims.alpha(t, alpha) + if not alpha then + return meta[t].color[1] + end + local m = meta[t] + windower.prim.set_color(m.name, alpha, m.color[2], m.color[3], m.color[4]) + m.color[1] = alpha +end + +function prims.argb(t, a, r, g, b) + if not b then + return unpack(meta[t].color) + end + + local m = meta[t] + windower.prim.set_color(m.name, a, r, g, b) + m.color[1], m.color[2], m.color[3], m.color[4] = a, r, g, b +end +-- Sets/returns prim transparency. Based on percentage values, with 1 being fully transparent, while 0 is fully opaque. +function prims.transparency(t, alpha) + if not alpha then + return 1 - meta[t].color[1]/255 + end + + alpha = math.floor(255*(1-alpha)) + local m = meta[t] + windower.prim.set_color(m.name, alpha, m.color[2], m.color[3], m.color[4]) + m.color[1] = alpha +end + +function prims.tile(t, x_rep, y_rep) + if not y_rep then + return unpack(meta[t].tile) + end + + local m = meta[t] + windower.prim.set_repeat(m.name, x_rep, y_rep) + m.tile[1], m.tile[2] = x_rep, y_rep +end + +function prims.texture(t, path) + if not path then + return meta[t].texture + end + + windower.prim.set_texture(meta[t].name, path) + meta[t].texture = path +end + +function prims.fit(t, fit) + if fit == nil then + return meta[t].fit + end + + windower.prim.set_fit_to_texture(meta[t].name, fit) + meta[t].fit = fit +end + +function prims.hover(t, x, y) + local m = meta[t] + return (m.x2 >= x + and m.x1 <= x + and m.y1 >= y + and m.y2 <= y) +end + +function prims.destroy(t) + windower.prim.delete(meta[t].name) + meta[t] = nil + t = nil +end + +function prims.get_events(t) + return meta[t].events +end + +function prims.register_event(t, event, fn) + if not events[event] then + error('The event ' .. event .. ' is not available to the ' .. class(t) .. ' class.') + return + end + + local m = meta[t].events + + m[event] = m[event] or {n = 0} + local n = #m[event] + 1 + m[event][n] = fn + m[event].n = m[event].n > n and m[event].n or n + + return n +end + +function prims.unregister_event(t, event, n) + if not (events[event] and meta[t].events[event]) then + return + end + + if type(n) == 'number' then + meta[t].events[event][n] = nil + else + for i = 1, meta[t].events[event].n do + if meta[t].events[event][i] == n then + meta[t].events[event][i] = nil + return + end + end + end +end + +return prims diff --git a/Data/DefaultContent/Libraries/addons/addons/Nostrum/variables.lua b/Data/DefaultContent/Libraries/addons/addons/Nostrum/variables.lua new file mode 100644 index 0000000..bf7a024 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Nostrum/variables.lua @@ -0,0 +1,593 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Nostrum nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv 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.--]] + +w=29 +h=24 +tab_keys={[15]=true,[16]=true,[18]=true,[28]=true,[82]=true,[203]=true,[205]=true,[210]=true,} +spell_default='' +send_string = '' + +saved_prims=S{} +saved_texts=S{} +prims_by_layer={L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{}} +texts_by_layer={L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{},L{}} +misc_hold_for_up={texts=T{},prims=T{}} +macro = {S{},S{},S{}} +macro_visibility = {[1]=true,[2]=true,[3]=true} +text_coordinates={x=T{},y=T{},visible=T{}} +prim_coordinates={x=T{},y=T{},visible=T{},a=T{},r=T{},g=T{},b=T{}} +party_keys = S{'p0', 'p1', 'p2', 'p3', 'p4', 'p5'} +party_two_keys = S{'a10', 'a11', 'a12', 'a13', 'a14', 'a15'} +party_three_keys = S{'a20', 'a21', 'a22', 'a23', 'a24', 'a25'} +seeking_information={} +macro_order=T{nil,L{},nil,L{},L{}} +mouse_map2=T{} +buff_map={{{},{}},{{},{}},{{},{}},{{},{}},{{},{}},{{},{}},} +vacancies={0,0,0} + +help_text = [[Nostrum command list. +help: Prints a list of these commands in the console. +refresh(r): Compares the macro's current party structures to + - the alliance structure in memory. +hide(h): Toggles the macro's visibility. +cut(c): Trims the macro down to size, removing blank spaces. +profile(p) <name>: Loads a new profile from the settings file. +send(s) <name>: Requires 'send' addon. Sends commands to the + - character whose name is provided. If no name is provided, + - send will reset and commands will be sent to the character + - with Nostrum loaded.]] + +position = { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +} +out_of_zone={} +out_of_range={ + false,false,false,false,false,false, + false,false,false,false,false,false, + false,false,false,false,false,false, +} +out_of_view={ + false,false,false,false,false,false, + false,false,false,false,false,false, + false,false,false,false,false,false, +} +who_am_i={} + +dragged = false +is_zoning = false +is_hidden = false + +font_widths={ + ['I']=9,['II']=17,['III']=25,['IV']=24,['V']=16, + ['1']=10,['2']=11,['3']=11,['4']=11,['5']=11,['6']=12, + ['˹1˼']=25,['˹2˼']=25,['˹3˼']=25,['˹4˼']=25,['˹5˼']=25, + ['˹I˼']=23,['˹II˼']=31, +} + +tracked_buffs = { + { + [42] = 'regen', + [233] = 'regen', + [539] = 'regen', + [43] = 'regen',-- = 'refresh', + [234] = 'regen',-- = 'refresh', + [541] = 'regen',-- = 'refresh', + [228] = 'embrava', + [580] = 'haste', + [33] = 'haste', + [40] = 'protect', + [41] = 'shell', + [113] = 'reraise', + [600] = 'roll', + [310] = 'roll', + [314] = 'roll', + [318] = 'roll', + [322] = 'roll', + [326] = 'roll', + [334] = 'roll', + [315] = 'roll', + [319] = 'roll', + [323] = 'roll', + [327] = 'roll', + [331] = 'roll', + [312] = 'roll', + [316] = 'roll', + [320] = 'roll', + [324] = 'roll', + [328] = 'roll', + [332] = 'roll', + [581] = 'haste', + [188] = 'sublimation', + [187] = 'sublimation', + [336] = 'roll', + [265] = 'haste', + [339] = 'roll', + [338] = 'roll', + [337] = 'roll', + [313] = 'roll', + [317] = 'roll', + [321] = 'roll', + [325] = 'roll', + [329] = 'roll', + [333] = 'roll', + [335] = 'roll', + [330] = 'roll', + [311] = 'roll', + [193] = 'song', + [197] = 'song', + [201] = 'song', + [205] = 'song', + [209] = 'song', + [213] = 'song', + [221] = 'song', + [198] = 'song', + [202] = 'song', + [206] = 'song', + [210] = 'song', + [214] = 'song', + [218] = 'song', + [222] = 'song', + [195] = 'song', + [199] = 'song', + [203] = 'song', + [207] = 'song', + [211] = 'song', + [215] = 'song', + [219] = 'song', + [196] = 'song', + [200] = 'song', + [208] = 'song', + [212] = 'song', + [216] = 'song', + [220] = 'song', + [223] = 'song', + }, + { + [194] = 'song', + [16] = 'amnesia', + [17] = 'charm', + [11] = 'bind', + [12] = 'weight', + [13] = 'slow', + [14] = 'charm', + [565] = 'slow', + [567] = 'weight', + [15] = 'doom', + [149] = 'defdown', + [558] = 'defdown', + [2] = 'sleep', + [19] = 'sleep', + [26] = 'weakness', + [1] = 'weakness', + } +} + +for k, v in pairs(tracked_buffs[1]) do + tracked_buffs[1][k] = windower.windower_path .. 'addons/Nostrum/icons/' .. v .. '.png' +end +for k, v in pairs(tracked_buffs[2]) do + tracked_buffs[2][k] = windower.windower_path .. 'addons/Nostrum/icons/' .. v .. '.png' +end + +tracked_buffs[2][540] = windower.windower_path .. "\\plugins\\icons\\spells\\00293.png" +tracked_buffs[2][3] = windower.windower_path .. "\\plugins\\icons\\spells\\00293.png" +tracked_buffs[2][4] = windower.windower_path .. "\\plugins\\icons\\spells\\00289.png" +tracked_buffs[2][5] = windower.windower_path .. "\\plugins\\icons\\spells\\00295.png" +tracked_buffs[2][6] = windower.windower_path .. "\\plugins\\icons\\spells\\00290.png" +tracked_buffs[2][7] = windower.windower_path .. "\\plugins\\icons\\spells\\00291.png" +tracked_buffs[2][8] = windower.windower_path .. "\\plugins\\icons\\spells\\00293.png" +tracked_buffs[2][9] = windower.windower_path .. "\\plugins\\icons\\spells\\00292.png" +tracked_buffs[2][15] = windower.windower_path .. "\\plugins\\icons\\spells\\00293.png" +tracked_buffs[2][566] = windower.windower_path .. "\\plugins\\icons\\spells\\00289.png" + +color_over_texture = { + [43] = {255, 255, 100, 255}, + [234] = {255, 255, 100, 255}, + [541] = {255, 255, 100, 255}, + [310] = {255, 208, 132, 124}, --'Fighter\'s Roll', + [314] = {255, 210, 179, 160}, --'Warlock\'s Roll', + [318] = {255, 250, 192, 220}, --'Beast Roll', + [322] = {255, 162, 206, 172}, --'Ninja Roll', + [326] = {255, 230, 233, 122}, --'Corsair\'s Roll', + [334] = {255, 162, 157, 189}, --'Tactician\'s Roll', + [315] = {255, 193, 165, 193}, --'Rogue\'s Roll', + [319] = {255, 97, 92, 83}, --'Choral Roll', + [327] = {255, 205, 125, 200}, --'Puppet Roll', + [316] = {255, 250, 250, 218}, --'Gallant\'s Roll', + [320] = {255, 197, 159, 184}, --'Hunter\'s Roll', + [324] = {255, 141, 202, 141}, --'Evoker\'s Roll', + [328] = {255, 0, 151, 255}, --'Dancer\'s Roll', + [332] = {255, 198, 158, 160}, --'Courser\'s Roll', + [581] = {255, 0, 255, 0}, --'Flurry', + [336] = {255, 217, 196, 213}, --'Miser\'s Roll', + [265] = {255, 0, 255, 0}, --'Flurry', + [338] = {255, 236, 252, 245}, --'Avenger\'s Roll', + [337] = {255, 120, 152, 179}, --'Companion\'s Roll', + [313] = {255, 182, 166, 195}, --'Wizard\'s Roll', + [317] = {255, 202, 66, 1}, --'Chaos Roll', + [321] = {255, 225, 162, 117}, --'Samurai Roll', + [325] = {255, 119, 164, 207}, --'Magus\'s Roll', + [329] = {255, 255, 217, 179}, --'Scholar\'s Roll', + [333] = {255, 240, 227, 179}, --'Blitzer\'s Roll', + [311] = {255, 145, 197, 207}, --'Monk\'s Roll', + [193] = {255, 51, 51, 102}, --'Lullaby' + [197] = {255, 204, 204, 0}, --'Minne', + [201] = {255, 0, 204, 0}, --'Mambo', + [221] = {255, 102, 153, 153}, --'Dirge', + [198] = {255, 204, 51, 51}, --'Minuet', + [214] = {255, 0, 153, 204}, --'March', + [218] = {255, 248, 224, 152}, --'Hymnus', + [222] = {255, 153, 204, 204}, --'Scherzo', + [195] = {255, 0, 102, 204}, --'Paeon', + [199] = {255, 153, 51, 204}, --'Madrigal', + [215] = {255, 153, 131, 102}, --'Etude', + [196] = {255, 0, 153, 0}, --'Ballad', + [200] = {255, 184, 243, 189}, --'Prelude', + [216] = {255, 153, 102, 0}, --'Carol', + [220] = {255, 255, 102, 102}, --'Sirvente', +} + +xml_to_lua = { + ["cure"]="Cure", + ["cureii"]="Cure II", + ["cureiii"]="Cure III", + ["cureiv"]="Cure IV", + ["curev"]="Cure V", + ["curevi"]="Cure VI", + ["curaga"]="Curaga", + ["curagaii"]="Curaga II", + ["curagaiii"]="Curaga III", + ["curagaiv"]="Curaga IV", + ["curagav"]="Curaga V", + ["sacrifice"]="Sacrifice", + ["erase"]="Erase", + ["paralyna"]="Paralyna", + ["silena"]="Silena", + ["blindna"]="Blindna", + ["poisona"]="Poisona", + ["viruna"]="Viruna", + ["stona"]="Stona", + ["cursna"]="Cursna", + ["haste"]="Haste", + ["hasteii"]="Haste II", + ["flurry"]="Flurry", + ["flurryii"]="Flurry II", + ["protect"]="Protect", + ["shell"]="Shell", + ["protectii"]="Protect II", + ["shellii"]="Shell II", + ["protectiii"]="Protect III", + ["shelliii"]="Shell III", + ["protectiv"]="Protect IV", + ["shelliv"]="Shell IV", + ["protectv"]="Protect V", + ["shellv"]="Shell V", + ["refresh"]="Refresh", + ["refreshii"]="Refresh II", + ["regen"]="Regen", + ["regenii"]="Regen II", + ["regeniii"]="Regen III", + ["regeniv"]="Regen IV", + ["regenv"]="Regen V", + ["phalanxii"]="Phalanx II", + ["adloquium"]="Adloquium", + ["animusaugeo"]="Animus Augeo", + ["animusminuo"]="Animus Minuo", + ["embrava"]="Embrava", + ["curingwaltz"]="Curing Waltz", + ["curingwaltzii"]="Curing Waltz II", + ["curingwaltziii"]="Curing Waltz III", + ["curingwaltziv"]="Curing Waltz IV", + ["curingwaltzv"]="Curing Waltz V", + ["divinewaltz"]="Divine Waltz", + ["divinewaltzii"]="Divine Waltz II", + ["healingwaltz"]="Healing Waltz", +} + +prefix={ + ["Blindna"]='/ma', + ["Poisona"]='/ma', + ["Cure III"]='/ma', + ["Curaga V"]='/ma', + ["Sacrifice"]='/ma', + ["Embrava"]='/ma', + ["Curaga II"]='/ma', + ["Stona"]='/ma', + ["Protect III"]='/ma', + ["Shell IV"]='/ma', + ["Curaga IV"]='/ma', + ["Paralyna"]='/ma', + ["Protect"]='/ma', + ["Phalanx II"]='/ma', + ["Protect V"]='/ma', + ["Curaga III"]='/ma', + ["Adloquium"]='/ma', + ["Divine Waltz II"]='/ja', + ["Cure"]='/ma', + ["Divine Waltz"]='/ja', + ["Curing Waltz V"]='/ja', + ["Cure II"]='/ma', + ["Erase"]='/ma', + ["Cure VI"]='/ma', + ["Haste II"]='/ma', + ["Curing Waltz"]='/ja', + ["Animus Augeo"]='/ma', + ["Curing Waltz III"]='/ja', + ["Cure IV"]='/ma', + ["Regen II"]='/ma', + ["Animus Minuo"]='/ma', + ["Curing Waltz IV"]='/ja', + ["Cure V"]='/ma', + ["Regen V"]='/ma', + ["Regen"]='/ma', + ["Refresh"]='/ma', + ["Regen IV"]='/ma', + ["Viruna"]='/ma', + ["Haste"]='/ma', + ["Curing Waltz II"]='/ja', + ["Healing Waltz"]='/ja', + ["Protect IV"]='/ma', + ["Cursna"]='/ma', + ["Silena"]='/ma', + ["Curaga"]='/ma', + ["Shell II"]='/ma', + ["Refresh II"]='/ma', + ["Shell"]='/ma', + ["Protect II"]='/ma', + ["Shell III"]='/ma', + ["Flurry II"]='/ma', + ["Flurry"]='/ma', + ["Regen III"]='/ma', + ["Shell V"]='/ma', + [""]='/echo No spell selected: ', +} + +options={ + cures=L{ + "cure", + "cureii", + "cureiii", + "cureiv", + "curev", + "curevi", + "curingwaltz", + "curingwaltzii", + "curingwaltziii", + "curingwaltziv", + "curingwaltzv", + }, + curagas=L{ + "curaga", + "curagaii", + "curagaiii", + "curagaiv", + "curagav", + "divinewaltz", + "divinewaltzii", + }, + buffs=L{ + "haste", + "hasteii", + "flurry", + "flurryii", + "protect", + "shell", + "protectii", + "shellii", + "protectiii", + "shelliii", + "protectiv", + "shelliv", + "protectv", + "shellv", + "refresh", + "refreshii", + "regen", + "regenii", + "regeniii", + "regeniv", + "regenv", + "phalanxii", + "adloquium", + "animusminuo", + "animusaugeo", + "embrava" + }, + na=L{ + "erase", + "paralyna", + "silena", + "blindna", + "poisona", + "viruna", + "stona", + "cursna", + "sacrifice", + "healingwaltz", + }, + aliases={ + ["Cure"]="1", + ["Cure II"]="2", + ["Cure III"]="3", + ["Cure IV"]="4", + ["Cure V"]="5", + ["Cure VI"]="6", + ["Curaga"]="I", + ["Curaga II"]="II", + ["Curaga III"]="III", + ["Curaga IV"]="IV", + ["Curaga V"]="V", + ["Sacrifice"]="Sac", + ["Erase"]="Eras", + ["Paralyna"]="Para", + ["Silena"]="Slna", + ["Blindna"]="Blnd", + ["Poisona"]="Psna", + ["Viruna"]="Viru", + ["Stona"]="Stna", + ["Cursna"]="Curs", + ["Haste"]="Haste", + ["Haste II"]="Haste", + ["Flurry"]="Flry", + ["Flurry II"]="Flry", + ["Protect"]="Pro", + ["Protect II"]="Pro", + ["Protect III"]="Pro", + ["Protect IV"]="Pro", + ["Protect V"]="Pro", + ["Shell"]="Shl", + ["Shell II"]="Shl", + ["Shell III"]="Shl", + ["Shell IV"]="Shl", + ["Shell V"]="Shl", + ["Refresh"]="Ref", + ["Refresh II"]="Ref", + ["Regen"]="Reg", + ["Regen II"]="Reg", + ["Regen III"]="Reg", + ["Regen IV"]="Reg", + ["Regen V"]="Reg", + ["Phalanx II"]="Phlx", + ["Adloquium"]="TP+", + ["Animus Augeo"]="Enm+", + ["Animus Minuo"]="Enm-", + ["Embrava"]="Embr", + ["Curing Waltz"]="˹1˼", + ["Curing Waltz II"]="˹2˼", + ["Curing Waltz III"]="˹3˼", + ["Curing Waltz IV"]="˹4˼", + ["Curing Waltz V"]="˹5˼", + ["Divine Waltz"]="˹I˼", + ["Divine Waltz II"]="˹II˼", + ["Healing Waltz"]="HW", + }, + images={ + ["Sacrifice"]="spells\\00294.png", + ["Erase"]="spells\\00294.png", + ["Paralyna"]="spells\\00289.png", + ["Silena"]="spells\\00290.png", + ["Blindna"]="spells\\00295.png", + ["Poisona"]="spells\\00293.png", + ["Viruna"]="spells\\00288.png", + ["Stona"]="spells\\00291.png", + ["Cursna"]="spells\\00292.png", + ["Haste"]="spells\\00057.png", + ["Haste II"]="spells\\00358.png", + ["Flurry"]="spells\\00056.png", + ["Flurry II"]="spells\\00357.png", + ["Protect"]="spells\\00043.png", + ["Protect II"]="spells\\00044.png", + ["Protect III"]="spells\\00045.png", + ["Protect IV"]="spells\\00046.png", + ["Protect V"]="spells\\00047.png", + ["Shell"]="spells\\00048.png", + ["Shell II"]="spells\\00049.png", + ["Shell III"]="spells\\00050.png", + ["Shell IV"]="spells\\00051.png", + ["Shell V"]="spells\\00052.png", + ["Refresh"]="spells\\00109.png", + ["Refresh II"]="spells\\00473.png", + ["Regen"]="spells\\00108.png", + ["Regen II"]="spells\\00110.png", + ["Regen III"]="spells\\00111.png", + ["Regen IV"]="spells\\00477.png", + ["Regen V"]="spells\\00504.png", + ["Phalanx II"]="spells\\00107.png", + ["Adloquium"]="spells\\00495.png", + ["Animus Augeo"]="spells\\00308.png", + ["Animus Minuo"]="spells\\00309.png", + ["Embrava"]="spells\\00478.png", + ["Healing Waltz"]="abilities\\00215.png", + }, +} + +settings={ + text={ + buttons={ + bold=true, + font="Times", + visible=true, + font_size=15, + position={x=0,y=0}, + right_justified=false + }, + name={ + bold=true, + font="Consolas", + font_size=10, + position={x=0,y=0}, + right_justified=false + }, + tp={ + bold=true, + font="Consolas", + font_size=10, + position={x=0,y=0}, + right_justified=false + }, + hp={ + bold=true, + font="Consolas", + font_size=10, + right_justified=true + }, + mp={ + bold=true, + font="Consolas", + font_size=10, + right_justified=true + }, + hpp={ + bold=true, + font="Times", + font_size=20, + right_justified=true + }, + na={ + bold=true, + font="Times", + font_size=10, + right_justified=false + }, + buffs={ + bold=true, + font="Times", + font_size=9, + right_justified=false, + }, + }, + primitives={ + hp_bar={ + color={a=176, r=176, g=176, b=176}, + visible=true + }, + }, + window={ + x_res=windower.get_windower_settings().x_res, + y_res=windower.get_windower_settings().y_res, + }, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/NyzulHelper.lua b/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/NyzulHelper.lua new file mode 100644 index 0000000..b39e235 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/NyzulHelper.lua @@ -0,0 +1,323 @@ +--[[ +Copyright © 2020, Glarin of Asura +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NyzulHelper nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Glarin BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'NyzulHelper' +_addon.author = 'Glarin' +_addon.version = '1.0' +_addon.commands = {'nh', 'nyzulhelper'} +_addon.language = 'english' + +require('logger') +require('coroutine') + +config = require('config') +packets = require('packets') +res = require('resources') +texts = require('texts') + +defaults = {} +defaults.interval = .1 + +settings = config.load(defaults) +box = texts.new('${current_string}', settings) + +pending_color = '\\cs(255,250,120)' +warning_color = '\\cs(255,165,0)' +good_color = '\\cs(0,255,0)' +bad_color = '\\cs(255,0,0)' + +frame_time = 0 +zone_timer = 0 +end_time = nil +has_armband = false +party_size = 1 +objective = '' +floor_clear = pending_color +restriction = '' +restriction_failed = warning_color +starting_floor = 0 +current_floor = 0 +completed = 0 +floor_penalities = 0 +potential_tokens = 0 + + +-- Handle addon args +windower.register_event('addon command', function(input, ...) + + local cmd = input and input:lower() or 'help' + local args = {...} + + if cmd == 'reset' then + reset() + elseif cmd == 'show' then + box:show() + elseif cmd == 'hide' then + box:hide() + elseif cmd == 'reload' then + windower.send_command('lua reload nyzulhelper') + elseif cmd == 'help' then + windower.add_to_chat(167, 'Commands:') + windower.add_to_chat(167, ' nyzulhelper reset') + windower.add_to_chat(167, ' nyzulhelper show') + windower.add_to_chat(167, ' nyzulhelper hide') + windower.add_to_chat(167, ' nyzulhelper reload') + else + log(cmd..' command unknown.') + end + +end) + +-- Event Handlers +windower.register_event('load', function() + + local info = windower.ffxi.get_info() + if info.logged_in and info.zone == 77 then + box:show() + end + +end) + +windower.register_event('prerender', function() + + local curr = os.clock() + if curr > frame_time + settings.interval then + if end_time ~= nil and zone_timer >= 1 and zone_timer ~= (end_time - os.time()) then + zone_timer = end_time - os.time() + end + + frame_time = curr + update_box() + end + +end) + +windower.register_event('zone change',function(new, old) + + box:hide() + + if new == 72 and old == 77 then + zone_timer = 0 + has_armband = false + else + reset() + end + + if new == 77 then + box:show() + party_size = windower.ffxi.get_party_info().party1_count + end + +end) + +windower.register_event('incoming chunk', function(id, data, modified, injected, blocked) + + if id == 0x55 and windower.ffxi.get_info().zone == 72 and has_value(windower.ffxi.get_key_items(), 797) then + has_armband = true + end + +end) + +windower.register_event('incoming text', function(original, modified, mode, _, blocked) + + local info = windower.ffxi.get_info() + if not info.logged_in or info.zone ~= 77 or blocked or original == '' then + return + end + + if mode == 123 then + + if string.find(original, 'Security field malfunction') then + restriction = string.strip_format(original) + restriction_failed = bad_color + update_box() + elseif string.find(original, 'Time limit has been reduced') then + set_timer(zone_timer - tonumber(original:match('%d+')) * 60) + elseif string.find(original, 'Potential token reward reduced') then + floor_penalities = floor_penalities + 1 + end + + elseif (mode == 146 or mode == 148) and string.find(original, '(Earth time)') then + + local multiplier = 1 + if string.find(original, 'minute') then multiplier = 60 end + + set_timer(tonumber(original:match('%d+')) * multiplier) + + elseif mode == 146 then + + if string.find(original,'Floor %d+ objective complete. Rune of Transfer activated.') then + completed = completed + 1 + floor_clear = good_color + restriction = '' + restriction_failed = warning_color + calculate_tokens() + update_box() + end + + elseif mode == 148 then + + if string.find(original,'Objective:') then + if string.find(original,'Commencing') then + objective = 'Complete on-site objectives' + else + objective = string.strip_format(original:sub(11)) + end + floor_clear = pending_color + elseif string.find(original, 'archaic') then + restriction = string.strip_format(original) + elseif string.find(original,'Transfer complete. Welcome to Floor %d+.') then + current_floor = tonumber(original:match('%d+')) + resync_values() + end + + end + +end) + +function reset() + + zone_timer = 0 + end_time = nil + objective = '' + floor_clear = pending_color + restriction = '' + restriction_failed = warning_color + starting_floor = 0 + current_floor = 0 + completed = 0 + floor_penalities = 0 + potential_tokens = 0 + +end + +function has_value(list, value) + + if list ~= nil and value ~= nil then + for _, v in pairs(list) do + if v == value then + return true + end + end + end + + return false + +end + +function set_timer(remaining) + + zone_timer = remaining + end_time = os.time() + zone_timer + +end + +function get_relative_floor() + + if current_floor < starting_floor then + return current_floor + 100 + end + + return current_floor + +end + +function get_token_rate() + + local rate = 1 + if has_armband then + rate = rate + .1 + end + + if party_size > 3 then + rate = rate - ((party_size - 3 ) * .1) + end + + return rate + +end + +function resync_values() + + if starting_floor == 0 then + starting_floor = current_floor + if zone_timer == 0 then + set_timer(1800) + end + end + + local relative_floor = get_relative_floor() + if (relative_floor - starting_floor) > completed then + completed = relative_floor - starting_floor + end + + floor_penalities = 0 + +end + +function get_token_penalty(rate) + + return math.round(117 * rate) * floor_penalities + +end + +function calculate_tokens() + + local relative_floor = get_relative_floor() + local rate = get_token_rate() + + local floor_bonus = 0 + if relative_floor > 1 then + floor_bonus = (10 * math.floor((relative_floor - 1) / 5)) + end + + potential_tokens = potential_tokens + ((200 + floor_bonus) * rate) - get_token_penalty(rate) + +end + +function update_box() + + local timer_color = '' + if zone_timer < 60 then + timer_color = bad_color + end + + local lines = L{} + lines:append(' Current Floor: '..current_floor) + lines:append('\n Time Remaining: '..timer_color..os.date('%M:%S', zone_timer)..'\\cr ') + lines:append('\n Objective: '..floor_clear..objective..'\\cr ') + if restriction ~= '' then + lines:append(' Restriction: '..restriction_failed..restriction..'\\cr ') + end + lines:append('\n Floors Completed: '..completed) + lines:append(' Reward Rate: %d%%':format(get_token_rate() * 100)) + lines:append(' Potential Tokens: '..potential_tokens) + + box.current_string = lines:concat('\n') + +end diff --git a/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/ReadMe.md new file mode 100644 index 0000000..d0f9f85 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/NyzulHelper/ReadMe.md @@ -0,0 +1,6 @@ +**Authors:** Glarin +**Version:** 1.0 +**Date:** 7/14/2020 + +**Description:** +NyzulHelper is an addon that tracks and displays the Current Floor, Time Remaining, Objective, Floors Completed, Reward Rate, and Potenial Tokens.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/Omen/Omen.lua b/Data/DefaultContent/Libraries/addons/addons/Omen/Omen.lua new file mode 100644 index 0000000..17afcb6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Omen/Omen.lua @@ -0,0 +1,259 @@ +-- Copyright © 2017, Braden, Sechs +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of Omen nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Braden OR Sechs BE LIABLE FOR ANY +-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'Omen' +_addon.author = 'Braden, Sechs' +_addon.version = '1.5' +_addon.command = 'omen' + +config = require ('config') +texts = require('texts') +--require('omen_test') + +defaults = T{} +defaults.text_R = 255 --Color values are in RGB, ranging from 0 to 255 +defaults.text_G = 255 +defaults.text_B = 255 +defaults.good_R = 0 +defaults.good_G = 255 +defaults.good_B = 0 +defaults.bad_R = 255 +defaults.bad_G = 0 +defaults.bad_B = 0 +defaults.pos_x = 0 +defaults.pos_y = 0 +defaults.font_size = 11 +defaults.bg_alpha = 255 + +settings = config.load(defaults) + +good_col = "\\cs("..tostring(settings.good_R)..","..tostring(settings.good_G)..","..tostring(settings.good_B)..")" +bad_col = "\\cs("..tostring(settings.bad_R)..","..tostring(settings.bad_G)..","..tostring(settings.bad_B)..")" +omens = 0 +obj_time = 0 +floor_obj = "Waiting for objectives..." +floor_clear = "" +--image = texts.new("image", settings) +image = texts.new("image") + +texts.color(image,settings.text_R,settings.text_G,settings.text_B) +texts.size(image,settings.font_size) +texts.pos_x(image,settings.pos_x) +texts.pos_y(image,settings.pos_y) +texts.bg_alpha(image,settings.bg_alpha) + +function reset_objectives() + objectives = { + [1] = {id=1,mes=0,amt=0,req=0}, + [2] = {id=2,mes=0,amt=0,req=0}, + [3] = {id=3,mes=0,amt=0,req=0}, + [4] = {id=4,mes=0,amt=0,req=0}, + [5] = {id=5,mes=0,amt=0,req=0}, + [6] = {id=6,mes=0,amt=0,req=0}, + [7] = {id=7,mes=0,amt=0,req=0}, + [8] = {id=8,mes=0,amt=0,req=0}, + [9] = {id=9,mes=0,amt=0,req=0}, + [10] = {id=10,mes=0,amt=0,req=0} + } + obj_time = 0 + floor_clear = "" +end +reset_objectives() + +function refresh() + header = floor_clear..floor_obj.."\\cr Omens: "..omens + body = "\n Bonus Objectives "..os.date('%M:%S', obj_time) + for k,v in pairs (hide_timer) do + if string.find(header,v) then + body = "" + texts.text(image,header) + return + end + end + for v, objective in ipairs(objectives) do + if objective.mes ~= 0 then + local msg = objective.mes + local cur = objective.amt + local fin = objective.req + if cur == fin then + body = body.."\n "..good_col..v..": "..messages[msg].short.." ["..cur.."/"..fin.."]\\cr" + elseif obj_time < 1 and cur < fin then + body = body.."\n "..bad_col..v..": "..messages[msg].short.." ["..cur.."/"..fin.."]\\cr" + else + body = body.."\n "..v..": "..messages[msg].short.." ["..cur.."/"..fin.."]" + end + end + end + body = string.gsub(body,"%-1","%?%?%?") + texts.text(image,header..body) +end + +hide_timer = {"Kin","Gin","Kei","Kyou","Fu","Ou","Craver","Gorger","Thinker","Treasure","Waiting"} +refresh() + +windower.register_event('prerender', function() + if obj_time < 1 then return end + if obj_time ~= (end_time - os.time()) then + obj_time = end_time - os.time() + refresh() + end +end) + +windower.register_event('zone change', function(zone) + image:hide() + floor_obj = "Waiting for objectives..." + reset_objectives() + if zone == 292 then -- Reisenjima Henge + image:show() + end +end) + +image:hide() +if windower.ffxi.get_info().zone == 292 then -- 292 is the code for Reisenjima Henge + image:show() +end + +windower.register_event('incoming text', function(original, modified, mode) + local objective = objectives[tonumber(original:match("^%d+"))] + if mode == 161 then -- Omen messages are 161 color, except total time extension messages which are 121 and irrelevant + if string.match(original,"^%d") then + for k,v in pairs (messages) do + if string.find(original,v.init) then + if objective.mes ~= tonumber(v.id) then -- New Objective + objective.amt = 0 + end + objective.mes = tonumber(v.id) + objective.req = tonumber(string.sub(original:match(v.check),1,-2)) + elseif string.find(original,v.eval) then + objective.amt = tonumber(string.sub(original:match(v.check),1,-2)) + if objective.mes == 0 then -- if loading mid-floor + objective.mes = tonumber(v.id) + objective.req = -1 + end + end + refresh() + end + elseif string.find(original,"%d+ omen") then + omens = original:match("%d+") + refresh() + elseif string.find(original,"You have %d+ seconds remaining.") then + if obj_time == 0 then + obj_time = tonumber(original:match("%d+")) + end_time = os.time() + obj_time + refresh() + end + elseif string.find(original,"A spectral light flares up.") then + floor_clear = good_col + refresh() + windower.play_sound(windower.addon_path..'big_clear.wav') + elseif string.find(original,"A faint light twinkles into existence.") then + windower.play_sound(windower.addon_path..'small_clear.wav') + elseif string.find(original,"Vanquish") or string.find(original,"Open %d treasure portent") then + local str1 = string.gsub(original,string.char(0x7f).."1","") + local str1 = string.gsub(str1,"%p","") + local str1 = string.gsub(str1,"(%s%a)",string.upper) + floor_obj = string.gsub(str1,"The","the") + if floor_clear == good_col then + reset_objectives() + end + refresh() + elseif string.find(original,"The light shall come even if you fail to obey.") then + floor_obj = "Free Floor!" + if floor_clear == good_col then + reset_objectives() + end + refresh() + end + end +end) + +windower.register_event('addon command',function(command) + command = command and command:lower() or 'help' + if texts.visible(image) then + image:hide() + else + image:show() + end +end) + +messages = { +[1] = {id="1",long="Weapon Skill Damage",short="WS Damage",check="%d+%su", +init="%d: Reduce your foe's HP by %a*%s*%a*%s*%d+ using a single weapon skill.", +eval="%d: You have reduced your foe's HP by %a*%s*%a*%s*%d+ using a single weapon skill.", +fail="%d: You have failed to reduce your foe's HP by %a*%s*%a*%s*%d+ using a single weapon skill."}, +[2] = {id="2",long="Magic Burst Damage",short="MB Damage",check="%d+%su", +init="%d: Reduce your foe's HP by %a*%s*%a*%s*%d+ using a single magic burst.", +eval="%d: You have reduced your foe's HP by %a*%s*%a*%s*%d+ using a single magic burst.", +fail="%d: You have failed to reduce your foe's HP by %a*%s*%a*%s*%d+ using a single magic burst."}, +[3] = {id="3",long="Non-MB Nuke Damage",short="Non-MB Nuke",check="%d+%su", +init="%d: Reduce your foe's HP by %a*%s*%a*%s*%d+ using a single magic attack without performing a magic burst.", +eval="%d: You have reduced your foe's HP by %a*%s*%a*%s*%d+ using a single magic attack without performing a magic burst.", +fail="%d: You have failed to reduce your foe's HP by %a*%s*%a*%s*%d+ using a single magic attack without performing a magic burst."}, +[4] = {id="4",long="Auto-attack Damage",short="Melee Round",check="%d+%si", +init="%d: Reduce your foe's HP by %a*%s*%a*%s*%d+ in a single auto%-attack.", +eval="%d: You have reduced your foe's HP by %a*%s*%a*%s*%d+ in a single auto%-attack.", +fail="%d: You have failed to reduce your foe's HP by %a*%s*%a*%s*%d+ in a single auto%-attack."}, +[5] = {id="5",long="Kills",short="Kills",check="%d+%sf", +init="%d: Vanquish %d+ %a+.", +eval="%d: You have vanquished %d+ %a+.", +fail="%d: You have failed to vanquish %d+ %a+."}, +[6] = {id="6",long="Critical Hits",short="Critical Hits",check="%d+%sc", +init="%d: Deal %d+ critical %a+ to your foes.", +eval="%d: You have dealt %d+ critical %a+ to your foes.", +fail="%d: You have failed to deal %d+ critical %a+ to your foes."}, +[7] = {id="7",long="Abilities",short="Abilities",check="%d+%sa", +init="%d: Use %d+ %a+ on your foes.", +eval="%d: You have used %d+ %a+ on your foes.", +fail="%d: You have failed to use %d+ %a+ on your foes."}, +[8] = {id="8",long="Spells",short="Spells",check="%d+%ss", +init="%d: Cast %d+ %a+ on your foes.", +eval="%d: You have cast %d+ %a+ on your foes.", +fail="%d: You have failed to cast %d+ %a+ on your foes."}, +[9] = {id="9",long="Magic Bursts",short="Magic Bursts",check="%d+%sm", +init="%d: Perform %d+ magic %a+ on your foes.", +eval="%d: You have performed %d+ magic %a+ on your foes.", +fail="%d: You have failed to perform %d+ magic %a+ on your foes."}, +[10] = {id="10",long="Consecutive SCs",short="Skillchains",check="%d+%ss", +init="%d: Execute %d+ %a+ using weapon %a+ on your foes!", +eval="%d: You have executed %d+ %a+ using weapon %a+ on your foes!", +fail="%d: You have failed to execute %d+ %a+ using weapon %a+ on your foes!"}, +[11] = {id="11",long="All Weapon Skills",short="All WS",check="%d+%sw", +init="%d: Use %d+ weapon %a+ on your foes.", +eval="%d: You have used %d+ weapon %a+ on your foes.", +fail="%d: You have failed to use %d+ weapon %a+ on your foes."}, +[12] = {id="12",long="Physical Weapon Skills",short="Physical WS",check="%d+%sp", +init="%d: Use %d+ physical weapon %a+ on your foes.", +eval="%d: You have used %d+ physical weapon %a+ on your foes.", +fail="%d: You have failed to use %d+ physical weapon %a+ on your foes."}, +[13] = {id="13",long="Magical Weapon Skills",short="Magic WS",check="%d+%se", +init="%d: Use %d+ elemental weapon %a+ on your foes.", +eval="%d: You have used %d+ elemental weapon %a+ on your foes.", +fail="%d: You have failed to use %d+ elemental weapon %a+ on your foes."}, +[14] = {id="14",long="Heals for 500 HP",short="500 HP Cures",check="%d+%st", +init="%d: Restore at least 500 HP %d+ %a+.", +eval="%d: You have restored at least 500 HP %d+ %a+.", +fail="%d: You have failed to restore at least 500 HP %d+ %a+."} +} diff --git a/Data/DefaultContent/Libraries/addons/addons/Omen/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Omen/ReadMe.md new file mode 100644 index 0000000..8fca2d0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Omen/ReadMe.md @@ -0,0 +1,12 @@ +**Authors:** Braden, Sechs +**Version:** 1.5 +**Date:** 26/03/2017 + +**Description:** +Omen is an addon that creates a custom window that tracks the current floor's Primary and Secondary objectives. +The addon also tracks time left for secondary objs and their current in-progress status. +Text colour, font size, background alpha and window position can be configured inside the config.xml file that gets created the first time you run the addon, alternatively the window can be dragged and dropped wherever on the screen. + +**Config file:** +Add a `<yourcharname></yourcharname>` section into the config.xml and report the fields you wish to set to different values from the default ones. +Colours are expressed in R, G, B values and they range from 0 to 255 diff --git a/Data/DefaultContent/Libraries/addons/addons/PetSchool/Data/Delete Me.txt b/Data/DefaultContent/Libraries/addons/addons/PetSchool/Data/Delete Me.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/PetSchool/Data/Delete Me.txt @@ -0,0 +1 @@ + diff --git a/Data/DefaultContent/Libraries/addons/addons/PetSchool/PetSchool.lua b/Data/DefaultContent/Libraries/addons/addons/PetSchool/PetSchool.lua new file mode 100644 index 0000000..4ee0d9f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/PetSchool/PetSchool.lua @@ -0,0 +1,149 @@ +--Copyright (c) 2013, Banggugyangu +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +_addon.name = 'PetSchool' +_addon.commands = {'petschool','ps'} +_addon.author = 'Banggugyangu' +_addon.version = '1.0.0' + +windower.register_event('load',function () + version = '1.0.0' + PetNuke = ' ' + PetHeal = ' ' + TP_Set = ' ' + Idle_Set = ' ' + options_load() +end) + +--Function Designer: Byrth +function options_load() + local f = io.open(windower.addon_path..'data/settings.txt', "r") + if f == nil then + local g = io.open(windower.addon_path..'data/settings.txt', "w") + g:write('Release Date: 2:16 PM, 4-22-13\46\n') + g:write('Author Comment: This document is whitespace sensitive, which means that you need the same number of spaces between things as exist in this initial settings file\46\n') + g:write('Author Comment: It looks at the first two words separated by spaces and then takes anything as the value in question if the first two words are relevant\46\n') + g:write('Author Comment: If you ever mess it up so that it does not work, you can just delete it and PetSchool will regenerate it upon reload\46\n') + g:write('Author Comment: For the Gearset, simply place the name of the spellcast set for each setting exactly how it is spelled in spellcast.\n') + g:write('Author Comment: The design of the settings file is credited to Byrthnoth as well as the creation of the settings file.\n\n\n\n') + g:write('Fill In Settings Below:\n') + g:write('PetNuke Set: PetNuke\nPetHeal Set: PetHeal\nTP Set: TP\nIdle Set: Movement\n') + g:close() + + print('Default settings file created') + windower.add_to_chat(17,'PetSchool created a settings file and loaded!') + windower.add_to_chat(17,'Please Modify the Settings file to fit your spellcast .XML file') + else + f:close() + for curline in io.lines(windower.addon_path..'data/settings.txt') do + local splat = split(curline,' ') + local cmd = '' + if splat[2] ~=nil then + cmd = (splat[1]..' '..splat[2]):gsub(':',''):lower() + end + if cmd == 'petnuke set' then + PetNuke = splat[3] + elseif cmd == 'petheal set' then + PetHeal = splat[3] + elseif cmd == 'tp set' then + TP_Set = splat[3] + elseif cmd == 'idle set' then + Idle_Set = splat[3] + end + end + windower.add_to_chat(17,'PetSchool read from a settings file and loaded!') + end +end + +windower.register_event('action',function (act) + local pet = windower.ffxi.get_mob_by_target('pet') + if not pet then + return + end + + local actor = act.actor_id + local category = act.category + local targets = act.targets + + if actor == pet then + if category == 8 then + local actionTarget = windower.ffxi.get_mob_by_id(targets[1].id) + if actionTarget.is_npc == true then + windower.send_command('sc set ' .. PetNuke) + windower.add_to_chat(17, ' Pet Spellcast Started: Nuking') + elseif actionTarget.is_npc == false then + windower.send_command('sc set ' .. PetHeal) + windower.add_to_chat(17, ' Pet Spellcast Started: Curing/Buffing') + end + elseif category == 4 then + local status = windower.ffxi.get_player().status + if (status == 1) then + windower.send_command('sc set ' .. TP_Set) + windower.add_to_chat(17, ' Pet Spellcast Finished') + elseif (status == 0) then + windower.send_command('sc set ' .. Idle_Set) + windower.add_to_chat(17, ' Pet Spellcast Finished') + end + end + end +end) + +--Function Author: Byrth +function split(msg, match) + local length = msg:len() + local splitarr = {} + local u = 1 + while u <= length do + local nextanch = msg:find(match,u) + if nextanch ~= nil then + splitarr[#splitarr+1] = msg:sub(u,nextanch-match:len()) + if nextanch~=length then + u = nextanch+match:len() + else + u = lengthlua + end + else + splitarr[#splitarr+1] = msg:sub(u,length) + u = length+1 + end + end + return splitarr +end + +--Function Designer: Byrth +windower.register_event('addon command',function (...) + local term = table.concat({...}, ' ') + local splitarr = split(term,' ') + if splitarr[1]:lower() == 'reload' then + options_load() + elseif splitarr[1]:lower() == 'help' then + windower.add_to_chat(17, 'PetSchool v'..version..'commands:') + windower.add_to_chat(17, '//ps [options]') + windower.add_to_chat(17, ' reload - Reloads settings') + windower.add_to_chat(17, ' help - Displays this help text') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/PetSchool/ReadMe.txt b/Data/DefaultContent/Libraries/addons/addons/PetSchool/ReadMe.txt new file mode 100644 index 0000000..9c6af47 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/PetSchool/ReadMe.txt @@ -0,0 +1,24 @@ +PetSchool v 1.0.0 + +PetSchool is a simple helper addon for Spellcast. I created this because before lua, I had to make really strange XML's for both Spellcast and Autoexec to let spellcast know when sneak attack and trick attack were no longer in effect (landed or wore). This addon is merely a middleman that tells spellcast when it wears and switches to your appropriate gear sets. Another function of this addon is to inform spellcast when Treasure Hunter has been initially placed on the mob. + +To install, have the PetSchool folder containing this readme, the PetSchool.lua, and a "data" folder in your "addons" folder for windower v4. + +To use, simply type "//lua l PetSchool" in game. This can be added to init.txt or loaded automatically via the windower v4 launcher. All updates will be available via the launcher. + +Upon initial load, a settings file will be created with default values. YOU WILL WANT TO EDIT THIS SETTINGS FILE TO SUPPORT YOUR SPELLCAST XML. + +The settings that are loaded are: + +PetNuke Set: <name_of_Pet_Nuking_Set_in_your_spellcast_XML> +PetHeal Set: <name_of_Pet_Healing_Set_in_your_spellcast_XML> +TP Set: <name_of_TP_Set_in_your_spellcast_XML> +Idle Set: <name_of_Idle_Set_in_your_spellcast_XML> + +Simply type the names of the respective sets after the colon (:) exactly how they appear in your spellcast XML. If you use spaces in the spellcast XML, they will need to be removed or this addon will not function correctly. + +Commands: + +//ps [options] + reload - Reloads Settings + help - Displays Version information and commands diff --git a/Data/DefaultContent/Libraries/addons/addons/PetTP/PetTP.lua b/Data/DefaultContent/Libraries/addons/addons/PetTP/PetTP.lua new file mode 100644 index 0000000..76171b6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/PetTP/PetTP.lua @@ -0,0 +1,416 @@ +config = require ('config') +texts = require('texts') +packets = require('packets') +require('pack') + +_addon.name = 'PetTP' +_addon.author = 'SnickySnacks' +_addon.version = '1.02' +_addon.commands = {'ptp','pettp'} + +petname = nil +mypet_idx = nil +current_hp = 0 +max_hp = 0 +current_mp = 0 +max_mp = 0 +current_hp_percent = 0 +current_mp_percent = 0 +current_tp_percent = 0 +petactive = false +verbose = false +superverbose = false +timercountdown = 0 + +defaults = T{} + +defaults.autocolor = true + +defaults.pos = T{} +defaults.pos.x = windower.get_windower_settings().x_res*2/3 +defaults.pos.y = windower.get_windower_settings().y_res-17 + +defaults.bg = T{} +defaults.bg.alpha = 255 +defaults.bg.red = 0 +defaults.bg.green = 0 +defaults.bg.blue = 0 +defaults.bg.visible = true + +defaults.flags = {} +defaults.flags.right = false +defaults.flags.bottom = false +defaults.flags.bold = false +defaults.flags.italic = false + +defaults.text = {} +defaults.text.size = 10 +defaults.text.font = 'Courier New' +defaults.text.alpha = 255 +defaults.text.red = 255 +defaults.text.green = 255 +defaults.text.blue = 255 + +settings = config.load(defaults) +pettp = texts.new(settings) + +function make_visible() + petactive = true + pettp:visible(true); + if verbose == true then windower.add_to_chat(8, 'PetTP Visible') end +end + +function make_invisible() + if petactive then + pettp:text('') + pettp:visible(false) + if verbose == true then windower.add_to_chat(8, 'PetTP Invisible') end + end + petactive = false + mypet_idx = nil + petname = nil + current_hp = 0 + max_hp = 0 + current_mp = 0 + max_mp = 0 + current_hp_percent = 0 + current_mp_percent = 0 + current_tp_percent = 0 +end + +function valid_pet(source,pet_idx_in, own_idx_in) + local player = windower.ffxi.get_player() + if superverbose == true then windower.add_to_chat(8, 'valid_pet('..source..'): petactive: '..tostring(petactive)..', mypet_idx: '..(mypet_idx or 'nil')..', pet_idx_in: '..(pet_idx_in or 'nil')..', own_idx_in: '..(own_idx_in or 'nil')..', player.index '..player.index) end + if player.vitals.hp == 0 then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : false : Player is dead') end + timercountdown = 0 + return + end + + if petactive then + if mypet_idx then + if not pet_idx_in or mypet_idx == pet_idx_in then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : true : using mypet_idx') end + return mypet_idx + else + if superverbose == true then + windower.add_to_chat(8, 'mypet_idx ~= pet_idx_in '..mypet_idx..' vs. '..pet_idx_in) + end + end + elseif own_idx_in and player.index == own_idx_in then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : true : using pet_idx_in') end + mypet_idx = pet_idx_in + return mypet_idx + end + end + + local pet = windower.ffxi.get_mob_by_target('pet') + if pet_idx_in and pet and pet_idx_in ~= pet.index then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : false : pet.index ~= pet_idx_in '..pet.index..' vs. '..pet_idx_in) end + return + elseif pet_idx_in and player.mob and player.mob.pet_index and pet_idx_in ~= player.mob.pet_index then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : false : player.mob.pet_index ~= pet_idx_in '..player.mob.pet_index..' vs. '..pet_idx_in) end + return + elseif pet then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : true : Using pet.index') end + mypet_idx = pet.index + return mypet_idx + elseif player.mob and player.mob.pet_index then + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : true : Using player.mob.pet_index') end + mypet_idx = player.mob.pet_index + return mypet_idx + end + if superverbose == true then windower.add_to_chat(8, 'valid_pet() : false : No pet found') end + return +end +function update_pet(source,pet_idx_in,own_idx_in) + pet_idx = valid_pet(source,pet_idx_in,own_idx_in) + + if pet_idx == nil then + if superverbose == true then windower.add_to_chat(8, 'update_pet() : false : pet_idx == nil, pet_idx_in: '..(pet_idx_in or 'nil')..', own_idx_in: '..(own_idx_in or 'nil')) end + return false + end + + local pet_table = windower.ffxi.get_mob_by_index(pet_idx) + if pet_table == nil then + if petactive then -- presumably we have a pet, he just hasn't loaded, yet... + if superverbose == true then windower.add_to_chat(8, 'update_pet() : true : pet_table == nil, pet_idx: '..(pet_idx or 'nil')..', '..(own_idx_in or 'nil')) end + return true + end + if superverbose == true then windower.add_to_chat(8, 'update_pet() : false: pet_table == nil, pet_idx: '..(pet_idx or 'nil')..', '..(own_idx_in or 'nil')) end + make_invisible() + return false + end + + petname = pet_table['name'] + if superverbose == true then windower.add_to_chat(8, 'update_pet() : Updating PetName: '..petname) end + current_hp_percent = pet_table['hpp'] + if not petactive and current_hp_percent == 0 then -- we're likely picking up a dead or despawning pet + if superverbose == true then windower.add_to_chat(8, 'update_pet() : Picked up a likely dead pet') end + make_invisible() + return false + end + if superverbose == true then windower.add_to_chat(8, 'update_pet() : true : Picked up a pet: '..petname..', hp%: '..current_hp_percent..', pet_idx: '..pet_idx) end + return true +end + +function printpettp(pet_idx_in,own_idx_in) + if not petactive then + return + end + if petname == nil then + if update_pet('printpettp',pet_idx_in,own_idx_in) == false then + return + end + end + + local output + + output = (petname or 'Unknown')..': ' + if settings.autocolor == true then + if current_hp_percent > 75 then + output = output..'\\cr\\cs(128,255,128)' + elseif current_hp_percent > 50 then + output = output..'\\cr\\cs(255,255,0)' + elseif current_hp_percent > 25 then + output = output..'\\cr\\cs(255,160,0)' + else + output = output..'\\cr\\cs(255,0,0)' + end + end + if max_hp > 0 then + output = output..current_hp..'/'..max_hp..' '..'('..current_hp_percent..'%)' + else + output = output..current_hp_percent..'%' + end + if settings.autocolor == true then output = output..'\\cr\\cs('..settings.text.red..','..settings.text.green..','..settings.text.blue..')' end + output = output..' [' + if settings.autocolor == true and current_tp_percent >= 1000 then output = output..'\\cr\\cs(128,255,128)' end + output = output..current_tp_percent + if settings.autocolor == true and current_tp_percent >= 1000 then output = output..'\\cr\\cs('..settings.text.red..','..settings.text.green..','..settings.text.blue..')' end + output = output..']' + if max_mp > 0 then + if current_mp_percent > 75 then + output = output..'\\cr\\cs(128,255,128)' + elseif current_mp_percent > 50 then + output = output..'\\cr\\cs(255,255,0)' + elseif current_mp_percent > 25 then + output = output..'\\cr\\cs(255,160,0)' + else + output = output..'\\cr\\cs(255,0,0)' + end + output = output..' '..current_mp..'/'..max_mp..' ('..current_mp_percent..'%)' + if settings.autocolor == true then output = output..'\\cr\\cs('..settings.text.red..','..settings.text.green..','..settings.text.blue..')' end + end + output = output..'\\cr' + + pettp:text(output) +end + +windower.register_event('time change', function() + if timercountdown == 0 then + return + elseif petactive then + if superverbose == true then windower.add_to_chat(8, 'SCAN: Pet appeared between scans!') end + timercountdown = 0 + else + timercountdown = timercountdown - 1 + if update_pet('scan') == true then + if superverbose == true then windower.add_to_chat(8, 'SCAN: Found a pet!') end + timercountdown = 0 + make_visible() + printpettp() + elseif timercountdown == 0 then + if superverbose == true then windower.add_to_chat(8, 'SCAN: No pet found in 5 ticks') end + end + end +end) + +windower.register_event('incoming chunk',function(id,original,modified,injected,blocked) + if not injected then + if id == 0x44 then + if original:unpack('C', 0x05) == 0x12 then -- puppet update + local new_current_hp, new_max_hp, new_current_mp, new_max_mp = original:unpack('HHHH', 0x069) + + if (not petactive) or (petname == nil) or (petname == "") or (new_current_hp ~= current_hp) or (new_max_hp ~= max_hp) or (new_current_mp ~= current_mp) or (new_max_mp ~= max_mp) then + if superverbose == true then + windower.add_to_chat(8, '0x44' + ..', cur_hp: '..new_current_hp + ..', max_hp: '..new_max_hp + ..', cur_mp: '..new_current_mp + ..', max_mp: '..new_max_mp + ..', name: '.. original:unpack('z', 0x59) + ) + end + + if petactive then + local new_petname = original:unpack('z', 0x59) + if petname == nil or petname == "" then + if superverbose == true then windower.add_to_chat(8, 'Updating PuppetName: '..new_petname) end + petname = new_petname + end + if petname == new_petname then -- make sure we only update if we actually have a puppet out + current_hp = new_current_hp + max_hp = new_max_hp + current_mp = new_current_mp + max_mp = new_max_mp + if max_hp ~= 0 then + current_hp_percent=math.floor(100*current_hp/max_hp) + else + current_hp_percent=0 + end + if max_mp ~= 0 then + current_mp_percent=math.floor(100*current_mp/max_mp) + else + current_mp_percent=0 + end + printpettp() + else + if superverbose == true then windower.add_to_chat(8, '0x44, pet is not a puppet') end + end + else + if superverbose == true then windower.add_to_chat(8, '0x44, puppet not active') end + end + end + end + elseif id == 0x67 or id == 0x068 then -- general hp/tp/mp update + local packet = packets.parse('incoming', original) + local msg_type = packet['Message Type'] + local msg_len = packet['Message Length'] + pet_idx = packet['Pet Index'] + own_idx = packet['Owner Index'] + + if (msg_type == 0x04) and id == 0x067 then + pet_idx, own_idx = own_idx, pet_idx + end + + if superverbose == true and id == 0x067 and not ( + (msg_type == 0x02) -- not pet related + or (msg_type == 0x03 and (own_idx == 0)) -- NPC pops + or (msg_type == 0x03 and (own_idx ~= windower.ffxi.get_player().index)) -- other people summoning + ) then + windower.add_to_chat(8, '0x67' + ..', msg_type: '..string.format('0x%02x', msg_type) + ..', msg_len: '..msg_len + ..', pet_idx: '..pet_idx + ..', pet_id: '..(original:byte(0x09)+original:byte(0x0A)*256) + ..', own_idx: '..own_idx + ..', hp%: '..original:byte(0x0F) + ..', mp%: '..original:byte(0x10) + ..', tp%: '..(original:byte(0x11)+original:byte(0x12)*256) + ..', name: '.. ((msg_len > 24) and original:unpack('z', 0x19) or "") + ) + end + if (msg_type == 0x04) then + if (pet_idx == 0) then + if verbose == true then windower.add_to_chat(8, 'Pet died/despawned') end + make_invisible() + else + local newpet = false + if not petactive then + petactive = true -- force our pet to appear even if it's not attached to us yet + if update_pet('0x67-0x*4',pet_idx,own_idx) == true then + make_visible() + newpet = true + else + if superverbose == true then windower.add_to_chat(8, 'Pet not found') end + make_invisible() + end + end + local new_hp_percent = packet['Current HP%'] + local new_mp_percent = packet['Current MP%'] + local new_tp_percent = packet['Pet TP'] + if newpet or (new_hp_percent ~= current_hp_percent) or (new_mp_percent ~= current_mp_percent) or (new_tp_percent ~= current_tp_percent) or (petname == nil) or (petname == "") then + if (max_hp ~= 0) and (new_hp_percent ~= current_hp_percent) then + current_hp = math.floor(new_hp_percent * max_hp / 100) + end + if (max_mp ~= 0) and (new_mp_percent ~= current_mp_percent) then + current_mp = math.floor(new_mp_percent * max_mp / 100) + end + if ((petname == nil) or (petname == "")) then + petname = packet['Pet Name'] + if superverbose == true then windower.add_to_chat(8, 'Updated PetName: '..petname) end + end + current_hp_percent = new_hp_percent + current_mp_percent = new_mp_percent + current_tp_percent = new_tp_percent + printpettp(pet_idx,own_idx) + end + end + elseif not petactive and (msg_type == 0x03) and (own_idx == windower.ffxi.get_player().index) then + if update_pet('0x67-0x03',pet_idx,own_idx) == true then + make_visible() + printpettp(pet_idx,own_idx_in) + else -- last resort + timercountdown = 5 + if superverbose == true then windower.add_to_chat(8, 'Starting to scan for a pet...') end + end + end + elseif id==0x0E and S{0x07,0x0F}:contains(original:byte(0x0B)) then -- npc update + if mypet_idx == original:unpack('H', 0x09) then + if current_hp_percent ~= original:byte(0x1F) then + if superverbose == true then windower.add_to_chat(8, '0x0E - '..original:byte(0x0B)..': '..original:byte(0x1F)) end + current_hp_percent = original:byte(0x1F) + if max_hp ~= 0 then + current_hp = math.floor(current_hp_percent * max_hp / 100) + end + printpettp(mypet_idx) + end + end + elseif id==0x0E and not S{0x00,0x01,0x08,0x09,0x20}:contains(original:byte(0x0B)) and mypet_idx == (original:byte(0x09)+original:byte(0x0A)*256) then + if superverbose == true then windower.add_to_chat(8, '0x0E ~ '..original:byte(0x0B)..': '..original:byte(0x1F)) end + end + end +end) + +windower.register_event('load', function() + if superverbose == true then + windower.add_to_chat(8, 'Player index: '..windower.ffxi.get_player().index) + if windower.ffxi.get_mob_by_target('pet') then + windower.add_to_chat(8, 'Pet index: '..windower.ffxi.get_mob_by_target('pet').index) + end + end + if windower.ffxi.get_player() then + if update_pet('load') == true then + make_visible() + printpettp() + end + end +end) + +windower.register_event('zone change', function() + mypet_idx = nil + if update_pet('zone') == true then + if verbose == true then windower.add_to_chat(8, 'Found pet after zoning...') end + make_visible() + printpettp() + elseif petactive then + make_invisible() + if verbose == true then windower.add_to_chat(8, 'Lost pet after zoning...') end + end +end) + +windower.register_event('job change', function() + make_invisible() +end) + +windower.register_event('addon command', function(...) + local splitarr = {...} + + for i,v in pairs(splitarr) do + if v:lower() == 'save' then + config.save(settings, 'all') + elseif v:lower() == 'verbose' then + verbose = not verbose + windower.add_to_chat(121,'PetTP: Verbose Mode flipped! - '..tostring(verbose)) + elseif v:lower() == 'superverbose' then + superverbose = not superverbose + windower.add_to_chat(121,'PetTP: SuperVerbose Mode flipped! - '..tostring(superverbose)) + elseif v:lower() == 'help' then + print(' ::: '.._addon.name..' ('.._addon.version..') :::') + print('Utilities:') + print(' 1. verbose --- Some light logging, Default = false') + print(' 2. help --- shows this menu') + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Pouches/Pouches.lua b/Data/DefaultContent/Libraries/addons/addons/Pouches/Pouches.lua new file mode 100644 index 0000000..815278f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Pouches/Pouches.lua @@ -0,0 +1,83 @@ +--[[ +Copyright © 2016, Omnys of Valefor +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Pouches nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL OMNYS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'Pouches' +_addon.version = '1' +_addon.author = 'Omnys@Valefor' +_addon.command = 'pouches' + +--Requiring libraries used in this addon +--These should be saved in addons/libs +require('logger') +require('tables') +require('strings') +res = require('resources') + +inverted = {} +item = {} + +windower.register_event('load', function(...) + for k,v in pairs(res.items) do + inverted[string.lower(v.english)] = {id = k, targets = v.targets, cast = v.cast_time} + end +end) + +function use_item() + windower.chat.input('/item "' .. item.name .. '" <me>') + item.count = item.count - 1 + if item.count > 0 and windower.ffxi.get_player().status == 0 then + use_item:schedule(item.delay) + end +end + +windower.register_event('addon command', function(...) + local inv = windower.ffxi.get_items(0) -- get main inventory + local args = T{...}:map(string.lower) + + item.name = table.concat(args," ") + item.count = 0 + if inverted[item.name] == nil then + log('Item, "'..item.name..'", does not exist.') + else + item.id = inverted[item.name].id + for b,v in ipairs(inv) do + if v.id == item.id and inverted[item.name].targets.Self == true then + item.count = item.count + v.count + item.delay = inverted[item.name].cast + 2 + end + end + if item.count > 0 then + log('Found '..item.count..' '..item.name..'. Commencing Use.') + log('You may simply type /heal to stop.') + use_item() + else + log('Item, '..item.name..' not found in main inventory, or is not the type of item that can be used.') + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/RAWR/RAWR.lua b/Data/DefaultContent/Libraries/addons/addons/RAWR/RAWR.lua new file mode 100644 index 0000000..2f34b5b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/RAWR/RAWR.lua @@ -0,0 +1,68 @@ +--Copyright © 2016, geno3302 +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of RAWR nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL geno3302 BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'RAWR' +_addon.author = 'Genoxd' +_addon.version = '1.0.0.0' + +require('tables') + +unity_leaders = +T{ +'{Pieuje}', +'{Ayame}', +'{Invincible Shield}', --galka suck. +'{Apururu}', +'{Maat}', +'{Aldo}', +'{Jakoh Wahcondalo}', +'{Naja Salaheem}', +'{Flaviria}', +'{Sylvie}', +'{Yoran-Oran}' +} + +dragons = +T{ +'Azi Dahaka', +'Naga Raja', +'Quetzalcoatl' +} + +windower.register_event("incoming text", function(original,modified,original_mode,modified_mode, blocked) + if original_mode == 212 or original_mode == 211 then --Unity chat = 211/212, 211 might be outgoing + for i,dragon in pairs(dragons) do + if(windower.wc_match(original, "*"..dragon.."*")) then + for i2,leader in pairs(unity_leaders) do + if(windower.wc_match(original, leader.."*"..dragon.."*")) then + windower.play_sound(windower.addon_path..'sounds/RAWR.wav') + return + end + end + end + end + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/RAWR/sounds/RAWR.wav b/Data/DefaultContent/Libraries/addons/addons/RAWR/sounds/RAWR.wav Binary files differnew file mode 100644 index 0000000..b93755f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/RAWR/sounds/RAWR.wav diff --git a/Data/DefaultContent/Libraries/addons/addons/README.txt b/Data/DefaultContent/Libraries/addons/addons/README.txt new file mode 100644 index 0000000..c47be3d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/README.txt @@ -0,0 +1,7 @@ +Lua addons are similar to Windower Plugins, in that they are loaded once and continue to remain active, acting upon events and commands. + +You can load Lua addons via the //lua load command, similar to plugins. For example, if you had an addon named test.lua, you would load it by typing: +//lua load test +or +//lua l test + diff --git a/Data/DefaultContent/Libraries/addons/addons/Rhombus/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Rhombus/ReadMe.md new file mode 100644 index 0000000..3749fc2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Rhombus/ReadMe.md @@ -0,0 +1,45 @@ +#Rhombus + +Creates a click-able onscreen menu based on customizable user files. Allows for significantly more organization than the in-game menu. + +### Usage + +This addon has no commands. Loading it will create an icon with four rhombi. Each rhombus indicates the region where clicking will open a menu. +Starting at the top-most rhombus and moving clock-wise, the menus that will open are: weapon skills (green), magic (red), job abilities (blue), and pet commands (yellow). +The menu can be repositioned by holding down shift and clicking anywhere within the icon. + +#### Creating Custom Menus + +Upon load or log in, the following files are searched for and loaded if found: + +* `P:/ath/to/Windower/addons/Rhombus/data/spells_template.lua` +* `P:/ath/to/Windower/addons/Rhombus/data/ws_template.lua` +* `P:/ath/to/Windower/addons/Rhombus/data/ja_template.lua` +* `P:/ath/to/Windower/addons/Rhombus/data/pet_command_template.lua` +* `P:/ath/to/Windower/addons/Rhombus/data/spell_aliases.lua` + +Each file is expected to return a table. The first four files create the structure of the corresponding menus, while the fourth is a simple mapping of in-game spell or ability names +to custom names. (You might use it to rename 'Goblin Gavotte' to 'Resist Bind', for example. Renaming spells and abilities within the menus has no effect on how they are sent as commands.) + +To create a menu, simply list the spell ids (obtained from windower's resources) that you want to appear. If you want to create a sub-menu within that menu, add a key/value pair to your table with key 'sub_menu' and a table as the value. +List each sub-menu as you would like it to appear in your menu within the sub_menu table. Values in the sub_menu table must be strings to avoid conflicts. To add spells to a sub-menu, create a key with the same name as the sub-menu in the table that contains the sub_menu key. +Sub-menus may also contain sub_menu keys: there is no limit to the number of sub-menus you can create. + +The ja_template, ws_template, and pet_command_template files work only slightly differently than the spells_template file. The table returned by each of these files should have keys corresponding to each in-game job's name that you want to create a menu for. These keys should +have table values which will contain the structure of the menu as described above. + +In the event that no file is found for a template, the default values from memory will be used. If a table within that file (in the case of job abilities, weapon skills, and pet commands) is found for either your main or sub job, but not both, the remaining abilities found in memory +will be appended to the menu you constructed. If both tables are found, the menus will be merged with sub-menus and contents appearing in order. + +Example menus can be found at the following location: https://github.com/trv6/Windower4/tree/master/Rhombus%20examples + +#### Using the Mouse + +Left-clicking on the rhombus icons opens the corresponding menu, or closes it if the menu is already open. New menus can be opened without closing +the current menu, and if a menu is closed by opening a new menu rather than by clicking a second time on the corresponding rhombus, the position within that +menu will be saved. The next time the menu is opened, it will open to the saved location rather than the beginning of the menu. +Left-clicking a menu item will input the ability or spell to the console, while left-clicking with the shift key held down will do the same but with the (hopefully) appropriate subtarget command appended. +Note that in order for left-clicking to work properly, the shortcuts addon must also be loaded. +Right-clicking on the menu will travel back one menu, or close the menu if the base menu is open. +Right-clicking on the rhombus icon will close the currently opened menu, saving the position within the menu. +Only 12 menu items can be displayed at a time. The rest may be scrolled to using the scroll-wheel of your mouse. diff --git a/Data/DefaultContent/Libraries/addons/addons/Rhombus/Rhombus.lua b/Data/DefaultContent/Libraries/addons/addons/Rhombus/Rhombus.lua new file mode 100644 index 0000000..1622b43 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Rhombus/Rhombus.lua @@ -0,0 +1,596 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Rhombus nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.--]] + +_addon.name = 'Rhombus' +_addon.author = 'trv' +_addon.version = '1.2.1' + +config = require('config') +texts = require('texts') +res = require('resources') +bit = require('bit') +require 'tables' +require('lists') +require('sets') +require('logger') +require('defs') +require('helper_functions') + +config.register(_defaults, function(settings_table) + x_offset = settings_table.x_offset + y_offset = settings_table.y_offset + selector_pos.x = 102 + x_offset + + windower.prim.set_position('menu_backdrop',selector_pos.x,y_offset) + windower.prim.set_position('selector_rectangle',selector_pos.x,y_offset) + windower.prim.set_position('scroll_bar',selector_pos.x + 150,y_offset) + + display_text:pos(95 + x_offset, y_offset) + + menu_icon:pos(-12 + x_offset, -22 + y_offset) + menu_icon:show() +end) + +function get_templates() + if not windower.ffxi.get_info().logged_in then return end + local player = windower.ffxi.get_player() + player_info.id = player.id + player_info.main_job = main_job_id or player.main_job_id + player_info.sub_job = sub_job_id or player.sub_job_id + player_info.main_job_level = main_job_level or player.main_job_level + player_info.sub_job_level = sub_job_level or player.sub_job_level + + local main = res.jobs[player_info.main_job].en + local sub = res.jobs[player_info.sub_job].en + + local t_temp = L(res.spells:levels(function(t) return t[player_info.main_job] or t[player_info.sub_job] end):keyset()) + spells_template = loadfile(windower.addon_path .. 'data/spells_template.lua') + if not spells_template then + print('No template for spells was found.') + spells_template = t_temp + else + spells_template = spells_template() + count_table_elements(spells_template) + end + + t_temp = L(res.weapon_skills:keyset()) + ws_template = loadfile(windower.addon_path .. 'data/ws_template.lua') + if not ws_template then + print('No template for weapon skills was found.') + ws_template = t_temp + else + ws_template = ws_template() + count_table_elements(ws_template) + if ws_template[main] then + if ws_template[sub] then + ws_template = recursively_merge_tables(ws_template[main], ws_template[sub]) + else + ws_template = recursively_merge_tables(ws_template[main],t_temp) + end + elseif ws_template[sub] then + ws_template = recursively_merge_tables(ws_template[sub],t_temp) + else + ws_template = t_temp + end + end + + t_temp = L(res.job_abilities:prefix('/jobability'):keyset()) + ja_template = loadfile(windower.addon_path .. 'data/ja_template.lua') + if not ja_template then + print('No template for job abilities was found.') + ja_template = t_temp + else + ja_template = ja_template() + count_table_elements(ja_template) + if ja_template[main] then + if ja_template[sub] then + ja_template = recursively_merge_tables(ja_template[main], ja_template[sub]) + else + ja_template = recursively_merge_tables(ja_template[main],t_temp) + end + elseif ja_template[sub] then + ja_template = recursively_merge_tables(ja_template[sub],t_temp) + else + ja_template = t_temp + end + end + + t_temp = L(res.job_abilities:prefix('/pet'):keyset()) + pet_command_template = loadfile(windower.addon_path .. 'data/pet_command_template.lua') + if not pet_command_template then + print('No template for pet commands was found.') + pet_command_template = t_temp + else + pet_command_template = pet_command_template() + count_table_elements(pet_command_template) + if pet_command_template[main] then + if pet_command_template[sub] then + pet_command_template = recursively_merge_tables(pet_command_template[main], pet_command_template[sub]) + else + pet_command_template = recursively_merge_tables(pet_command_template[main],t_temp) + end + elseif pet_command_template[sub] then + pet_command_template = recursively_merge_tables(pet_command_template[sub],t_temp) + else + pet_command_template = t_temp + end + end + + spell_aliases = loadfile(windower.addon_path .. 'data/spell_aliases.lua') + if not spell_aliases then + spell_aliases = {} + else + spell_aliases = spell_aliases() + end + +end + +function menu_general_layout(t,t2,n) + available_category = t + if is_menu_open then + if last_menu_open.type == n then + is_menu_open = false + menu_layer_record:clear() + close_a_menu() + last_menu_open = {} + current_menu = {} + menu_history[n] = false + else + menu_history[last_menu_open.type] = list.copy(menu_layer_record) + if menu_history[n] then + last_menu_open.type = n + menu_layer_record = menu_history[n] + current_menu = recursively_copy_spells(t2) + if current_menu then + last_menu_open = current_menu + last_menu_open.type = n + for i = 1,menu_layer_record.n do + if current_menu[menu_layer_record[i]] then + current_menu = current_menu[menu_layer_record[i]] + else + for j = 1,menu_layer_record.n+1-i do + menu_layer_record:remove() + end + break + end + end + build_a_menu(current_menu) + else + close_a_menu() + current_menu = {} + end + else + menu_layer_record:clear() + last_menu_open.type = n + current_menu = recursively_copy_spells(t2) + if current_menu then + last_menu_open = current_menu + last_menu_open.type = n + build_a_menu(current_menu) + else + close_a_menu() + current_menu = {} + end + end + end + else + if menu_history[n] then + last_menu_open.type = n + menu_layer_record = menu_history[n] + current_menu = recursively_copy_spells(t2) + if current_menu then + last_menu_open = current_menu + last_menu_open.type = n + for i = 1,menu_layer_record.n do + if current_menu[menu_layer_record[i]] then + current_menu = current_menu[menu_layer_record[i]] + else + for j = 1,menu_layer_record.n+1-i do + menu_layer_record:remove() + end + break + end + end + build_a_menu(current_menu) + else + close_a_menu() + current_menu = {} + end + else + menu_layer_record:clear() + last_menu_open.type = n + current_menu = recursively_copy_spells(t2) + if current_menu then + last_menu_open = current_menu + last_menu_open.type = n + build_a_menu(current_menu) + else + close_a_menu() + current_menu = {} + end + end + end +end + +windower.register_event('incoming chunk', function(id, data) + if is_menu_open and id == 0x0AC and last_menu_open.type ~= 1 then + if not S(windower.ffxi.get_abilities()[category_to_resources[last_menu_open.type]]):equals(available_category) then + available_category = S(windower.ffxi.get_abilities()[category_to_resources[last_menu_open.type]]) + current_menu = recursively_copy_spells({spells_template,ws_template,ja_template,pet_command_template}[last_menu_open.type]) + menu_building_snippet() + end + end +end) + +windower.register_event('gain buff', function(buff_id) + if is_menu_open and refresh_ma_when[buff_id] and last_menu_open.type == 1 then + active_buffs:add(buff_id) + number_of_jps = count_job_points() + current_menu = recursively_copy_spells(spells_template) + menu_building_snippet() + end +end) + +windower.register_event('lose buff', function(buff_id) + if is_menu_open and refresh_ma_when[buff_id] and last_menu_open.type == 1 then + active_buffs:remove(buff_id) + number_of_jps = count_job_points() + current_menu = recursively_copy_spells(spells_template) + menu_building_snippet() + end +end) + +windower.register_event('job change',get_templates) + +windower.register_event('login', function() + get_templates:schedule(10) +end) + +windower.register_event('load', function() + get_templates() +end) + +windower.register_event('logout', function() + close_a_menu() + display_text:hide() + menu_icon:hide() +end) + +windower.register_event('mouse', function(type, x, y, delta, blocked) + if blocked then + return + end + if type == 0 then + local _x,_y = x-(51+x_offset),y-(51+y_offset) + if drag_and_drop then + x_offset = x-drag_and_drop.x + y_offset = y-drag_and_drop.y + display_text:pos(x_offset+95,y_offset) + menu_icon:pos(x_offset-12,y_offset-22) + selector_pos.x = x_offset+102 + windower.prim.set_position('menu_backdrop',selector_pos.x,y-drag_and_drop.y) + windower.prim.set_position('selector_rectangle',selector_pos.x,y_offset+selector_pos.y) + windower.prim.set_position('scroll_bar',selector_pos.x + 150,y_offset + ((12 * font_height_est * (1 - 12 / menu_list.n)) / (menu_list.n - 12)) * (menu_start - 1)) + elseif math.abs(_x) + math.abs(_y) <= 51 then + local tan = (_y)/(_x) + if _x > 0 then + if tan <= -1 then + if not is_icon.G then + menu_icon:color(111,255,111) + colors_of_the_wind('G') + end + elseif tan >= -1 and tan <= 1 then + if not is_icon.R then + menu_icon:color(255,111,111) + colors_of_the_wind('R') + end + elseif tan >= 1 then + if not is_icon.B then + menu_icon:color(111,111,255) + colors_of_the_wind('B') + end + end + elseif _x < 0 then + if tan <= -1 then + if not is_icon.B then + menu_icon:color(111,111,255) + colors_of_the_wind('B') + end + elseif tan >= -1 and tan <= 1 then + if not is_icon.Y then + menu_icon:color(255,255,111) + colors_of_the_wind('Y') + end + elseif tan >= 1 then + if not is_icon.G then + menu_icon:color(111,255,111) + colors_of_the_wind('G') + end + end + end + elseif is_menu_open then + if (x >= display_text:pos_x() and x <= display_text:pos_x() + 150) then + local _,_y = display_text:extents() + if y <= y_offset or y >= y_offset + _y then return end + local y_17 = math.ceil((y-y_offset)/font_height_est) + if (y_17) ~= selector_pos.y then + selector_pos.y = (y_17 - 1) * font_height_est + windower.prim.set_position('selector_rectangle',selector_pos.x,selector_pos.y + y_offset) + end + else + if not is_icon[letter_to_n[last_menu_open.type]] then + menu_icon:color(unpack(n_to_color[last_menu_open.type])) + colors_of_the_wind(letter_to_n[last_menu_open.type]) + end + end + elseif not is_icon.W then + menu_icon:color(255,255,255) + colors_of_the_wind('W') + end + elseif type == 1 then + local _x,_y = x-(51+x_offset),y-(51+y_offset) + if math.abs(_x) + math.abs(_y) <= 51 then + mouse_safety = true + if is_shift_modified then + drag_and_drop = {x=_x+51,y=_y+51} + return true + else + local tan = (_y)/(_x) + if _x >= 0 then + if tan <= -1 then + mouse_func[2]() + elseif tan >= -1 and tan <= 1 then + mouse_func[1]() + elseif tan >= 1 then + mouse_func[3]() + end + elseif _x < 0 then + if tan <= -1 then + mouse_func[3]() + elseif tan >= -1 and tan <= 1 then + mouse_func[4]() + elseif tan >= 1 then + mouse_func[2]() + end + end + return true + end + elseif is_menu_open and x >= display_text:pos_x() and x <= display_text:pos_x() + 150 then + local _,_y = display_text:extents() + if y <= y_offset or y >= y_offset + _y then return end + + local y_17 = math.ceil((y-y_offset)/font_height_est + menu_start - 1) + if current_menu.sub_menus and y_17 <= current_menu.sub_menus.n then + menu_layer_record:append(current_menu.sub_menus[y_17]) + current_menu = current_menu[menu_layer_record:last()] + build_a_menu(current_menu) + mouse_safety = true + return true + else + format_response(last_menu_open.type,current_menu[y_17-(current_menu.sub_menus and current_menu.sub_menus.n or 0)],is_shift_modified) + mouse_safety = true + return true + end + end + elseif type == 2 then + if drag_and_drop then + _defaults.x_offset = x_offset + _defaults.y_offset = y_offset + _defaults:save() + drag_and_drop = false + end + if mouse_safety then + mouse_safety = false + return true + end + elseif type == 10 then + if is_menu_open and menu_list.n > 12 + and x >= display_text:pos_x() and x<= display_text:pos_x() + 150 + and y >= display_text:pos_y() and y <= display_text:pos_y() + 12 * font_height_est then + + menu_start = menu_start - delta + if menu_start < 1 then menu_start = 1 end + if menu_start + 11 > menu_list.n then menu_start = menu_list.n - 11 end + display_text:text(menu_list:concat('\n',menu_start,menu_start+11)) + windower.prim.set_position('scroll_bar',selector_pos.x + 150,y_offset + ((12 * font_height_est * (1 - 12 / menu_list.n)) / (menu_list.n - 12)) * (menu_start - 1)) + return true + end + elseif type == 4 then + local _x,_y = x-(51+x_offset),y-(51+y_offset) + if math.abs(_x) + math.abs(_y) <= 51 then + mouse_safety = true + if is_menu_open then + menu_history[last_menu_open.type] = list.copy(menu_layer_record) + end + close_a_menu() + return true + elseif is_menu_open and x >= display_text:pos_x() and x <= display_text:pos_x() + 150 then + if y <= y_offset or y >= y_offset + font_height_est * 12 then return end + if menu_layer_record.n == 0 then + close_a_menu() + else + open_previous_menu() + end + mouse_safety = true + return true + end + elseif type == 5 then + if mouse_safety then + mouse_safety = false + return true + end + end +end) + +mouse_func = { + [1] = function() + active_buffs = S(windower.ffxi.get_player().buffs) + number_of_jps = count_job_points() + menu_general_layout(windower.ffxi.get_spells(),spells_template,1) + end, + [2] = function() + menu_general_layout(S(windower.ffxi.get_abilities().weapon_skills),ws_template,2) + end, + [3] = function() + menu_general_layout(S(remove_categories(windower.ffxi.get_abilities().job_abilities)),ja_template,3) + end, + [4] = function() + menu_general_layout(S(remove_categories(windower.ffxi.get_abilities().job_abilities)),pet_command_template,4) + end, +} + +function build_a_menu(t) + menu_start = 1 + is_menu_open = true + menu_list:clear() + if t.sub_menus and (t.sub_menus.n + t.n == 1) then + menu_layer_record:append(current_menu.sub_menus[1]) + current_menu = current_menu[menu_layer_record:last()] + build_a_menu(current_menu) + else + if t.sub_menus then + for i = 1,t.sub_menus.n do + menu_list:append(' \\cs(' .. (custom_menu_colors[t.sub_menus[i]] or '239,195,255') .. ')' .. t.sub_menus[i] .. '\\cr') + end + end + for i = 1,t.n do + menu_list:append(' ' .. get_string_from_id(t[i])) + end + if menu_list.n > 12 then + windower.prim.set_size('scroll_bar',10,(font_height_est * 144 / menu_list.n)) + windower.prim.set_visibility('scroll_bar',true) + windower.prim.set_position('scroll_bar',selector_pos.x + 150,y_offset) + else + windower.prim.set_visibility('scroll_bar',false) + end + display_text:text(menu_list:concat('\n',1,12)) + windower.prim.set_visibility('menu_backdrop',true) + selector_pos.y = y_offset + windower.prim.set_position('selector_rectangle',selector_pos.x,selector_pos.y) + windower.prim.set_visibility('selector_rectangle',true) + display_text:show() + end +end + +function close_a_menu() + is_menu_open = false + menu_layer_record:clear() + windower.prim.set_visibility('scroll_bar',false) + windower.prim.set_visibility('menu_backdrop',false) + windower.prim.set_visibility('selector_rectangle',false) + display_text:hide() +end + +function open_previous_menu() + menu_layer_record:remove() + current_menu = last_menu_open + for i = 1,menu_layer_record.n do + current_menu = current_menu[menu_layer_record[i]] + end + if current_menu.sub_menus and (current_menu.sub_menus.n + current_menu.n == 1) then + if menu_layer_record.n == 0 then + close_a_menu() + else + open_previous_menu() + end + else + build_a_menu(current_menu) + end +end + +function format_response(n,p,bool) + local t + if n == 1 then + if bool then + if not (res.spells[p].targets['Self'] or res.spells[p].targets['Corpse']) then + t = ' <stnpc>' + else + t = ' <stpc>' + end + else + t = '' + end + n,p = '/ma',res.spells[p].en + elseif n == 2 then + if bool then + if not (res.weapon_skills[p].targets['Self'] or res.weapon_skills[p].targets['Corpse']) then + t = ' <stnpc>' + else + t = ' <stpc>' + end + else + t = '' + end + n,p = '/ws',res.weapon_skills[p].en + else + if bool then + if not (res.job_abilities[p].targets['Self'] or res.job_abilities[p].targets['Corpse']) then + t = ' <stnpc>' + else + t = ' <stpc>' + end + else + t = '' + end + n,p = res.job_abilities[p].prefix,res.job_abilities[p].en + end + + windower.send_command('input %s %q%s':format(n,p,t)) +end + +windower.register_event('keyboard', function(dik, down, flags, blocked) + if dik == 42 and not bit.is_set(flags, 6) then + is_shift_modified = down + end +end) + +function determine_accessibility(spell,type) -- ability filter, slightly modified. Credit: Byrth + if type == 1 then + local spell_jobs = spell.levels + if not available_category[spell.id] and not (spell.id == 503) then + return false + elseif (not spell_jobs[player_info.main_job] or not (spell_jobs[player_info.main_job] <= player_info.main_job_level or + (spell_jobs[player_info.main_job] > 99 and number_of_jps >= spell_jobs[player_info.main_job]))) and + (not spell_jobs[player_info.sub_job] or not (spell_jobs[player_info.sub_job] <= player_info.sub_job_level)) then + return false + elseif player_info.main_job == 20 and ((addendum_white[spell.id] and not active_buffs[401] and not active_buffs[416]) or + (addendum_black[spell.id] and not active_buffs[402] and not active_buffs[416])) and + not (spell_jobs[player_info.sub_job] and spell_jobs[player_info.sub_job] <= player_info.sub_job_level) then + return false + elseif player_info.sub_job == 20 and ((addendum_white[spell.id] and not active_buffs[401] and not active_buffs[416]) or + (addendum_black[spell.id] and not active_buffs[402] and not active_buffs[416])) and + not (spell_jobs[player_info.main_job] and spell_jobs[player_info.main_job] <= player_info.main_job_level) then + return false + elseif spell.type == 'BlueMagic' and not ((player_info.main_job == 16 and table.contains(windower.ffxi.get_mjob_data().spells,spell.id)) or + ((active_buffs[485] or active_buffs[505]) and unbridled_learning_set[spell.english])) and not + (player.sub_job_id == 16 and table.contains(windower.ffxi.get_sjob_data().spells,spell.id)) then + return false + end + return true + else + return available_category:contains(spell.id) + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/Rhombus/defs.lua b/Data/DefaultContent/Libraries/addons/addons/Rhombus/defs.lua new file mode 100644 index 0000000..59487d8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Rhombus/defs.lua @@ -0,0 +1,170 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Rhombus nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv 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.--]] + +defaults={ + x_offset = 0, + y_offset = 0, +} + +_defaults = config.load(defaults) + +display_text = texts.new('', { + pos = { + x = 95, + y = 0, + }, + bg = { + visible = false, + }, + flags = { + bold = true, + draggable = false, + }, + text = { + font = 'Consolas', + size = 10, + alpha = 255, + red = 255, + green = 255, + blue = 255, + }, +}) + +menu_icon = texts.new('v', { + pos = { + x = -12, + y = -22, + }, + bg = { + visible = false, + }, + flags = { + bold = true, + draggable = false, + }, + text = { + font = 'Wingdings', + size = 100, + alpha = 100, + red = 255, + blue = 255, + green = 255, + stroke = { + width = 1, + red = 0, + blue = 0, + green = 0, + alpha = 255, + }, + }, +}) + +addendum_white = {[14]="Poisona",[15]="Paralyna",[16]="Blindna",[17]="Silena",[18]="Stona",[19]="Viruna",[20]="Cursna", + [143]="Erase",[13]="Raise II",[140]="Raise III",[141]="Reraise II",[142]="Reraise III",[135]="Reraise"} + +addendum_black = {[253]="Sleep",[259]="Sleep II",[260]="Dispel",[162]="Stone IV",[163]="Stone V",[167]="Thunder IV", + [168]="Thunder V",[157]="Aero IV",[158]="Aero V",[152]="Blizzard IV",[153]="Blizzard V",[147]="Fire IV",[148]="Fire V", + [172]="Water IV",[173]="Water V",[255]="Break"} + +unbridled_learning_set = {['Thunderbolt']=true,['Harden Shell']=true,['Absolute Terror']=true, + ['Gates of Hades']=true,['Tourbillion']=true,['Pyric Bulwark']=true,['Bilgestorm']=true, + ['Bloodrake']=true,['Droning Whirlwind']=true,['Carcharian Verve']=true,['Blistering Roar']=true, + ['Uproot']=true,['Crashing Thunder']=true,['Polar Roar']=true} + +not_a_spell = S{ + 'Stratagems', 'Blood Pact: Rage', 'Sambas', 'Waltzes', 'Steps', 'Flourishes I', 'Flourishes II', 'Flourishes III', + 'Blood Pact: Ward', 'Phantom Roll', 'Rune Enchantment', 'Jigs', 'Ready' +} + +is_icon = { + W = true, + R = false, + G = false, + B = false, + Y = false, +} + +letter_to_n = { + 'R', + 'G', + 'B', + 'Y' +} + +n_to_color = { + {255,111,111}, + {111,255,111}, + {111,111,255}, + {255,255,111} +} + +category_to_resources = { + 'spells', + 'weapon_skills', + 'job_abilities', + 'job_abilities', +} + +custom_menu_colors = { + Fire = '255,133,133', + Earth = '255,255,133', + Water = '177,168,255', + Wind = '111,255,111', + Ice = '168,251,255', + Thunder = '250,156,255', + Light = '255,255,255', + Darkness = '100,100,100', +} + +refresh_ma_when = {[401]=true,[402]=true,[416]=true,[485]=true,} + +player_info = {} +menu_layer_record = L{} +menu_history = {} +menu_list = L{} +menu_start = 1 +is_menu_open = false +last_menu_open = {} +font_height_est = 16 +selector_pos = {x=0,y=0} +drag_and_drop = false +mouse_safety = false +is_shift_modified = false + +windower.prim.create('menu_backdrop') +windower.prim.set_color('menu_backdrop',200,0,0,0) +windower.prim.set_visibility('menu_backdrop',false) +windower.prim.set_size('menu_backdrop',150,12 * font_height_est) + +windower.prim.create('selector_rectangle') +windower.prim.set_color('selector_rectangle',100,255,255,255) +windower.prim.set_visibility('selector_rectangle',false) +windower.prim.set_size('selector_rectangle',150,font_height_est) + +windower.prim.create('scroll_bar') +windower.prim.set_color('scroll_bar',200,255,255,255) +windower.prim.set_visibility('scroll_bar',false) +windower.prim.set_size('scroll_bar',10,1) diff --git a/Data/DefaultContent/Libraries/addons/addons/Rhombus/helper_functions.lua b/Data/DefaultContent/Libraries/addons/addons/Rhombus/helper_functions.lua new file mode 100644 index 0000000..50e33de --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Rhombus/helper_functions.lua @@ -0,0 +1,188 @@ +--[[Copyright © 2014-2015, trv +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Rhombus nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL trv 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.--]] + +function colors_of_the_wind(s) + for k,_ in pairs(is_icon) do + is_icon[k] = false + end + is_icon[s] = true +end + +function remove_categories(t) + local u = {} + local res = res.job_abilities + for i = 1,#t do + if not not_a_spell:contains(res[t[i]].en) then + u[#u + 1] = t[i] + end + end + return u +end + +function get_string_from_id(n) + return (spell_aliases[n] or res[category_to_resources[last_menu_open.type]][n].en) +end + +function recursively_merge_tables(m_menu,s_menu) + local duplicates = flatten(m_menu) + if s_menu.sub_menus then + if m_menu.sub_menus then + local main_table_sub_menus = S(m_menu.sub_menus) + for i=1,s_menu.sub_menus.n do + if not main_table_sub_menus[s_menu.sub_menus[i]] then + m_menu.sub_menus[m_menu.sub_menus.n + 1] = s_menu.sub_menus[i] + m_menu.sub_menus.n = m_menu.sub_menus.n + 1 + m_menu[s_menu.sub_menus[i]] = s_menu[s_menu.sub_menus[i]] + else + m_menu[s_menu.s_menu[i]] = recursively_merge_tables(m_menu[s_menu.s_menu[i]],s_menu[s_menu.s_menu[i]]) + end + end + for i=1,s_menu.n do + if not duplicates:contains(s_menu[i]) then + m_menu[m_menu.n + 1] = s_menu[i] + m_menu.n = m_menu.n + 1 + end + end + else + m_menu.sub_menus = s_menu.sub_menus + for i=1,m_menu.sub_menus.n do + m_menu[m_menu.sub_menus[i]] = s_menu[s_menu.sub_menus[i]] + end + for i=1,s_menu.n do + if not duplicates:contains(s_menu[i]) then + m_menu[m_menu.n + 1] = s_menu[i] + m_menu.n = m_menu.n + 1 + end + end + end + s_menu.sub_menus = nil + else + if m_menu.sub_menus then + for i=1,s_menu.n do + if not duplicates:contains(s_menu[i]) then + m_menu[m_menu.n + 1] = s_menu[i] + m_menu.n = m_menu.n + 1 + end + end + else + for i=1,s_menu.n do + if not duplicates:contains(s_menu[i]) then + m_menu[m_menu.n + 1] = s_menu[i] + m_menu.n = m_menu.n + 1 + end + end + end + end + return m_menu +end + +function flatten(t) + local s = S{} + if t.sub_menus then + for i = 1,#t.sub_menus do + s = s + flatten(t[t.sub_menus[i]]) + end + end + for i = 1,#t do + s:add(t[i]) + end + return s +end + +function recursively_copy_spells(t) + local _t = {} + if t.sub_menus then + _t.sub_menus = L{} + for i = 1,#t.sub_menus do + _t[t.sub_menus[i]] = recursively_copy_spells(t[t.sub_menus[i]]) + if _t[t.sub_menus[i]] then + _t.sub_menus:append(t.sub_menus[i]) + end + end + end + for i = 1,#t do + if determine_accessibility(res[category_to_resources[last_menu_open.type]][t[i]],last_menu_open.type) then + _t[#_t+1] = t[i] + end + end + _t.n = #_t + if _t.sub_menus then + if not (_t.n == 0 and _t.sub_menus.n == 0) then + return _t + else + return nil + end + else + if not (_t.n == 0) then + return _t + else + return nil + end + end +end + +function count_table_elements(t) + for k,v in pairs(t) do + if type(v) == 'table' then + count_table_elements(t[k]) + end + end + + t.n = #t +end + +function count_job_points() + local n = 0 + for _,v in pairs(windower.ffxi.get_player().job_points[res.jobs[player_info.main_job].ens:lower()]) do + n = n + v*(v+1) + end + return n/2 +end + +function menu_building_snippet() + if current_menu then + current_menu.type = last_menu_open.type + last_menu_open = current_menu + for i = 1,menu_layer_record.n do + if current_menu[menu_layer_record[i]] then + current_menu = current_menu[menu_layer_record[i]] + else + for j = 1,menu_layer_record.n+1-i do + menu_layer_record:remove() + end + break + end + end + build_a_menu(current_menu) + else + close_a_menu() + current_menu = {} + end +end + +function bit.is_set(val, pos) -- Credit: Arcon + return bit.band(val, 2^(pos - 1)) > 0 +end diff --git a/Data/DefaultContent/Libraries/addons/addons/SATACast/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/SATACast/ReadMe.md new file mode 100644 index 0000000..cfd7db8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SATACast/ReadMe.md @@ -0,0 +1,26 @@ +SATACast v 1.1.0 + +SATACast is a simple helper addon for Spellcast. I created this because before lua, I had to make really strange XML's for both Spellcast and Autoexec to let spellcast know when sneak attack and trick attack were no longer in effect (landed or wore). This addon is merely a middleman that tells spellcast when it wears and switches to your appropriate gear sets. Another function of this addon is to inform spellcast when Treasure Hunter has been initially placed on the mob. + +To install, have the SATACast folder containing this readme, the SATACast.lua, and a "data" folder in your "addons" folder for windower v4. + +To use, simply type "//lua l satacast" in game. This can be added to init.txt or loaded automatically via the windower v4 launcher. All updates will be available via the launcher. + +Upon initial load, a settings file will be created with default values. YOU WILL WANT TO EDIT THIS SETTINGS FILE TO SUPPORT YOUR SPELLCAST XML. + +The settings that are loaded are: + +SA Set: <name_of_Sneak_Attack_Set_in_your_spellcast_XML> +TA Set: <name_of_Trick_Attack_Set_in_your_spellcast_XML> +SATA Set: <name_of_SATA_Set_in_your_spellcast_XML> +TP Set: <name_of_TP_Set_in_your_spellcast_XML> +TH Set: <name_of_Treasure_Hunter_Set_in_your_spellcast_XML> +Idle Set: <name_of_Idle_Set_in_your_spellcast_XML> + +Simply type the names of the respective sets after the colon (:) exactly how they appear in your spellcast XML. If you use spaces in the spellcast XML, they will need to be removed or this addon will not function correctly. + +Commands: + +//scast [options] + reload - Reloads Settings + help - Displays Version information and commands diff --git a/Data/DefaultContent/Libraries/addons/addons/SATACast/SATACast.lua b/Data/DefaultContent/Libraries/addons/addons/SATACast/SATACast.lua new file mode 100644 index 0000000..f1ea5f6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SATACast/SATACast.lua @@ -0,0 +1,169 @@ +--Copyright (c) 2013, Banggugyangu +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +_addon.version = '1.1.0' +_addon.command = 'scast' +_addon.name = 'SATACast' +_addon.author = 'Banggugyangu' + +res = require('resources') + +windower.register_event('load',function () + + version = '1.1.0' + SA_Set = ' ' + TA_Set = ' ' + SATA_Set = ' ' + TP_Set = ' ' + TH_Set = ' ' + Idle_Set = ' ' + TH_ON = 0 + TP_ON = 0 + windower.add_to_chat(17, 'SATACast v' .. version .. ' loaded. Author: Banggugyangu') + windower.add_to_chat(17, 'Attempting to load settings from file.') + options_load() + +end) + +--Function Designer: Byrth +function options_load() + local f = io.open(windower.addon_path..'data/settings.txt', "r") + if f == nil then + local g = io.open(windower.addon_path..'data/settings.txt', "w") + g:write('Release Date: 11:50 PM, 4-06-13\46\n') + g:write('Author Comment: This document is whitespace sensitive, which means that you need the same number of spaces between things as exist in this initial settings file\46\n') + g:write('Author Comment: It looks at the first two words separated by spaces and then takes anything as the value in question if the first two words are relevant\46\n') + g:write('Author Comment: If you ever mess it up so that it does not work, you can just delete it and SATACast will regenerate it upon reload\46\n') + g:write('Author Comment: For the output customization lines, simply place the name of the spellcast set for each setting exactly how it is spelled in spellcast.\n') + g:write('Author Comment: The design of the settings file is credited to Byrthnoth as well as the creation of the settings file.\n\n\n\n') + g:write('Fill In Settings Below:\n') + g:write('SA Set: SneakAttack\nTA Set: TrickAttack\nSATA Set: SATA\nTP Set: TP\nTH Set: TreasureHunter\nIdle Set: Movement\n') + g:close() + + print('Default settings file created') + windower.add_to_chat(17,'SATACast created a settings file and loaded!') + else + f:close() + for curline in io.lines(windower.addon_path..'data/settings.txt') do + local splat = split(curline,' ') + local cmd = '' + if splat[2] ~=nil then + cmd = (splat[1]..' '..splat[2]):gsub(':',''):lower() + end + if cmd == 'sa set' then + SA_Set = splat[3] + elseif cmd == 'ta set' then + TA_Set = splat[3] + elseif cmd == 'sata set' then + SATA_Set = splat[3] + elseif cmd == 'tp set' then + TP_Set = splat[3] + elseif cmd == 'th set' then + TH_Set = splat[3] + elseif cmd == 'idle set' then + Idle_Set = splat[3] + end + end + windower.add_to_chat(17,'SATACast read from a settings file and loaded!') + end +end + +--Function Author: Byrth +function split(msg, match) + local length = msg:len() + local splitarr = {} + local u = 1 + while u <= length do + local nextanch = msg:find(match,u) + if nextanch ~= nil then + splitarr[#splitarr+1] = msg:sub(u,nextanch-match:len()) + if nextanch~=length then + u = nextanch+match:len() + else + u = lengthlua + end + else + splitarr[#splitarr+1] = msg:sub(u,length) + u = length+1 + end + end + return splitarr +end + +windower.register_event('lose buff', function(id) + local status = windower.ffxi.get_player().status + local name = res.buffs[id].english + if name == ('Sneak Attack' or 'Trick Attack') then + if status == 1 then + windower.send_command('sc set ' .. TP_Set) + elseif status == 0 then + windower.send_command('sc set ' .. Idle_Set) + end + end +end) + +windower.register_event('action', function(act) + local actor = act.actor_id + local category = act.category + local actor = act.actor_id + local category = act.category + local param = act.param + local player = windower.ffxi.get_player() + + if player.status == 1 then + if actor == (player.id or player.index) then + if category == 1 then + if TH_ON == 0 then + windower.send_command('sc set ' .. TH_Set) + TH_ON = 1 + elseif TH_ON == 1 then + if TP_ON == 0 then + windower.send_command('sc set ' .. TP_Set) + TP_ON = 1 + elseif TP_ON == 1 then + end + end + end + end + elseif player.status == 0 then + TH_ON = 0 + end +end) + +--Function Designer: Byrth +windower.register_event('addon command',function (...) + local term = table.concat({...}, ' ') + local splitarr = split(term,' ') + if splitarr[1]:lower() == 'reload' then + options_load() + elseif splitarr[1]:lower() == 'help' then + windower.add_to_chat(17, 'SATACast v'..version..'commands:') + windower.add_to_chat(17, '//scast [options]') + windower.add_to_chat(17, ' reload - Reloads settings') + windower.add_to_chat(17, ' help - Displays this help text') + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/SATACast/data/settings.txt b/Data/DefaultContent/Libraries/addons/addons/SATACast/data/settings.txt new file mode 100644 index 0000000..46245f8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SATACast/data/settings.txt @@ -0,0 +1,16 @@ +Release Date: 9:00 PM, 4-01-13. +Author Comment: This document is whitespace sensitive, which means that you need the same number of spaces between things as exist in this initial settings file. +Author Comment: It looks at the first two words separated by spaces and then takes anything as the value in question if the first two words are relevant. +Author Comment: If you ever mess it up so that it does not work, you can just delete it and SATACast will regenerate it upon reload. +Author Comment: For the output customization lines, simply place the name of the spellcast set for each setting exactly how it is spelled in spellcast. +Author Comment: The design of the settings file is credited to Byrthnoth as well as the creation of the settings file. + + + +Fill In Settings Below: +SA Set: SneakAttack +TA Set: TrickAttack +SATA Set: SATA +TP Set: TP +Idle Set: Movement + diff --git a/Data/DefaultContent/Libraries/addons/addons/SetTarget/SetTarget.lua b/Data/DefaultContent/Libraries/addons/addons/SetTarget/SetTarget.lua new file mode 100644 index 0000000..7d32772 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SetTarget/SetTarget.lua @@ -0,0 +1,26 @@ +local packets = require('packets') + +_addon.name = 'SetTarget' +_addon.author = 'Arcon' +_addon.commands = {'settarget', 'st'} +_addon.version = '1.0.0.0' + +windower.register_event('addon command', function(id) + id = tonumber(id) + if id == nil then + return + end + + local target = windower.ffxi.get_mob_by_id(id) + if not target then + return + end + + local player = windower.ffxi.get_player() + + packets.inject(packets.new('incoming', 0x058, { + ['Player'] = player.id, + ['Target'] = target.id, + ['Player Index'] = player.index, + })) +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Silence/readme.md b/Data/DefaultContent/Libraries/addons/addons/Silence/readme.md new file mode 100644 index 0000000..8578b0d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Silence/readme.md @@ -0,0 +1,3 @@ +Use the line "silence mode 0" to not allow any lines to go through. +Use the line "silence mode 1" to allow 1 line through indicting that your gear has been changed. + diff --git a/Data/DefaultContent/Libraries/addons/addons/Silence/silence.lua b/Data/DefaultContent/Libraries/addons/addons/Silence/silence.lua new file mode 100644 index 0000000..ae8a97b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Silence/silence.lua @@ -0,0 +1,79 @@ +_addon.name = 'Silence' +_addon.author = 'Ihina' +_addon.version = '1.0.2.1' + +config = require('config') +defaults = {} +defaults.ShowOne = false +settings = config.load(defaults) + +last = {} +last['Equipment changed.'] = 0 +last['You cannot use that command at this time.'] = 0 +last['You cannot use that command while viewing the chat log.'] = 0 +last['You must close the currently open window to use that command.'] = 0 +last['Equipment removed.'] = 0 +last['You were unable to change your equipped items.'] = 0 +last['You cannot use that command while unconscious.'] = 0 +last['You cannot use that command while charmed.'] = 0 +last['You can only use that command during battle.'] = 0 +last['You cannot perform that action on the selected sub-target.'] = 0 + +windower.register_event('incoming text', function(str) + if last[str] then + if not settings.ShowOne then + return true + else + if os.clock() - last[str] < .75 then + return true + else + last[str] = os.clock() + end + end + end +end) + +windower.register_event('unhandled command', function(...) + local param = L{...} + if param[1] == 'silence' then + if param[2] == 'showone' then + if param[3] == 'true' then + settings.ShowOne = true + print('-showone set to true-') + elseif param[3] == 'false' then + settings.ShowOne = false + print('-showone set to false-') + end + settings:save() + end + end +end) + +--[[ +Copyright (c) 2013, Ihina +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Silence nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL IHINA 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. +]] +--Original plugin by Taj diff --git a/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/ReadMe.md new file mode 100644 index 0000000..83e530c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/ReadMe.md @@ -0,0 +1,3 @@ +# SpeedChecker + +Displays a small box indicating your current movement speed modifier (+/- X%). diff --git a/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/SpeedChecker.lua b/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/SpeedChecker.lua new file mode 100644 index 0000000..3d637fe --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpeedChecker/SpeedChecker.lua @@ -0,0 +1,36 @@ +_addon.name = 'SpeedChecker' +_addon.version = '1.0.0.0' + +config = require('config') +texts = require('texts') + +settings = config.load({}) +speed = texts.new(settings) +speed:show() + +get_speed = function() + return +end + +windower.register_event('prerender', function() + local me = windower.ffxi.get_mob_by_target('me') + if me then + speed:text('%+.0f %%':format(100*(me.movement_speed / 5 - 1))) + speed:show() + else + speed:hide() + end +end) + +--[[ +Copyright © 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/SpellBook/SpellBook.lua b/Data/DefaultContent/Libraries/addons/addons/SpellBook/SpellBook.lua new file mode 100644 index 0000000..311f287 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpellBook/SpellBook.lua @@ -0,0 +1,447 @@ +_addon.name = 'SpellBook' +_addon.author = 'SigilBaram' +_addon.version = '1.0.1' +_addon.commands = {'spellbook','spbk'} + +require('tables') +res = require('resources') + +spell_types = { + whitemagic = { type = 'WhiteMagic', readable = 'White Magic spells' }, + blackmagic = { type = 'BlackMagic', readable = 'Black Magic spells' }, + songs = { type = 'BardSong', readable = 'Bard songs' }, + ninjutsu = { type = 'Ninjutsu', readable = 'Ninjutsu' }, + summoning = { type = 'SummonerPact', readable = 'Summoning spells' }, + bluemagic = { type = 'BlueMagic', readable = 'Blue Magic spells' }, + geomancy = { type = 'Geomancy', readable = 'Geomancy spells' }, + trusts = { type = 'Trust', readable = 'Trusts'}, + all = { type = 'all', readable = 'spells of all types'} +} + +windower.register_event('addon command', function (command, ...) + local args = L{...} + local jobs = build_job_list() + + command = command and command:lower() or 'current' + + if command == 'help' then + display_help() + elseif command == 'current' then + local player = windower.ffxi.get_player() + spells_by_current(player) + elseif command == 'main' then + local player = windower.ffxi.get_player() + local level = player.main_job_level + local job_points = player.job_points[player.main_job:lower()].jp_spent + if job_points > 99 then + level = job_points + end + level = args[1] or level + if level == 'all' then + level = 1500 + end + level = tonumber(level) + if level then + spells_by_job(player.main_job_id, level) + else + invalid_input() + end + elseif command == 'sub' then + local player = windower.ffxi.get_player() + if not player.sub_job then + windower.add_to_chat(7, "You don't have a subjob equipped.") + return + end + local level = args[1] or player.sub_job_level + if level == 'all' then + level = 1500 + end + level = tonumber(level) + if level then + spells_by_job(player.sub_job_id, level) + else + invalid_input() + end + elseif spell_types[command] then + if args[1] == 'all' then + local player = windower.ffxi.get_player() + spells_by_type(player, spell_types[command], false) + elseif args[1] == nil then + local player = windower.ffxi.get_player() + spells_by_type(player, spell_types[command], true) + else + invalid_input() + end + elseif jobs[command] then + local job = jobs[command] + local player = windower.ffxi.get_player() + local level = args[1] or player.jobs[res.jobs[job].english_short] + if level == 'all' then + level = 1500 + end + level = tonumber(level) + if level then + spells_by_job(job, level) + else + invalid_input() + end + else + invalid_input() + end +end) + +-------------------------------------------------------------------------------- +--Name: build_job_list +-------------------------------------------------------------------------------- +--Returns: +---- (table) list of jobs with short name as the key and id as the value +-------------------------------------------------------------------------------- +function build_job_list() + local jobs = {} + for id,val in pairs(res.jobs) do + jobs[val.english_short:lower()] = id + end + return jobs +end + +-------------------------------------------------------------------------------- +--Name: invalid_input +---- Display an error message for invalid input. +-------------------------------------------------------------------------------- +function invalid_input() + windower.add_to_chat(7, 'Invalid input. See //spbk help.') +end + +-------------------------------------------------------------------------------- +--Name: display_help +-- Display help text for the addon. +-------------------------------------------------------------------------------- +function display_help() + windower.add_to_chat(7, _addon.name .. ' version ' .. _addon.version) + windower.add_to_chat(7, 'Spent jp can be specified for spells learned from Gifts by entering a value of 100-1500.') + windower.add_to_chat(7, 'Spells are never given as Gifts for less than 100 jp and values under 100 are treated as level.') + windower.add_to_chat(7, '//spbk help -- Show this help text.') + windower.add_to_chat(7, '//spbk [current] -- Show learnable spells based on current main and sub job and level/jp.') + windower.add_to_chat(7, '//spbk <main|sub> [<level|spent jp|all>] -- Show missing spells for current main or sub job. Defaults to the job\'s current level/jp.') + windower.add_to_chat(7, '//spbk <job> [<level|spent jp|all>] -- Show missings spells for specified job and level. Defaults to the job\'s level/jp.') + windower.add_to_chat(7, '//spbk <category> [all] -- Show learnable spells by category. Limited to spells which are learnable, unless all is added after the category.') + windower.add_to_chat(7, 'Categories: whitemagic, blackmagic, songs, ninjustu, summoning, bluemagic, geomancy, trusts, all (Trusts are not included in all)') +end + +-------------------------------------------------------------------------------- +--Name: is_learnable +--Args: +---- player (table): player object from windower.ffxi.get_player() +---- spell (table): a spell from resources.spells +-------------------------------------------------------------------------------- +--Returns: +---- (bool) true if player has a job that is high enough level to learn spell +-------------------------------------------------------------------------------- +function is_learnable(player, spell) + local player_levels = player.jobs + for job,level in pairs(spell.levels) do + if player_levels[res.jobs[job].english_short] >= level then + return true + end + end + return false +end + +-------------------------------------------------------------------------------- +--Name: format_spell +-- Formats a spell as the spell's name followed by a list of jobs and levels +-- which would qualify to learn that spell. +--Args: +---- spell (table): a spell from resources.spells +-------------------------------------------------------------------------------- +--Returns: +---- (string) the formatted string +-------------------------------------------------------------------------------- +function format_spell(spell) + local format + + if spell.type ~= 'Trust' then + local jobs = T{} + local levels = T{} + for job_id,_ in pairs(spell.levels) do + jobs:append(job_id) + end + jobs:sort() + for _,job_id in ipairs(jobs) do + if spell.levels[job_id] <= 99 then + levels:append(res.jobs[job_id].english_short .. ' Lv.' .. + tostring(spell.levels[job_id])) + else + levels:append(res.jobs[job_id].english_short .. ' Jp.' .. + tostring(spell.levels[job_id])) + end + end + format = levels:concat(' / ') + else + format = ' ( Trust )' + end + return string.format('%-20s %s', spell.english, format) +end + +-------------------------------------------------------------------------------- +--Name: spells_by_type +-- List spells of a given type, i.e. white magic. +--Args: +---- player (T): player object from windower.ffxi.get_player() +---- spell_type (table): one of the types from spell_types global +---- learnable_only (bool): if true then the output is limited to spell for +---- which the player has a job that is high enough level +-------------------------------------------------------------------------------- +function spells_by_type(player, spell_type, learnable_only) + local missing_spells = T{} + local player_spells = windower.ffxi.get_spells() + local spell_count = 0 + + for spell_id,spell in pairs(res.spells) do + if ((spell_type.type == 'all' and spell.type ~= 'Trust') or + spell.type == spell_type.type) and + not table.empty(spell.levels) and + not player_spells[spell_id] and + (is_learnable(player, spell) or not learnable_only) and + not (spell_type.type == 'Trust' and spell.name:match('.*%(UC%)')) and + not spell.unlearnable then + + missing_spells:append(format_spell(spell)) + spell_count = spell_count + 1 + end + end + + if learnable_only then + windower.add_to_chat(7, string.format('Showing learnable %s.', + spell_type.readable)) + else + windower.add_to_chat(7, string.format('Showing all missing %s.', + spell_type.readable)) + end + + if not missing_spells:empty() then + missing_spells:sort() + for _,spell in ipairs(missing_spells) do + windower.add_to_chat(7, spell) + end + if learnable_only then + windower.add_to_chat(7,string.format( + 'List Complete. You are missing %d learnable %s.', + spell_count, spell_type.readable)) + else + windower.add_to_chat(7,string.format( + 'List Complete. You are missing %d %s.', + spell_count, spell_type.readable)) + end + else + if learnable_only then + windower.add_to_chat(7,string.format( + 'Congratulations! You know all currently learnable %s.', + spell_type.readable)) + else + windower.add_to_chat(7,string.format( + 'Congratulations! You know all %s.', + spell_type.readable)) + end + end +end + +-------------------------------------------------------------------------------- +--Name: spells_by_job +-- List unknown spells by job. +--Args: +---- job (int): the job's id +---- level_cap (int): the max level/jp required by listed spells +-------------------------------------------------------------------------------- +function spells_by_job(job, level_cap) + local missing_spells = T{} + local player_spells = windower.ffxi.get_spells() + local spell_count = 0 + + for spell_id,spell in pairs(res.spells) do + local spell_level = spell.levels[job] + if spell_level and spell_level <= level_cap and + spell.type ~= 'Trust' and not player_spells[spell_id] and + not spell.unlearnable then + + missing_spells[spell_level] = missing_spells[spell_level] or T{} + missing_spells[spell_level]:append(spell.english) + spell_count = spell_count + 1 + end + end + + if not missing_spells:empty() then + if level_cap > 99 then + windower.add_to_chat(7, string.format( + 'Showing missing spells for %s up to %d spent job points.', + res.jobs[job].en, level_cap)) + else + windower.add_to_chat(7, string.format( + 'Showing missing spells for %s up to level %d.', + res.jobs[job].en, level_cap)) + end + + for level=1,level_cap do + if missing_spells[level] then + missing_spells[level]:sort() + if level > 99 then + windower.add_to_chat(7, + level .. 'jp: ' .. missing_spells[level]:concat(', ')) + else + windower.add_to_chat(7, + level .. ': ' .. missing_spells[level]:concat(', ')) + end + end + end + if level_cap > 99 then + windower.add_to_chat(7, string.format( + 'List Complete. You are missing %d %s spells up to %d spent job points.', + spell_count, res.jobs[job].en, level_cap)) + else + windower.add_to_chat(7, string.format( + 'List Complete. You are missing %d %s spells up to level %d.', + spell_count, res.jobs[job].en, level_cap)) + end + else + if level_cap >= 1500 then + windower.add_to_chat(7,string.format( + 'Congratulations! You know all spells for %s.', + res.jobs[job].en)) + elseif level_cap > 99 then + windower.add_to_chat(7,string.format( + 'Congratulations! You know all spells for %s up to %d spent job points!', + res.jobs[job].en, level_cap)) + else + windower.add_to_chat(7,string.format( + 'Congratulations! You know all spells for %s up to level %d!', + res.jobs[job].en, level_cap)) + end + end +end + +-------------------------------------------------------------------------------- +--Name: spells_by_current +-- Show missing spells for the current main and sub jobs. +--Args: +---- player (T): player object from windower.ffxi.get_player() +-------------------------------------------------------------------------------- +function spells_by_current(player) + local missing_spells = T{} + local player_spells = windower.ffxi.get_spells() + local spell_count = 0 + + + local main_job_level = player.main_job_level + local main_job_jp = player.job_points[player.main_job:lower()].jp_spent + + local main_level + local sub_level + + -- If the player has over 99 spend jp, then switch to treating JP as level. + if main_job_jp > 99 then + main_job_level = main_job_jp + end + + for spell_id,spell in pairs(res.spells) do + local main_level = spell.levels[player.main_job_id] + local sub_level = spell.levels[player.sub_job_id] + local spell_level = nil + + + if main_level and main_level > main_job_level then + main_level = nil + end + if sub_level and sub_level > player.sub_job_level then + sub_level = nil + end + + if main_level and sub_level then + spell_level = math.min(main_level, sub_level) + else + spell_level = spell_level or main_level or sub_level + end + + if spell_level and spell.type ~= 'Trust' and + not player_spells[spell_id] and not spell.unlearnable then + + missing_spells:append(format_spell(spell)) + spell_count = spell_count + 1 + end + end + + if not missing_spells:empty() then + if main_job_jp > 0 then + if player.sub_job then + windower.add_to_chat(7, string.format('Showing learnable spells for %s%d with %d spent job points and %s%d.', + player.main_job, player.main_job_level, + player.job_points[player.main_job:lower()].jp_spent, + player.sub_job, player.sub_job_level)) + else + windower.add_to_chat(7, string.format('Showing learnable spells for %s%d with %d spent job points.', + player.main_job, player.main_job_level, + player.job_points[player.main_job:lower()].jp_spent)) + end + else + if player.sub_job then + windower.add_to_chat(7, string.format('Showing learnable spells for %s%d/%s%d.', + player.main_job, player.main_job_level, + player.sub_job, player.sub_job_level)) + else + windower.add_to_chat(7, string.format('Showing learnable spells for %s%d.', + player.main_job, player.main_job_level)) + end + end + + missing_spells:sort() + for _,spell in ipairs(missing_spells) do + windower.add_to_chat(7, spell) + end + if player.sub_job then + windower.add_to_chat(7, string.format('List Complete. You are missing %d learnable spells for %s%d/%s%d.', + spell_count, + player.main_job, player.main_job_level, + player.sub_job, player.sub_job_level)) + else + windower.add_to_chat(7, string.format('List Complete. You are missing %d learnable spells for %s%d.', + spell_count, + player.main_job, player.main_job_level)) + end + else + if player.sub_job then + windower.add_to_chat(7, string.format('Congratulations! You know all learnable spells for %s%d/%s%d!', + player.main_job, player.main_job_level, + player.sub_job, player.sub_job_level)) + else + windower.add_to_chat(7, string.format('Congratulations! You know all learnable spells for %s%d!', + player.main_job, player.main_job_level)) + end + end +end + +--[[ +Copyright © 2018, John S Hobart +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of SpellBook nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL John S Hobart 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. +--]] diff --git a/Data/DefaultContent/Libraries/addons/addons/SpellBook/readme.md b/Data/DefaultContent/Libraries/addons/addons/SpellBook/readme.md new file mode 100644 index 0000000..aef96a2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpellBook/readme.md @@ -0,0 +1,37 @@ +# SpellBook + +This addon helps you find missing spells. You can search by job and level, +or by category. + +## Usage +Spent jp can be specified for spells learned from Gifts by entering a value of 100-1500. +Spells are never given as Gifts for less than 100 jp and values under 100 are treated as level. + + +``` +//spbk help +``` +Show help text. +``` +//spbk [current] +``` +Show learnable spells based on current main and sub job and level/jp. +``` +//spbk <main|sub> [<level|spent jp|all>] +``` +Show missing spells for current main or sub job. Defaults to the job\'s current level/jp. +``` +//spbk <job> [<level|spent jp|all>] +``` +Show missings spells for specified job and level. Defaults to the job\'s level/jp. +``` +//spbk <category> [all] +``` +Show learnable spells by category. Limited to spells which are learnable, unless all is added after the category. + +Categories: whitemagic, blackmagic, songs, ninjustu, summoning, bluemagic, geomancy, trusts, all (Trusts are not included in all) + + +## Credits + +Inspired by the SpellCheck addon by Zubis diff --git a/Data/DefaultContent/Libraries/addons/addons/SpellCheck/README.md b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/README.md new file mode 100644 index 0000000..15b7180 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/README.md @@ -0,0 +1,24 @@ +#SpellCheck + +This addon lists spells you haven't unlocked yet. + +##Usage + +//spellcheck whm | blm | smn | nin | brd | blu | geo | tru + +##Credits +shinpad, trv, Arcon et al. at +http://forums.windower.net/index.php?/topic/938-blu-spell-checklist-script/ +and +http://forums.windower.net/index.php?/topic/973-trust-checklist-script/ + +##Release Notes + +###1.0.2 - 2015-12-15 +* Added Sleepga I/II spell exception as the spells were listed in spells.lua twice. + +###1.0.1 - 2015-11-21 +* Added spell exceptions. + +###1.0.0 - 2015-11-15 +* Initial release. diff --git a/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellCheck.lua b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellCheck.lua new file mode 100644 index 0000000..0d77066 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellCheck.lua @@ -0,0 +1,117 @@ +--Copyright © 2015, Damien Dennehy +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of SpellCheck nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL DAMIEN DENNEHY BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'SpellCheck' +_addon.author = 'Zubis' +_addon.version = '1.0.2' +_addon.command = 'SpellCheck' + +require('sets') +require('tables') +res = require('resources') + +require('SpellExceptions') + +--Declare valid spell types +spell_type = {whm='WhiteMagic',blm='BlackMagic',smn='SummonerPact',nin='Ninjutsu',brd='BardSong',blu='BlueMagic',geo='Geomancy',tru='Trust'} + +--Declare friendly name of spell types for chat output +display_spell_type = {whm='White Magic',blm='Black Magic',smn='Summoner',nin='Ninjutsu',brd='Bard',blu='Blue Magic',geo='Geomancy',tru='Trust'} + +--Register the base //SpellCheck command +windower.register_event('addon command',function (command, ...) + command = command and command:lower() or 'help' + if command == 'help' or command == 'h' or command == '?' then + display_help() + elseif spell_type[command] == nil then + display_error(command) + else + display_spell_count(command) + end +end) + +--Display a basic help section +function display_help() + windower.add_to_chat(7, _addon.name .. ' v.' .. _addon.version) + windower.add_to_chat(7, 'Usage: //spellcheck whm | blm | smn | nin | brd | blu | geo | tru') + windower.add_to_chat(7, 'Sample: //spellcheck whm') +end + +--Display error based on invalid selection +function display_error(command) + windower.add_to_chat(7, _addon.name .. ' v.' .. _addon.version) + windower.add_to_chat(7, 'Error: ' .. command .. ' is not a valid option.') + windower.add_to_chat(7, 'Usage: //spellcheck whm | blm | smn | nin | brd | blu | geo | tru') +end + +--Get spells +function display_spell_count(command) + + missing_spells_len = 0 + missing_spell_names = {} + + --Get all, current and missing spells + all_spells = res.spells:type(spell_type[command]):keyset() + current_spells = T(windower.ffxi.get_spells()):filter(boolean._true):keyset() + + missing_spells = all_spells - current_spells + current_spells = all_spells * current_spells + + --Add missing spells to table for sorting + for spell in missing_spells:it() do + --Trust and spells must be processed separately + if command == 'tru' then + --Only include non Unity trusts + if not res.spells[spell].name:endswith('(UC)') then + missing_spells_len = missing_spells_len + 1 + table.insert(missing_spell_names, res.spells[spell].name) + end + else + --Add to missing spell list only if it's a valid spell + --And it's not in the spell exception list + if not table.empty(res.spells[spell].levels) and spell_exceptions[res.spells[spell].id] == nil then + missing_spells_len = missing_spells_len + 1 + table.insert(missing_spell_names, res.spells[spell].name) + end + end + end + + --Sort missing spells by name + table.sort(missing_spell_names) + + --If there are missing spells, display that they are about to be listed + if missing_spells_len > 0 then + windower.add_to_chat(7, 'SpellCheck: Listing missing ' .. display_spell_type[command] .. ' spells...') + end + + --List all missing spell names + for i, spell in ipairs(missing_spell_names) do + windower.add_to_chat(7, ' - Missing \'' .. spell .. '\'') + end + + --Display summary + windower.add_to_chat(7, 'SpellCheck: You are missing ' .. missing_spells_len .. ' ' .. display_spell_type[command] .. ' spells.') +end diff --git a/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellExceptions.lua b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellExceptions.lua new file mode 100644 index 0000000..7db8f26 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SpellCheck/SpellExceptions.lua @@ -0,0 +1,25 @@ +spell_exceptions = { + [31] = {id=31,en="Banish IV",ja="バニシュIV"}, + [40] = {id=40,en="Banishga III",ja="バニシュガIII"}, + [34] = {id=34,en="Diaga II",ja="ディアガII"}, + [35] = {id=35,en="Diaga III",ja="ディアガIII"}, + [358] = {id=358,en="Hastega",ja="ヘイスガ"}, + [356] = {id=356,en="Paralyga",ja="パライガ"}, + [359] = {id=359,en="Silencega",ja="サイレガ"}, + [357] = {id=357,en="Slowga",ja="スロウガ"}, + [361] = {id=361,en="Blindga",ja="ブライガ"}, + [362] = {id=362,en="Bindga",ja="バインガ"}, + [257] = {id=257,en="Curse",ja="カーズ"}, + [360] = {id=360,en="Dispelga",ja="ディスペガ"}, + [226] = {id=226,en="Poisonga II",ja="ポイゾガII"}, + [256] = {id=256,en="Virus",ja="ウィルス"}, + [244] = {id=244,en="Meteor II",ja="メテオII"}, + [351] = {id=351,en="Dokumori: Ni",ja="毒盛の術:弐"}, + [342] = {id=342,en="Jubaku: Ni",ja="呪縛の術:弐"}, + [349] = {id=349,en="Kurayami: San",ja="暗闇の術:参"}, + [355] = {id=355,en="Tonko: San",ja="遁甲の術:参"}, + [416] = {id=416,en="Cactuar Fugue",ja="サボテンダーフーガ"}, + [407] = {id=407,en="Chocobo Hum",ja="チョコボのハミング"}, + [363] = {id=363,en="Sleepga",ja="スリプガ"}, + [364] = {id=364,en="Sleepga II",ja="スリプガII"} +} diff --git a/Data/DefaultContent/Libraries/addons/addons/StratHelper/Readme.md b/Data/DefaultContent/Libraries/addons/addons/StratHelper/Readme.md new file mode 100644 index 0000000..4197b63 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/StratHelper/Readme.md @@ -0,0 +1,11 @@ +StratHelper +===== + +StratHelper is a simple helper addon for Spellcast for Scholar Stratagems. It will automatically calculate the number of stratagems you have and push them into spellcast variables to allow you to use them in your xmls. This should be accurate to within a second or so. + +If at any time your strat value is incorrect, then wait until all your stratagems have reset then type `//resetstrats`. This will reset everything back to neutral again. + +The variables to use in spellcast are: +`_SCH_Strats_Current` for the current number of strats available +`_SCH_Strats_Max` for the maximum number of strats available to you + diff --git a/Data/DefaultContent/Libraries/addons/addons/StratHelper/StratHelper.lua b/Data/DefaultContent/Libraries/addons/addons/StratHelper/StratHelper.lua new file mode 100644 index 0000000..a06dd0d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/StratHelper/StratHelper.lua @@ -0,0 +1,115 @@ +-- Copyright (c) 2013, Andy 'Ihm' Taylor +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +require('tables') +require('sets') + +_addon.name = 'StratHelper' +_addon.author = 'Ihm' +_addon.version = '0.2.0.0' + +function reinit() + clock_current = 0 + strat_max = 0 + windower.send_command('@wait 1; lua i StratHelper strat_max_calc') +end + +strat_max = 0 +strat_cur = 0 +strat_ids = S{215,216,217,218,219,220,221,222,234,235,240,241,242,243,316,317} +scvar_strats_current = '_SCH_Strats_Current' +scvar_strats_max = '_SCH_Strats_Max' +clock_current = 0 +loop_active = false +windower.send_command('alias resetstrats lua i StratHelper reinit') + +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in then + reinit() + end +end) + +windower.register_event('unload', windower.send_command:prepare('unalias resetstrats')) + +windower.register_event('action', function(act) + if act.actor_id == windower.ffxi.get_player().id then + if act.category == 6 then + if act.param == 210 then + clock_current = os.clock() + strat_cur = strat_max + windower.send_command('sc var set ' .. scvar_strats_current .. ' ' .. strat_cur) + elseif strat_ids:contains(act.param) then + strat_max_calc() + if T(windower.ffxi.get_player().buffs):contains(377) == false then + strat_cur = strat_cur - 1 + windower.send_command('sc var set ' .. scvar_strats_current .. ' ' .. strat_cur) + end + if loop_active == false then + loop_active = true + clock_current = os.clock() + windower.send_command('@wait 0.5; lua i StratHelper strat_loop') + end + end + end + end +end) + +windower.register_event('job change', reinit) + +windower.register_event('login', reinit) + +function strat_max_calc() + local set_cur = false + if strat_max == 0 then + set_cur = true + end + if windower.ffxi.get_player().main_job == 'SCH' then + strat_max = math.floor(((windower.ffxi.get_player().main_job_level - 10) / 20) + 1) + elseif windower.ffxi.get_player().sub_job == 'SCH' then + strat_max = math.floor(((windower.ffxi.get_player().sub_job_level - 10) / 20) + 1) + end + if set_cur then + strat_cur = strat_max + windower.send_command('sc var set ' .. scvar_strats_current .. ' ' .. strat_cur) + end + windower.send_command('sc var set ' .. scvar_strats_max .. ' ' .. strat_max) +end + +function strat_loop() + if (240 / strat_max) - (os.clock() - clock_current) < 0 then + clock_current = os.clock() + strat_cur = strat_cur + 1 + windower.send_command('sc var set ' .. scvar_strats_current .. ' ' .. strat_cur) + end + if strat_cur < strat_max then + windower.send_command('@wait 0.5; lua i StratHelper strat_loop') + else + loop_active = false + end + if strat_cur > strat_max then + strat_cur = strat_max + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/Stubborn/Stubborn.lua b/Data/DefaultContent/Libraries/addons/addons/Stubborn/Stubborn.lua new file mode 100644 index 0000000..69865fa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Stubborn/Stubborn.lua @@ -0,0 +1,54 @@ +--[[Copyright © 2021, Arico +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Stubborn nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Arico BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --]] + +_addon.name = 'Stubborn' +_addon.author = 'Arico' +_addon.version = '1' +_addon.commands = {'stubborn','cfh'} + +require('logger') +packets = require('packets') + +windower.register_event('outgoing chunk', function(id, original, modified, injected, blocked) + if id == 0x01A and not injected then + local p = packets.parse('outgoing', original) + if p['Category'] == 5 then + return true + end + end +end) + +windower.register_event('addon command', function(...) + local target = windower.ffxi.get_mob_by_target('t') + if target and target.claim_id ~= 0 then + local p = packets.new('outgoing', 0x01A, { + ['Target'] = target['id'], + ['Target Index'] = target['index'], + ['Category'] = 5, + }) + packets.inject(p) + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Stubborn/readme.md b/Data/DefaultContent/Libraries/addons/addons/Stubborn/readme.md new file mode 100644 index 0000000..64d3751 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Stubborn/readme.md @@ -0,0 +1,6 @@ +# Stubborn +A Windower 4 addon to prevent unnecessary "Call for help!" + +Stubborn is a light weight replacement to the old CFHProtect addon. + +If you need to call for help. Type //stubborn or //cfh. diff --git a/Data/DefaultContent/Libraries/addons/addons/SubTarget/README.txt b/Data/DefaultContent/Libraries/addons/addons/SubTarget/README.txt new file mode 100644 index 0000000..87d57af --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SubTarget/README.txt @@ -0,0 +1,42 @@ +name = 'SubTarget' +version = '1.0' +author = 'Sebyg666' + +Must have the "send" addon to work + +can use //subtarget or //sta + +Command "HELP" or no command: + + Explanation of the addons usage printed to the console, essentially the 2 commands below. + +Command "GO": + + subtarget|sta go mule_name spell_name + - Main usage. + - create an ingame macro with 2 lines + - line 1: /target <stal> + - line 2: /con sta go mule_name spell_name + - This sends your mule the spell + the target selected from <stal> + +Command "TOGGLE": + + subtarget|sta toggle + - turns on|off ingame text verification for debugging + +I created this addon for the perpous of fasilitating casting buffs from my alt to the chosen party member from my mains +window without having to write 6 macros for the same spell. If you use <stal> in your macro you can easily target +alliance members too. you can also use this with <stpc>. + +Currently due to the limitations of mob targeting this does not apply to <stnpc> as i do not know how to force the alt to target a mob by ID. + +********************************************************************************** + +example macro: + +line 1: /target <stal> +line 2: /con sta go Arcon haste + +if main name is Iroku, Arcon the mule and Byrth the target of <stal>, then essentially the macro makes your mule, +Arcon, cast haste on Byrth. + diff --git a/Data/DefaultContent/Libraries/addons/addons/SubTarget/SubTarget.lua b/Data/DefaultContent/Libraries/addons/addons/SubTarget/SubTarget.lua new file mode 100644 index 0000000..bb392aa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/SubTarget/SubTarget.lua @@ -0,0 +1,76 @@ +--Copyright (c) 2014, Sebyg666 +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'SubTarget' +_addon.version = '1.0' +_addon.author = 'Sebyg666' +_addon.commands = {'SubTarget','STa'} + +require('tables') +require('logger') +require('strings') + + +toggle = false + +windower.register_event('addon command', function(command, ...) + command = command or 'help' + + if command == 'help' then + print(' The correct functionality is:') + print(' Command "GO":') + print(' subtarget|sta go mule_name spell_name') + print(' - Main usage.') + print(' - create an ingame macro with 2 lines') + print(' - line 1: /target <stal>') + print(' - line 2: /con sta go mule_name spell_name') + print(' - This sends your mule the spell + the target selected from <stal>') + print(' Command "TOGGLE":') + print(' subtarget|sta toggle') + print(' - turns on|off ingame text verification for debugging.') + + + elseif command == 'go' then + local name + local lastst = windower.ffxi.get_mob_by_target('lastst') + if not lastst then + name = L{...}[-1] + print('Last sub target does not exist.') + print('Setting last sub to the recipient of the send command.') + else + name = lastst.name + end + if toggle then + log('Command to send-> "send ' .. table.concat({...}, ' ') .. ' ' .. name ..'".') + end + windower.send_command('send ' .. table.concat({...}, ' ') .. ' ' .. name) + + elseif command == 'toggle' then + toggle = not toggle + log('Text now ' .. (toggle and 'on' or 'off')) + + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/TParty/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/TParty/ReadMe.md new file mode 100644 index 0000000..09c1b95 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/TParty/ReadMe.md @@ -0,0 +1,3 @@ +# TParty + +Shows a target's HP percentage next to their health bar and displays party and alliance member's TP. Unlike SE's version of TP display this also works on Trusts. Both options can be disabled in the settings. diff --git a/Data/DefaultContent/Libraries/addons/addons/TParty/TParty.lua b/Data/DefaultContent/Libraries/addons/addons/TParty/TParty.lua new file mode 100644 index 0000000..3ea5a66 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/TParty/TParty.lua @@ -0,0 +1,152 @@ +_addon.name = 'TParty' +_addon.author = 'Cliff' +_addon.version = '2.0.1.2' + +require('sets') +require('functions') +texts = require('texts') +config = require('config') + +defaults = {} +defaults.ShowTargetHPPercent = true +defaults.ShowPartyTP = true + +settings = config.load(defaults) + +hpp = texts.new('${hpp}', { + pos = { + x = -104, + }, + bg = { + visible = false, + }, + flags = { + right = true, + bottom = true, + bold = true, + draggable = false, + italic = true, + }, + text = { + size = 10, + alpha = 185, + red = 115, + green = 166, + blue = 213, + }, +}) + +tp = T{} + +do + local x_pos = windower.get_windower_settings().ui_x_res - 118 + + for i = 0, 17 do + local party = (i / 6):floor() + 1 + local key = {'p%i', 'a1%i', 'a2%i'}[party]:format(i % 6) + local pos_base = {-34, -389, -288} + tp[key] = texts.new('${tp}', { + pos = { + x = x_pos, + y = pos_base[party] + 16 * (i % 6) + }, + bg = { + visible = false, + }, + flags = { + right = false, + bottom = true, + bold = true, + draggable = false, + italic = true, + }, + text = { + size = i < 6 and 10 or 8, + alpha = 185, + red = 255, + green = 255, + blue = 255, + }, + }) + end +end + +hpp_y_pos = {} +for i = 1, 6 do + hpp_y_pos[i] = -51 - 20 * i +end + +key_indices = { + p0 = 1, + p1 = 2, + p2 = 3, + p3 = 4, + p4 = 5, + p5 = 6, +} +tp_y_pos = {} +for i = 1, 6 do + tp_y_pos[i] = -34 - 20 * (6 - i) +end + +windower.register_event('prerender', function() + -- HP % text + if settings.ShowTargetHPPercent then + local mob = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') + + if mob then + local party_info = windower.ffxi.get_party_info() + + -- Adjust position for party member count + hpp:pos_y(hpp_y_pos[party_info.party1_count]) + + hpp:update(mob) + hpp:show() + else + hpp:hide() + end + else + hpp:hide() + end + + -- Alliance TP texts + if settings.ShowPartyTP then + local party = T(windower.ffxi.get_party()) + local zone = windower.ffxi.get_info().zone + + for text, key in tp:it() do + local member = party[key] + if member and member.zone == zone then + -- Adjust position for party member count + if key:startswith('p') then + text:pos_y(tp_y_pos[key_indices[key] + 6 - party.party1_count]) + end + + -- Color TP display green when TP > 1000 + if member.tp >= 1000 then + text:color(0, 255, 0) + else + text:color(255, 255, 255) + end + + text:update(member) + text:show() + else + text:hide() + end + end + end +end) + +--[[ +Copyright © 2014-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/Tab/README.md b/Data/DefaultContent/Libraries/addons/addons/Tab/README.md new file mode 100644 index 0000000..a985132 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Tab/README.md @@ -0,0 +1,9 @@ +# Tab + +### English +- Simple addon Replace Tab key input to `<stnpc>` or `<stpc>`. (X+Tab) +- **Tips:** if you won't to include pet or trust in `<stnpc>` by other players, use `/ignorepet on` and `/ignoretrust on` (in-game commands) + +### 日本語 +- Tabキーの動作を`<stnpc>`または`<stpc>`(X+Tab)に置き換えます。 +- **Tips:** `<stnpc>`に他プレイヤーのペットやフェイスを含めたくない場合、`/ignorepet on` または `/ignorefaith on`(ゲーム内コマンド)をご利用ください。 diff --git a/Data/DefaultContent/Libraries/addons/addons/Tab/tab.lua b/Data/DefaultContent/Libraries/addons/addons/Tab/tab.lua new file mode 100644 index 0000000..cae63ea --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Tab/tab.lua @@ -0,0 +1,49 @@ +--[[ +Copyright © 2018, from20020516 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Tab nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL from20020516 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] +_addon.name = 'Tab' +_addon.author = 'from20020516' +_addon.version = '1.0' + +require('sets') +st = false +x_pressed = false + +--replace input tab. validated in Japanese keyboard. please let me know if problems occur with the English keyboard. +windower.register_event('keyboard',function(dik,pressed,flags,blocked) + if dik == 45 then + x_pressed = pressed + elseif not windower.chat.is_open() then + if dik == 15 and pressed and not st then --Tab + st = x_pressed and '<stpc>' or '<stnpc>' + windower.chat.input('/ta '..st) + return true; --tab input blocking. it's probably broken.. + elseif S{1,28}[dik] and pressed then --Esc or Enter + st = false + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Text/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Text/ReadMe.md new file mode 100644 index 0000000..858d9ed --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Text/ReadMe.md @@ -0,0 +1,33 @@ +# Text + +Allows creation and manipulation of text objects on the screen. + +### Commands + +All commands are of the following form: +``` +text <name> <command> [args1 [arg2 [...]]] +``` + +There are two special commands to create and delete text objects: +* `create`: Creates a text object with the specified name and optionally sets the contents to the following string +* `delete`: Deletes the text object with the specified name + +All other commands and their arguments can be found in the [`texts` library](https://github.com/Windower/Lua/blob/4.1-dev/addons/libs/texts.lua). Every function in there that modifies the text object can be used as a command. + +### Examples + +The following list of commands will create a text object containing the text "Awkward" at position (500, 500) in a huge font and bright yellow color. + +``` +text foo create Awkward +text foo pos 500 500 +text foo color 255 255 0 +text foo size 50 +text foo italic true +``` + +This will delete it again +``` +text foo delete +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/Text/Text.lua b/Data/DefaultContent/Libraries/addons/addons/Text/Text.lua new file mode 100644 index 0000000..dbb7915 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Text/Text.lua @@ -0,0 +1,75 @@ +_addon.name = 'Text' +_addon.author = 'Dewin' +_addon.version = '1.0.0.0' +_addon.command = 'text' + +require('lists') +require('logger') +texts = require('texts') + +text_map = {} + +bool_commands = S{'visible', 'right_justified', 'left_justified', 'top_justified', 'bottom_justified', 'bold', 'italic', 'bg_visible'} +string_commands = S{'font', 'append', 'appendline'} +concat_commands = S{'text'} + +windower.register_event('addon command', function(name, command, ...) + command = command and command:lower() or 'help' + local key = name:lower() + local args = L{...} + + if command == 'create' or command == 'c' then + if text_map[key] then + warning('Text "%s" already exists.':format(name)) + return + end + + local t = texts.new(args:concat(' ')) + t:show() + text_map[key] = t + + elseif command == 'delete' then + local t = text_map[key] + if not t then + warning('Text "%s" does not exist.':format(name)) + return + end + + t:destroy() + text_map[key] = nil + + else + local t = text_map[key] + if not t then + error('Text "%s" does not exist.':format(name)) + return + end + + local args = L{...} + + if bool_commands:contains(command) then + args = args:map(functions.equals('true')) + elseif concat_commands:contains(command) then + args = L{args:concat(' ')} + elseif not string_commands:contains(command) then + args = args:map(tonumber) + end + + + texts[command:lower()](t, args:unpack()) + + end +end) + +--[[ +Copyright 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/TreasurePool/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/TreasurePool/ReadMe.md new file mode 100644 index 0000000..49e25e6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/TreasurePool/ReadMe.md @@ -0,0 +1,3 @@ +# TreasurePool + +Replacement for the Treasure Pool thats shows time till the item auto drop and who is the current winning lot. diff --git a/Data/DefaultContent/Libraries/addons/addons/TreasurePool/TreasurePool.lua b/Data/DefaultContent/Libraries/addons/addons/TreasurePool/TreasurePool.lua new file mode 100644 index 0000000..65529ad --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/TreasurePool/TreasurePool.lua @@ -0,0 +1,163 @@ +--[[Copyright © 2019, Kenshi +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of TreasurePool nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL KENSHI BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'TreasurePool' +_addon.author = 'Kenshi' +_addon.version = '2.0' + +require('luau') +texts = require('texts') +packets = require('packets') + +-- Config + +defaults = {} +defaults.display = {} +defaults.display.pos = {} +defaults.display.pos.x = 0 +defaults.display.pos.y = 0 +defaults.display.bg = {} +defaults.display.bg.red = 0 +defaults.display.bg.green = 0 +defaults.display.bg.blue = 0 +defaults.display.bg.alpha = 102 +defaults.display.bg.visible = true +defaults.display.text = {} +defaults.display.text.font = 'Consolas' +defaults.display.text.red = 255 +defaults.display.text.green = 255 +defaults.display.text.blue = 255 +defaults.display.text.alpha = 255 +defaults.display.text.size = 12 + +settings = config.load(defaults) +box = texts.new('${current_string}', settings) + +local items = T{} + +windower.register_event('load', function() + local treasure = windower.ffxi.get_items().treasure + for i = 0, 9 do + if treasure[i] and treasure[i].item_id then + local item = res.items[treasure[i].item_id] and res.items[treasure[i].item_id].en or treasure[i].item_id + local pos = treasure[i].timestamp + i + table.insert(items, {position = pos, index = i, name = item, timestamp = treasure[i].timestamp, + temp = treasure[i].timestamp + 300, lotter = nil, lot = nil}) + end + end + table.sort(items, function(a,b) return a and b and a.position < b.position end) +end) + +windower.register_event('incoming chunk', function(id, data) + if id == 0x0D2 then + local packet = packets.parse('incoming', data) + -- Ignore gil drop + if packet.Item == 0xFFFF then + return + end + -- Double packet and leaving pt fix + for key, value in pairs(items) do + if value and value.index == packet.Index then + if value.timestamp == packet.Timestamp then + return + else + table.remove(items, key) + end + end + end + -- Ignore item 0 packets + if packet.Item == 0 then + return + end + -- Create table + local time_check = packet.Timestamp + 300 + local diff = os.difftime(time_check, os.time()) + local item = res.items[packet.Item] and res.items[packet.Item].en or packet.Item + local pos = packet.Timestamp + packet.Index + if diff <= 300 then + table.insert(items, {position = pos, index = packet.Index, name = item, timestamp = packet.Timestamp, + temp = packet.Timestamp + 300, lotter = nil, lot = nil}) + else + table.insert(items, {position = pos, index = packet.Index, name = item, timestamp = packet.Timestamp, + temp = os.time() + 300, lotter = nil, lot = nil}) + end + -- Sort table + table.sort(items, function(a,b) return a and b and a.position < b.position end) + end + if id == 0x0D3 then + local packet = packets.parse('incoming', data) + for key, value in pairs(items) do + if value.index == packet.Index then + if packet.Drop ~= 0 then + table.remove(items, key) + table.sort(items, function(a,b) return a and b and a.position < b.position end) + else + value.lotter = packet['Highest Lotter Name'] + value.lot = packet['Highest Lot'] + end + end + end + end + if id == 0xB then + items = T{} + end +end) + +windower.register_event('prerender', function() + if items:empty() then + box:hide() + return + end + local current_string = 'Treasure Pool:' + for key, value in pairs(items) do + if value and value.temp then + local diff = os.difftime(value.temp, os.time()) + local timer = os.date('!%M:%S', diff) + if diff >= 0 then + current_string = current_string..'\n['..key..']' + current_string = ( + diff < 60 and + current_string..'\\cs(255,0,0) '..value.name..' → '..timer + or diff > 180 and + current_string..'\\cs(0,255,0) '..value.name..' → '..timer + or + current_string..'\\cs(255,128,0) '..value.name..' → '..timer)..'\\cr' + if value.lotter and value.lot and value.lot > 0 then + current_string = current_string..' | ' + current_string = (current_string..'\\cs(0,255,255)'..value.lotter..': '..value.lot)..'\\cr' + end + else + table.remove(items, key) + end + box:show() + end + end + box.current_string = current_string +end) + +windower.register_event('logout', function() + items = T{} +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/Treasury/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Treasury/ReadMe.md new file mode 100644 index 0000000..0b23864 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Treasury/ReadMe.md @@ -0,0 +1,52 @@ +# Treasury + +An addon that manages the treasure pool for you and keeps your inventory clean of unwanted items. It does three things: +1. It lots/passes on items in the treasure pool based on per-character rules as defined in the settings file +2. It automatically stacks items in your inventory after it changes, if enabled +3. If automatically drops unwanted items from your inventory, if enabled + +### Commands + +Note: +All commands can be shortened to `//tr`. `lot` and `pass` can be shortened to `l` and `p` respectively. `add` and `remove` can be shortened to `a` or `+` and `r` or `-` respectively. + +`//treasuy lot|pass|drop add|remove [global] <name>` + +This will add to or remove from the lot list, pass list or drop list all items matching `name`. `name` can contain standard Windower wildcards (`*`, `?`, `|`). It will add those for the current character only, unless `global` is specified, in which case it will add it for all characters. + +There are a few special key words for `name`: +* `crystals` matches all crystal items (excluding HQ synthing crystals) +* `geodes` matches all geode items (NQ) +* `avatarites` matches all geode items (HQ) +* `currency` matches all Dynamis currency (all three tiers of all three kinds) +* `seals` matches the standard seals found in the field (BS, KS, KC, HKC, SKC) +* `detritus` matches Swart Astral Detritus and Murky Astral Detritus +* `heroism` matches Heroism Crystal and Heroism Aggregate +* `moldy` matches all Moldy weapons and neck items from Dynamis Divergence +* `dynad` matches all three card types, all three medal types, and the crafting materials from Dynamis Divergence +* `papers` matches all shards and all void items from Dynamis Divergence +* `pool` matches your current treasure pool + +'//treasury lot|pass|drop clear|list` + +This will either clear the specified list (for the current character only) or list all items on the specified list. + +`//treasury lotall|passall` + +Lots/passes on all items currently in the pool + +`//treasury clearall` + +Clears all character-specific settings (it will keep global settings) + +`//treasury delay <value>` + +Sets the delay that should pass before lotting/passing/dropping items, in seconds. It will lot/pass/drop items in a random time interval between half the value specified and the full value. I.e. if you specify 5 seconds it will lot/pass/drop between 2.5 seconds and 5 seconds randomly. + +`//treasury <autodrop|autostack|verbose> [on|off]` + +Sets the provided setting to true or false. If neither is provided, it toggles the current setting. + +`//treasury save` + +Saves the current character's settings for all characters. diff --git a/Data/DefaultContent/Libraries/addons/addons/Treasury/Treasury.lua b/Data/DefaultContent/Libraries/addons/addons/Treasury/Treasury.lua new file mode 100644 index 0000000..dda4966 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Treasury/Treasury.lua @@ -0,0 +1,376 @@ +_addon.name = 'Treasury' +_addon.author = 'Ihina' +_addon.version = '1.2.1.1' +_addon.commands = {'treasury', 'tr'} + +res = require('resources') +config = require('config') +packets = require('packets') +require('logger') + +defaults = {} +defaults.Pass = S{} +defaults.Lot = S{} +defaults.Drop = S{} +defaults.AutoDrop = false +defaults.AutoStack = true +defaults.Delay = 0 +defaults.Verbose = false + +settings = config.load(defaults) + +all_ids = T{} +for item in res.items:it() do + local name = item.name:lower() + if not all_ids[name] then + all_ids[name] = S{} + end + local name_log = item.name_log:lower() + if not all_ids[name_log] then + all_ids[name_log] = S{} + end + all_ids[name]:add(item.id) + all_ids[name_log]:add(item.id) +end + +code = {} +code.pass = S{} +code.lot = S{} +code.drop = S{} + +local flatten = function(s) + return s:reduce(function(s1, s2) + return s1 + s2 + end, S{}) +end + +local extract_ids = function(settings_table, key) + local valid = settings_table[key]:filter(function(name) + local found = all_ids[name:lower()] ~= nil + if not found then + print('Treasury: Item "%s" not found in %s list.':format(name, key)) + end + return found + end) + return flatten(valid:map(table.get+{all_ids} .. string.lower)) +end + +config.register(settings, function(settings_table) + code.pass = extract_ids(settings_table, 'Pass') + code.lot = extract_ids(settings_table, 'Lot') + code.drop = extract_ids(settings_table, 'Drop') +end) + +lotpassdrop_commands = T{ + lot = 'Lot', + l = 'Lot', + pass = 'Pass', + p = 'Pass', + drop = 'Drop', + d = 'Drop', +} + +addremove_commands = T{ + add = 'add', + a = 'add', + ['+'] = 'add', + remove = 'remove', + r = 'remove', + ['-'] = 'remove', +} + +bool_values = T{ + ['on'] = true, + ['1'] = true, + ['true'] = true, + ['off'] = false, + ['0'] = false, + ['false'] = false, +} + +inventory_id = res.bags:with('english', 'Inventory').id + +function lotpassdrop(command1, command2, ids) + local action = command1:lower() + names = ids:map(table.get-{'name'} .. table.get+{res.items}) + if command2 == 'add' then + log('Adding to ' .. action .. ' list:', names) + code[action] = code[action] + ids + settings[command1] = settings[command1] + names + else + log('Removing from ' .. action .. ' list:', names) + code[action] = code[action] - ids + settings[command1] = settings[command1] - names + end + + settings:save() + force_check(command1 == 'Drop') +end + +function act(action, output, id, ...) + if settings.Verbose then + log('%s %s':format(output, res.items[id].name:color(258))) + end + windower.ffxi[action]:prepare(...):schedule((math.random() + 1) / 2 * settings.Delay) +end + +pass = act+{'pass_item', 'Passing'} +lot = act+{'lot_item', 'Lotting'} +drop = act+{'drop_item', 'Dropping'} + +function force_check() + local items = windower.ffxi.get_items() + + -- Check treasure pool + for index, item in pairs(items.treasure) do + check(index, item.item_id) + end + + -- Check inventory for unwanted items + if settings.AutoDrop then + for index, item in pairs(items.inventory) do + if type(item) == 'table' and code.drop:contains(item.id) and item.status == 0 then + drop(item.id, index, item.count) + end + end + end +end + +function check(slot_index, item_id) + if (code.drop:contains(item_id) or code.pass:contains(item_id)) and not code.lot:contains(item_id) then + pass(item_id, slot_index) + elseif code.lot:contains(item_id) then + local inventory = windower.ffxi.get_items(inventory_id) + if inventory.max - inventory.count > 1 then + lot(item_id, slot_index) + end + end +end + +function find_id(name) + if name == 'pool' then + return pool_ids() + + elseif name == 'seals' then + return S{1126, 1127, 2955, 2956, 2957} + + elseif name == 'currency' then + return S{1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457} + + elseif name == 'geodes' then + return S{3297, 3298, 3299, 3300, 3301, 3302, 3303, 3304} + + elseif name == 'avatarites' then + return S{3520, 3521, 3522, 3523, 3524, 3525, 3526, 3527} + + elseif name == 'crystals' then + return S{4096, 4097, 4098, 4099, 4100, 4101, 4102, 4103} + + elseif name == 'detritus' then + return S{9875, 9876} + + elseif name == 'heroism' then + return S{9877, 9878} + + elseif name == 'moldy' then + return S{9773, 9830, 9831, 9832, 9833, 9834, 9835, 9836, 9837, 9838, 9839, 9840, 9841, 9843, 9868, 9869, 9870, 9871, 9872, 9873, 9874} + + elseif name == 'dynad' then + return S{9538, 9539, 9540, 9541, 9542, 9543, 9844, 9845} + + elseif name == 'papers' then + return S{9544, 9545, 9546, 9547, 9548, 9549, 9550, 9551, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9581, 9582, 9583, 9584, 9585, 9586, 9587, 9588, 9589, 9590, 9591, 9592, 9593, 9594, 9595, 9596, 9597, 9598, 9599, 9600, 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608, 9609, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, 9624, 9625, 9626, 9627, 9628, 9629, 9630, 9631, 9632, 9633, 9634, 9635, 9636, 9637, 9638, 9639, 9640, 9641, 9642, 9643, 9644, 9645, 9646, 9647, 9648, 9649, 9650, 9651, 9652, 9653, 9654, 9655, 9656, 9657, 9658, 9659, 9660, 9661, 9662, 9663, 9664, 9665, 9666, 9667, 9668, 9669, 9670, 9671, 9672, 9673, 9674, 9675, 9676, 9677, 9678, 9679, 9680, 9681, 9682, 9683, 9684, 9685, 9686, 9687, 9688, 9689, 9690, 9691, 9692, 9693, 9694, 9695, 9696, 9697, 9698, 9699, 9700, 9701, 9702, 9703, 9704, 9705, 9706, 9707, 9708, 9709, 9710, 9711, 9712, 9713, 9714, 9715, 9716, 9717, 9718, 9719, 9720, 9721, 9722, 9723, 9724, 9725, 9726, 9727, 9728, 9729, 9730, 9731, 9732, 9733, 9734, 9735, 9736, 9737, 9738, 9739, 9740, 9741, 9742, 9743, 9744, 9745, 9746, 9747, 9748, 9749, 9750, 9751, 9752, 9753, 9754, 9755, 9756, 9757, 9758, 9759, 9760, 9761, 9762, 9763} + + else + return flatten(S(all_ids:key_filter(windower.wc_match-{name}))) + + end +end + +function pool_ids() + return S(T(windower.ffxi.get_items().treasure):map(table.get-{'item_id'})) +end + +stack = function() + local wait_time = 0 + + return function() + if os.clock() - last_stack_time > 2 then + packets.inject(packets.new('outgoing', 0x03A)) + last_stack_time = os.clock() + wait_time = 0 + elseif os.clock() - last_stack_time > wait_time then + wait_time = wait_time + 0.45 + stack:schedule(0.5) + end + end:cond(function() + return settings.AutoStack + end) +end() + +stack_ids = S{0x01F, 0x020} +last_stack_time = 0 +windower.register_event('incoming chunk', function(id, data) + if id == 0x0D2 then + local treasure = packets.parse('incoming', data) + check(treasure.Index, treasure.Item) + + elseif stack_ids:contains(id) then + local chunk = packets.parse('incoming', data) + + -- Ignore items in other bags + if chunk.Bag ~= inventory_id then + return + end + + if id == 0x020 and settings.AutoDrop and code.drop:contains(chunk.Item) and chunk.Status == 0 then + drop(chunk.Item, chunk.Index, chunk.Count) + else + -- Don't need to stack in the other case, as a new inventory packet will come in after the drop anyway + stack() + end + end +end) + +windower.register_event('ipc message', function(msg) + local args = msg:split(' ') + if args:remove(1) == 'treasury' then + command1 = args:remove(1) + command2 = args:remove(1) + lotpassdrop(command1, command2, S(args):map(tonumber)) + end +end) + +windower.register_event('load', force_check:cond(table.get-{'logged_in'} .. windower.ffxi.get_info)) + +windower.register_event('addon command', function(command1, command2, ...) + local args = L{...} + local global = false + + if args[1] == 'global' then + global = true + args:remove(1) + end + + command1 = command1 and command1:lower() or 'help' + command2 = command2 and command2:lower() or nil + + local name = args:concat(' ') + if lotpassdrop_commands:containskey(command1) then + command1 = lotpassdrop_commands[command1] + + if addremove_commands:containskey(command2) then + command2 = addremove_commands[command2] + + local ids = find_id(name) + if ids:empty() then + error('No items found that match: %s':format(name)) + return + end + lotpassdrop(command1, command2, ids) + + if global then + windower.send_ipc_message('treasury %s %s %s':format(command1, command2, ids:concat(' '))) + end + + elseif command2 == 'clear' then + code[command1:lower()]:clear() + settings[command1]:clear() + config.save(settings) + + elseif command2 == 'list' then + log(command1 .. ':') + for item in settings[command1]:it() do + log(' ' .. item) + end + + end + + elseif command1 == 'passall' then + for slot_index, item_table in pairs(windower.ffxi.get_items().treasure) do + windower.ffxi.pass_item(slot_index) + end + + elseif command1 == 'lotall' then + for slot_index, item_table in pairs(windower.ffxi.get_items().treasure) do + windower.ffxi.lot_item(slot_index) + end + + elseif command1 == 'clearall' then + code.pass:clear() + code.lot:clear() + code.drop:clear() + settings.Pass:clear() + settings.Lot:clear() + settings.Drop:clear() + config.save(settings) + + elseif command1 == 'autodrop' then + if command2 then + settings.AutoDrop = bool_values[command2:lower()] + else + settings.AutoDrop = not settings.AutoDrop + end + + config.save(settings) + log('AutoDrop %s':format(settings.AutoDrop and 'enabled' or 'disabled')) + + elseif command1 == 'autostack' then + if command2 then + settings.AutoStack = bool_values[command2:lower()] + else + settings.AutoStack = not settings.AutoStack + end + + config.save(settings) + log('AutoStack %s':format(settings.AutoStack and 'enabled' or 'disabled')) + + elseif command1 == 'delay' then + if not (command2 and tonumber(command2)) then + error('Please specify a value in seconds for the new delay') + return + end + + settings.Delay = tonumber(command2) + log('Delay set to %f seconds':format(settings.Delay)) + + elseif command1 == 'verbose' then + if command2 then + settings.Verbose = bool_values[command2:lower()] + else + settings.Verbose = not settings.Verbose + end + + config.save(settings) + log('Verbose output %s':format(settings.Verbose and 'enabled' or 'disabled')) + + elseif command1 == 'save' then + config.save(settings, 'all') + + elseif command1 == 'help' then + print('%s v%s':format(_addon.name, _addon.version)) + print(' \\cs(255,255,255)lot|pass|drop add|remove <name>\\cr - Adds or removes all items matching <name> to the specified list') + print(' \\cs(255,255,255)lot|pass|drop clear\\cr - Clears the specified list for the current character') + print(' \\cs(255,255,255)lot|pass list\\cr - Lists all items on the specified list for the current character') + print(' \\cs(255,255,255)lotall|passall\\cr - Lots/Passes all items currently in the pool') + print(' \\cs(255,255,255)clearall\\cr - Removes lotting/passing/dropping settings for this character') + print(' \\cs(255,255,255)autodrop [on|off]\\cr - Enables/disables (or toggles) the auto-drop setting') + print(' \\cs(255,255,255)verbose [on|off]\\cr - Enables/disables (or toggles) the verbose setting') + print(' \\cs(255,255,255)autostack [on|off]\\cr - Enables/disables (or toggles) the autostack feature') + print(' \\cs(255,255,255)delay <value>\\cr - Allows you to change the delay of actions (default: 0)') + + end +end) + +--[[ +Copyright © 2014-2018, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/Trusts/README.md b/Data/DefaultContent/Libraries/addons/addons/Trusts/README.md new file mode 100644 index 0000000..463026e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Trusts/README.md @@ -0,0 +1,36 @@ +# Trusts + +- //tru save `<setname>` : Save trusts in current party. +- //tru `<setname>` : Calls trusts you saved. +- //tru list : Lists your saved sets. +- //tru random : What's your fortune today? +- //tru check : List of unlearned trusts. gotta catch 'em all! + +### 使い方 + +- //tru save `<setname>` + - 呼び出し中のフェイスをセットに保存(セット名は半角英字) +- //tru `<setname>` + - 保存したセットのフェイスを召喚 + - 呼び出し先と"同枠"のフェイスは戻す + - 既にPTにいる場合は戻さない = 倒れたフェイスの補充が可能 + - リキャストが不足している場合は戻さない +- //tru list + - 保存したセットを一覧表示します。 +- //tru random + - フェイスガチャ。PT枠上限までランダムで召喚 +- //tru check + - 未習得フェイスを表示 + +### data/settings.xml (auto-generated) +- language : `<Japanese>` OR `<English>` +- auto : `true` OR `false` + - 登録フェイスを全て自動で呼び出すか、1体ごとに1クリックするか選択 +- wait + - ユーザーのPC環境に応じて長くする。ファストキャストは無関係 + - aftercast : `number` + - 詠唱完了から次の詠唱までの待機時間。初期値3秒 + - retr : `number` + - フェイスを戻したあと次の詠唱までの待機時間。初期値1.25秒 + - retrall : `number` + - フェイスを全て戻したあと次の詠唱までの待機時間。初期値3秒 diff --git a/Data/DefaultContent/Libraries/addons/addons/Trusts/Trusts.lua b/Data/DefaultContent/Libraries/addons/addons/Trusts/Trusts.lua new file mode 100644 index 0000000..11e9c6d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Trusts/Trusts.lua @@ -0,0 +1,373 @@ +--[[ +Copyright © 2018, from20020516 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Trusts nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL from20020516 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] +_addon.name='Trusts' +_addon.author='from20020516' +_addon.version='1.1' +_addon.commands={'trusts','tru'} + +config = require('config') +math = require('math') +math.randomseed(os.clock()) +require('logger') + +windower.register_event('load',function() + defaults = { + auto=true, + language=windower.ffxi.get_info().language, + sets={ + ['default']={ + English={ + ['1']='Valaineral', + ['2']='Mihli Aliapoh', + ['3']='Tenzen', + ['4']='Adelheid', + ['5']='Joachim'}, + Japanese={ + ['1']='ヴァレンラール', + ['2']='ミリ・アリアポー', + ['3']='テンゼン', + ['4']='アーデルハイト', + ['5']='ヨアヒム'}}[windower.ffxi.get_info().language]}, + wait={ + ['aftercast']=3, + ['retr']=1.25, + ['retrall']=3},} + settings = config.load(defaults) + lang = string.lower(settings.language) + player = windower.ffxi.get_player() +end) + +windower.register_event('login',function() + player = windower.ffxi.get_player() +end) + +windower.register_event('addon command',function(...) + cmd = {...} + if cmd[1] == 'help' then + local chat = windower.add_to_chat + local color = string.color + chat(1,'Trusts - Command List:') + chat(207,'//tru '..color('save <setname>',166,160)..' --Save trusts in current party.') + chat(207,'//tru '..color('<setname>',166,160)..' --Calls trusts you saved.') + chat(207,'//tru '..color('list',166,160)..' --Lists your saved sets.') + chat(207,'//tru '..color('random',166,160)..' --What\'s your fortune today?') + chat(207,'//tru '..color('check',166,160)..' --List of unlearned trusts. gotta catch \'em all!') + elseif cmd[1] == 'save' then + save_set(cmd[2]) + elseif cmd[1] == 'check' then + check_learned() + elseif cmd[1] == 'list' then + list_sets() + else + call_set(cmd[1] or 'default') + end +end) + +function save_set(set) + settings.sets[set] = {} + local trust_ind = 0 + local get_party = windower.ffxi.get_party() + for i=1,5 do + local trust = get_party['p'..i] + if trust and trust.mob.spawn_type == 14 then + trust_ind = trust_ind + 1 + settings.sets[set][tostring(trust_ind)]=trusts:with('models',trust.mob.models[1])[lang] + end + end + settings:save('all') + log('set '..set..' saved.') +end + +function list_sets() + local chat = windower.add_to_chat + settings = config.load() + chat(1, 'Trusts - Saved sets:') + + for set, _ in pairs(settings.sets) do + if set ~= 'default' then + chat(207, set) + end + end +end + +function check_lang(entity) + return {japanese=entity.japanese,english=entity.english}[lang]; +end + +function check_limit() + for i,v in pairs(windower.ffxi.get_key_items()) do + --Trust permit,Rhapsody in.. + limit = S{2497,2499,2501}[v] and 3 or v==2884 and 4 or v==2886 and 5 or limit or 0 + end + return limit; +end + +function call_trust() + if #queue > 0 then + windower.chat.input('/ma "'..windower.to_shift_jis(check_lang(queue[1]))..'" <me>') + end +end + +function check_exist() + local party = {} --include only trusts. --['name']=models + local party_ind = {} -- index of trust's name in current party. {'name1','name2',...,'name5'} + local get_party = windower.ffxi.get_party() + for i=1,5 do + local member = get_party['p'..i] + if member then + if member.mob.spawn_type == 14 then + party[member.name] = member.mob.models[1] + table.insert(party_ind,member.name) + end + end + end + return {party,party_ind}; +end + +function call_set(set) + queue = {} --trusts to be cast. + settings = config.load() + local party,party_ind = unpack(check_exist()) + local limit = check_limit() --upper limit # of calls trust in current player. + local time = os.clock() --window open + local get_spells = windower.ffxi.get_spells() + local get_spell_recasts = windower.ffxi.get_spell_recasts() + + if set == 'random' then + local checked = {} + local others = windower.ffxi.get_party().party1_count - #party_ind - 1 --# of human in party exept <me>. + + if limit == #party_ind then + windower.chat.input('/retr all') + calls = limit + coroutine.sleep(settings.wait.retrall) + else + calls = limit - #party_ind - others + end + + repeat + local index = trusts[math.random(1,#trusts)] + if not table.find(checked,index.name) then + table.insert(checked,index.name) + if get_spells[index.id] and get_spell_recasts[index.id] == 0 then + table.insert(queue,index) + end + end + until #queue >= calls or #checked >= 103 --# of unique names w/o Cornelia + + elseif settings.sets[set] then + retr = {unpack(party_ind)} + for i=1,limit do + if settings.sets[set][tostring(i)] then + local entity = trusts:with(lang,settings.sets[set][tostring(i)]) + if not party[entity.name] + or party[entity.name] ~= entity.models then + if get_spell_recasts[entity.id] == 0 then + if get_spells[entity.id] then + table.insert(queue,entity) + else + table.remove(retr,table.find(retr,party_ind[i])) + error('You aren\'t trusted by '..entity.english..'.') + end + else + table.remove(retr,table.find(retr,party_ind[i])) + local recast = math.floor(get_spell_recasts[entity.id] / 6) / 10 + log(entity.english..' needs '..recast..' secs break.') + end + else + table.remove(retr,table.find(retr,entity.name)) + if settings.auto then + log(entity.english..' already exists.') + end + end + end + end + for index,name in pairs(retr) do + if #retr == #party_ind then + windower.chat.input('/retr all') + coroutine.sleep(settings.wait.retrall) + break; + else + windower.chat.input('/retr '..name) + coroutine.sleep(settings.wait.retr) + end + end + else + error('Unknown set name '..(set or '')) + end + --if /retr then wait at least 3secs. + local delay = (limit - #party_ind) == 0 and math.max(0,settings.wait.retrall + time - os.clock()) or 0 + coroutine.schedule(call_trust,delay) +end + +windower.register_event('action', function(act) + if settings.auto and act.actor_id == player.id and queue and #queue > 0 then + if act.category == 4 and act.param == table.remove(queue,1).id then + coroutine.schedule(call_trust,settings.wait.aftercast) + elseif act.category == 8 and act.param == 28787 and act.targets[1].actions[1].param == queue[1].id then + coroutine.schedule(call_trust,settings.wait.aftercast) + end + end +end) + +function check_learned() + local learned = {} + local get_spells = windower.ffxi.get_spells() + for i,value in ipairs(trusts) do + if get_spells[value.id] == false and not value.english:endswith('(UC)') then + table.insert(learned,value.id) + log(check_lang(value)) + end + end + log('You haven\'t trusted yet from '..#learned..' trusts.') +end + +trusts = T{ + [1]={id=896,japanese="シャントット",english="Shantotto",name="Shantotto",models=3000}, + [2]={id=897,japanese="ナジ",english="Naji",name="Naji",models=3001}, + [3]={id=898,japanese="クピピ",english="Kupipi",name="Kupipi",models=3002}, + [4]={id=899,japanese="エグセニミル",english="Excenmille",name="Excenmille",models=3003}, + [5]={id=900,japanese="アヤメ",english="Ayame",name="Ayame",models=3004}, + [6]={id=901,japanese="ナナー・ミーゴ",english="Nanaa Mihgo",name="NanaaMihgo",models=3005}, + [7]={id=902,japanese="クリルラ",english="Curilla",name="Curilla",models=3006}, + [8]={id=903,japanese="フォルカー",english="Volker",name="Volker",models=3007}, + [9]={id=904,japanese="アジドマルジド",english="Ajido-Marujido",name="Ajido-Marujido",models=3008}, + [10]={id=905,japanese="トリオン",english="Trion",name="Trion",models=3009}, + [11]={id=906,japanese="ザイド",english="Zeid",name="Zeid",models=3010}, + [12]={id=907,japanese="ライオン",english="Lion",name="Lion",models=3011}, + [13]={id=908,japanese="テンゼン",english="Tenzen",name="Tenzen",models=3012}, + [14]={id=909,japanese="ミリ・アリアポー",english="Mihli Aliapoh",name="MihliAliapoh",models=3013}, + [15]={id=910,japanese="ヴァレンラール",english="Valaineral",name="Valaineral",models=3014}, + [16]={id=911,japanese="ヨアヒム",english="Joachim",name="Joachim",models=3015}, + [17]={id=912,japanese="ナジャ・サラヒム",english="Naja Salaheem",name="NajaSalaheem",models=3016}, + [18]={id=913,japanese="プリッシュ",english="Prishe",name="Prishe",models=3017}, + [19]={id=914,japanese="ウルミア",english="Ulmia",name="Ulmia",models=3018}, + [20]={id=915,japanese="スカリーZ",english="Shikaree Z",name="ShikareeZ",models=3019}, + [21]={id=916,japanese="チェルキキ",english="Cherukiki",name="Cherukiki",models=3020}, + [22]={id=917,japanese="アイアンイーター",english="Iron Eater",name="IronEater",models=3021}, + [23]={id=918,japanese="ゲッショー",english="Gessho",name="Gessho",models=3022}, + [24]={id=919,japanese="ガダラル",english="Gadalar",name="Gadalar",models=3023}, + [25]={id=920,japanese="ライニマード",english="Rainemard",name="Rainemard",models=3024}, + [26]={id=921,japanese="イングリッド",english="Ingrid",name="Ingrid",models=3025}, + [27]={id=922,japanese="レコ・ハボッカ",english="Lehko Habhoka",name="LehkoHabhoka",models=3026}, + [28]={id=923,japanese="ナシュメラ",english="Nashmeira",name="Nashmeira",models=3027}, + [29]={id=924,japanese="ザザーグ",english="Zazarg",name="Zazarg",models=3028}, + [30]={id=925,japanese="アヴゼン",english="Ovjang",name="Ovjang",models=3029}, + [31]={id=926,japanese="メネジン",english="Mnejing",name="Mnejing",models=3030}, + [32]={id=927,japanese="サクラ",english="Sakura",name="Sakura",models=3031}, + [33]={id=928,japanese="ルザフ",english="Luzaf",name="Luzaf",models=3032}, + [34]={id=929,japanese="ナジュリス",english="Najelith",name="Najelith",models=3033}, + [35]={id=930,japanese="アルド",english="Aldo",name="Aldo",models=3034}, + [36]={id=931,japanese="モーグリ",english="Moogle",name="Moogle",models=3035}, + [37]={id=932,japanese="ファブリニクス",english="Fablinix",name="Fablinix",models=3036}, + [38]={id=933,japanese="マート",english="Maat",name="Maat",models=3037}, + [39]={id=934,japanese="D.シャントット",english="D. Shantotto",name="D.Shantotto",models=3038}, + [40]={id=935,japanese="星の神子",english="Star Sibyl",name="StarSibyl",models=3039}, + [41]={id=936,japanese="カラハバルハ",english="Karaha-Baruha",name="Karaha-Baruha",models=3040}, + [42]={id=937,japanese="シド",english="Cid",name="Cid",models=3041}, + [43]={id=938,japanese="ギルガメッシュ",english="Gilgamesh",name="Gilgamesh",models=3042}, + [44]={id=939,japanese="アレヴァト",english="Areuhat",name="Areuhat",models=3043}, + [45]={id=940,japanese="セミ・ラフィーナ",english="Semih Lafihna",name="SemihLafihna",models=3044}, + [46]={id=941,japanese="エリヴィラ",english="Elivira",name="Elivira",models=3045}, + [47]={id=942,japanese="ノユリ",english="Noillurie",name="Noillurie",models=3046}, + [48]={id=943,japanese="ルー・マカラッカ",english="Lhu Mhakaracca",name="LhuMhakaracca",models=3047}, + [49]={id=944,japanese="フェリアスコフィン",english="Ferreous Coffin",name="FerreousCoffin",models=3048}, + [50]={id=945,japanese="リリゼット",english="Lilisette",name="Lilisette",models=3049}, + [51]={id=946,japanese="ミュモル",english="Mumor",name="Mumor",models=3050}, + [52]={id=947,japanese="ウカ・トトゥリン",english="Uka Totlihn",name="UkaTotlihn",models=3051}, + [53]={id=948,japanese="クララ",english="Klara",name="Klara",models=3053}, + [54]={id=949,japanese="ロマー・ミーゴ",english="Romaa Mihgo",name="RomaaMihgo",models=3054}, + [55]={id=950,japanese="クイン・ハスデンナ",english="Kuyin Hathdenna",name="KuyinHathdenna",models=3055}, + [56]={id=951,japanese="ラーアル",english="Rahal",name="Rahal",models=3056}, + [57]={id=952,japanese="コルモル",english="Koru-Moru",name="Koru-Moru",models=3057}, + [58]={id=953,japanese="ピエージェ(UC)",english="Pieuje (UC)",name="Pieuje",models=3058}, + [59]={id=954,japanese="I.シールド(UC)",english="I. Shield (UC)",name="InvincibleShld",models=3060}, + [60]={id=955,japanese="アプルル(UC)",english="Apururu (UC)",name="Apururu",models=3061}, + [61]={id=956,japanese="ジャコ(UC)",english="Jakoh (UC)",name="JakohWahcondalo",models=3062}, + [62]={id=957,japanese="フラヴィリア(UC)",english="Flaviria (UC)",name="Flaviria",models=3059}, + [63]={id=958,japanese="ウェイレア",english="Babban",name="Babban",models=3067}, + [64]={id=959,japanese="アベンツィオ",english="Abenzio",name="Abenzio",models=3068}, + [65]={id=960,japanese="ルガジーン",english="Rughadjeen",name="Rughadjeen",models=3069}, + [66]={id=961,japanese="クッキーチェブキー",english="Kukki-Chebukki",name="Kukki-Chebukki",models=3070}, + [67]={id=962,japanese="マルグレート",english="Margret",name="Margret",models=3071}, + [68]={id=963,japanese="チャチャルン",english="Chacharoon",name="Chacharoon",models=3072}, + [69]={id=964,japanese="レイ・ランガヴォ",english="Lhe Lhangavo",name="LheLhangavo",models=3073}, + [70]={id=965,japanese="アシェラ",english="Arciela",name="Arciela",models=3074}, + [71]={id=966,japanese="マヤコフ",english="Mayakov",name="Mayakov",models=3075}, + [72]={id=967,japanese="クルタダ",english="Qultada",name="Qultada",models=3076}, + [73]={id=968,japanese="アーデルハイト",english="Adelheid",name="Adelheid",models=3077}, + [74]={id=969,japanese="アムチュチュ",english="Amchuchu",name="Amchuchu",models=3078}, + [75]={id=970,japanese="ブリジッド",english="Brygid",name="Brygid",models=3079}, + [76]={id=971,japanese="ミルドリオン",english="Mildaurion",name="Mildaurion",models=3080}, + [77]={id=972,japanese="ハルヴァー",english="Halver",name="Halver",models=3087}, + [78]={id=973,japanese="ロンジェルツ",english="Rongelouts",name="Rongelouts",models=3088}, + [79]={id=974,japanese="レオノアーヌ",english="Leonoyne",name="Leonoyne",models=3089}, + [80]={id=975,japanese="マクシミリアン",english="Maximilian",name="Maximilian",models=3090}, + [81]={id=976,japanese="カイルパイル",english="Kayeel-Payeel",name="Kayeel-Payeel",models=3091}, + [82]={id=977,japanese="ロベルアクベル",english="Robel-Akbel",name="Robel-Akbel",models=3092}, + [83]={id=978,japanese="クポフリート",english="Kupofried",name="Kupofried",models=3093}, + [84]={id=979,japanese="セルテウス",english="Selh\'teus",name="Selh\'teus",models=3094}, + [85]={id=980,japanese="ヨランオラン(UC)",english="Yoran-Oran (UC)",name="Yoran-Oran",models=3095}, + [86]={id=981,japanese="シルヴィ(UC)",english="Sylvie (UC)",name="Sylvie",models=3096}, + [87]={id=982,japanese="アブクーバ",english="Abquhbah",name="Abquhbah",models=3098}, + [88]={id=983,japanese="バラモア",english="Balamor",name="Balamor",models=3099}, + [89]={id=984,japanese="オーグスト",english="August",name="August",models=3100}, + [90]={id=985,japanese="ロスレーシャ",english="Rosulatia",name="Rosulatia",models=3101}, + [91]={id=986,japanese="テオドール",english="Teodor",name="Teodor",models=3103}, + [92]={id=987,japanese="ウルゴア",english="Ullegore",name="Ullegore",models=3105}, + [93]={id=988,japanese="マッキーチェブキー",english="Makki-Chebukki",name="Makki-Chebukki",models=3106}, + [94]={id=989,japanese="キング・オブ・ハーツ",english="King of Hearts",name="KingOfHearts",models=3107}, + [95]={id=990,japanese="モリマー",english="Morimar",name="Morimar",models=3108}, + [96]={id=991,japanese="ダラクァルン",english="Darrcuiln",name="Darrcuiln",models=3109}, + [97]={id=992,japanese="アークHM",english="AAHM",name="ArkHM",models=3113}, + [98]={id=993,japanese="アークEV",english="AAEV",name="ArkEV",models=3114}, + [99]={id=994,japanese="アークMR",english="AAMR",name="ArkMR",models=3115}, + [100]={id=995,japanese="アークTT",english="AATT",name="ArkTT",models=3116}, + [101]={id=996,japanese="アークGK",english="AAGK",name="ArkGK",models=3117}, + [102]={id=997,japanese="イロハ",english="Iroha",name="Iroha",models=3111}, + [103]={id=998,japanese="ユグナス",english="Ygnas",name="Ygnas",models=3118}, + [104]={id=1004,japanese="エグセニミルII",english="Excenmille [S]",name="Excenmille",models=3052}, + [105]={id=1005,japanese="アヤメ(UC)",english="Ayame (UC)",name="Ayame",models=3063}, + [106]={id=1006,japanese="マート(UC)",english="Maat (UC)",name="Maat",models=3064}, --expected models + [107]={id=1007,japanese="アルド(UC)",english="Aldo (UC)",name="Aldo",models=3065}, --expected models + [108]={id=1008,japanese="ナジャ(UC)",english="Naja (UC)",name="NajaSalaheem",models=3066}, + [109]={id=1009,japanese="ライオンII",english="Lion II",name="Lion",models=3081}, + [110]={id=1010,japanese="ザイドII",english="Zeid II",name="Zeid",models=3086}, + [111]={id=1011,japanese="プリッシュII",english="Prishe II",name="Prishe",models=3082}, + [112]={id=1012,japanese="ナシュメラII",english="Nashmeira II",name="Nashmeira",models=3083}, + [113]={id=1013,japanese="リリゼットII",english="Lilisette II",name="Lilisette",models=3084}, + [114]={id=1014,japanese="テンゼンII",english="Tenzen II",name="Tenzen",models=3097}, + [115]={id=1015,japanese="ミュモルII",english="Mumor II",name="Mumor",models=3104}, + [116]={id=1016,japanese="イングリッドII",english="Ingrid II",name="Ingrid",models=3102}, + [117]={id=1017,japanese="アシェラII",english="Arciela II",name="Arciela",models=3085}, + [118]={id=1018,japanese="イロハII",english="Iroha II",name="Iroha",models=3112}, + [119]={id=1019,japanese="シャントットII",english="Shantotto II",name="Shantotto",models=3110}, +-- [120]={id=1003,japanese="コーネリア",english="Cornelia",name="Cornelia",models=3119}, --goodbye, my love + [121]={id=999,japanese="モンブロー",english="Monberaux",name="Monberaux",models=3120}, + [122]={id=1003,japanese="マツイP",english="Matsui-P",name="Matsui-P",models=3121}, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/README b/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/README new file mode 100644 index 0000000..120d9c4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/README @@ -0,0 +1,24 @@ +This script is a Avatar's Favor display addon to be used with Windower +for Final Fantasy XI. + +You can report issues, get the latest version, and view the source at: + https://github.com/svanheulen/visibleffavor-windower-addon + +The addon will display a Geomancy bubble around your avatar so that it's +possible to see the area of effect of your Avatar's Favor and Blood +Pact: Wards. Unfortunately the Geomancy bubble visuals a slightly +smaller then Favor/Ward ranges (9 yalms vs 10 yalms) but it's close +enough to still be helpful. + +The available commands are: + vf toggle/on/off + Toggles, enabled or disables the effects. + vf display all/self/favor + Sets the conditions in which the bubbles will be displayed. + all = Display for all player's avatars, all the time. + self = Display only for your avatar, all the time. + favor = Display only for your avatar, when Avatar's Favor is active. + vf effect buff/debuff + Sets the type of Geomancy bubble visual to use. + buff = Use the visual effect of enhancing Geomancy. + debuff = Use the visual effect of enfeebling Geomancy. diff --git a/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/VisibleFavor.lua b/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/VisibleFavor.lua new file mode 100644 index 0000000..14ffcde --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/VisibleFavor/VisibleFavor.lua @@ -0,0 +1,131 @@ +--[[ +Copyright © 2015, Seth VanHeulen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR 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. +--]] + +_addon.name = 'VisibleFavor' +_addon.version = '1.0.0' +_addon.command = 'visiblefavor' +_addon.commands = {'vf'} +_addon.author = 'Seth VanHeulen (Acacia@Odin)' + +config = require('config') +require('chat') +require('pack') + +model_to_effect = { + [0x10] = 0x66, -- Carbuncle + [0x11] = 0x67, -- Fenrir + [0x12] = 0x60, -- Ifrit + [0x13] = 0x63, -- Titan + [0x14] = 0x65, -- Leviathan + [0x15] = 0x62, -- Garuda + [0x16] = 0x61, -- Shiva + [0x17] = 0x64, -- Ramuh + [0x19] = 0x67, -- Diabolos + [0x1c] = 0x66, -- Cait Sith +} + +refresh = true +pet_index = 0 +favor_buff = false + +defaults = {} +defaults.enabled = true +defaults.display = 'favor' +defaults.effect = 'debuff' + +settings = config.load(defaults) + +function check_incoming_chunk(id, original, modified, injected, blocked) + if id == 0x037 then + refresh = true + elseif settings.enabled and id == 0x00E then + if refresh and settings.display ~= 'all' then + local player = windower.ffxi.get_player() + pet_index = windower.ffxi.get_mob_by_index(player.index).pet_index + if settings.display == 'favor' then + for _,buff_id in pairs(player.buffs) do + if buff_id == 431 then + favor_buff = true + break + end + favor_buff = false + end + end + end + local npc_index = original:unpack('H', 9) + if settings.display == 'all' or settings.display == 'self' and pet_index == npc_index or settings.display == 'favor' and favor_buff and pet_index == npc_index then + local npc_model = original:unpack('H', 0x33) + if model_to_effect[npc_model] then + local effect = model_to_effect[npc_model] + if settings.effect == 'debuff' then + effect = effect + 8 + end + return modified:sub(1, 38) .. string.char(effect) .. modified:sub(40) + end + end + end +end + +function visiblefavor_command(...) + local arg = {...} + for k,v in pairs(arg) do + arg[k] = arg[k]:lower() + end + if #arg == 1 and arg[1] == 'toggle' then + settings.enabled = not settings.enabled + elseif #arg == 1 and arg[1] == 'on' then + settings.enabled = true + elseif #arg == 1 and arg[1] == 'off' then + settings.enabled = false + elseif #arg == 2 and arg[1] == 'display' and arg[2] == 'all' then + settings.display = 'all' + elseif #arg == 2 and arg[1] == 'display' and arg[2] == 'self' then + settings.display = 'self' + elseif #arg == 2 and arg[1] == 'display' and arg[2] == 'favor' then + settings.display = 'favor' + elseif #arg == 2 and arg[1] == 'effect' and arg[2] == 'buff' then + settings.effect = 'buff' + elseif #arg == 2 and arg[1] == 'effect' and arg[2] == 'debuff' then + settings.effect = 'debuff' + else + windower.add_to_chat(167, 'Command usage:') + windower.add_to_chat(167, ' vf toggle/on/off') + windower.add_to_chat(167, ' vf display all/self/favor') + windower.add_to_chat(167, ' vf effect buff/debuff') + return + end + windower.add_to_chat(207, 'VisibleFavor: enable = ' .. (settings.enabled and 'yes':color(204) or 'no':color(167)) .. ', display = ' .. settings.display:color(200) .. ', effect = ' .. settings.effect:color(200)) + settings:save() +end + +windower.register_event('incoming chunk', check_incoming_chunk) +windower.register_event('addon command', visiblefavor_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/Yush/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/Yush/ReadMe.md new file mode 100644 index 0000000..3a2818a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Yush/ReadMe.md @@ -0,0 +1,177 @@ +# Yush + +A portable macro engine based on customizable Lua files. Triggers faster than in-game macros and allow a significantly higher number of key combinations. + +### Usage + +This addon has no commands, it only works with custom Lua user files. Upon load, login or job change it tries to load one of the following files in the specified order: + +* `P:/ath/to/Windower/addons/Yush/data/Name_MAIN_SUB.lua` +* `P:/ath/to/Windower/addons/Yush/data/Name_MAIN.lua` +* `P:/ath/to/Windower/addons/Yush/data/Name.lua` +* `P:/ath/to/Windower/addons/Yush/data/binds.lua` + +The file needs to return a table. The table is a key -> action mapping, where the key is a combination of keys to press (denoted by the `+` sign) and the action can either be a Windower command or another table. If it's another table, it will open that table and new keys will be looked up in that table. + +To go back to the base level, press the button that has been defined in the `data/settings.xml` file as `ResetKey`. To go back only one level, press the button that has been defined in the same file as `BackKey`. They default to `` ` `` and `Backspace` respectively. + +### Settings + +**ResetKey** + +The key which resets the current macro set to the root set (the same that is active when the file is loaded). + +**BackKey** + +The key which resets the current macro set to the previous set. + +**Verbose** + +If true, will display the current macro set you are in. The name it displays is the same name it has in the file it loads. + +**VerboseOutput** + +Determines where the current macro set will be displayed (only effective if the *Verbose* setting is `true`). The following options are available: +* **Chat**: Will display the current macro set in the FFXI chat log. +* **Console**: Will display the current macro set in the Windower console. +* **Text** (**default**): Will display the current macro set in a text box. + +**Label** + +The properties of the text object holding the current macro set name, if *Verbose* is enabled and *VerboseOutput* set to `Text`. + +### Commands + +``` +yush reset +``` + +Resets the current macro set to the root set (the same that is active when the file is loaded). + +``` +yush back +``` + +Resets the current macro set to the previous set. + +``` +yush press [keys...] +``` + +Simulates a macro key press. This has no effect outside of *Yush* macros and is only there so you can set up commands to simulate *Yush* macro changes. + +``` +yush set <BackKey|ResetKey|Verbose> [value] +``` + +Sets the corresponding settings key to the provided value and saves it for the current character. If no value is provided, it displays the current settings. + +``` +yush save +``` + +Saves the current character's settings for all characters. + +### Includes + +Yush supports inclusion of base files, in case certain jobs share a macro structure. + +If you define a table `WAR` in a file called `WAR-include.lua` that looks like this: +```lua +WAR = { + ['Ctrl+1'] = 'input /ja "Berserk" <me>', + ['Ctrl+2'] = 'input /ja "Warcry" <me>', +} +``` + +You can make use of that file by including it in another file as follows: +```lua +include('WAR-include.lua') +``` + +It's even possible to define tables in multiple files. The order in which they are included is the order that entries will be overwritten in. So if you define a `WAR` table in both `WAR-include.lua` as well as the file you're including it in (`Arcon_THF.lua` in this example), the table would contain entries from both files without any necessary functions or special syntax, where duplicate entries from `Arcon_THF.lua` would take priority. Simply define the table twice and values will be overwritten in the order they appear in in the file. + +Following is another example. This piece is from `WAR-include.lua`: +```lua +WAR = { + ['Ctrl+1'] = 'input /ja "Berserk" <me>', + ['Ctrl+2'] = 'input /ja "Warcry" <me>', +} +``` + +This is from `Arcon_THF.lua`: +```lua +include('WAR-include.lua') + +WAR = { + ['Ctrl+2'] = 'input /ja "Aggressor" <me>', +} +``` + +The result would be *Berserk* on `Ctrl+1` and *Aggressor* on `Ctrl+2`, since the include came first and defined *Warcry* on `Ctrl+2`, but then it was overwritten by the *Aggressor* definition below. + +### Logic + +Yush supports the full use of the Lua language, as well as all Windower API functions and most Lua libraries (possibly all, but they weren't all tested). For example, in the `Arcon_THF.lua` file we can disambiguate which macros to include depending on the subjob: + +```lua +local sub = windower.ffxi.get_player().sub_job +if sub == 'WAR' then + include('WAR-include.lua') +elseif sub == 'DNC' then + include('DNC-include.lua') +end +``` + +### Example + +This is what an example file called `Arcon_THF.lua` in the addon's `data` folder would look like: + +```lua +WAR = { + ['Ctrl+2'] = 'input /ja "Provoke" <me>', + ['Ctrl+3'] = 'input /ja "Warcry" <me>', + ['Ctrl+4'] = 'input /ja "Aggressor" <me>', + ['Ctrl+5'] = 'input /ja "Berserk" <me>', + ['Alt+2'] = 'input /ja "Defender" <me>', +} + +JA = { + ['Ctrl+1'] = 'input /ja "Perfect Dodge" <me>', + ['Ctrl+2'] = 'input /ja "Sneak Attack" <me>', + ['Ctrl+3'] = 'input /ja "Trick Attack" <me>', + ['Ctrl+4'] = 'input /ja "Bully" <me>', + ['Ctrl+5'] = 'input /ja "Hide" <me>', + ['Alt+2'] = WAR, -- Goes to WAR sub table + ['Alt+4'] = 'input /ja "Collaborator" <stpt>', + ['Alt+3'] = 'input /ja "Flee" <me>', +} + +Magic = { + ['Ctrl+2'] = 'input /ma "Utsusemi: Ichi" <me>', + ['Ctrl+3'] = 'input /ma "Utsusemi: Ni" <me>', + ['Alt+2'] = 'input /ma "Monomi: Ichi" <me>', + ['Alt+3'] = 'input /ma "Tonko: Ni" <me>', +} + +WS = { + ['Ctrl+2'] = 'input /ja "Assassin\'s Charge" <me>', + ['Ctrl+3'] = 'input /ws "Aeolian Edge" <t>', + ['Alt+2'] = 'input /ws "Exenterator" <t>', + ['Alt+3'] = 'input /ws "Mercy Stroke" <t>', + ['Alt+4'] = 'input /ws "Evisceration" <t>', +} + +return { + ['Ctrl+1'] = 'input /ja "Perfect Dodge" <me>', + ['Ctrl+2'] = 'autoset', -- Custom alias, equips current idle set according to variables + ['Ctrl+3'] = 'set Regen', -- Custom alias, equips Regen set + ['Ctrl+4'] = 'set Magical', -- Custom alias, equips PDT set + ['Ctrl+5'] = 'set Physical', -- Custom alias, equips MDT set + ['Ctrl+9'] = 'var treasurehunter nil; autoset', + ['Ctrl+0'] = 'var treasurehunter TreasureHunter; autoset', + ['Alt+2'] = JA, -- Goes to JA sub table + ['Alt+3'] = Magic, -- Goes to Magic sub table + ['Alt+5'] = WS, -- Goes to WS sub table +} +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/Yush/Yush.lua b/Data/DefaultContent/Libraries/addons/addons/Yush/Yush.lua new file mode 100644 index 0000000..0bee580 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/Yush/Yush.lua @@ -0,0 +1,422 @@ +_addon.author = 'Arcon' +_addon.version = '2.2.0.0' +_addon.language = 'English' +_addon.command = 'yush' + +require('luau') +require('logger') +texts = require('texts') + +_innerG = {} +for k, v in pairs(_G) do + rawset(_innerG, k, v) +end +_innerG._innerG = nil +_innerG._G = _innerG +_innerG._binds = {} +_innerG._names = {} + +_innerG.include = function(path) + local full_path = '%sdata/%s':format(windower.addon_path, path) + + local file = loadfile(full_path) + if not file then + warning('Include file %s not found.':format(path)) + return + end + + setfenv(file, _innerG) + file() +end + +setmetatable(_innerG, { + __index = function(g, k) + local t = rawget(rawget(g, '_binds'), k) + if not t then + t = {} + rawset(rawget(g, '_binds'), k, t) + rawset(rawget(g, '_names'), t, k) + end + return t + end, + __newindex = function(g, k, v) + local t = rawget(rawget(g, '_binds'), k) + if t and type(v) == 'table' then + for k, v in pairs(v) do + t[k] = v + end + else + rawset(rawget(g, '_binds'), k, v) + if type(v) == 'table' then + rawset(rawget(g, '_names'), v, k) + end + end + end +}) + +defaults = {} +defaults.ResetKey = '`' +defaults.BackKey = 'backspace' +defaults.Verbose = false +defaults.VerboseOutput = 'Text' +defaults.Label = {} + +settings = config.load(defaults) + +label = texts.new(settings.Label, settings) + +binds = {} +names = {} +current = binds +stack = L{binds} +keys = S{} + +output = function() + if settings.Verbose then + names[current] = names[current] or 'Unnamed ' .. tostring(current):sub(8) + + if settings.VerboseOutput == 'Text' then + label:text(names[current]) + elseif settings.VerboseOutput == 'Chat' then + log('Changing into macro set %s.':format(names[current])) + elseif settings.VerboseOutput == 'Console' then + print('Changing into macro set %s.':format(names[current])) + end + end +end + +reset = function() + current = binds + stack = L{binds} + output() +end + +back = function() + if stack:length() == 1 then + current = binds + else + current = stack[stack:length() - 1] + stack:remove() + end + output() +end + +check = function(keyset) + keyset = keyset or keys + for key, val in pairs(current) do + if key <= keyset then + if type(val) == 'string' then + windower.send_command(val) + elseif type(val) == 'function' then + val() + else + current = val + stack:append(current) + output() + end + + return true + end + end + + return false +end + +parse_binds = function(fbinds, top) + top = top or binds + + rawset(names, top, rawget(_innerG._names, fbinds)) + for key, val in pairs(fbinds) do + key = S(key:split('+')):map(string.lower) + if type(val) == 'string' or type(val) == 'function' then + rawset(top, key, val) + else + local sub = {} + rawset(top, key, sub) + parse_binds(val, sub) + end + end +end + +windower.register_event('load', 'login', 'job change', 'logout', function() + local player = windower.ffxi.get_player() + local file, path, filename, filepath, err + local basepath = windower.addon_path .. 'data/' + if player then + for filepath_template in L{ + {path = 'name_main_sub.lua', format = '%s\'s %s/%s'}, + {path = 'name_main.lua', format = '%s\'s %s'}, + {path = 'name.lua', format = '%s\'s'}, + {path = 'binds.lua', format = '"binds"'}, + }:it() do + path = filepath_template.format:format(player.name, player.main_job, player.sub_job or '') + filename = filepath_template.path:gsub('name', player.name):gsub('main', player.main_job):gsub('sub', player.sub_job or '') + filepath = basepath .. filename + if windower.file_exists(filepath) then + file, err = loadfile(filepath) + break + end + end + end + + if file and not err then + _innerG._names = {} + _innerG._binds = {} + binds = {} + names = {} + keys = S{} + + setfenv(file, _innerG) + local root = file() + if not root then + _innerG._names = {} + _innerG._binds = {} + error('Malformatted %s Lua file: no return value.':format(path)) + return + end + + _innerG._names[root] = _innerG._names[root] or 'Root' + parse_binds(root) + reset() + + print('Yush: Loaded %s Lua file':format(path)) + elseif err then + print('\nYush: Error loading file: '..err:gsub('\\','/')) + elseif player then + print('Yush: No matching file found for %s (%s%s)':format(player.name, player.main_job, player.sub_job and '/' .. player.sub_job or '')) + + end +end) + +dikt = { -- Har har + [1] = 'esc', + [2] = '1', + [3] = '2', + [4] = '3', + [5] = '4', + [6] = '5', + [7] = '6', + [8] = '7', + [9] = '8', + [10] = '9', + [11] = '0', + [12] = '-', + [13] = '=', + [14] = 'backspace', + [15] = 'tab', + [16] = 'q', + [17] = 'w', + [18] = 'e', + [19] = 'r', + [20] = 't', + [21] = 'y', + [22] = 'u', + [23] = 'i', + [24] = 'o', + [25] = 'p', + [26] = '[', + [27] = ']', + [28] = 'enter', + [29] = 'ctrl', + [30] = 'a', + [31] = 's', + [32] = 'd', + [33] = 'f', + [34] = 'g', + [35] = 'h', + [36] = 'j', + [37] = 'k', + [38] = 'l', + [39] = ';', + [40] = '\'', + [41] = '`', + [42] = 'shift', + [43] = '\\', + [44] = 'z', + [45] = 'x', + [46] = 'c', + [47] = 'v', + [48] = 'b', + [49] = 'n', + [50] = 'm', + [51] = ',', + [52] = '.', + [53] = '/', + [54] = nil, + [55] = 'num*', + [56] = 'alt', + [57] = 'space', + [58] = nil, + [59] = 'f1', + [60] = 'f2', + [61] = 'f3', + [62] = 'f4', + [63] = 'f5', + [64] = 'f6', + [65] = 'f7', + [66] = 'f8', + [67] = 'f9', + [68] = 'f10', + [69] = 'num', + [70] = 'scroll', + [71] = 'num7', + [72] = 'num8', + [73] = 'num9', + [74] = 'num-', + [75] = 'num4', + [76] = 'num5', + [77] = 'num6', + [78] = 'num+', + [79] = 'num1', + [80] = 'num2', + [81] = 'num3', + [82] = 'num0', + + [199] = 'home', + [200] = 'up', + [201] = 'pageup', + [202] = nil, + [203] = 'left', + [204] = nil, + [205] = 'right', + [206] = nil, + [207] = 'end', + [208] = 'down', + [209] = 'pagedown', + [210] = 'insert', + [211] = 'delete', + [219] = 'win', + [220] = 'rwin', + [221] = 'apps', +} + +windower.register_event('keyboard', function(dik, down) + local key = dikt[dik] + if not key then + return + end + + if not down then + keys:remove(key) + return + end + + if not keys:contains(key) then + keys:add(key) + + if not windower.ffxi.get_info().chat_open then + if key == settings.ResetKey then + reset() + return true + elseif key == settings.BackKey then + back() + return true + end + end + + return check() + end +end) + +windower.register_event('prerender', function() + if settings.Verbose and settings.VerboseOutput == 'Text' then + label:show() + else + label:hide() + end +end) + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + local args = {...} + + if command == 'reset' then + reset() + + elseif command == 'back' then + back() + + elseif command == 'press' then + check(S(args):map(string.lower)) + + elseif command == 'set' then + if not args[1] then + error('Specify a settings category.') + return + end + + local category = args[1]:lower() + local param = args[2] and args[2]:lower() or nil + + if category == 'verbose' then + if param == 'true' then + settings.Verbose = true + elseif param == 'false' then + settings.Verbose = false + elseif param == 'toggle' then + settings.Verbose = not settings.Verbose + else + log('Verbose settings are %s.':format(settings.Verbose and 'on' or 'off')) + return + end + + elseif category == 'backkey' then + if not param then + log('Current "Back" key: %s':format(settings.BackKey)) + return + elseif not table.find(param) then + error('Key %s unknown.':format(param)) + return + else + settings.BackKey = param + end + + elseif category == 'resetkey' then + if not param then + log('Current "Reset" key: %s':format(settings.ResetKey)) + return + elseif not table.find(param) then + error('Key %s unknown.':format(param)) + return + else + settings.ResetKey = param + end + + elseif category == 'verboseoutput' then + if not param then + log('Currently verbose mode outputs to %s.':format( + settings.VerboseOutput == 'Text' and 'a text object' + or settings.VerboseOutput == 'Chat' and 'the chat log' + or settings.VerboseOutput == 'Console' and 'the console' + )) + return + elseif param == 'text' then + settings.VerboseOutput = 'Text' + elseif param == 'chat' then + settings.VerboseOutput = 'Chat' + elseif param == 'console' then + settings.VerboseOutput = 'Console' + end + + end + + config.save(settings) + + elseif command == 'save' then + config.save(settings, 'all') + + end +end) + +--[[ +Copyright © 2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/addons.xml b/Data/DefaultContent/Libraries/addons/addons/addons.xml new file mode 100644 index 0000000..d5ed626 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/addons.xml @@ -0,0 +1,861 @@ +<?xml version="1.0" encoding="utf-8"?> +<addons> + <addon> + <name>Example</name> + <author>Iryoku</author> + <description> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dolor est, + tristique vitae lacinia quis, vulputate sit amet mi. Duis lacinia, massa + at faucibus sagittis. + </description> + <bugtracker>https://github.com/IryokuChevalier/Lua/issues</bugtracker> + <support>http://forums.windower.net/topic/21775-random-question-thread/</support> + </addon> + <addon> + <name>AEcho</name> + <description>Automatically uses echo drops when you get silenced. Also, uses send to send a message to an alt that you got debuffed.</description> + <author>Nitrous (Shiva)</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + </addon> + <addon> + <name>AnnounceTarget</name> + <author>JoshK6656, Sechs</author> + <description>Creates special manual or automatic chat messages conveying advanced information about some specific targets</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Asura/Sechs</support> + </addon> + <addon> + <name>AnsweringMachine</name> + <author>Byrth</author> + <description>Stores tells that you receive for later recall.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>AutoControl</name> + <description>Automated automaton equipment setting and burden tracker.</description> + <author>Nitrous (Shiva)</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + </addon> + <addon> + <name>autoenterkey</name> + <author>Chiaia</author> + <description>Automatically hits the enter key twice when first starting so you don't timeout on the warning message.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>autoinvite</name> + <author>r3g1stry</author> + <description>Automatically invites players when sent a tell with a specified keyword.</description> + <bugtracker>https://github.com/r3g1stry/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/user/R3G1STRY</support> + </addon> + <addon> + <name>AutoJoin</name> + <author>Arcon</author> + <description>Automatically joins or declines party invites. Configurable with blacklist/whitelist mode and auto-decline settings.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>AutoRA</name> + <author>Banggugyangu</author> + <description>Causes Ranged Attacks to behave the same as melee Auto-Attack.</description> + <bugtracker>https://github.com/banggugyangu/Lua/issues</bugtracker> + </addon> + <addon> + <name>AzureSets</name> + <description>Automated blue magic spell setting.</description> + <author>Nitrous (Shiva)</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + </addon> + <addon> + <name>BarFiller</name> + <description>Displays an experience bar above the Help Window.</description> + <author>Morath</author> + <support>http://forums.windower.net/index.php?/topic/807-barfiller</support> + <bugtracker>https://github.com/Morath86/Issues</bugtracker> + </addon> + <addon> + <name>BattleMod</name> + <author>Byrth</author> + <description>Customizes battle chat messages.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>BattleStations</name> + <author>Sjshovan (Apogee)</author> + <description>Change or remove the default battle music.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Blist</name> + <author>Ikonic</author> + <description>More detailed blist with tiered display options. Allows for blist to be active on any or all of several chat types.</description> + <bugtracker>https://github.com/Icinoki/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Ragnarok/Ikonic</support> + </addon> + <addon> + <name>bluGuide</name> + <author>Anissa</author> + <description>Change your blue magic on the fly with this clickable menu. Sort your spells by traits, buffs, procs, and more.</description> + <bugtracker>https://github.com/AniAniBobbani/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Cerberus/Anissa</support> + </addon> + <addon> + <name>boxdestroyer</name> + <author>Seth VanHeulen (Acacia@Odin)</author> + <description>Tracks and displays info about possible combinations for treasure caskets.</description> + <bugtracker>https://github.com/svanheulen/boxdestroyer-windower-addon/issues</bugtracker> + <support>http://www.ffxiah.com/player/Odin/Acacia</support> + </addon> + <addon> + <name>Cancel</name> + <author>Byrth</author> + <description>Mimics the cancel plugin, but also accepts buff names instead of just IDs.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>cBlock</name> + <description>Blacklist addon for FFOChat.</description> + <author>Nitrous (Shiva)</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + </addon> + <addon> + <name>CellHelp</name> + <author>Krizz and Balloon</author> + <description>Tracks cells that are needed, displays your position in the lot order, and creates the appropriate LL profiles.</description> + <bugtracker>https://github.com/tehkrizz/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bahamut/Krizz</support> + </addon> + <addon> + <name>chars</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Lets you input special chars using simple tags (ex.: <note> for ♪).</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>ChatLink</name> + <author>Arcon</author> + <description>Allows opening links posted into the FFXI chat.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>ChatPorter</name> + <author>Ikonic</author> + <description>Displays tell, party, and linkshell chat to alternate character and optional textbox. Also, allows you to reply from either character.</description> + <bugtracker>https://github.com/Icinoki/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Ragnarok/Ikonic</support> + </addon> + <addon> + <name>Clock</name> + <author>Arcon</author> + <description>Displays an on-screen clock in a custom format with options to display several different time zones.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>ConsoleBG</name> + <author>Arcon</author> + <description>Displays a dark (by default) background behind the Windower console to make it more readable.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>digger</name> + <author>Seth VanHeulen (Acacia@Odin)</author> + <description>Displays Chocobo digging accuracy, fatigue and remaining greens after each dig. Also shows dig "recast" timer using the Timers plugin.</description> + <bugtracker>https://github.com/svanheulen/digger-windower-addon/issues</bugtracker> + <support>http://www.ffxiah.com/player/Odin/Acacia</support> + </addon> + <addon> + <name>Dimmer</name> + <author>Chiaia</author> + <description>Helps warp you to Reisenjima using (Dim) Rings.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Distance</name> + <author>Arcon</author> + <description>Shows the distance to your current target.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>DistancePlus</name> + <author>Sammeh</author> + <description>Replacement for Distance Addon to show distance to current target and distance from pet. Also color coordinates distance based on job and abilities, such as magic, ranged attacks, and JA's. Also shows height differential for fights requiring height delta to avoid AOE.</description> + <bugtracker>https://github.com/SammehFFXI/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Quetzalcoatl/Sammeh</support> + </addon> + <addon> + <name>DressUp</name> + <author>Cair</author> + <description>Emulates BlinkMeNot functionality. Allows for customization of gear display for you or anyone else.</description> + <bugtracker>https://github.com/cairface/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>DynamisHelper</name> + <description>Displays a timer when a mob is procced, tracks currency obtained, and can create a LL to lot all currency.</description> + <bugtracker>https://github.com/tehkrizz/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bahamut/Krizz</support> + </addon> + <addon> + <name>EmpyPopTracker</name> + <author>Dean James (Xurion of Bismarck)</author> + <description>Tracks items and key items for popping various NMs, such as Briareus, Apademak and Warder of Courage.</description> + <bugtracker>https://github.com/xurion/ffxi-empy-pop-tracker/issues</bugtracker> + <support>https://www.ffxiah.com/forum/topic/54376/empyrean-pop-tracker-addon-10-years-late/</support> + </addon> + <addon> + <name>Enemybar</name> + <author>mmckee</author> + <description>This is an addon for Windower4 for FFXI. It creates a big health bar for the target to make it easy to see.</description> + <bugtracker>https://github.com/mjmckee/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Ragnarok/Pesche</support> + </addon> + <addon> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <name>enternity</name> + <description>Enters "Enter" automatically when prompted during a cutscene or when talking to NPCs. It will not skip choice dialog boxes.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>Equipviewer</name> + <author>Tako, Rubenator</author> + <description>Displays current equipment grid on screen. Also can show current Ammo count and current Encumbrance.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>eval</name> + <author>Aureus</author> + <description>Allows developers to run arbitrary lua code in the console.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>FastCS</name> + <author>Cair</author> + <description>Dramatically speeds up cutscenes by disabling the frame rate cap. Requires the config plugin.</description> + <bugtracker>https://github.com/cairface/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>FFOColor</name> + <author>Nitrous (Shiva)</author> + <description>Allows you to show FFOChat text in one of the 5 game chat channels. As well as specify colors for the text</description> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>findAll</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Searches items stored on all your characters and allows live on-screen tracking of specified items.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>Gametime</name> + <author>Omnys@Valefor</author> + <description>Displays game time, game-week, moon information, and travel times.</description> + <bugtracker>https://github.com/thatdudegrim/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Valefor/Omnys</support> + </addon> + <addon> + <name>GearSwap</name> + <author>Byrth</author> + <description>Changes gear in response to player actions. [[Uses Packets.]]</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + </addon> + <addon> + <name>Highlight</name> + <description>Highlights party members names when mentioned in the chatlog.</description> + <author>Balloon</author> + <bugtracker>https://github.com/Podginator/Lua/issues</bugtracker> + <support>Podginator@gmail.com</support> + </addon> + <addon> + <name>instaLS</name> + <author>Byrth</author> + <description>Enables instant linkshell chat after zoning.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>InfoBar</name> + <author>Kenshi</author> + <description>Displays a configurable bar showing information on your targets.</description> + <bugtracker>https://github.com/KenshiDRK/Lua/issues</bugtracker> + <support>https://www.ffxiah.com/player/Ragnarok/Kenshi</support> + </addon> + <addon> + <name>InfoReplacer</name> + <author>Cair</author> + <description>Replaces outgoing text prefixed by % with respective game information.</description> + <bugtracker>https://github.com/cairface/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Itemizer</name> + <author>Ihina</author> + <description>Use commands to move items and gear between bags.</description> + <bugtracker>https://github.com/Jayjs20/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bismarck/Ihina</support> + </addon> + <addon> + <name>JobChange</name> + <author>Sammeh</author> + <description>Command line Job Changer</description> + <bugtracker>https://github.com/SammehFFXI/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Quetzalcoatl/Sammeh</support> + </addon> + <addon> + <name>latentchecker</name> + <author>byrth,smd111</author> + <description>Checks weapon skill points of weapon skill trial weapons and mythic weapons</description> + <bugtracker>https://github.com/smd111/LatentChecker/issues</bugtracker> + <support>https://github.com/smd111/LatentChecker/issues</support> + </addon> + <addon> + <name>Linker</name> + <author>Arcon</author> + <description>Allows opening links to certain websites from within the game, with an optional search parameter.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Logger</name> + <author>Arcon</author> + <description>Logs the chat log to a file.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Lookup</name> + <author>Karuberu</author> + <description>Lookup website information from an in-game command. Accepts auto-translate text and selectors like "<t>". Opens in a browser window.</description> + <bugtracker>https://github.com/Karuberu/Lua/issues</bugtracker> + <support>https://github.com/Karuberu/Lua/issues</support> + </addon> + <addon> + <name>Lottery</name> + <author>Arcon</author> + <description>Automatically passes an item on all accounts if lotted by another.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>MacroChanger</name> + <author>Banggugyangu</author> + <description>Automatically switches Macro Book and Page according to job changes.</description> + <bugtracker>https://github.com/banggugyangu/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>MobCompass</name> + <description>A compass to show your position relative to the target (not players) for geo and has a setup for Sneak attack</description> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>MountMuzzle</name> + <author>Sjshovan (Apogee)</author> + <description>Change or remove the default mount music.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>MountRoulette</name> + <author>Dean James (Xurion of Bismarck)</author> + <description>Summon a mount at random, similar to Mount Roulette in FFXIV.</description> + <bugtracker>https://github.com/xurion/ffxi-mount-roulette/issues</bugtracker> + <support>https://www.ffxiah.com/forum/topic/52094/randommount-a-random-mount-selector-for-windower/</support> + </addon> + <addon> + <name>NoCampaignMusic</name> + <description>Prevents campaign battle music from playing in Shadowreign areas.</description> + <author>Dean James (Xurion of Bismarck)</author> + <bugtracker>https://github.com/xurion/ffxi-no-campaign-music/issues</bugtracker> + <support>https://www.ffxiah.com/forum/topic/52507/sick-of-campaign-music-prevent-it-with-this-addon/</support> + </addon> + <addon> + <name>Nostrum</name> + <description>Creates a click-able on-screen macro to help avoid targeting problems while curing.</description> + <author>trv</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/trv6/Lua/issues</bugtracker> + </addon> + <addon> + <name>NyzulHelper</name> + <author>Glarin of Asura</author> + <description>Tracks and displays the Current Floor, Time Remaining, Objective, Floors Completed, Reward Rate, and Potenial Tokens.</description> + <bugtracker>https://github.com/GlarinAsura/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>obiaway</name> + <description>Automatically collect and remove elemental obi based on day/weather/storm conditions.</description> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Omen</name> + <author>Braden, Sechs</author> + <description>Tracks primary and secondary objectives within the Omen event in a customizable on-screen window</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Asura/Sechs</support> + </addon> + <addon> + <name>OhShi</name> + <description>Keeps track of various event related things. Such as, VW proc messages, mob casting, mob tp moves, TH procs and cor rolls, as well as others.</description> + <author>Nitrous (Shiva)</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + </addon> + <addon> + <name>Organizer</name> + <description>A multi-purpose inventory management solution. Similar to GearCollector.</description> + <author>Byrth, Rooks</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + </addon> + <addon> + <name>pet_fix</name> + <author>Byrth</author> + <description>Temporary addon that fixes a null pointer animation error with pets that is causing crashes.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>PetSchool</name> + <author>Banggugyangu</author> + <description>A helper addon for PUPs using spellcast, it informs spellcast of pet casting (healing or nuking).</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>PetTP</name> + <author>SnickySnacks</author> + <description>Tracks pet vitals (HP/TP/MP)</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>plasmon</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>tracks plasm, killed mobs and dropped airlixirs during a delve.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>plugin_manager</name> + <author>Byrth</author> + <description>Allows you to specify which plugins and addons will be used with which characters.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>PointWatch</name> + <author>Byrth</author> + <description>Allows you to monitor your XP/CP gains and keep track of the Dynamis time limit.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>porter</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Shows the slips' items highlighting those that are stored.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>Pouches</name> + <author>Omnys@Valefor</author> + <description>Simple addon to use all of a named item in player's main inventory, like //pouches bead pouch.</description> + <bugtracker>https://github.com/thatdudegrim/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Valefor/Omnys</support> + </addon> + <addon> + <name>RAWR</name> + <author>Genoxd</author> + <description>Plays a sound effect when your unity leader says something about dragons spawning.</description> + <bugtracker>https://github.com/geno3302/Lua</bugtracker> + <support>http://www.ffxiah.com/player/Lakshmi/Genoxd</support> + </addon> + <addon> + <name>reive</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Tracks exp, bayld, momentum scores and bonuses during a reive.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>Remember</name> + <author>Byrth</author> + <description>Should request spawn packets for players / mobile NPCs that failed to spawn.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Respond</name> + <author>Byrth</author> + <description>Respond to tells and FFOchat PMs using //r.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Rhombus</name> + <description>Creates a highly customizable, click-able, on-screen menu.</description> + <author>trv</author> + <support>https://discord.gg/aUrHCvk</support> + <bugtracker>https://github.com/trv6/Lua/issues</bugtracker> + </addon> + <addon> + <name>ROE</name> + <author>Cair</author> + <description>Lets you save your Records of Eminence objectives to profiles for easily swapping out objectives.</description> + <bugtracker>https://github.com/cairface/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>RollTracker</name> + <author>Balloon</author> + <description>Simplifies Cor rolls, tells you the bonus they give, stops you from doubling up on lucky rolls and reports your chance to bust.</description> + <bugtracker>https://github.com/Podginator/Lua/issues</bugtracker> + <support>Podginator@gmail.com</support> + </addon> + <addon> + <name>salvage2</name> + <author>Krizz</author> + <description>Displays pathos that have not been removed in Salvage 2. Displays zone timer.</description> + <bugtracker>https://github.com/tehkrizz/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bahamut/Krizz</support> + </addon> + <addon> + <name>SATACast</name> + <author>Banggugyangu</author> + <description>Informs Spellcast about changes to Sneak Attack and Trick Attack status.</description> + <bugtracker>https://github.com/banggugyangu/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Scoreboard</name> + <author>Suji</author> + <description>Basic in-game damage parser. It displays live DPS and works even when chat filters are enabled.</description> + <bugtracker>https://github.com/jerryhebert/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>setbgm</name> + <author>Seth VanHeulen (Acacia@Odin)</author> + <description>Set your background/battle/other music to any music in the game.</description> + <bugtracker>https://github.com/svanheulen/setbgm-windower-addon/issues</bugtracker> + <support>http://www.ffxiah.com/player/Odin/Acacia</support> + </addon> + <addon> + <name>Shortcuts</name> + <author>Byrth</author> + <description>Applys spellcast-like command completion (interpretation and target completion) to commands. Includes emotes, /check, and /pcmd.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Silence</name> + <author>Ihina</author> + <description>Eliminates the flood of equipment change messages.</description> + <bugtracker>https://github.com/Jayjs20/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bismarck/Ihina</support> + </addon> + <addon> + <name>ShoutHelper</name> + <author>Jandel</author> + <description>Help managing party job in an alliance shout. Displays a in-game editable list (initially empty) in which one can set job and players</description> + <bugtracker>https://github.com/Jandella/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Ragnarok/Jandel</support> + </addon> + <addon> + <name>Send</name> + <author>Byrth</author> + <description>Sends commands between windower instances using IPC.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>SetTarget</name> + <author>Arcon</author> + <description>Sets the target to a given ID.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>SpeedChecker</name> + <author>Arcon</author> + <description>Displays a small box indicating your current movement speed modifier (+/- X%).</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/b275nMv</support> + </addon> + <addon> + <name>SpellCheck</name> + <author>Damien Dennehy (Zubis@Asura)</author> + <description>This addon lists spells you haven't unlocked yet.</description> + <bugtracker>https://github.com/DamienDennehy/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Asura/Zubis</support> + </addon> + <addon> + <name>SpellBook</name> + <author>Sigil Baram (Ravlyn@Asura)</author> + <description>List unknown spells by job or category. Can also limit results by level or spent jp required by the spell.</description> + <bugtracker>https://github.com/sigilbaram/SpellBook/issues</bugtracker> + <support>https://discord.gg/b275nMv</support> + </addon> + <addon> + <name>StaggerTrack</name> + <author>Nitrous (Shiva)</author> + <description>Catches voidwatch weakness messages and prints them to a textbox in case you miss them in the battle spam.</description> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + <support>https://discord.gg/b275nMv</support> + </addon> + <addon> + <name>STNA</name> + <author>Nitrous (Shiva)</author> + <description>One-button status removal for dual boxing.</description> + <bugtracker>https://github.com/nitrous24/Lua/issues</bugtracker> + <support>https://discord.gg/b275nMv</support> + </addon> + <addon> + <name>stopwatch</name> + <author>Patrick Finnigan (Puhfyn@Ragnarok)</author> + <description>Timer that counts up and allows you to track time since events e.g. spawns.</description> + <bugtracker>https://github.com/finnigantime/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Ragnarok/Puhfyn</support> + </addon> + <addon> + <name>StratHelper</name> + <author>Ihm</author> + <description>A simple helper addon for Spellcast for Scholar Stratagems. It will automatically calculate the number of stratagems you have and push them into spellcast variables.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Stubborn</name> + <author>Arico</author> + <description>An addon to block accidental calls for help.</description> + <bugtracker>https://github.com/ianandersonlol/stubborn/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>subTarget</name> + <author>Sebyg666</author> + <description>Enhances the usage of the send addon by allowing a stal or stpc or st target to be sent as the spell recipient. This removes the need for multiple macros for the same spell.</description> + <bugtracker>https://github.com/sebyg666/Lua/issues</bugtracker> + <support>https://github.com/sebyg666/Lua/tree/4.1-dev/addons/SubTarget</support> + </addon> + <addon> + <name>TargetInfo</name> + <author>Arcon</author> + <description>Displays information about your current target in memory.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Text</name> + <author>Arcon</author> + <description>Allows creating and manipulating on-screen text objects through Windower commands.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Treasury</name> + <author>Arcon</author> + <description>Lots or passes items based on configurable lists, drops unwanted items from the inventory and automatically stacks items when they drop.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>thtracker</name> + <author>Krizz</author> + <description>Displays latest TH level from chat.</description> + <bugtracker>https://github.com/tehkrizz/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bahamut/Krizz</support> + </addon> + <addon> + <name>timestamp</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Prefixes any chat message with a timestamp. Based on Timestamp plugin.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>TParty</name> + <author>Arcon</author> + <description>Shows a target's HP percentage next to their health bar as well as party/alliance members's TP.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>translate</name> + <author>Byrth</author> + <description>Gives a rough JP->EN translation using the resources and custom dictionaries.</description> + <bugtracker>https://github.com/Byrth/Lua-Byrth/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>TreasurePool</name> + <author>Kenshi</author> + <description>Replacement for the Treasure Pool thats shows time till the item auto drop and who is the current winning lot.</description> + <bugtracker>https://github.com/KenshiDRK/Lua/issues</bugtracker> + <support>https://www.ffxiah.com/player/Ragnarok/Kenshi</support> + </addon> + <addon> + <name>Update</name> + <author>Arcon</author> + <description>Updates and reloads all plugins and addons when typing //update.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>VisibleFavor</name> + <author>Seth VanHeulen (Acacia@Odin)</author> + <description>Displays a GEO bubble around your avatar to visualize Favor/Ward ranges.</description> + <bugtracker>https://github.com/svanheulen/visiblefavor-windower-addon/issues</bugtracker> + <support>http://www.ffxiah.com/player/Odin/Acacia</support> + </addon> + <addon> + <name>vwhl</name> + <author>Giuliano Riccio (Zohno@Phoenix - R.I.P)</author> + <description>Redirects the nm's weaknesses (VW or Abyssea) to the "tell" stream so that they can be held using the chat filters' function and highlights the important info.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Phoenix/Zohno</support> + </addon> + <addon> + <name>XIVBar</name> + <author>SirEdeonX (Edeon@Asura)</author> + <description>Displays HP, MP and TP bars on the center of the screen, akin to FFXIV, for easy tracking</description> + <bugtracker>https://github.com/SirEdeonX/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Asura/Edeon</support> + </addon> + <addon> + <name>Yush</name> + <author>Arcon</author> + <description>A file-based macro engine</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>zonetimer</name> + <author>Ihina</author> + <description>Displays the amount of time one has been in zone. Original by Krellion.</description> + <bugtracker>https://github.com/Jayjs20/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Bismarck/Ihina</support> + </addon> + <addon> + <name>temps</name> + <author>Mojo</author> + <description>Makes buying temporary items from escha npcs much easier.</description> + <bugtracker>https://github.com/Snapsmojo/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Fenrir/Snaps</support> + </addon> + <addon> + <name>CapeTrader</name> + <author>Lygre, Burntwaffle</author> + <description>Automates the ambuscade cape augmentation process.</description> + <bugtracker>https://github.com/OdinBurntwaffle/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Odin/Burntwaffle</support> + </addon> + <addon> + <name>craft</name> + <author>Mojo</author> + <description>A Final Fantasy XI Crafting Addon</description> + <bugtracker>https://github.com/Snapsmojo/Lua/issues</bugtracker> + <support>http://www.ffxiah.com/player/Fenrir/Snaps</support> + </addon> + <addon> + <name>Checkparam</name> + <author>Centurio aka from20020516</author> + <description>Displays the total of property of target player's current equipments.</description> + <bugtracker>https://github.com/from20020516/Lua/issues</bugtracker> + <support>https://twitter.com/from20020516</support> + </addon> + <addon> + <name>Bonanza</name> + <author>Centurio aka from20020516</author> + <description>Purchace and Judge Bonanza Marbles.</description> + <bugtracker>https://github.com/from20020516/Lua/issues</bugtracker> + <support>https://twitter.com/from20020516</support> + </addon> + <addon> + <name>Trusts</name> + <author>Centurio aka from20020516</author> + <description>Save and Summon trust sets.</description> + <bugtracker>https://github.com/from20020516/Lua/issues</bugtracker> + <support>https://twitter.com/from20020516</support> + </addon> + <addon> + <name>MyHome</name> + <author>Centurio aka from20020516</author> + <description>Automatically choose and use a warp spell or an item.</description> + <bugtracker>https://github.com/from20020516/Lua/issues</bugtracker> + <support>https://twitter.com/from20020516</support> + </addon> + <addon> + <name>Debuffed</name> + <author>Auk</author> + <description>Tracks and displays debuffs on your current target.</description> + <bugtracker>https://github.com/aukon/Lua/issues</bugtracker> + <support>https://discord.gg/aUrHCvk</support> + </addon> + <addon> + <name>Tab</name> + <author>Centurio aka from20020516</author> + <description>Simple addon Replace Tab key input to <stnpc> or X+Tab to <stpc>. </description> + <bugtracker>https://github.com/from20020516/Lua/issues</bugtracker> + <support>https://twitter.com/from20020516</support> + </addon> + <addon> + <name>EasyNuke</name> + <author>FaceDesk</author> + <description>Unified commands to handle single target and AOE nukes and cures, plus Drain/Aspir and Absorbs.</description> + <bugtracker>https://github.com/deadman80/Lua/issues</bugtracker> + <support>https://www.ffxiah.com/user/Nyarlko</support> + </addon> + <addon> + <name>giltracker</name> + <author>Sylandro</author> + <description>Displays the current gil, similar to the FFXIV Gil HUD widget.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://github.com/azamorapl</support> + </addon> + <addon> + <name>invtracker</name> + <author>Sylandro</author> + <description>Displays a grid detailing empty and filled inventory slots, similar to the FFXIV Inventory Grid HUD widget.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://github.com/azamorapl</support> + </addon> + <addon> + <name>IndiNope</name> + <author>Lili</author> + <description>Blocks graphical effects from Geomancer's Indi- spells.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://github.com/lili-ffxi</support> + </addon> + <addon> + <name>position_manager</name> + <author>Lili</author> + <description>Allows you to set a screen position per character name. Each character will be moved to that screen position on login. Requires the WinControl plugin to be installed.</description> + <bugtracker>https://github.com/Windower/Lua/issues</bugtracker> + <support>https://github.com/lili-ffxi</support> + </addon> +</addons> diff --git a/Data/DefaultContent/Libraries/addons/addons/aecho/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/aecho/ReadMe.md new file mode 100644 index 0000000..cb8ebcd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/aecho/ReadMe.md @@ -0,0 +1,16 @@ +**Author:** Ricky Gall +**Version:** 2.02 +**Description:** +Watches buffs/debuffs and sends messages to alts when you gain certain +ones. Also, automatically uses echo drops if you get silenced. + +**Abbreviation:** //aecho + +**Commands:** + 1. aecho watch <buffname> --adds buffname to the tracker + 2. aecho unwatch <buffname> --removes buffname from the tracker + 3. aecho trackalt --Toggles alt buff/debuff messages on main (this requires send addon) + 4. aecho sitrack --When sneak/invis begin wearing passes this message to your alts + 5. aecho list --lists buffs being tracked + 6. aecho toggle --Toggles off automatic echo drop usage (in case you need this off. does not remain off across loads.) +
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/aecho/aecho.lua b/Data/DefaultContent/Libraries/addons/addons/aecho/aecho.lua new file mode 100644 index 0000000..10223c0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/aecho/aecho.lua @@ -0,0 +1,129 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'AEcho' +_addon.version = '2.02' +_addon.author = 'Nitrous (Shiva)' +_addon.command = 'aecho' + +require('tables') +require('strings') +require('logger') +require('sets') +config = require('config') +chat = require('chat') +res = require('resources') + +defaults = {} +defaults.buffs = S{ "light arts","addendum: white","penury","celerity","accession","perpetuance","rapture", + "dark arts","addendum: black","parsimony","alacrity","manifestation","ebullience","immanence", + "stun","petrified","silence","stun","sleep","slow","paralyze" + } +defaults.alttrack = true +defaults.sitrack = true + +settings = config.load(defaults) + +autoecho = true + +windower.register_event('gain buff', function(id) + local name = res.buffs[id].english + for key,val in pairs(settings.buffs) do + if key:lower() == name:lower() then + if name:lower() == 'silence' and autoecho then + windower.send_command('input /item "Echo Drops" '..windower.ffxi.get_player()["name"]) + end + if settings.alttrack then + windower.send_command('send @others atc '..windower.ffxi.get_player()["name"]..' - '..name) + end + end + end +end) + +windower.register_event('incoming text', function(old,new,color) + if settings.sitrack then + local sta,ea,txt = string.find(new,'The effect of ([%w]+) is about to wear off.') + if sta ~= nil then + windower.send_command('@send @others atc '..windower.ffxi.get_player()['name']..' - '..txt..' wearing off.') + end + end + return new,color +end) + +windower.register_event('addon command', function(...) + local args = {...} + if args[1] ~= nil then + local comm = args[1]:lower() + if comm == 'help' then + local helptext = [[AEcho - Command List: + 1. aecho watch <buffname> --adds buffname to the tracker + 2. aecho unwatch <buffname> --removes buffname from the tracker + 3. aecho trackalt --Toggles alt buff/debuff messages on main (this requires send addon) + 4. aecho sitrack --When sneak/invis begin wearing passes this message to your alts + 5. aecho list --lists buffs being tracked + 6. aecho toggle --Toggles off automatic echo drop usage (in case you need this off. does not remain off across loads.)]] + for _, line in ipairs(helptext:split('\n')) do + windower.add_to_chat(207, line..chat.controls.reset) + end + elseif S{'watch','trackalt','unwatch','sitrack'}:contains(comm) then + local list = '' + local spacer = '' + if comm == 'watch' then + for i = 2, #args do + if i > 2 then spacer = ' ' end + list = list..spacer..args[i] + end + if settings.buffs[list] == nil then + settings.buffs:add(list:lower()) + notice(list..' added to buffs list.') + end + elseif comm == 'unwatch' then + for i = 2, #args do + if i > 2 then spacer = ' ' end + list = list..spacer..args[i] + end + if settings.buffs[list] ~= nil then + settings.buffs:remove(list:lower()) + notice(list..' removed from buffs list.') + end + elseif comm == 'trackalt' then + settings.alttrack = not settings.alttrack + elseif comm == 'sitrack' then + settings.sitrack = not settings.sitrack + end + settings:save() + elseif comm == 'list' then + settings.buffs:print() + elseif comm == 'toggle' then + autoecho = not autoecho + else + return + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/aecho/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/aecho/data/settings.xml new file mode 100644 index 0000000..b095981 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/aecho/data/settings.xml @@ -0,0 +1,8 @@ +<?xml version="1.1" ?> +<settings> + <global> + <alttrack>false</alttrack> + <buffs>accession, addendum: black, addendum: white, alacrity, celerity, dark arts, ebullience, immanence, light arts, manifestation, paralyze, parsimony, penury, perpetuance, petrified, rapture, silence, sleep, slow, stun</buffs> + <sitrack>true</sitrack> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/answeringmachine/README.md b/Data/DefaultContent/Libraries/addons/addons/answeringmachine/README.md new file mode 100644 index 0000000..df2e0c3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/answeringmachine/README.md @@ -0,0 +1,21 @@ +Author: Byrth +Version: 1.4 +Answering Machine with basic functionality + +Abbreviation: //am, //answeringmachine + +Commands: +* list : Lists the number of messages recorded from each person that has sent you a tell. +* play <name> : Plays available messages. Will default to playing all messages if a name is not provided. +* clear <name> : Clears available messages. Will default to clearing all messages if a name is not provided. +* help : Lists commands in game. +* msg <message> : Sets your away message, which will be sent to non-GMs the first time they send you a tell after loading the plugin or clearing messages from them. + +Purpose: +To record conversations and play them back at your leisure. + +Version History: +1.4 - Added a black/red box that appears if you have messages that you probably haven't read. +1.3 - Adjusted trim function to prevent error when dealing with 15 character names. +1.2 - Timestamps added. Massive refactoring. Outgoing tells now included. +1.1 - Version History started, fundamental recording and answering features created.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/answeringmachine/answeringmachine.lua b/Data/DefaultContent/Libraries/addons/addons/answeringmachine/answeringmachine.lua new file mode 100644 index 0000000..7a86d27 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/answeringmachine/answeringmachine.lua @@ -0,0 +1,212 @@ +_addon.commands = {'answeringmachine','am'} +_addon.name = 'AnsweringMachine' +_addon.author = 'Byrth' +_addon.version = '1.4' + + +texts = require('texts') +AM_box = texts.new('') +AM_box:size(24) + +last_activity = os.time() +unseen_message_count = 0 + +textspeed = 0 +text_list = {[1]=os.time()} +text_list[2] = text_list[1] +text_list[3] = text_list[1] +text_list[4] = text_list[1] +text_list[5] = text_list[1] +text_list[6] = text_list[1] +text_list[7] = text_list[1] +text_list[8] = text_list[1] +text_list[9] = text_list[1] +text_list[10] = text_list[1] +text_index = 1 + +recording = {} + +windower.register_event('addon command',function (...) + term = table.concat({...}, ' ') + local broken = split(term, ' ') + if broken[1] ~= nil then + if broken[1]:upper() == "CLEAR" then + if broken[2] == nil then + recording = {} + windower.add_to_chat(4,'Answering Machine>> Blanking the recordings') + elseif recording[broken[2]:upper()] then + windower.add_to_chat(4,'Answering Machine>> Deleting conversation with '..uc_first(broken[2])) + recording[broken[2]:upper()]=nil + else + windower.add_to_chat(5,'Cancel error: Could not find specified player in tell history') + end + elseif broken[1]:upper() == "LIST" then + local trig + for i,v in pairs(recording) do + windower.add_to_chat(5,#v..' exchange'..pl(#v)..' with '..uc_first(i)) + trig = true + end + if not trig then + windower.add_to_chat(5,'No exchanges recorded.') + end + elseif broken[1]:upper() == "PLAY" then + if broken[2] then + if recording[broken[2]:upper()] then + local num = #recording[broken[2]:upper()] + windower.add_to_chat(5,num..' exchange'..pl(num)..'with '..uc_first(broken[2])) + print_messages(recording[broken[2]:upper()],broken[2]) + end + else + windower.add_to_chat(4,'Answering Machine>> Playing back all messages') + for i,v in pairs(recording) do + windower.add_to_chat(5,#v..' exchange'..pl(#v)..' with '..uc_first(i)) + print_messages(v,i) + end + end + elseif broken[1]:upper() == "HELP" then + print('am clear <name> : Clears current messages, or only messages from <name> if provided') + print('am help : Lists these commands!') + print('am list : Lists the names of people who have sent you tells') + print('am msg <message> : Sets your away message, which will be sent to non-GMs only once after plugin load or message clear') + print('am play <name> : Plays current messages, or only messages from <name> if provided') + elseif broken[1]:upper() == "MSG" then + table.remove(broken,1) + if #broken ~= 0 then + away_msg=table.concat(broken,' ') + windower.add_to_chat(123,'AnsweringMachine: Message set to: '..away_msg) + end + elseif broken[1] == 'box' and broken[2] == 'pos' and tonumber(broken[3]) and tonumber(broken[4]) then + AM_box:pos(tonumber(broken[3]),tonumber(broken[4])) + end + end +end) + +windower.register_event('chat message',function(message,player,mode,isGM) + if mode==3 then + if recording[player:upper()] then + recording[player:upper()][#recording[player:upper()]+1] = {message=message,outgoing=false,timestamp=os.time(),seen=false} + else + recording[player:upper()] = {{message=message,outgoing=false,timestamp=os.time(),seen=false}} + if away_msg and not isGM then + windower.send_command('@input /tell '..player..' '..away_msg) + end + end + unseen_message_count = unseen_message_count + 1 + end +end) + +windower.register_event('outgoing chunk',function(id,original,modified,injected,blocked) + if not blocked and id == 0x0B6 then + local name = trim(original:sub(0x6,0x14)) + local message = trim(original:sub(0x15)) + if recording[name:upper()] then + recording[name:upper()][#recording[name:upper()]+1] = {message=message,outgoing=true,timestamp=os.time(),seen=true} + else + recording[name:upper()] = {{message=message,outgoing=true,timestamp=os.time(),seen=true}} + end + end +end) + +windower.register_event('incoming text',function(org,mod,org_m,mod_m,blocked) + if not blocked then + text_list[text_index] = os.time() + text_index = text_index + 1 + if text_index%11==0 then text_index = 1 end + end +end) + +function split(msg, match) + local length = msg:len() + local splitarr = {} + local u = 1 + while u < length do + local nextanch = msg:find(match,u) + if nextanch ~= nil then + splitarr[#splitarr+1] = msg:sub(u,nextanch-1) + if nextanch~=length then + u = nextanch+1 + else + u = length + end + else + splitarr[#splitarr+1] = msg:sub(u,length) + u = length + end + end + return splitarr +end + +function uc_first(msg) + local length = msg:len() + local first_char = msg:sub(1,1) + local rest = msg:sub(2,length) + return first_char:upper()..rest:lower() +end + +function trim(msg) + for i=2,string.len(msg) do + if msg:byte(i) == 0 then + return msg:sub(1,i-1) + end + end + return msg +end + +function pl(num) + if num > 1 then + return 's' + else + return '' + end +end + +function arrows(bool,name) + if bool then + return name..'>> ' + else + return '>>'..name..' : ' + end +end + +function print_messages(tab,name) + for p,q in ipairs(tab) do + windower.add_to_chat(4,os.date('%H:%M:%S',q.timestamp)..' '..arrows(q.outgoing,uc_first(name))..q.message) + tab[p].seen = true + end +end + +windower.register_event('postrender',function() + AM_box:clear() + AM_box:append(unseen_message_count..' Message'..pl(unseen_message_count)) + local t = os.clock()%1 + AM_box:bg_color(255,150+100*math.sin(t*math.pi),150+100*math.sin(t*math.pi)) + AM_box:color(0,0,0) + if unseen_message_count > 0 then + AM_box:show() + else + AM_box:hide() + end +end) + +function activity() + last_activity = os.time() + if unseen_message_count > 0 then + local temp_message_count = 0 + for i,v in pairs(recording) do + for n,m in pairs(v) do + if not m.seen then + if m.timestamp < text_list[((text_index+1)==11 and 1) or (text_index+1)] then + temp_message_count = temp_message_count + 1 + else + recording[i][n].seen = true + end + end + end + end + unseen_message_count = temp_message_count + end +end + +windower.register_event('keyboard',activity) + +windower.register_event('mouse',activity)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/autoabils.lua b/Data/DefaultContent/Libraries/addons/addons/autocontrol/autoabils.lua new file mode 100644 index 0000000..ea44bdc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/autoabils.lua @@ -0,0 +1,73 @@ +autoabils = { + [1688] = {name = 'Shield Bash', recast = 180, icon = "00210"}, + [1689] = {name = 'Strobe', recast = 30, icon = "00210"}, + [1690] = {name = 'Shock Absorber', recast = 180, icon = "00210"}, + [1691] = {name = 'Flashbulb', recast = 45, icon = "00210"}, + [1692] = {name = 'Mana Converter', recast = 180, icon = "00210"}, + [1755] = {name = 'Reactive Shield', recast = 65, icon = "00210"}, + [1765] = {name = 'Eraser', recast = 30, icon = "00210"}, + [1812] = {name = 'Economizer', recast = 180, icon = "00210"}, + [1876] = {name = 'Replicator', recast = 60, icon = "00210"}, + [2489] = {name = 'Heat Capacitator', recast = 90, icon = "00210"}, + [2490] = {name = 'Barrage Turbine', recast = 180, icon = "00210"}, + [2491] = {name = 'Disruptor', recast = 60, icon = "00210"}, + [3485] = {name = 'Regulator', recast = 60, icon = "00210" } +} +attachments_to_abilities = { + [8225] = 1688, + [8449] = 1689, + [8454] = 1755, + [8456] = 2489, + [8457] = 1689, + [8461] = 2489, + [8519] = 1876, + [8520] = 2490, + [8545] = 1690, + [8553] = 1690, + [8557] = 1690, + [8642] = 1691, + [8645] = 1765, + [8674] = 1692, + [8678] = 1812, + [8680] = 2491, + [8682] = 3485, +} + +local player_id + +windower.register_event("login", function() + player_id = windower.ffxi.get_player().id +end) + +windower.register_event("load", function() + local player = windower.ffxi.get_player() + player_id = player and player.id +end) + +windower.register_event("action", function(act) + local abil_ID = act['param'] + local actor_id = act['actor_id'] + local pet_index = windower.ffxi.get_mob_by_id(player_id)['pet_index'] + + if act['category'] == 6 and actor_id == player_id and (abil_ID == 136 or abil_ID == 310 or abil_ID == 139) then + local avalible_abilities = {} + local automaton = windower.ffxi.get_mjob_data() + + if attachments_to_abilities[automaton.frame] then + table.insert(avalible_abilities, autoabils[attachments_to_abilities[automaton.frame]]) + end + + for _, id in pairs(automaton.attachments) do + if attachments_to_abilities[id] then + table.insert(avalible_abilities, autoabils[attachments_to_abilities[id]]) + end + end + + for _, ability in pairs(avalible_abilities) do -- if abil_ID is deactivate delete ability timers, otherwise create them. + windower.send_command('timers '.. (abil_ID == 139 and "d" or "c") .. ' "'..ability.name..'" ' .. (abil_ID == 139 and "" or ability.recast..' up abilities/' .. ability.icon)) + end + elseif autoabils[abil_ID-256] and windower.ffxi.get_mob_by_id(actor_id)['index'] == pet_index and pet_index ~= nil then + local abil = abil_ID - 256 + windower.send_command('@timers c "'..autoabils[abil].name..'" '..autoabils[abil].recast..' up') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/autocontrol.lua b/Data/DefaultContent/Libraries/addons/addons/autocontrol/autocontrol.lua new file mode 100644 index 0000000..3c0d51c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/autocontrol.lua @@ -0,0 +1,305 @@ +--[[ +Copyright © 2013, 2014, 2020 Ricky Gall, Nifim +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'autocontrol' +_addon.version = '2.0.5' +_addon.author = 'Nitrous (Shiva)' +_addon.commands = {'autocontrol','acon'} + +require('tables') +require('strings') +require('logger') +require('sets') +res = require('resources') +config = require('config') +files = require('files') +chat = require('chat') + +defaults = {} +defaults.bg = {} +defaults.bg.red = 0 +defaults.bg.blue = 0 +defaults.bg.green = 0 +defaults.pos = {} +defaults.pos.x = 400 +defaults.pos.y = 300 +defaults.text = {} +defaults.text.red = 255 +defaults.text.green = 255 +defaults.text.blue = 255 +defaults.text.font = 'Consolas' +defaults.text.size = 10 +defaults.autosets = T{} +defaults.autosets.default = T{ } +defaults.AutoActivate = true +defaults.AutoDeusExAutomata = false +defaults.maneuvertimers = true +defaults.burdentracker = true + +settings = config.load(defaults) + +burden_hud = require('burdometer') -- has to be loaded after settings are parsed. +require("autoabils") +recast_ids = {} +recast_ids.deactivate = res.job_abilities:with('english', 'Deactivate').recast_id +recast_ids.activate = res.job_abilities:with('english', 'Activate').recast_id +recast_ids.deusex = res.job_abilities:with('english', 'Deus Ex Automata').recast_id + +petlessZones = S{50,235,234,224,284,233,70,257,251,14,242,250,226,245, + 237,249,131,53,252,231,236,246,232,240,247,243,223,248,230, + 26,71,244,239,238,241,256,257} + +function initialize() + local player = windower.ffxi.get_player() + if not player then + windower.send_command('@wait 5;lua i autocontrol initialize') + return + end + + mjob_id = player.main_job_id + atts = res.items:category('Automaton') + + local playermob = windower.ffxi.get_mob_by_index(player.index) + while(playermob == nil) do + coroutine.sleep(1) + playermob = windower.ffxi.get_mob_by_index(player.index) + end + + if mjob_id == 18 then + if playermob.pet_index then + if settings.burdentracker then + burden_hud:show() + end + end + end +end + +windower.register_event('load', 'login', initialize) + +function attach_set(autoset) + if windower.ffxi.get_player().main_job_id ~= 18 or not settings.autosets[autoset] then + return + end + if settings.autosets[autoset]:map(string.lower):equals(get_current_autoset():map(string.lower)) then + log('The '..autoset..' set is already equipped.') + return + end + + local playermob = windower.ffxi.get_mob_by_index(windower.ffxi.get_player().index) + if playermob.pet_index and playermob.pet_index ~= 0 then + local recast = windower.ffxi.get_ability_recasts()[recast_ids.deactivate] + if recast == 0 then + windower.send_command('input /pet "Deactivate" <me>') + log('Deactivating ' .. windower.ffxi.get_mjob_data().name .. '.') + windower.send_command('@wait 2;lua i autocontrol attach_set '..autoset) + elseif recast then + error('Deactivate on cooldown wait ' .. (recast / 60) .. ' seconds and try again') + end + else + windower.ffxi.reset_attachments() + log('Starting to equip '..autoset..' to '..windower.ffxi.get_mjob_data().name..'.') + set_attachments_from_autoset(autoset, 'head') + end +end + +function set_attachments_from_autoset(autoset,slot) + if slot == 'head' then + local tempHead = settings.autosets[autoset].head:lower() + if tempHead then + for att in atts:it() do + if att.name:lower() == tempHead and att.id > 5000 then + windower.ffxi.set_attachment(att.id) + break + end + end + end + coroutine.schedule(set_attachments_from_autoset:prepare(autoset, 'frame'), 0.5) + elseif slot == 'frame' then + local tempFrame = settings.autosets[autoset].frame:lower() + if tempFrame then + for att in atts:it() do + if att.name:lower() == tempFrame and att.id > 5000 then + windower.ffxi.set_attachment(att.id) + break + end + end + end + coroutine.schedule(set_attachments_from_autoset:prepare(autoset, '1'), 0.5) + else + local tempname = settings.autosets[autoset]['slot' .. tostring(slot):zfill(2)]:lower() + if tempname then + for att in atts:it() do + if att.name:lower() == tempname and att.id > 5000 then + windower.ffxi.set_attachment(att.id, tonumber(slot)) + break + end + end + end + + if tonumber(slot) < 12 then + coroutine.schedule(set_attachments_from_autoset:prepare(autoset, slot + 1), 0.5) + else + log(windower.ffxi.get_mjob_data().name..' has been equipped with the '..autoset..' set.') + if petlessZones:contains(windower.ffxi.get_info().zone) then + return + else + local recasts = windower.ffxi.get_ability_recasts() + if settings.AutoActivate and recasts[recast_ids.activate] == 0 then + windower.send_command('input /ja "Activate" <me>') + elseif settings.AutoDeusExAutomata and recasts[recast_ids.deusex] == 0 then + log('Activate is down, using Deus Ex Automata instead.') + windower.send_command('input /ja "Deus Ex Automata" <me>') + elseif settings.AutoActivate and settings.AutoDeusExAutomata then + log('Activate and Deus Ex Automata timers were not ready.') + elseif settings.AutoActivate then + log('Activate timer was not ready.') + elseif settings.AutoDeusExAutomata then + log('Deus Ex Automata timer was not ready.') + end + end + end + end +end + +function get_current_autoset() + if windower.ffxi.get_player().main_job_id == 18 then + local autoTable = T{} + local mjob_data = windower.ffxi.get_mjob_data() + local tmpTable = mjob_data.attachments + for i = 1, 12 do + local t = '' + if tmpTable[i] then + if i < 10 then + t = '0' + end + autoTable['slot' .. tostring(i):zfill(2)] = atts[tmpTable[i]].name:lower() + end + end + autoTable.head = atts[mjob_data.head].name:lower() + autoTable.frame = atts[mjob_data.frame].name:lower() + return autoTable + end +end + +function save_set(setname) + if setname == 'default' then + error('Please choose a name other than default.') + return + end + + settings.autosets[setname] = get_current_autoset() + config.save(settings, 'all') + notice('Set '..setname..' saved.') +end + +function get_autoset_list() + log('Listing sets:') + for key,_ in pairs(settings.autosets) do + if key ~= 'default' then + log('\t' .. key .. ': ' .. (settings.autosets[key]:length()-2) .. ' attachments.') + end + end +end + +function get_autoset_content(autoset) + log('Getting '..autoset..'\'s attachment list:') + settings.autosets[autoset]:vprint() +end + +windower.register_event('addon command', function(comm, ...) + if windower.ffxi.get_player().main_job_id ~= 18 then + error('You are not on Puppetmaster.') + return + end + + local args = L{...} + comm = comm or 'help' + + if comm == 'saveset' then + if args[1] then + save_set(args[1]) + end + elseif comm == 'add' then + if args[2] then + local slot = table.remove(args,1) + local attach = args:sconcat() + add_attachment(attach,slot) + end + elseif comm == 'equipset' then + if args[1] then + attach_set(args[1]) + end + elseif comm == 'setlist' then + get_autoset_list() + elseif comm == 'attlist' then + if args[1] then + get_autoset_content(args[1]) + end + elseif comm == 'list' then + get_current_autoset():vprint() + elseif comm == "maneuvertimers" or comm == "mt" then + maneuvertimers = not maneuvertimers + elseif S{'fonttype','fontsize','pos','bgcolor','txtcolor'}:contains(comm) then + if comm == 'fonttype' then burden_hud:font(args[1]) + elseif comm == 'fontsize' then burden_hud:size(args[1]) + elseif comm == 'pos' then burden_hud:pos(args[1], args[2]) + elseif comm == 'bgcolor' then burden_hud:bgcolor(args[1], args[2], args[3]) + elseif comm == 'txtcolor' then burden_hud:color(args[1], args[2], args[3]) + end + config.save(settings, 'all') + elseif comm == 'show' then burden_hud:show() + elseif comm == 'hide' then burden_hud:hide() + elseif comm == 'settings' then + log('BG: R: '..settings.bg.red..' G: '..settings.bg.green..' B: '..settings.bg.blue) + log('Font: '..settings.text.font..' Size: '..settings.text.size) + log('Text: R: '..settings.text.red..' G: '..settings.text.green..' B: '..settings.text.blue) + log('Position: X: '..settings.pos.x..' Y: '..settings.pos.y) + else + log('Autosets command list:') + log(' 1. help - Brings up this menu.') + log(' 2. setlist - list all saved automaton sets.') + log(' 3. saveset <setname> - saves <setname> to your settings.') + log(' 4. equipset <setname> - equips <setname> to your automaton.') + log(' 5. attlist <setname> - gets the attachment list for <setname>') + log(' 6. list - gets the list of currently equipped attachments.') + log(' 7. maneuvertimers - Toggles showing maneuver timers on/off.') + log('The following all correspond to the burden tracker:') + log(' fonttype <name> | fontsize <size> | pos <x> <y>') + log(' bgcolor <r> <g> <b> | txtcolor <r> <g> <b>') + log(' settings - shows current settings') + log(' show/hide - toggles visibility of the tracker so you can make changes.') + end +end) + +windower.register_event("job change", function(main_job_id) + if main_job_id == 18 and settings.burdentracker then + burden_hud:show() + else + burden_hud:hide() + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/burden.lua b/Data/DefaultContent/Libraries/addons/addons/autocontrol/burden.lua new file mode 100644 index 0000000..51a8136 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/burden.lua @@ -0,0 +1,186 @@ +local set = require("sets") +local packets = require("packets") + +local o = { + fire = 0, + earth = 0, + water = 0, + wind = 0, + ice = 0, + thunder = 0, + light = 0, + dark = 0, +} +local burden = {} +local mt = { + __index = burden +} +setmetatable(o, mt) +local updaters = {} +local heatsink + +windower.register_event('incoming chunk',function(id,org,modi,is_injected,is_blocked) + if id == 0x044 then + local attachments = windower.ffxi.get_mjob_data().attachments + if attachments then + for _, id in pairs(attachments) do + heatsink = (id == 8610) + if heatsink then + break + end + end + end + end +end) + +local thresholdModifiers = +{ + [11101] = 40, -- Cirque Farsetto +2 + [11201] = 20, -- Cirque Farsetto +1 + [14930] = 5, -- Pup. Dastanas + [15030] = 5, -- Pup. Dastanas +1 + [16281] = 5, -- Buffoon's Collar + [16282] = 5, -- Buffoon's Collar +1 + [20520] = 40, -- Midnights + [26263] = 10, -- Visucius's Mantle + [26932] = 40, -- Kara. Farsetto + [26933] = 40, -- Kara. Farsetto +1 + [27960] = 5, -- Foire Dastanas + [27981] = 5, -- Foire Dastanas +1 + [28634] = 5, -- Dispersal Mantle +} +burden.threshold = 30 + +local pet_actions = +{ + [136] = "activate", + [139] = "deactivate", + [141] = "fire", + [142] = "ice", + [143] = "wind", + [144] = "earth", + [145] = "thunder", + [146] = "water", + [147] = "light", + [148] = "dark", + [309] = "cooldown", + [310] = "deus_ex_automata", +} +function burden:update(action) + updaters[action](self) +end + +function burden:zone() + for k in pairs(self) do + self[k] = 15 + end +end + +function burden.set_decay_event(func) + burden.decay_event = func +end +function updaters.deactivate(self) + for k in pairs(self) do + self[k] = 0 + end +end + +function updaters.activate(self) + for _, id in pairs(windower.ffxi.get_mjob_data().attachments) do + heatsink = (id == 8610) + if heatsink then + break + end + end + burden.update_decay_rate() + for k in pairs(self) do + self[k] = 15 + end +end +updaters.deus_ex_automata = updaters.activate + +function updaters.cooldown(self) + for k in pairs(self) do + self[k] = self[k] / 2 + end +end + +function updaters.maneuver(self, type) + self[type] = self[type] + 15 + local inventory = windower.ffxi.get_items() + local equipment = { + sub = {}, + ammo = {}, + main = {}, + head = {}, + body = {}, + back = {}, + legs = {}, + feet = {}, + neck = {}, + hands = {}, + range = {}, + waist = {}, + left_ear = {}, + left_ring = {}, + right_ear = {}, + right_ring = {}, + } + for k, v in pairs(inventory.equipment) do + equipment[string.gsub(k ,"_bag","")][k] = v + end + burden.threshold = 30 + for k, v in pairs(equipment) do + item = windower.ffxi.get_items(v[k .. "_bag"], v[k]) + if thresholdModifiers[item.id] then + burden.threshold = burden.threshold + thresholdModifiers[item.id] + end + end +end + +function updaters.ice(self) updaters.maneuver(self, "ice") end +function updaters.fire(self) updaters.maneuver(self, "fire") end +function updaters.wind(self) updaters.maneuver(self, "wind") end +function updaters.dark(self) updaters.maneuver(self, "dark") end +function updaters.earth(self) updaters.maneuver(self, "earth") end +function updaters.water(self) updaters.maneuver(self, "water") end +function updaters.light(self) updaters.maneuver(self, "light") end +function updaters.thunder(self) updaters.maneuver(self, "thunder") end + +burden.decay_rate = 1 +function burden.decay() + for k in pairs(o) do + if o[k] > burden.decay_rate then + o[k] = o[k] - burden.decay_rate + elseif o[k] > 0 then + o[k] = 0 + end + end + if burden.decay_event then + burden.decay_event() + end + coroutine.schedule(burden.decay, 3) +end +coroutine.schedule(burden.decay, os.date("*t").sec % 3) + +local count_to_decay_rate = { + [0] = 2, + [1] = 4, + [2] = 5, + [3] = 6, +} +function burden.update_decay_rate() + if heatsink then + local count = 0 + for _, v in pairs(windower.ffxi.get_player().buffs) do + if v == 305 then + count = count + 1 + end + end + burden.decay_rate = count_to_decay_rate[count]; + else + burden.decay_rate = 1 + end +end + +return o diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/burdometer.lua b/Data/DefaultContent/Libraries/addons/addons/autocontrol/burdometer.lua new file mode 100644 index 0000000..c36f645 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/burdometer.lua @@ -0,0 +1,96 @@ +local sets = require("sets") +local texts = require("texts") +local burden = require("burden") + +local pet_actions = +{ + [136] = "activate", + [139] = "deactivate", + [141] = "fire", + [142] = "ice", + [143] = "wind", + [144] = "earth", + [145] = "thunder", + [146] = "water", + [147] = "light", + [148] = "dark", + [309] = "cooldown", + [310] = "deus_ex_automata", +} + +local maneuvers = +S{ + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, +} + +str = 'Fire: \\cs(${color_fire|255,255,255})${fire|0}\\cr - ${time_fire|0}s - ${risk_fire|0}%' +str = str .. '\nIce: \\cs(${color_ice|255,255,255})${ice|0}\\cr - ${time_ice|0}s - ${risk_ice|0}%' +str = str .. '\nWind: \\cs(${color_wind|255,255,255})${wind|0}\\cr - ${time_wind|0}s - ${risk_wind|0}%' +str = str .. '\nEarth: \\cs(${color_earth|255,255,255})${earth|0}\\cr - ${time_earth|0}s - ${risk_earth|0}%' +str = str .. '\nThunder: \\cs(${color_thunder|255,255,255})${thunder|0}\\cr - ${time_thunder|0}s - ${risk_thunder|0}%' +str = str .. '\nWater: \\cs(${color_water|255,255,255})${water|0}\\cr - ${time_water|0}s - ${risk_water|0}%' +str = str .. '\nLight: \\cs(${color_light|255,255,255})${light|0}\\cr - ${time_light|0}s - ${risk_light|0}%' +str = str .. '\nDark: \\cs(${color_dark|255,255,255})${dark|0}\\cr - ${time_dark|0}s - ${risk_dark|0}%' + +local hud = texts.new(str, settings) + +function update_hud(element) + hud[element] = burden[element] + + risk = burden[element] - burden.threshold + hud["risk_" .. element] = risk > 0 and risk or 0 + hud["color_" .. element] = "255," .. (risk > 33 and 0 or 255) .. "," .. (risk > 0 and 0 or 255) + hud["time_" .. element] = (burden[element] / burden.decay_rate) * 3 +end + +windower.register_event("action", function(act) + local abil_id = act['param'] + local actor_id = act['actor_id'] + local player = windower.ffxi.get_player() + local pet_index = windower.ffxi.get_mob_by_index(player.index).pet_index + + if player.main_job_id ~= 18 then + return + end + + if act["category"] == 6 and actor_id == player.id and pet_actions[abil_id] then + burden:update(pet_actions[abil_id]) -- Always assumes good burden (+15). + if maneuvers:contains(abil_id) then + if act.targets[1].actions[1].param > 0 then + burden[pet_actions[abil_id]] = burden.threshold + act.targets[1].actions[1].param -- Corrects for bad burden when over threshold. + end + update_hud(pet_actions[abil_id]) + end + end +end) + +local function decay_event() + for element in pairs(burden) do + update_hud(element) + end +end +burden.set_decay_event(decay_event) + +windower.register_event("zone change", function() + burden:zone() +end) + +local function update_decay_rate(buff_id) + if buff_id == 305 then + burden:update_decay_rate() + end +end + +windower.register_event("gain buff", update_decay_rate) +windower.register_event("lose buff", update_decay_rate) + +decay_event() + +return hud diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/autocontrol/data/settings.xml new file mode 100644 index 0000000..095c82e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/data/settings.xml @@ -0,0 +1,58 @@ +<?xml version="1.1" ?> +<settings> + <global> + <autosets> + <default /> + <healer> + <frame>Stormwaker Frame</frame> + <head>Soulsoother Head</head> + <slot01>Disruptor</slot01> + <slot02>Economizer</slot02> + <slot03>Mana Conserver</slot03> + <slot04>Vivi-Valve</slot04> + <slot05>Percolator</slot05> + <slot06>Damage Gauge</slot06> + <slot07>Eraser</slot07> + <slot08>Optic Fiber</slot08> + <slot09>Steam Jacket</slot09> + <slot10>Condenser</slot10> + <slot11>Mana Channeler</slot11> + <slot12>Stealth Screen</slot12> + </healer> + <melrng> + <frame>Sharpshot Frame</frame> + <head>Valoredge Head</head> + <slot01>Tension Spring</slot01> + <slot02>Turbo Charger</slot02> + <slot03>Equalizer</slot03> + <slot04>Dynamo</slot04> + <slot05>Tension Spring II</slot05> + <slot06>Scope</slot06> + <slot07>Auto-Repair Kit II</slot07> + <slot08>Coiler</slot08> + <slot09>Tactical Processor</slot09> + <slot10>Armor Plate II</slot10> + <slot11>Optic Fiber</slot11> + <slot12>Volt Gun</slot12> + </melrng> + </autosets> + <bg> + <blue>0</blue> + <green>0</green> + <red>0</red> + </bg> + <padding>0</padding> + <pos> + <x>1600</x> + <y>920</y> + </pos> + <text> + <blue>255</blue> + <font>Consolas</font> + <green>255</green> + <red>255</red> + <size>10</size> + </text> + <visible>false</visible> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/autocontrol/readme.md b/Data/DefaultContent/Libraries/addons/addons/autocontrol/readme.md new file mode 100644 index 0000000..eb14bce --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autocontrol/readme.md @@ -0,0 +1,22 @@ +**Author:** Ricky Gall +**Version:** 2.0.4 +**Description:** +Addon to make setting automaton attachments easier. Currently only works as pup main. Includes burden tracker. + +**Abbreviations:** acon, autocontrol + +**Commands:** + 1. help - Brings up this menu. + 2. setlist - list all saved automaton sets. + 3. saveset <setname> - saves <setname> to your settings. + 4. equipset <setname> - equips <setname> to your automaton. + 5. attlist <setname> - gets the attachment list for <setname> + 6. list - gets the list of currently equipped attachments. + 7. maneuvertimers - Toggle showing maneuver timers (abbreviation mt) + +**The following all correspond to the burden tracker:** + 1. fonttype <name> | fontsize <size> | pos <x> <y> + 2. bgcolor <r> <g> <b> | txtcolor <r> <g> <b> + 3. settings - shows current settings + 4. show/hide - toggles visibility of the tracker so you can make changes. + diff --git a/Data/DefaultContent/Libraries/addons/addons/autoenterkey/README.md b/Data/DefaultContent/Libraries/addons/addons/autoenterkey/README.md new file mode 100644 index 0000000..d8e7f3d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autoenterkey/README.md @@ -0,0 +1,9 @@ +Author: Chiaia +Version: 1.0 +Automatically hits the enter key twice as you first load up the game so you don't timeout on the warning message if you happen to like to tab out as it launches. +It take about 5 to 10 seconds to start to do this. If you happen to manually do it faster if will just unload itself since it serves no other purpose. + +Abbreviation: //ake, //autoinvite + +Commands: +* There are currently no commands. diff --git a/Data/DefaultContent/Libraries/addons/addons/autoenterkey/autoenterkey.lua b/Data/DefaultContent/Libraries/addons/addons/autoenterkey/autoenterkey.lua new file mode 100644 index 0000000..fa3cdb2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autoenterkey/autoenterkey.lua @@ -0,0 +1,46 @@ +--Copyright (c) 2019, Chiaia +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of Auto Enter Key nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL Chiaia BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'Auto Enter Key' +_addon.version = '1.0' +_addon.author = 'Chiaia (Asura)' +_addon.commands = {'ake',} + +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in then + windower.send_command('lua u autoenterkey') + else + coroutine.sleep(5) + windower.send_command('setkey enter down;') + coroutine.sleep(0.3) + windower.send_command('setkey enter up;') + coroutine.sleep(5) + windower.send_command('setkey enter down;') + coroutine.sleep(0.3) + windower.send_command('setkey enter up;') + windower.send_command('lua u autoenterkey') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/autoinvite/README.md b/Data/DefaultContent/Libraries/addons/addons/autoinvite/README.md new file mode 100644 index 0000000..f3eb809 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autoinvite/README.md @@ -0,0 +1,16 @@ +Author: Registry +Version: 1.0 +Automatically invites players when sent a tell with a specified keyword. + +Abbreviation: //ai, //autoinvite + +Commands: +* <whitelist|blacklist> <add|remove> <player> - adds or removes a player from blacklist or whitelist. +* <keyword> <add|remove> <word> - adds or removes a word from keyword list. +* mode <whitelist|blacklist> - changes to whitelist or blacklist, if no mode specified then it will print current mode. +* tellback <on|off> - turns tellback mode on or off, if no status specified then it will print current status. +* status - will print status of current options, including full whitelist, blacklist, and keyword list. + + +If tellback mode is turned on and you are unable to send an invite to the player who sent you a tell with the +specified keyword, you will automatically send them a tell back saying that you were unable to invite them.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/autoinvite/autoinvite.lua b/Data/DefaultContent/Libraries/addons/addons/autoinvite/autoinvite.lua new file mode 100644 index 0000000..858e422 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autoinvite/autoinvite.lua @@ -0,0 +1,225 @@ +--[[ +Copyright (c) 2013, Registry +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of autoinvite nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Registry 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. +]] + +require('luau') + +_addon.name = 'AutoInvite' +_addon.author = 'Registry' +_addon.commands = {'autoinvite','ai'} +_addon.version = 1.0 +_addon.language = 'english' + +defaults = T{} +defaults.mode = 'whitelist' +defaults.whitelist = S{} +defaults.blacklist = S{} +defaults.keywords = S{} +defaults.tellback = 'on' + +-- Statuses that stop you from sending invites. +statusblock = S{ + 'Dead', 'Event', 'Charmed' +} + +-- Aliases to access correct modes based on supplied arguments. +aliases = T{ + wlist = 'whitelist', + white = 'whitelist', + whitelist = 'whitelist', + blist = 'blacklist', + black = 'blacklist', + blacklist = 'blacklist', + key = 'keywords', + keyword = 'keywords', + keywords = 'keywords' +} + +-- Aliases to access the add and item_to_remove routines. +addstrs = S{'a', 'add', '+'} +rmstrs = S{'r', 'rm', 'remove', '-'} + +-- Aliases for tellback mode. +on = S{'on', 'yes', 'true'} +off = S{'off', 'no', 'false'} + +modes = S{'whitelist', 'blacklist'} + +-- Load settings from file +settings = config.load(defaults) + +-- Check for keyword +windower.register_event('chat message', function(message, player, mode, is_gm) + local word = false + if mode == 3 then + for item in settings.keywords:it() do + if message:lower():match(item:lower()) then + word = true + break + end + end + -- if keyword is not found, return + if word == false then + return + end + + if settings.mode == 'blacklist' then + if settings.blacklist:contains(player) then + return + else + try_invite(player) + end + elseif settings.mode == 'whitelist' then + if settings.whitelist:contains(player) then + try_invite(player) + end + end + end +end) + +-- Attempts to send an invite +function try_invite(player) + if windower.ffxi.get_party().p5 then + notice(player.. 'cannot be invited - party is full') + if settings.tell_back == 'on' then + windower.send_command('input /t '..player..' Party is currently full.') + end + return + end + + local status = res.statuses[windower.ffxi.get_player().status].english + if statusblock:contains(status) then + notice(player.. 'cannot be invited - you cannot send an invite at this time (' .. status .. ').') + if settings.tell_back == 'on' then + windower.send_command('input /t '..player..' An invite cannot be sent at this time (' .. status .. ').') + end + return + end + + windower.send_command('input /pcmd add '..player) +end + +-- Adds names/items to a given list type. +function add_item(mode, ...) + local names = S{...} + local doubles = names * settings[mode] + if not doubles:empty() then + if aliases[mode] == 'keywords' then + notice('Keyword':plural(doubles)..' '..doubles:format()..' already on keyword list.') + else + notice('User':plural(doubles)..' '..doubles:format()..' already on '..aliases[mode]..'.') + end + end + local new = names - settings[mode] + if not new:empty() then + settings[mode] = settings[mode] + new + log('Added '..new:format()..' to the '..aliases[mode]..'.') + end +end + +-- Removes names/items from a given list type. +function remove_item(mode, ...) + local names = S{...} + local dummy = names - settings[mode] + if not dummy:empty() then + if aliases[mode] == 'keywords' then + notice('Keyword':plural(dummy)..' '..dummy:format()..' not found on keyword list.') + else + notice('User':plural(dummy)..' '..dummy:format()..' not found on '..aliases[mode]..'.') + end + end + local item_to_remove = names * settings[mode] + if not item_to_remove:empty() then + settings[mode] = settings[mode] - item_to_remove + log('Removed '..item_to_remove:format()..' from the '..aliases[mode]..'.') + end +end + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'status' + local args = L{...} + -- Changes whitelist/blacklist mode + if command == 'mode' then + local mode = args[1] or 'status' + if aliases:keyset():contains(mode) then + settings.mode = aliases[mode] + log('Mode switched to '..settings.mode..'.') + elseif mode == 'status' then + log('Currently in '..settings.mode..' mode.') + else + error('Invalid mode:', args[1]) + return + end + + -- Turns tellback on or off + elseif command == 'tellback' then + status = args[1] or 'status' + status = string.lower(status) + if on:contains(status) then + settings.tellback = 'on' + log('Tellback turned on.') + elseif off:contains(status) then + settings.tellback = 'off' + log('Tellback turned off.') + elseif status == 'status' then + log('Tellback currently '..settings.tellback..'.') + else + error('Invalid status:', args[1]) + return + end + + elseif aliases:keyset():contains(command) then + mode = aliases[command] + names = args:slice(2):map(string.ucfirst..string.lower) + if args:empty() then + log(mode:ucfirst()..':', settings[mode]:format('csv')) + else + if addstrs:contains(args[1]) then + add_item(mode, names:unpack()) + elseif rmstrs:contains(args[1]) then + remove_item(mode, names:unpack()) + else + notice('Invalid operator specified. Specify add or remove.') + end + end + + -- Print current settings status + elseif command == 'status' then + log('Mode:', settings.mode) + log('Tell Back:', settings.tellback) + log('Whitelist:', settings.whitelist:empty() and '(empty)' or settings.whitelist:format('csv')) + log('Blacklist:', settings.blacklist:empty() and '(empty)' or settings.blacklist:format('csv')) + log('Keywords:', settings.keywords:empty() and '(empty)' or settings.keywords:format('csv')) + + -- Ignores (and prints a warning) if unknown command is passed. + else + warning('Unkown command \''..command..'\', ignored.') + + end + + config.save(settings) +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/autojoin/autojoin.lua b/Data/DefaultContent/Libraries/addons/addons/autojoin/autojoin.lua new file mode 100644 index 0000000..fadcdff --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autojoin/autojoin.lua @@ -0,0 +1,194 @@ +-- Setup + +require('luau') +packets = require('packets') + +_addon.name = 'AutoJoin' +_addon.author = 'Arcon' +_addon.commands = {'autojoin', 'aj'} +_addon.version = '2.0.0.1' + +defaults = {} +defaults.mode = 'whitelist' +defaults.whitelist = S{} +defaults.blacklist = S{} +defaults.autodecline = false + +settings = config.load(defaults) + +-- Aliases to access correct modes based on supplied arguments. +aliases = T{ + w = 'whitelist', + wlist = 'whitelist', + white = 'whitelist', + whitelist = 'whitelist', + b = 'blacklist', + blist = 'blacklist', + black = 'blacklist', + blacklist = 'blacklist' +} + +-- String-sets for quick aceess. +add_strs = S{'a', 'add', '+'} +rm_strs = S{'r', 'rm', 'remove', '-'} +dec_strs = S{'decline', 'autodecline', 'auto-decline'} +alias_strs = aliases:keyset() + +do + local join_packet = packets.new('outgoing', 0x074, {Join = true}) + join = function() + local time = 0 + -- Wait until pool is empty... + while not table.empty(windower.ffxi.get_items().treasure) do + coroutine.sleep(1) + time = time + 1 + + -- ... but only until the max join time expires + if time > 90 then + return + end + end + + packets.inject(join_packet) + windower.send_command:schedule(1, 'input /join') + end + + local decline_packet = packets.new('outgoing', 0x074, {Join = false}) + decline = function() + packets.inject(decline_packet) + windower.send_command:schedule(1, 'input /decline') + end +end + +-- Invite handler +windower.register_event('party invite', function(sender) + if settings.mode == 'whitelist' and settings.whitelist:contains(sender) or settings.mode == 'blacklist' and not settings.blacklist:contains(sender)then + join() + elseif settings.mode == 'blacklist' and settings.autodecline then + decline() + end +end) + +-- Adds names to a given list type. +function add_name(mode, ...) + local names = S{...} + local duplicates = names * settings[mode] + if not duplicates:empty() then + notice(('User'):plural(duplicates) .. ' ' .. duplicates:format() .. ' already on ' .. aliases[mode] .. '.') + end + local new = names - settings[mode] + if not new:empty() then + settings[mode] = settings[mode] + new + log('Added ' .. new:format() .. ' to the ' .. aliases[mode] .. '.') + end + settings:save() +end + +-- Removes names from a given list type. +function rm_name(mode, ...) + local names = S{...} + local dummy = names - settings[mode] + if not dummy:empty() then + notice(('User'):plural(dummy) .. ' ' .. dummy:format() .. ' not found on ' .. aliases[mode] .. '.') + end + local remove = names * settings[mode] + if not remove:empty() then + settings[mode] = settings[mode] - remove + log('Removed ' .. remove:format() .. ' from the ' .. aliases[mode] .. '.') + end + settings:save() +end + +-- Interpreter + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'status' + local args = T{...} + + -- Mode switch + if command == 'mode' then + -- If no mode provided, print status. + local mode = args[1] or 'status' + if alias_strs:contains(mode) then + settings.mode = aliases[mode] + log('Mode switched to ' .. settings.mode .. '.') + settings:save() + elseif mode == 'status' then + log('Currently in ' .. settings.mode .. ' mode.') + else + error('Invalid mode:', args[1]) + return + end + + -- List management + elseif alias_strs:contains(command) then + mode = aliases[command] + names = args:slice(2):map(string.ucfirst .. string.lower) + + -- If no operator provided + if args:empty() then + log(mode:ucfirst() .. ':', settings[mode]:format('csv')) + else + if add_strs:contains(args[1]) then + add_name(mode, names:unpack()) + elseif rm_strs:contains(args[1]) then + rm_name(mode, names:unpack()) + -- If no qualifier provided + else + notice('Invalid operator specified. Specify add or remove.') + end + end + + -- Auto-decline settings + elseif dec_strs:contains(command) then + if args[1] ~= nil then + local decline = args[1]:lower() + local success = true + if decline == 'true' then + settings.autodecline = true + elseif decline == 'false' then + settings.autodecline = false + else + log('Invalid command for autodecline. Specify true or false.') + success = false + end + + if success then + log('Set auto-decline to ' .. tostring(settings.autodecline) .. '.') + settings:save() + end + else + log('Auto-decline is currently ' .. (settings.autodecline and 'on' or 'off') .. '.') + end + + -- Save settings. This is only needed for global or cross-character settings, as current-chracter settings will be saved every time something is changed. + elseif command == 'save' then + settings:save(args[1] or 'all') + log('Settings saved.') + + -- Print current settings status + elseif command == 'status' then + log('Mode:', settings.mode) + log('Whitelist:', settings.whitelist:empty() and '(empty)' or settings.whitelist:format('csv')) + log('Blacklist:', settings.blacklist:empty() and '(empty)' or settings.blacklist:format('csv')) + log('Auto-decline:', settings.autodecline) + + -- Unknown command handler + else + warning('Unkown command \'' .. command .. '\', ignored.') + + end +end) + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/autojoin/readme.md b/Data/DefaultContent/Libraries/addons/addons/autojoin/readme.md new file mode 100644 index 0000000..a37ff91 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/autojoin/readme.md @@ -0,0 +1,49 @@ +# AutoJoin + +Once loaded, will automatically join party and alliance invites. It will not process the invite if the treasure pool is not empty. It will also wait until the treasure pool is cleared to join, although it can be forced by typing `/join` normally. + +### Modes + +AutoJoin can operate in two modes (can be changed in the settings file): + +* Whitelist mode [default] +Will only join invites from people you have specifically whitelisted. See below for whitelist commands. + +* Blacklist mode +Will always join, except invites from people you blacklisted. Note that this is _not_ your FFXI blacklist, but another blacklist you have to create for this. + +### Commands + +All commands are prefaced with `//autojoin`, or `//aj`. + +#### Mode switch + +``` +//autojoin mode [[b|black|blist|blacklist]|[w|white|wlist|whitelist]] +``` + +If no mode is specified, the current mode will be printed. + +#### Add names to blacklist/whitelist + +``` +//autojoin [[b|black|blist|blacklist]|[w|white|wlist|whitelist]] [[+|a|add]|[-|r|remove]] [name [...]] +``` + +Will add or remove the specified names to the blacklist or whitelist. If no names are specified, it will print out the current blacklist or whitelist. + +#### Print current settings + +``` +//autojoin status +``` + +Shows the current mode, the people on the blacklist and whitelist, as well as if auto-decline for blacklisted people is activated. + +#### Save settings + +``` +//autojoin save +``` + +Saves current settings for all characters. This will overwrite all user-defined settings. Nothing is needed to save settings for the current character, that happens automatically any time a setting is changed. diff --git a/Data/DefaultContent/Libraries/addons/addons/azureSets/Readme.md b/Data/DefaultContent/Libraries/addons/addons/azureSets/Readme.md new file mode 100644 index 0000000..9e2d25b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/azureSets/Readme.md @@ -0,0 +1,42 @@ +**Author:** Ricky Gall +**Version:** 1.24 +**Description:** +Addon to make setting blue spells easier. Currently only works as blu main. + +**Abbreviations:** aset, azureset + +**Commands:** + 1. //aset removeall - Unsets all spells. + 2. //aset spellset <setname> [ClearFirst|PreserveTraits] -- Set (setname)'s spells, + optional parameter: ClearFirst or PreserveTraits: overrides + setting to clear spells first or remove individually, + preserving traits where possible. Default: use settings or + preservetraits if settings not configured. + 3. //aset set <setname> [ClearFirst|PreserveTraits] -- Same as spellset + 4. //aset add <slot> <spell> -- Set (spell) to slot (slot (number)). + 5. //aset save <setname> -- Saves current spellset as (setname). + 6. //aset currentlist -- Lists currently set spells. + 7. //aset setlist -- Lists all spellsets. + 8. //aset spelllist <setname> -- List spells in (setname) + 9. //aset help --Shows this menu. + +**Changes:** +v1.23 - v1.24 + * Changed default spellset method to preserve traits. + * Added setting for setmethod to either PresereTraits or ClearFirst. + * Added setting for setspeed. Wait time between each spell being set. Faster timing + may result in multiple attempts at setting the spell and could lead to + increased total set time. Default: 0.65 seconds. + +v1.15 - v1.22 + * Fixed spells that were missing + * Recoded for 4.1 + +v1.1 - v1.15 + * Added spellset listing + * Added listing a given set's spells + * Added default VW sets (VW1 and VW2) which include Wind, Thunder, Light, Dark and Fire, Ice, Water, Earth (respectively) + * Added //aset list into the help menu given in game (it worked before but i forgot to include it) + +v1.0 - v1.1 + * Fixed issue with saving sets. diff --git a/Data/DefaultContent/Libraries/addons/addons/azureSets/azuresets.lua b/Data/DefaultContent/Libraries/addons/addons/azureSets/azuresets.lua new file mode 100644 index 0000000..41bab29 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/azureSets/azuresets.lua @@ -0,0 +1,298 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of azureSets nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL 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. +]] + + +_addon.name = 'AzureSets' +_addon.version = '1.23' +_addon.author = 'Nitrous (Shiva)' +_addon.commands = {'aset','azuresets','asets'} + +require('tables') +require('strings') +require('logger') +config = require('config') +files = require('files') +res = require('resources') +chat = require('chat') + +defaults = {} +defaults.setmode = 'PreserveTraits' +defaults.setspeed = 0.65 +defaults.spellsets = {} +defaults.spellsets.default = T{} +defaults.spellsets.vw1 = T{slot01='Firespit', slot02='Heat Breath', slot03='Thermal Pulse', slot04='Blastbomb', +slot05='Infrasonics', slot06='Frost Breath', slot07='Ice Break', slot08='Cold Wave', +slot09='Sandspin', slot10='Magnetite Cloud', slot11='Cimicine Discharge', slot12='Bad Breath', +slot13='Acrid Stream', slot14='Maelstrom', slot15='Corrosive Ooze', slot16='Cursed Sphere', +slot17='Awful Eye' +} +defaults.spellsets.vw2 = T{slot01='Hecatomb Wave', slot02='Mysterious Light', slot03='Leafstorm', slot04='Reaving Wind', +slot05='Temporal Shift', slot06='Mind Blast', slot07='Blitzstrahl', slot08='Charged Whisker', +slot09='Blank Gaze', slot10='Radiant Breath', slot11='Light of Penance', slot12='Actinic Burst', +slot13='Death Ray', slot14='Eyes On Me', slot15='Sandspray' +} + +settings = config.load(defaults) + +function initialize() + spells = res.spells:type('BlueMagic') + get_current_spellset() +end + +windower.register_event('load', initialize:cond(function() return windower.ffxi.get_info().logged_in end)) + +windower.register_event('login', initialize) + +windower.register_event('job change', initialize:cond(function(job) return job == 16 end)) + +function set_spells(spellset, setmode) + if windower.ffxi.get_player()['main_job_id'] ~= 16 --[[and windower.ffxi.get_player()['sub_job_id'] ~= 16]] then + error('Main job not set to Blue Mage.') + return + end + if settings.spellsets[spellset] == nil then + error('Set not defined: '..spellset) + return + end + if is_spellset_equipped(settings.spellsets[spellset]) then + log(spellset..' was already equipped.') + return + end + + log('Starting to set '..spellset..'.') + if setmode:lower() == 'clearfirst' then + remove_all_spells() + set_spells_from_spellset:schedule(settings.setspeed, spellset, 'add') + elseif setmode:lower() == 'preservetraits' then + set_spells_from_spellset(spellset, 'remove') + else + error('Unexpected setmode: '..setmode) + end +end + +function is_spellset_equipped(spellset) + return S(spellset):map(string.lower) == S(get_current_spellset()) +end + +function set_spells_from_spellset(spellset, setPhase) + local setToSet = settings.spellsets[spellset] + local currentSet = get_current_spellset() + + if setPhase == 'remove' then + -- Remove Phase + for k,v in pairs(currentSet) do + if not setToSet:contains(v:lower()) then + setSlot = k + local slotToRemove = tonumber(k:sub(5, k:len())) + + windower.ffxi.remove_blue_magic_spell(slotToRemove) + --log('Removed spell: '..v..' at #'..slotToRemove) + set_spells_from_spellset:schedule(settings.setspeed, spellset, 'remove') + return + end + end + end + -- Did not find spell to remove. Start set phase + -- Find empty slot: + local slotToSetTo + for i = 1, 20 do + local slotName = 'slot%02u':format(i) + if currentSet[slotName] == nil then + slotToSetTo = i + break + end + end + + if slotToSetTo ~= nil then + -- We found an empty slot. Find a spell to set. + for k,v in pairs(setToSet) do + if not currentSet:contains(v:lower()) then + if v ~= nil then + local spellID = find_spell_id_by_name(v) + if spellID ~= nil then + windower.ffxi.set_blue_magic_spell(spellID, tonumber(slotToSetTo)) + --log('Set spell: '..v..' ('..spellID..') at: '..slotToSetTo) + set_spells_from_spellset:schedule(settings.setspeed, spellset, 'add') + return + end + end + end + end + end + + -- Unable to find any spells to set. Must be complete. + log(spellset..' has been equipped.') + windower.send_command('@timers c "Blue Magic Cooldown" 60 up') +end + +function find_spell_id_by_name(spellname) + for spell in spells:it() do + if spell['english']:lower() == spellname:lower() then + return spell['id'] + end + end + return nil +end + +function set_single_spell(setspell,slot) + if windower.ffxi.get_player()['main_job_id'] ~= 16 --[[and windower.ffxi.get_player()['sub_job_id'] ~= 16]] then return nil end + + local tmpTable = T(get_current_spellset()) + for key,val in pairs(tmpTable) do + if tmpTable[key]:lower() == setspell then + error('That spell is already set.') + return + end + end + if tonumber(slot) < 10 then slot = '0'..slot end + --insert spell add code here + for spell in spells:it() do + if spell['english']:lower() == setspell then + --This is where single spell setting code goes. + --Need to set by spell id rather than name. + windower.ffxi.set_blue_magic_spell(spell['id'], tonumber(slot)) + windower.send_command('@timers c "Blue Magic Cooldown" 60 up') + tmpTable['slot'..slot] = setspell + end + end + tmpTable = nil +end + +function get_current_spellset() + if windower.ffxi.get_player().main_job_id ~= 16 then return nil end + return T(windower.ffxi.get_mjob_data().spells) + -- Returns all values but 512 + :filter(function(id) return id ~= 512 end) + -- Transforms them from IDs to lowercase English names + :map(function(id) return spells[id].english:lower() end) + -- Transform the keys from numeric x or xx to string 'slot0x' or 'slotxx' + :key_map(function(slot) return 'slot%02u':format(slot) end) +end + +function remove_all_spells(trigger) + windower.ffxi.reset_blue_magic_spells() + notice('All spells removed.') +end + +function save_set(setname) + if setname == 'default' then + error('Please choose a name other than default.') + return + end + local curSpells = T(get_current_spellset()) + settings.spellsets[setname] = curSpells + settings:save('all') + notice('Set '..setname..' saved.') +end + +function delete_set(setname) + if settings.spellsets[setname] == nil then + error('Please choose an existing spellset.') + return + end + settings.spellsets[setname] = nil + settings:save('all') + notice('Deleted '..setname..'.') +end + +function get_spellset_list() + log("Listing sets:") + for key,_ in pairs(settings.spellsets) do + if key ~= 'default' then + local it = 0 + for i = 1, #settings.spellsets[key] do + it = it + 1 + end + log("\t"..key..' '..settings.spellsets[key]:length()..' spells.') + end + end +end + +function get_spellset_content(spellset) + log('Getting '..spellset..'\'s spell list:') + settings.spellsets[spellset]:print() +end + +windower.register_event('addon command', function(...) + if windower.ffxi.get_player()['main_job_id'] ~= 16 --[[and windower.ffxi.get_player()['sub_job_id'] ~= 16]] then + error('You are not on (main) Blue Mage.') + return nil + end + local args = T{...} + if args ~= nil then + local comm = table.remove(args,1):lower() + if comm == 'removeall' then + remove_all_spells('trigger') + elseif comm == 'add' then + if args[2] ~= nil then + local slot = table.remove(args,1) + local spell = args:sconcat() + set_single_spell(spell:lower(),slot) + end + elseif comm == 'save' then + if args[1] ~= nil then + save_set(args[1]) + end + elseif comm == 'delete' then + if args[1] ~= nil then + delete_set(args[1]) + end + elseif comm == 'spellset' or comm == 'set' then + if args[1] ~= nil then + set_spells(args[1], args[2] or settings.setmode) + end + elseif comm == 'currentlist' then + get_current_spellset():print() + elseif comm == 'setlist' then + get_spellset_list() + elseif comm == 'spelllist' then + if args[1] ~= nil then + get_spellset_content(args[1]) + end + elseif comm == 'help' then + local helptext = [[AzureSets - Command List:') + 1. removeall - Unsets all spells. + 2. spellset <setname> [ClearFirst|PreserveTraits] -- Set (setname)'s spells, + optional parameter: ClearFirst or PreserveTraits: overrides + setting to clear spells first or remove individually, + preserving traits where possible. Default: use settings or + preservetraits if settings not configured. + 3. set <setname> (clearfirst|preservetraits) -- Same as spellset + 4. add <slot> <spell> -- Set (spell) to slot (slot (number)). + 5. save <setname> -- Saves current spellset as (setname). + 6. delete <setname> -- Delete (setname) spellset. + 7. currentlist -- Lists currently set spells. + 8. setlist -- Lists all spellsets. + 9. spelllist <setname> -- List spells in (setname) + 10. help --Shows this menu.]] + for _, line in ipairs(helptext:split('\n')) do + windower.add_to_chat(207, line..chat.controls.reset) + end + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_bg.png b/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_bg.png Binary files differnew file mode 100644 index 0000000..72a79f9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_bg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_fg.png b/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_fg.png Binary files differnew file mode 100644 index 0000000..fb8e329 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/barfiller/bar_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/barfiller/barfiller.lua b/Data/DefaultContent/Libraries/addons/addons/barfiller/barfiller.lua new file mode 100644 index 0000000..2a67a06 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/barfiller/barfiller.lua @@ -0,0 +1,147 @@ +--[[ BSD License Disclaimer + Copyright © 2015, Morath86 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of BarFiller nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Morath86 BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'BarFiller' +_addon.author = 'Morath' +_addon.version = '0.2.5' +_addon.commands = {'bf','barfiller'} +_addon.language = 'english' + +-- Windower Libs +config = require('config') +file = require('files') +packets = require('packets') +texts = require('texts') +images = require('images') + +-- BarFiller Libs +require('statics') + +settings = config.load(defaults) +config.save(settings) + +background_image = images.new(settings.Images.Background) +foreground_image = images.new(settings.Images.Foreground) +rested_bonus_image = images.new(settings.Images.RestedBonus) + +exp_text = texts.new(settings.Texts.Exp) + +debug = false +ready = false +chunk_update = false + +windower.register_event('load',function() + if windower.ffxi.get_info().logged_in then + initialize() + end +end) + +windower.register_event('login',function() + initialize() +end) + +windower.register_event('logout',function() + hide() +end) + +windower.register_event('addon command',function(command, ...) + local commands = {...} + local first_cmd = (command or 'help'):lower() + if approved_commands[first_cmd] and #commands >= approved_commands[first_cmd].n then + if first_cmd == 'clear' or first_cmd == 'c' then + initialize() + elseif first_cmd == 'visible' or first_cmd == 'v' then + if ready then hide() else show() end + elseif first_cmd == 'reload' or first_cmd == 'r' then + windower.add_to_chat(8,'BarFiller successfully reloaded.') + windower.send_command('lua r barfiller;') + elseif first_cmd == 'unload' or first_cmd == 'u' then + windower.send_command('lua u barfiller;') + windower.add_to_chat(8,'BarFiller successfully unloaded.') + elseif first_cmd == 'help' or first_cmd == 'h' then + display_help() + end + else + display_help() + end +end) + +windower.register_event('incoming chunk',function(id,org,modi,is_injected,is_blocked) + if is_injected then return end + if ready then + -- Thanks to smd111 for Packet parsing + local packet_table = packets.parse('incoming', org) + if id == 0x2D then + exp_msg(packet_table['Param 1'],packet_table['Message']) + elseif id == 0x61 then + xp.current = packet_table['Current EXP'] + xp.total = packet_table['Required EXP'] + xp.tnl = xp.total - xp.current + chunk_update = true + end + end +end) + +windower.register_event('prerender',function() + if ready and chunk_update then + local old_width = foreground_image:width() + local new_width = calc_new_width() + + -- Thanks to Iryoku for the logic on smooth animations + if new_width ~= nil and new_width > 0 then + if old_width < new_width then + local last_update = 0 + local x = old_width + math.ceil(((new_width - old_width) * 0.1)) + foreground_image:size(x, settings.Images.Foreground.Size.Height) + if debug then print(old_width, x, new_width) end + + local now = os.clock() + if now - last_update > 0.5 then + update_strings() + last_update = now + end + elseif old_width >= new_width then + foreground_image:size(new_width, settings.Images.Foreground.Size.Height) + chunk_update = false + if debug then print(chunk_update) end + end + end + end +end) + +windower.register_event('level up', function(level) + update_strings() +end) + +windower.register_event('level down', function(level) + update_strings() +end) + +windower.register_event('zone change', function(new_id,old_id) + mog_house() +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/barfiller/moon.png b/Data/DefaultContent/Libraries/addons/addons/barfiller/moon.png Binary files differnew file mode 100644 index 0000000..c7a119b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/barfiller/moon.png diff --git a/Data/DefaultContent/Libraries/addons/addons/barfiller/statics.lua b/Data/DefaultContent/Libraries/addons/addons/barfiller/statics.lua new file mode 100644 index 0000000..f15aa8b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/barfiller/statics.lua @@ -0,0 +1,312 @@ +--[[ BSD License Disclaimer + Copyright © 2015, Morath86 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of BarFiller nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Morath86 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. +]] + +defaults = {} +defaults.Images = {} +defaults.Images.Background = {} +defaults.Images.Background.Pos = {} +defaults.Images.Background.Pos.X = 164 +defaults.Images.Background.Pos.Y = 6 +defaults.Images.Background.Visible = true +defaults.Images.Background.Texture = {} +defaults.Images.Background.Texture.Path = windower.addon_path..'bar_bg.png' +defaults.Images.Background.Texture.Fit = true +defaults.Images.Background.Color = {} +defaults.Images.Background.Color.Alpha = 255 +defaults.Images.Background.Color.Red = 255 +defaults.Images.Background.Color.Green = 255 +defaults.Images.Background.Color.Blue = 255 +defaults.Images.Background.Size = {} +defaults.Images.Background.Size.Height = 5 +defaults.Images.Background.Size.Width = 472 +defaults.Images.Background.Repeatable = {} +defaults.Images.Background.Repeatable.X = 1 +defaults.Images.Background.Repeatable.Y = 1 +defaults.Images.Background.Draggable = false +defaults.Images.Foreground = {} +defaults.Images.Foreground.Pos = {} +defaults.Images.Foreground.Pos.X = 166 +defaults.Images.Foreground.Pos.Y = 6 +defaults.Images.Foreground.Visible = true +defaults.Images.Foreground.Texture = {} +defaults.Images.Foreground.Texture.Path = windower.addon_path..'bar_fg.png' +defaults.Images.Foreground.Texture.Fit = false +defaults.Images.Foreground.Color = {} +defaults.Images.Foreground.Color.Alpha = 255 +defaults.Images.Foreground.Color.Red = 255 +defaults.Images.Foreground.Color.Green = 255 +defaults.Images.Foreground.Color.Blue = 255 +defaults.Images.Foreground.Size = {} +defaults.Images.Foreground.Size.Height = 5 +defaults.Images.Foreground.Size.Width = 1 +defaults.Images.Foreground.Repeatable = {} +defaults.Images.Foreground.Repeatable.X = 1 +defaults.Images.Foreground.Repeatable.Y = 1 +defaults.Images.Foreground.Draggable = false +defaults.Images.RestedBonus = {} +defaults.Images.RestedBonus.Pos = {} +defaults.Images.RestedBonus.Pos.X = 636 +defaults.Images.RestedBonus.Pos.Y = 6 +defaults.Images.RestedBonus.Visible = true +defaults.Images.RestedBonus.Texture = {} +defaults.Images.RestedBonus.Texture.Path = windower.addon_path..'moon.png' +defaults.Images.RestedBonus.Texture.Fit = true +defaults.Images.RestedBonus.Color = {} +defaults.Images.RestedBonus.Color.Alpha = 255 +defaults.Images.RestedBonus.Color.Red = 255 +defaults.Images.RestedBonus.Color.Green = 255 +defaults.Images.RestedBonus.Color.Blue = 255 +defaults.Images.RestedBonus.Size = {} +defaults.Images.RestedBonus.Size.Height = 32 +defaults.Images.RestedBonus.Size.Width = 32 +defaults.Images.RestedBonus.Repeatable = {} +defaults.Images.RestedBonus.Repeatable.X = 1 +defaults.Images.RestedBonus.Repeatable.Y = 1 +defaults.Images.RestedBonus.Draggable = false +defaults.Texts = {} +defaults.Texts.Exp = {} +defaults.Texts.Exp.Pos = {} +defaults.Texts.Exp.Pos.X = 159 +defaults.Texts.Exp.Pos.Y = 13 +defaults.Texts.Exp.Background = {} +defaults.Texts.Exp.Background.Alpha = 0 +defaults.Texts.Exp.Background.Red = 0 +defaults.Texts.Exp.Background.Green = 0 +defaults.Texts.Exp.Background.Blue = 0 +defaults.Texts.Exp.Background.Visible = false +defaults.Texts.Exp.Flags = {} +defaults.Texts.Exp.Flags.Right = false +defaults.Texts.Exp.Flags.Bottom = false +defaults.Texts.Exp.Flags.Bold = false +defaults.Texts.Exp.Flags.Draggable = false +defaults.Texts.Exp.Flags.Italic = false +defaults.Texts.Exp.Padding = 0 +defaults.Texts.Exp.Text = {} +defaults.Texts.Exp.Text.Size = 10 +defaults.Texts.Exp.Text.Font = 'Montserrat' +defaults.Texts.Exp.Text.Fonts = {'Ubuntu Mono', 'sans-serif'} +defaults.Texts.Exp.Text.Alpha = 255 +defaults.Texts.Exp.Text.Red = 253 +defaults.Texts.Exp.Text.Green = 252 +defaults.Texts.Exp.Text.Blue = 250 +defaults.Texts.Exp.Text.Stroke = {} +defaults.Texts.Exp.Text.Stroke.Width = 1 +defaults.Texts.Exp.Text.Stroke.Alpha = 127 +defaults.Texts.Exp.Text.Stroke.Red = 136 +defaults.Texts.Exp.Text.Stroke.Green = 97 +defaults.Texts.Exp.Text.Stroke.Blue = 18 +defaults.ShowDetails = {} +defaults.ShowDetails.MainJob = true +defaults.ShowDetails.SubJob = true +defaults.ShowDetails.Level = true +defaults.ShowDetails.ExperiencePoints = true +defaults.ShowDetails.ToNextLevel = true +defaults.ShowDetails.Percent = true +defaults.ShowDetails.Rate = true + +approved_commands = S{ + 'clear','c', + 'visible','v', + 'reload','r', + 'unload','u', + 'help','h' +} + +approved_commands = { + clear={n=0},c={n=0}, + visible={n=0},v={n=0}, + reload={n=0},r={n=0}, + unload={n=0},u={n=0}, + help={n=0},h={n=0} +} + +function load_images() + background_image:pos(settings.Images.Background.Pos.X, settings.Images.Background.Pos.Y) + background_image:path(settings.Images.Background.Texture.Path) + background_image:repeat_xy(settings.Images.Background.Repeatable.X, settings.Images.Background.Repeatable.Y) + background_image:draggable(settings.Images.Background.Draggable) + background_image:show() + + foreground_image:pos(settings.Images.Foreground.Pos.X, settings.Images.Foreground.Pos.Y) + foreground_image:path(settings.Images.Foreground.Texture.Path) + foreground_image:fit(settings.Images.Foreground.Texture.Fit) + foreground_image:draggable(settings.Images.Foreground.Draggable) + foreground_image:show() + + rested_bonus_image:pos(settings.Images.RestedBonus.Pos.X, settings.Images.RestedBonus.Pos.Y) + rested_bonus_image:visible(settings.Images.RestedBonus.Visible) + rested_bonus_image:path(settings.Images.RestedBonus.Texture.Path) + rested_bonus_image:draggable(settings.Images.RestedBonus.Draggable) + mog_house() + + position_images() +end + +function load_text_box() + exp_text:pos(settings.Texts.Exp.Pos.X, settings.Texts.Exp.Pos.Y) + exp_text:bg_alpha(settings.Texts.Exp.Background.Alpha) + exp_text:bg_visible(settings.Texts.Exp.Background.Visible) + exp_text:font(settings.Texts.Exp.Text.Font, unpack(settings.Texts.Exp.Text.Fonts)) + exp_text:size(settings.Texts.Exp.Text.Size) + exp_text:color(settings.Texts.Exp.Text.Red, settings.Texts.Exp.Text.Green, settings.Texts.Exp.Text.Blue) + exp_text:stroke_alpha(settings.Texts.Exp.Text.Stroke.Alpha) + exp_text:stroke_color(settings.Texts.Exp.Text.Stroke.Red, settings.Texts.Exp.Text.Stroke.Green, + settings.Texts.Exp.Text.Stroke.Blue) + exp_text:stroke_width(settings.Texts.Exp.Text.Stroke.Width) + + exp_text:show() +end + +function initialize() + xp = { + registry = {}, + total = 0, + rate = 0, + current = 0, + tnl = 0 + } + player = { + job = '', + sub = '', + lvl = '', + exp = '', + tnl = '', + phr = '', + pct = '' + } + load_images() + load_text_box() + calc_new_width() + update_strings() + ready = true +end + +function update_strings() + info = windower.ffxi.get_player() + player.job = string.upper(info.main_job) + player.sub = (info.sub_job and '('..string.lower(info.sub_job)..') ' or '(---) ') + player.lvl = 'Lv'..info.main_job_level..' ' + player.exp = 'EXP '..xp.current..'/'..xp.total..' ' + player.tnl = '('..xp.tnl..') ' + player.pct = (xp.total > 0 and math.floor((xp.current / xp.total) * 100)..'% ' or '0% ') + player.phr = 'EXP/hr '..string.format('%.1f',math.floor(xp.rate/100)/10)..'k' + exp_text:clear() + if settings.ShowDetails.MainJob then exp_text:append(player.job) end + if settings.ShowDetails.SubJob then exp_text:append(player.sub) else exp_text:append(' ') end + if settings.ShowDetails.Level then exp_text:append(player.lvl) end + if settings.ShowDetails.ExperiencePoints then exp_text:append(player.exp) end + if settings.ShowDetails.ToNextLevel then exp_text:append(player.tnl) end + if settings.ShowDetails.Percent then exp_text:append(player.pct) end + if settings.ShowDetails.Rate then exp_text:append(player.phr) end +end + +function display_help() + windower.add_to_chat(8,_addon.name..' v'.._addon.version..': Command Listing') + windower.add_to_chat(8,' (c)lear - Resets EXP counter') + windower.add_to_chat(8,' (v)isible - Toggles visibility') + windower.add_to_chat(8,' (u)nload - Disables BarFiller') + windower.add_to_chat(8,' (r)eload - Reloads BarFiller') +end + +function exp_msg(val,msg) + local t = os.clock() + if msg == 8 or msg == 105 then + xp.registry[t] = (xp.registry[t] or 0) + val + xp.current = math.min(xp.current + val,55999) + if xp.current > xp.tnl then + xp.current = xp.current - xp.tnl + end + chunk_update = true + end +end + +function analyze_points_table(tab) + local t = os.clock() + local running_total = 0 + local maximum_timestamp = 29 + for ts,points in pairs(tab) do + local time_diff = t - ts + if t - ts > 600 then + tab[ts] = nil + else + running_total = running_total + points + if time_diff > maximum_timestamp then + maximum_timestamp = time_diff + end + end + end + + local rate + if maximum_timestamp == 29 then + rate = 0 + else + rate = math.floor((running_total/maximum_timestamp)*3600) + end + + return rate +end + +function calc_new_width() + if xp.current > 0 and xp.total > 0 then + local calc = math.floor((xp.current / xp.total) * 468) + xp.rate = analyze_points_table(xp.registry) + return calc + end +end + +function position_images() + local x = windower.get_windower_settings().x_res / 2 - settings.Images.Background.Size.Width / 2 + + background_image:pos(x, settings.Images.Background.Pos.Y) + foreground_image:pos(x + 2, settings.Images.Foreground.Pos.Y) + rested_bonus_image:pos(x + settings.Images.Background.Size.Width, settings.Images.Background.Pos.Y - 6) +end + +function position_text() + exp_text:pos((background_image:pos_x() - 6), (background_image:pos_y() + 4)) +end + +function hide() + background_image:hide() + foreground_image:hide() + rested_bonus_image:hide() + exp_text:hide() + ready = false +end + +function show() + background_image:show() + foreground_image:show() + mog_house() + exp_text:show() + ready = true +end + +function mog_house() + return (windower.ffxi.get_info().mog_house and rested_bonus_image:show() or rested_bonus_image:hide()) +end diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/README.md b/Data/DefaultContent/Libraries/addons/addons/battlemod/README.md new file mode 100644 index 0000000..2a9a823 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/README.md @@ -0,0 +1,42 @@ +Author: Byrth + +Version: 3.21 + +Date: 20/9/15 + +Battlemod, packet version + +Abbreviation: //bm + +Commands: + +Toggles: +* simplify - Condenses battle text using custom messages (Default: True) +* condensetargets - Collapse similar messages with multiple targets (Default: True) + * targetnumber - Toggle condensed target number display (Default: True) + * condensetargetname - Toggle target name condensation (Default: False) + * oxford - Toggle use of oxford comma (Default: True) + * commamode - Toggle comma-only mode (Default: False) +* condensedamage - Collapses similar damage messages with the same target (Default: True) + * swingnumber - Toggle condensed damage number display (Default: True) + * sumdamage - Sums condensed damage if true, comma-separated if false (Default: True) + * condensecrits - Condenses critical hits and normal hits together (Default: False) +* cancelmulti - Cancels multiple consecutive identical lines (Default: True) +* showonernames - Shows the name of the owner on pet messages (Default: False) +* crafting - Toggle early display of crafting results (Default: True) + +Utilities: +* colortest - Shows the 509 possible colors for use with the settings file +* reload - Reloads the settings file +* unload - unloads Battlemod +* help - shows a menu of these commands in game + +Purpose: To allow chat log customization. + +Settings Files: +The settings files for battlemod are composed of 3 to 25 xml files (depending on how much you like unique filters). XML files can be opened with Notepad, edited, and saved safely. If you are going to "Save As..." an xml from Notepad, be sure to change "Text Documents (.txt)" to "All Files" file type and make sure the file ends in ".xml" + +* data/settings.xml - contains basic flags that control the features of the program. +* data/colors.xml - contains all the color codes relevant to the program, which can be adjusted using colors from the colortext command. +* filters/filters.xml - contains the chat filter settings and is explained more thoroughly therein. +* filters/filters-<job>.xml - Several examples are provided, but these are specific filter files that will load for your individual jobs. You can use this to, for instance, make sure your healing jobs can always see damage taken (by unfiltering the <monsters></monsters> section or make sure your zerg jobs don't have to see the entire alliance's damage spam. The filter file is organized by actor, so if you wanted to filter by target you would have to go through each class of actor and change the setting that affected the given target. diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/battlemod.lua b/Data/DefaultContent/Libraries/addons/addons/battlemod/battlemod.lua new file mode 100644 index 0000000..5ae3426 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/battlemod.lua @@ -0,0 +1,508 @@ +require 'tables' +require 'sets' +file = require 'files' +config = require 'config' +require 'strings' +res = require 'resources' +require 'actions' +require 'pack' +bit = require 'bit' + +require 'generic_helpers' +require 'parse_action_packet' +require 'statics' + +_addon.version = '3.31' +_addon.name = 'BattleMod' +_addon.author = 'Byrth, maintainer: SnickySnacks' +_addon.commands = {'bm','battlemod'} + +windower.register_event('load',function() + if debugging then windower.debug('load') end + options_load() +end) + +windower.register_event('login',function (name) + if debugging then windower.debug('login') end + options_load:schedule(10) +end) + +windower.register_event('addon command', function(command, ...) + if debugging then windower.debug('addon command') end + local args = {...} + command = command and command:lower() + if command then + if command:lower() == 'commamode' then + commamode = not commamode + windower.add_to_chat(121,'Battlemod: Comma Mode flipped! - '..tostring(commamode)) + elseif command:lower() == 'oxford' then + oxford = not oxford + windower.add_to_chat(121,'Battlemod: Oxford Mode flipped! - '..tostring(oxford)) + elseif command:lower() == 'targetnumber' then + targetnumber = not targetnumber + windower.add_to_chat(121,'Battlemod: Target Number flipped! - '..tostring(targetnumber)) + elseif command:lower() == 'condensetargetname' then + condensetargetname = not condensetargetname + windower.add_to_chat(121,'Battlemod: Target Name Condensation flipped! - '..tostring(condensetargetname)) + elseif command:lower() == 'swingnumber' then + swingnumber = not swingnumber + windower.add_to_chat(121,'Battlemod: Round Number flipped! - '..tostring(swingnumber)) + elseif command:lower() == 'sumdamage' then + sumdamage = not sumdamage + windower.add_to_chat(121,'Battlemod: Sum Damage flipped! - '..tostring(sumdamage)) + elseif command:lower() == 'condensecrits' then + condensecrits = not condensecrits + windower.add_to_chat(121,'Battlemod: Condense Crits flipped! - '..tostring(condensecrits)) + elseif command:lower() == 'cancelmulti' then + cancelmulti = not cancelmulti + windower.add_to_chat(121,'Battlemod: Multi-canceling flipped! - '..tostring(cancelmulti)) + elseif command:lower() == 'reload' then + current_job = 'NONE' + options_load() + elseif command:lower() == 'unload' then + windower.send_command('@lua u battlemod') + elseif command:lower() == 'simplify' then + simplify = not simplify + windower.add_to_chat(121,'Battlemod: Text simplification flipped! - '..tostring(simplify)) + elseif command:lower() == 'condensedamage' then + condensedamage = not condensedamage + windower.add_to_chat(121,'Battlemod: Condensed Damage text flipped! - '..tostring(condensedamage)) + elseif command:lower() == 'condensetargets' then + condensetargets = not condensetargets + windower.add_to_chat(121,'Battlemod: Condensed Targets flipped! - '..tostring(condensetargets)) + elseif command:lower() == 'showownernames' then + showownernames = not showownernames + windower.add_to_chat(121,'Battlemod: Show pet owner names flipped! - '..tostring(showownernames)) + elseif command:lower() == 'crafting' then + crafting = not crafting + windower.add_to_chat(121,'Battlemod: Display crafting results flipped! - '..tostring(crafting)) + elseif command:lower() == 'showblocks' then + showblocks = not showblocks + windower.add_to_chat(121,'Battlemod: Show blocks with shield flipped! - '..tostring(showblocks)) + elseif command:lower() == 'showguards' then + showguards = not showguards + windower.add_to_chat(121,'Battlemod: Show guarding on hits flipped! - '..tostring(showguards)) + elseif command:lower() == 'showcritws' then + showcritws = not showcritws + windower.add_to_chat(121,'Battlemod: Show critical hit on ws/mob tp flipped! - '..tostring(showcritws)) + elseif command:lower() == 'showrollinfo' then + showrollinfo = not showrollinfo + windower.add_to_chat(121,'Battlemod: Show lucky/unlucky rolls flipped! - '..tostring(showrollinfo)) + elseif command:lower() == 'colortest' then + local counter = 0 + local line = '' + for n = 1, 262 do + if not color_redundant:contains(n) and not black_colors:contains(n) then + if n <= 255 then + loc_col = string.char(0x1F, n) + else + loc_col = string.char(0x1E, n - 254) + end + line = line..loc_col..string.format('%03d ', n) + counter = counter + 1 + end + if counter == 16 or n == 262 then + windower.add_to_chat(6, line) + counter = 0 + line = '' + end + end + windower.add_to_chat(122,'Colors Tested!') + elseif command:lower() == 'help' then + print(' ::: '.._addon.name..' ('.._addon.version..') :::') + print('Toggles: (* subtoggles)') + print(' 1. simplify - Condenses battle text using custom messages ('..tostring(simplify)..')') + print(' 2. condensetargets - Collapse similar messages with multiple targets ('..tostring(condensetargets)..')') + print(' * targetnumber - Toggle target number display ('..tostring(targetnumber)..')') + print(' * condensetargetname - Toggle target name condensation ('..tostring(condensetargetname)..')') + print(' * oxford - Toggle use of oxford comma ('..tostring(oxford)..')') + print(' * commamode - Toggle comma-only mode ('..tostring(commamode)..')') + print(' 3. condensedamage - Condenses damage messages within attack rounds ('..tostring(condensedamage)..')') + print(' * swingnumber - Show # of attack rounds ('..tostring(swingnumber)..')') + print(' * sumdamage - Sums condensed damage if true, comma-separated if false ('..tostring(sumdamage)..')') + print(' * condensecrits - Condenses critical hits and normal hits together ('..tostring(condensecrits)..')') + print(' 4. cancelmulti - Cancels multiple consecutive identical lines ('..tostring(cancelmulti)..')') + print(' 5. showonernames - Shows the name of the owner on pet messages ('..tostring(showownernames)..')') + print(' 6. crafting - Enables early display of crafting results ('..tostring(crafting)..')') + print(' 7. showblocks - Shows if a hit was blocked with shield ('..tostring(showblocks)..')') + print(' 8. showguards - Shows if a hit was guarded ('..tostring(showguards)..')') + print(' 9. showcritws - Shows if a ws or mob ability was a critical hit (shows on multihit if atleast 1 hit was a crit) ('..tostring(showcritws)..')') + print(' 10. showrollinfo - Shows lucky/unlucky rolls ('..tostring(showrollinfo)..')') + print('Utilities: 1. colortest - Shows the 509 possible colors for use with the settings file') + print(' 2. reload - Reloads settings file') + print(' 3. unload - Unloads Battlemod') + end + end +end) + +windower.register_event('incoming text',function (original, modified, color, color_m, blocked) + if debugging then windower.debug('incoming text') end + local redcol = color%256 + + if redcol == 121 and cancelmulti then + a,z = string.find(original,'Equipment changed') + + if a and not block_equip then + flip_block_equip:schedule(1) + block_equip = true + elseif a and block_equip then + modified = true + end + elseif redcol == 123 and cancelmulti then + a,z = string.find(original,'You were unable to change your equipped items') + b,z = string.find(original,'You cannot use that command while viewing the chat log') + c,z = string.find(original,'You must close the currently open window to use that command') + + if (a or b or c) and not block_cannot then + flip_block_cannot:schedule(1) + block_cannot = true + elseif (a or b or c) and block_cannot then + modified = true + end + end + if block_modes:contains(color) then + local endline = string.char(0x7F, 0x31) + local item = string.char(0x1E) + if not bm_message(original) then + if original:endswith(endline) then --allow add_to_chat messages with the modes we blocking + return true + end + elseif original:endswith(endline) and string.find(original, item) then --block items action messages + return true + end + end + + return modified,color +end) + +function bm_message(original) + local check = string.char(0x1E) + local check2 = string.char(0x1F) + if string.find(original, check) or string.find(original, check2) then + return true + end +end + +function flip_block_equip() + block_equip = not block_equip +end + +function flip_block_cannot() + block_cannot = not block_cannot +end + +function options_load() + if windower.ffxi.get_player() then + Self = windower.ffxi.get_player() + end + if not windower.dir_exists(windower.addon_path..'data\\') then + windower.create_dir(windower.addon_path..'data\\') + end + if not windower.dir_exists(windower.addon_path..'data\\filters\\') then + windower.create_dir(windower.addon_path..'data\\filters\\') + end + + local settingsFile = file.new('data\\settings.xml',true) + local filterFile=file.new('data\\filters\\filters.xml',true) + local colorsFile=file.new('data\\colors.xml',true) + + if not file.exists('data\\settings.xml') then + settingsFile:write(default_settings) + print('Default settings xml file created') + end + + local settingtab = config.load('data\\settings.xml',default_settings_table) + config.save(settingtab) + + for i,v in pairs(settingtab) do + _G[i] = v + end + + if not file.exists('data\\filters\\filters.xml') then + filterFile:write(default_filters) + print('Default filters xml file created') + end + local tempplayer = windower.ffxi.get_player() + if tempplayer then + if tempplayer.main_job ~= 'NONE' then + filterload(tempplayer.main_job) + elseif windower.ffxi.get_mob_by_id(tempplayer.id)['race'] == 0 then + filterload('MON') + else + filterload('DEFAULT') + end + else + filterload('DEFAULT') + end + if not file.exists('data\\colors.xml') then + colorsFile:write(default_colors) + print('Default colors xml file created') + end + local colortab = config.load('data\\colors.xml',default_color_table) + config.save(colortab) + for i,v in pairs(colortab) do + color_arr[i] = colconv(v,i) + end +end + +function filterload(job) + if current_job == job then return end + if file.exists('data\\filters\\filters-'..job..'.xml') then + default_filt = false + filter = config.load('data\\filters\\filters-'..job..'.xml',default_filter_table,false) + config.save(filter) + windower.add_to_chat(4,'Loaded '..job..' Battlemod filters') + elseif not default_filt then + default_filt = true + filter = config.load('data\\filters\\filters.xml',default_filter_table,false) + config.save(filter) + windower.add_to_chat(4,'Loaded default Battlemod filters') + end + current_job = job +end + +ActionPacket.open_listener(parse_action_packet) + +windower.register_event('incoming chunk',function (id,original,modified,is_injected,is_blocked) + if debugging then windower.debug('incoming chunk '..id) end + +------- ITEM QUANTITY ------- + if id == 0x020 and parse_quantity then + --local packet = packets.parse('incoming', original) + local item = original:unpack('H',0x0D) + local count = original:unpack('I',0x05) + if item == 0 then return end + if item_quantity.id == item then + item_quantity.count = count..' ' + end + +------- NOUNS AND PLURAL ENTITIES ------- + elseif id == 0x00E then + local mob_id = original:unpack('I',0x05) + local mask = original:unpack('C',0x0B) + local chat_info = original:unpack('C',0x28) + if bit.band(mask,4) == 4 then + if bit.band(chat_info,32) == 0 and not common_nouns:contains(mob_id) then + table.insert(common_nouns, mob_id) + elseif bit.band(chat_info,64) == 64 and not plural_entities:contains(mob_id) then + table.insert(plural_entities, mob_id) + elseif bit.band(chat_info,64) == 0 and plural_entities:contains(mob_id) then --Gears can change their grammatical number when they lose 2 gear? + for i, v in pairs(plural_entities) do + if v == mob_id then + table.remove(plural_entities, i) + break + end + end + end + end + elseif id == 0x00B then --Reset tables on Zoning + common_nouns = T{} + plural_entities = T{} + +------- ACTION MESSAGE ------- + elseif id == 0x29 then + local am = {} + am.actor_id = original:unpack('I',0x05) + am.target_id = original:unpack('I',0x09) + am.param_1 = original:unpack('I',0x0D) + am.param_2 = original:unpack('H',0x11)%2^9 -- First 7 bits + am.param_3 = math.floor(original:unpack('I',0x11)/2^5) -- Rest + am.actor_index = original:unpack('H',0x15) + am.target_index = original:unpack('H',0x17) + am.message_id = original:unpack('H',0x19)%2^15 -- Cut off the most significant bit + + local actor = player_info(am.actor_id) + local target = player_info(am.target_id) + local actor_article = common_nouns:contains(am.actor_id) and 'The ' or '' + local target_article = common_nouns:contains(am.target_id) and 'The ' or '' + targets_condensed = false + + -- Filter these messages + if not check_filter(actor,target,0,am.message_id) then return true end + + if not actor or not target then -- If the actor or target table is nil, ignore the packet + elseif am.message_id == 800 then -- Spirit bond message + local status = color_it(res.buffs[am.param_1][language],color_arr.statuscol) + local targ = color_it(target.name or '',color_arr[target.owner or target.type]) + local number = am.param_2 + local color = color_filt(res.action_messages[am.message_id].color, am.target_id==Self.id) + if simplify then + local msg = line_noactor + :gsub('${abil}',status or '') + :gsub('${target}',targ) + :gsub('${numb}',number or '') + windower.add_to_chat(color, msg) + else + local msg = res.action_messages[am.message_id][language] + msg = grammatical_number_fix(msg, number, am.message_id) + if plural_entities:contains(am.actor_id) then + msg = plural_actor(msg, am.message_id) + end + if plural_entities:contains(am.target_id) then + msg = plural_target(msg, am.message_id) + end + local msg = clean_msg(msg + :gsub('${status}',status or '') + :gsub('${target}',target_article..targ) + :gsub('${number}',number or ''), am.message_id) + windower.add_to_chat(color, msg) + end + elseif am.message_id == 206 and condensetargets then -- Wears off messages + -- Condenses across multiple packets + local status + + if enfeebling:contains(am.param_1) and res.buffs[param_1] then + status = color_it(res.buffs[param_1][language],color_arr.enfeebcol) + elseif color_arr.statuscol == rcol then + status = color_it(res.buffs[am.param_1][language],string.char(0x1F,191)) + else + status = color_it(res.buffs[am.param_1][language],color_arr.statuscol) + end + + if not multi_actor[status] then multi_actor[status] = player_info(am.actor_id) end + if not multi_msg[status] then multi_msg[status] = am.message_id end + + if not multi_targs[status] and not stat_ignore:contains(am.param_1) then + multi_targs[status] = {} + multi_targs[status][1] = target + multi_packet:schedule(0.5, status) + elseif not (stat_ignore:contains(am.param_1)) then + multi_targs[status][#multi_targs[status]+1] = target + else + -- This handles the stat_ignore values, which are things like Utsusemi, + -- Sneak, Invis, etc. that you don't want to see on a delay + multi_targs[status] = {} + multi_targs[status][1] = target + multi_packet(status) + end + am.message_id = false + elseif passed_messages:contains(am.message_id) then + local item,status,spell,skill,number,number2 + local outstr = res.action_messages[am.message_id][language] + if plural_entities:contains(am.actor_id) then + outstr = plural_actor(outstr, am.message_id) + end + if plural_entities:contains(am.target_id) then + outstr = plural_target(outstr, am.message_id) + end + + local fields = fieldsearch(outstr) + + if fields.status then + if log_form_messages:contains(am.message_id) then + status = res.buffs[am.param_1].english_log + else + status = nf(res.buffs[am.param_1],language) + end + if enfeebling:contains(am.param_1) then + status = color_it(status,color_arr.enfeebcol) + else + status = color_it(status,color_arr.statuscol) + end + end + + if fields.spell then + if not res.spells[am.param_1] then + return false + end + spell = nf(res.spells[am.param_1],language) + end + + if fields.item then + if not res.items[am.param_1] then + return false + end + item = nf(res.items[am.param_1],'english_log') + end + + if fields.number then + number = am.param_1 + end + + if fields.number2 then + number2 = am.param_2 + end + + if fields.skill and res.skills[am.param_1] then + skill = res.skills[am.param_1][language]:lower() + end + + if am.message_id > 169 and am.message_id <179 then + if am.param_1 > 2147483647 then + skill = 'to be level -1 ('..ratings_arr[am.param_2-63]..')' + else + skill = 'to be level '..am.param_1..' ('..ratings_arr[am.param_2-63]..')' + end + end + outstr = (clean_msg(outstr + :gsub('${actor}\'s',actor_article..color_it(actor.name or '',color_arr[actor.owner or actor.type])..'\'s'..actor.owner_name) + :gsub('${actor}',actor_article..color_it(actor.name or '',color_arr[actor.owner or actor.type])..actor.owner_name) + :gsub('${status}',status or '') + :gsub('${item}',color_it(item or '',color_arr.itemcol)) + :gsub('${target}\'s',target_article..color_it(target.name or '',color_arr[target.owner or target.type])..'\'s'..target.owner_name) + :gsub('${target}',target_article..color_it(target.name or '',color_arr[target.owner or target.type])..target.owner_name) + :gsub('${spell}',color_it(spell or '',color_arr.spellcol)) + :gsub('${skill}',color_it(skill or '',color_arr.abilcol)) + :gsub('${number}',number or '') + :gsub('${number2}',number2 or '') + :gsub('${skill}',skill or '') + :gsub('${lb}','\7'), am.message_id)) + windower.add_to_chat(res.action_messages[am.message_id]['color'],outstr) + am.message_id = false + elseif debugging and res.action_messages[am.message_id] then + -- 38 is the Skill Up message, which (interestingly) uses all the number params. + -- 202 is the Time Remaining message, which (interestingly) uses all the number params. + print('debug_EAM#'..am.message_id..': '..res.action_messages[am.message_id][language]..' '..am.param_1..' '..am.param_2..' '..am.param_3) + elseif debugging then + print('debug_EAM#'..am.message_id..': '..'Unknown'..' '..am.param_1..' '..am.param_2..' '..am.param_3) + end + if not am.message_id then + return true + end + +------------ SYNTHESIS ANIMATION -------------- + elseif id == 0x030 and crafting then + if windower.ffxi.get_player().id == original:unpack('I',5) or windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').id == original:unpack('I',5) then + local crafter_name = (windower.ffxi.get_player().id == original:unpack('I',5) and windower.ffxi.get_player().name) or windower.ffxi.get_mob_by_target('t').name + local result = original:byte(13) + if result == 0 then + windower.add_to_chat(8,' ------------- NQ Synthesis ('..crafter_name..') -------------') + elseif result == 1 then + windower.add_to_chat(8,' ---------------- Break ('..crafter_name..') -----------------') + elseif result == 2 then + windower.add_to_chat(8,' ------------- HQ Synthesis ('..crafter_name..') -------------') + else + windower.add_to_chat(8,'Craftmod: Unhandled result '..tostring(result)) + end + end + elseif id == 0x06F and crafting then + if original:byte(5) == 0 or original:byte(5) == 12 then + local result = original:byte(6) + if result == 1 then + windower.add_to_chat(8,' -------------- HQ Tier 1! --------------') + elseif result == 2 then + windower.add_to_chat(8,' -------------- HQ Tier 2! --------------') + elseif result == 3 then + windower.add_to_chat(8,' -------------- HQ Tier 3! --------------') + end + end + + ------------- JOB INFO ---------------- + elseif id == 0x01B then + filterload(res.jobs[original:byte(9)][language..'_short']) + end +end) + +function multi_packet(...) + local ind = table.concat({...},' ') + local targets = assemble_targets(multi_actor[ind],multi_targs[ind],0,multi_msg[ind]) + local outstr = targets_condensed and plural_target(res.action_messages[multi_msg[ind]][language], multi_msg[ind]) or res.action_messages[multi_msg[ind]][language] + outstr = clean_msg(outstr + :gsub('${target}\'s',targets) + :gsub('${target}',targets) + :gsub('${status}',ind), multi_msg[ind]) + windower.add_to_chat(res.action_messages[multi_msg[ind]].color,outstr) + multi_targs[ind] = nil + multi_msg[ind] = nil + multi_actor[ind] = nil +end diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-DNC.xml b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-DNC.xml new file mode 100644 index 0000000..7eddfdc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-DNC.xml @@ -0,0 +1,153 @@ +<?xml version="1.0" ?> +<!-- Filters are customizable based on the action user. So if you filter other pets, you're going + to eliminate all messages initiated by everyone's pet but your own. + True means "filter this" + False means "don't filter this" + + Generally, the outer tag is the actor and the inner tag is the action. + If the monster is the actor, then the inner tag is the target and the tag beyond that is the action.--> +<settings> + <global> + <me> <!-- You're doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- A party member is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- An alliance member is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>false</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- Some guy nearby is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>true</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>true</all> + </others> + <my_pet> <!-- Your pet is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <other_pets> <!-- Someone else's pet is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>true</all> + </other_pets> + + + <monsters> <!-- Monster is doing something with one of the below targets --> + <me> <!-- He's targeting you! --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- He's targeting a party member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- He's targeting an alliance member --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>true</readies> + <casting>true</casting> + <all>false</all> + </alliance> + <others> <!-- He's targeting some guy nearby --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </others> + <my_pet> <!-- He's targeting your pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <other_pets> <!-- He's targeting someone else's pet --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </other_pets> + + <monsters> <!-- He's targeting himself or another monster --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </monsters> + </monsters> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WAR.xml b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WAR.xml new file mode 100644 index 0000000..2dcbf00 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WAR.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" ?> +<!-- Filters are customizable based on the action user. So if you filter other pets, you're going + to eliminate all messages initiated by everyone's pet but your own. + True means "filter this" + False means "don't filter this" + + Generally, the outer tag is the actor and the inner tag is the action. + If the monster is the actor, then the inner tag is the target and the tag beyond that is the action.--> +<settings> + <global> + <me> <!-- You're doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- A party member is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>false</damage> + <healing>true</healing> + <misses>true</misses> + <items>false</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- An alliance member is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>false</damage> + <healing>true</healing> + <misses>true</misses> + <items>false</items> + <uses>true</uses> + <readies>true</readies> + <casting>true</casting> + <all>false</all> + </alliance> + <others> <!-- Some guy nearby is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>true</items> + <uses>true</uses> + <readies>true</readies> + <casting>true</casting> + <all>true</all> + </others> + <my_pet> <!-- Your pet is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>true</all> + </my_pet> + <other_pets> <!-- Someone else's pet is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>true</all> + </other_pets> + + + <monsters> <!-- Monster is doing something with one of the below targets --> + <me> <!-- He's targeting you! --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- He's targeting a party member --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- He's targeting an alliance member --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- He's targeting some guy nearby --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </others> + <my_pet> <!-- He's targeting your pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>true</all> + </my_pet> + <other_pets> <!-- He's targeting someone else's pet --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </other_pets> + + <monsters> <!-- He's targeting himself or another monster --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </monsters> + </monsters> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WHM.xml b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WHM.xml new file mode 100644 index 0000000..257100b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/data/filters/filters-WHM.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" ?> +<!-- Filters are customizable based on the action user. So if you filter other pets, you're going + to eliminate all messages initiated by everyone's pet but your own. + True means "filter this" + False means "don't filter this" + + Generally, the outer tag is the actor and the inner tag is the action. + If the monster is the actor, then the inner tag is the target and the tag beyond that is the action.--> +<settings> + <global> + <me> <!-- You're doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- A party member is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>false</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- An alliance member is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>false</items> + <uses>true</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- Some guy nearby is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <items>true</items> + <uses>true</uses> + <readies>true</readies> + <casting>true</casting> + <all>true</all> + </others> + <my_pet> <!-- Your pet is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <other_pets> <!-- Someone else's pet is doing something --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>true</readies> + <casting>true</casting> + <all>true</all> + </other_pets> + + + <monsters> <!-- Monster is doing something with one of the below targets --> + <me> <!-- He's targeting you! --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- He's targeting a party member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- He's targeting an alliance member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- He's targeting some guy nearby --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>true</readies> + <casting>true</casting> + <all>true</all> + </others> + <my_pet> <!-- He's targeting your pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <other_pets> <!-- He's targeting someone else's pet --> + <melee>true</melee> + <ranged>true</ranged> + <damage>true</damage> + <healing>true</healing> + <misses>true</misses> + <readies>true</readies> + <casting>true</casting> + <all>true</all> + </other_pets> + + <monsters> <!-- He's targeting himself or another monster --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>true</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </monsters> + </monsters> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/generic_helpers.lua b/Data/DefaultContent/Libraries/addons/addons/battlemod/generic_helpers.lua new file mode 100644 index 0000000..1f015ae --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/generic_helpers.lua @@ -0,0 +1,389 @@ +--Copyright © 2013, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +function nf(field,subfield) + if field ~= nil then + return field[subfield] + else + return nil + end +end + +function flip(p1,p1t,p2,p2t,cond) + return p2,p2t,p1,p1t,not cond +end + +function colconv(str,key) + -- Used in the options_load() function + local out + local strnum = tonumber(str) + if strnum >= 256 and strnum < 509 then + strnum = strnum - 254 + if strnum == 4 then strnum = 3 end --color 258 can bug chatlog + out = string.char(0x1E,strnum) + elseif strnum >0 then + out = string.char(0x1F,strnum) + elseif strnum == 0 then + out = rcol + else + print('You have an invalid color '..key) + out = string.char(0x1F,1) + end + return out +end + + +function color_it(to_color,color) + if not color and debugging then windower.add_to_chat(8,'Color was invalid.') end + if not color or color == 0 then return to_color end + + if to_color then + local colarr = string.split(to_color,' ') + colarr.n = nil + return color..table.concat(colarr,rcol..' '..color)..rcol + end +end + + +function conjunctions(pre,post,target_count,current) + if current < target_count or commamode then + pre = pre..', ' + else + if oxford and target_count >2 then + pre = pre..',' + end + pre = pre..' and ' + end + return pre..post +end + + + +function fieldsearch(message) + local fieldarr = {} + string.gsub(message,'{(.-)}', function(a) fieldarr[a] = true end) + return fieldarr +end + + +function check_filter(actor,target,category,msg) + -- This determines whether the message should be displayed or filtered + -- Returns true (don't filter) or false (filter), boolean + if not actor.filter or not target.filter then return false end + + if not filter[actor.filter] and debugging then windower.add_to_chat(8,'Battlemod - Filter Not Recognized: '..tostring(actor.filter)) end + + local filtertab = (filter[actor.filter] and filter[actor.filter][target.filter]) or filter[actor.filter] + + if filtertab['all'] + or category == 1 and filtertab['melee'] + or category == 2 and filtertab['ranged'] + or category == 12 and filtertab['ranged'] + or category == 5 and filtertab['items'] + or category == 9 and filtertab['uses'] + or nf(res.action_messages[msg],'color')=='D' and filtertab['damage'] + or nf(res.action_messages[msg],'color')=='M' and filtertab['misses'] + or nf(res.action_messages[msg],'color')=='H' and filtertab['healing'] + or (msg == 43 or msg == 326) and filtertab['readies'] + or (msg == 3 or msg==327) and filtertab['casting'] + then + return false + end + + return true +end + +function actor_noun(msg) + if msg then + msg = msg + :gsub('${actor}', 'The ${actor}') + end + return msg +end + +function plural_actor(msg, msg_id) + if msg then + if msg_id == 6 then + msg = msg:gsub('${actor} defeats ', '${actor} defeat ') + elseif msg_id == 9 then + msg = msg:gsub('${actor} attains ', '${actor} attain ') + elseif msg_id == 10 then + msg = msg:gsub('${actor} loses ', '${actor} lose ') + elseif msg_id == 11 then + msg = msg:gsub('${actor} falls ', '${actor} fall ') + elseif msg_id == 19 then + msg = msg:gsub('${actor} calls ' , '${actor} call ') + elseif msg_id == 35 then + msg = msg:gsub('${actor} lacks ' , '${actor} lack ') + elseif msg_id == 67 then + msg = msg:gsub('${actor} scores ' , '${actor} score ') + elseif msg_id == 124 then + msg = msg:gsub('${actor} achieves ' , '${actor} achieve ') + elseif msg_id == 129 then + msg = msg:gsub('${actor} mugs ' , '${actor} mug ') + elseif msg_id == 244 then + msg = msg:gsub('${actor} fails ' , '${actor} fail ') + elseif msg_id == 311 then + msg = msg:gsub('${actor} covers ' , '${actor} cover ') + elseif msg_id == 315 then + msg = msg:gsub('${actor} already has ' , '${actor} already have ') + elseif msg_id ==411 then + msg = msg + :gsub('${actor} attempts ' , '${actor} attempt ') + :gsub(' but lacks ' , ' but lack ') + elseif msg_id == 536 then + msg = msg:gsub('${actor} takes ' , '${actor} take ') + elseif msg_id == 563 then + msg = msg:gsub('${actor} destroys ' , '${actor} destroy ') + elseif msg_id == 772 then + msg = msg:gsub('${actor} stands ', '${actor} stand ') + elseif replacements_map.actor.hits:contains(msg_id) then + msg = msg:gsub('${actor} hits ', '${actor} hit ') + elseif replacements_map.actor.misses:contains(msg_id) then + msg = msg:gsub('${actor} misses ' , '${actor} miss ') + elseif replacements_map.actor.starts:contains(msg_id) then + msg = msg:gsub('${actor} starts ', '${actor} start ') + elseif replacements_map.actor.casts:contains(msg_id) then + msg = msg:gsub('${actor} casts ', '${actor} cast ') + if msg_id == 83 then + msg = msg:gsub('${actor} successfully removes ' , '${actor} successfully remove ') + elseif msg_id == 572 or msg_id == 642 then + msg = msg:gsub('${actor} absorbs ' , '${actor} absorb ') + end + elseif replacements_map.actor.readies:contains(msg_id) then + msg = msg:gsub('${actor} readies ' , '${actor} ready ') + elseif replacements_map.actor.recovers:contains(msg_id) then + msg = msg:gsub('${actor} recovers ' , '${actor} recover ') + elseif replacements_map.actor.gains:contains(msg_id) then + msg = msg:gsub('${actor} gains ', '${actor} gain ') + elseif replacements_map.actor.apos:contains(msg_id) then + msg = msg:gsub('${actor}\'s ', '${actor}\' ') + if msg_id == 33 then + msg = msg:gsub('${actor} takes ' , '${actor} take ') + elseif msg_id == 606 then + msg = msg:gsub('${actor} recovers ' , '${actor} recover ') + elseif msg_id == 799 then + msg = msg:gsub('${actor} is ' , '${actor} are ') + end + elseif replacements_map.actor.uses:contains(msg_id) then + msg = msg:gsub('${actor} uses ' , '${actor} use ') + if msg_id == 122 then + msg = msg:gsub('${actor} recovers ' , '${actor} recover ') + elseif msg_id == 123 then + msg = msg:gsub('${actor} successfully removes ' , '${actor} successfully remove ') + elseif msg_id == 126 or msg_id == 136 or msg_id == 528 then + msg = msg:gsub('${actor}\'s ', '${actor}\' ') + elseif msg_id == 137 or msg_id == 153 then + msg = msg:gsub('${actor} fails ' , '${actor} fail ') + elseif msg_id == 139 then + msg = msg:gsub(' but finds nothing' , ' but find nothing') + elseif msg_id == 140 then + msg = msg:gsub(' and finds a ${item2}' , ' and find a ${item2}') + elseif msg_id == 158 then + msg = msg:gsub('${ability}, but misses' , '${ability}, but miss') + elseif msg_id == 585 then + msg = msg:gsub('${actor} is ' , '${actor} are ') + elseif msg_id == 674 then + msg = msg:gsub(' and finds ${number}' , ' and find ${number}') + elseif msg_id == 780 then + msg = msg:gsub('${actor} takes ' , '${actor} take ') + elseif replacements_map.actor.steals:contains(msg_id) then + msg = msg:gsub('${actor} steals ' , '${actor} steal ') + elseif replacements_map.actor.butmissestarget:contains(msg_id) then + msg = msg:gsub(' but misses ${target}' , ' but miss ${target}') + end + elseif replacements_map.actor.is:contains(msg_id) then + msg = msg:gsub('${actor} is ' , '${actor} are ') + elseif replacements_map.actor.learns:contains(msg_id) then + msg = msg:gsub('${actor} learns ' , '${actor} learn ') + elseif replacements_map.actor.has:contains(msg_id) then + msg = msg:gsub('${actor} has ' , '${actor} have ') + elseif replacements_map.actor.obtains:contains(msg_id) then + msg = msg:gsub('${actor} obtains ' , '${actor} obtain ') + elseif replacements_map.actor.does:contains(msg_id) then + msg = msg:gsub('${actor} does ' , '${actor} do ') + elseif replacements_map.actor.leads:contains(msg_id) then + msg = msg:gsub('${actor} leads ' , '${actor} lead ') + elseif replacements_map.actor.eats:contains(msg_id) then + msg = msg:gsub('${actor} eats ' , '${actor} eat ') + if msg_id == 604 then + msg = msg:gsub(' but finds nothing' , ' but find nothing') + end + elseif replacements_map.actor.earns:contains(msg_id) then + msg = msg:gsub('${actor} earns ' , '${actor} earn ') + end + end + return msg +end + +function plural_target(msg, msg_id) + if msg then + if msg_id == 282 then + msg = msg:gsub('${target} evades', '${target} evade') + elseif msg_id == 359 then + msg = msg:gsub('${target} narrowly escapes ', '${target} narrowly escape ') + elseif msg_id == 419 then + msg = msg:gsub('${target} learns ', '${target} learn ') + elseif msg_id == 671 then + msg = msg:gsub('${target} now has ', '${target} now have ') + elseif msg_id == 764 then + msg = msg:gsub('${target} feels ', '${target} feel ') + elseif replacements_map.target.takes:contains(msg_id) then + msg = msg:gsub('${target} takes ', '${target} take ') + if msg_id == 197 then + msg = msg:gsub('${target} resists', '${target} resist') + end + elseif replacements_map.target.is:contains(msg_id) then + msg = msg:gsub('${target} is ', '${target} are ') + elseif replacements_map.target.recovers:contains(msg_id) then + msg = msg:gsub('${target} recovers ', '${target} recover ') + elseif replacements_map.target.apos:contains(msg_id) then --coincidence in 439 and 440 + msg = msg:gsub('${target}\'s ', targets_condensed and '${target} ' or '${target}\' ') + if msg_id == 439 or msg_id == 440 then + msg = msg:gsub('${target} regains ', '${target} regain ') + end + elseif replacements_map.target.falls:contains(msg_id) then + msg = msg:gsub('${target} falls ', '${target} fall ') + elseif replacements_map.target.uses:contains(msg_id) then + msg = msg:gsub('${target} uses ', '${target} use ') + elseif replacements_map.target.resists:contains(msg_id) then + msg = msg:gsub('${target} resists', '${target} resist') + elseif replacements_map.target.vanishes:contains(msg_id) then + msg = msg:gsub('${target} vanishes', '${target} vanish') + elseif replacements_map.target.receives:contains(msg_id) then + msg = msg:gsub('${target} receives ', '${target} receive ') + elseif replacements_map.target.seems:contains(msg_id) then + msg = msg:gsub('${target} seems ${skill}', '${target} seem ${skill}') + if msg_id ~= 174 then + msg = msg:gsub('${lb}It seems to have ', '${lb}They seem to have ') + end + elseif replacements_map.target.gains:contains(msg_id) then + msg = msg:gsub('${target} gains ', '${target} gain ') + elseif replacements_map.target.regains:contains(msg_id) then + msg = msg:gsub('${target} regains ', '${target} regain ') + elseif replacements_map.target.obtains:contains(msg_id) then + msg = msg:gsub('${target} obtains ', '${target} obtain ') + elseif replacements_map.target.loses:contains(msg_id) then + msg = msg:gsub('${target} loses ', '${target} lose ') + elseif replacements_map.target.was:contains(msg_id) then + msg = msg:gsub('${target} was ', '${target} were ') + elseif replacements_map.target.has:contains(msg_id) then + msg = msg:gsub('${target} has ', '${target} have ') + elseif replacements_map.target.compresists:contains(msg_id) then + msg = msg:gsub('${target} completely resists ', '${target} completely resist ') + end + end + return msg +end + +function clean_msg(msg, msg_id) + if msg then + msg = msg + :gsub(' The ', ' the ') + :gsub(': the ', ': The ') + :gsub('! the ', '! The ') + if replacements_map.the.point:contains(msg_id) then + msg = msg:gsub('%. the ', '. The ') + end + end + return msg +end + +function grammatical_number_fix(msg, number, msg_id) + if msg then + if number == 1 then + if replacements_map.number.points:contains(msg_id) then + msg = msg:gsub(' points', ' point') + elseif msg_id == 411 then + msg = msg:gsub('${number} Ballista Points', '${number} Ballista Point') + elseif msg_id == 589 then + msg = msg:gsub('healed of ${number} status ailments', 'healed of ${number} status ailment') + elseif msg_id == 778 then + msg = msg:gsub('magical effects from', 'magical effect from') + end + else + if replacements_map.number.absorbs:contains(msg_id) then + msg = msg:gsub(' absorbs', ' absorb') + elseif msg_id == 133 then + msg = msg:gsub(' Petra', ' Petras') + elseif replacements_map.number.attributes:contains(msg_id) then + msg = msg:gsub('attributes is', 'attributes are') + elseif replacements_map.number.status:contains(msg_id) then + msg = msg:gsub('status effect is', 'status effects are') + elseif msg_id == 557 then + msg = msg:gsub('piece', 'pieces') + elseif msg_id == 560 then + msg = msg:gsub('Finishing move now ', 'Finishing moves now ') + end + if replacements_map.number.disappears:contains(msg_id) then + msg = msg:gsub('disappears', 'disappear') + end + end + end + return msg +end + +function item_article_fix(id,id2,msg) + if id then + if string.gmatch(msg, ' a ${item}') then + local article = res.items_grammar[id] and res.items_grammar[id].article + if article == 1 then + msg = string.gsub(msg,' a ${item}',' an ${item}') + end + end + end + if id2 then + if string.gmatch(msg, ' a ${item2}') then + local article = res.items_grammar[id2] and res.items_grammar[id2].article + if article == 1 then + msg = string.gsub(msg,' a ${item2}',' an ${item2}') + end + end + end + return msg +end + +function add_item_article(id) + local article = '' + local article_type = res.items_grammar[id] and res.items_grammar[id].article or nil + if id then + if article_type == 2 then + article = 'pair of ' + elseif article_type == 3 then + article = 'suit of ' + end + end + return article +end + +function send_delayed_message(color,msg) + local message = msg + :gsub('${count}', item_quantity.count) + windower.add_to_chat(color,message) + item_quantity.id = 0 + item_quantity.count = '' + parse_quantity = false +end diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/parse_action_packet.lua b/Data/DefaultContent/Libraries/addons/addons/battlemod/parse_action_packet.lua new file mode 100644 index 0000000..05cfd36 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/parse_action_packet.lua @@ -0,0 +1,947 @@ +function parse_action_packet(act) + -- Make a function that returns the action array with additional information + -- actor : type, name, is_npc + -- target : type, name, is_npc + if not Self then + Self = windower.ffxi.get_player() + if not Self then + return act + end + end + act.actor = player_info(act.actor_id) + act.action = get_spell(act) -- Pulls the resources line for the action + act.actor.name = act.actor and act.actor.name and string.gsub(act.actor.name,'[- ]', {['-'] = string.char(0x81,0x7C), [' '] = string.char(0x81,0x3F)}) --fix for ffxi chat splits on trusts with - and spaces + targets_condensed = false + + if not act.action then + return act + end + for i,v in ipairs(act.targets) do + v.target = {} + v.target[1] = player_info(v.id) + if #v.actions > 1 then + for n,m in ipairs(v.actions) do + if res.action_messages[m.message] then m.fields = fieldsearch(res.action_messages[m.message][language]) end + if res.action_messages[m.add_effect_message] then m.add_effect_fields = fieldsearch(res.action_messages[m.add_effect_message][language]) end + if res.action_messages[m.spike_effect_message] then m.spike_effect_fields = fieldsearch(res.action_messages[m.spike_effect_message][language]) end + + if res.buffs[m.param] then --and m.param ~= 0 then + m.status = res.buffs[m.param][language] + end + if res.buffs[m.add_effect_param] then -- and m.add_effect_param ~= 0 then + m.add_effect_status = res.buffs[m.add_effect_param][language] + end + if res.buffs[m.spike_effect_param] then -- and m.spike_effect_param ~= 0 then + m.spike_effect_status = res.buffs[m.spike_effect_param][language] + end + m.number = 1 + if m.has_add_effect then + m.add_effect_number = 1 + end + if m.has_spike_effect then + m.spike_effect_number = 1 + end + if not check_filter(act.actor,v.target[1],act.category,m.message) then + m.message = 0 + m.add_effect_message = 0 + end + if m.spike_effect_message ~= 0 and not check_filter(v.target[1],act.actor,act.category,m.message) then + m.spike_effect_message = 0 + end + if condensedamage and n > 1 then -- Damage/Action condensation within one target + for q=1,n-1 do + local r = v.actions[q] + + if r.message ~= 0 and m.message ~= 0 then + if m.message == r.message or (condensecrits and S{1,67}:contains(m.message) and S{1,67}:contains(r.message)) then + if (m.effect == r.effect) or (S{1,67}:contains(m.message) and S{0,1,2,3}:contains(m.effect) and S{0,1,2,3}:contains(r.effect)) then -- combine kicks and crits + if m.reaction == r.reaction then --or (S{8,10}:contains(m.reaction) and S{8,10}:contains(r.reaction)) then -- combine hits and guards +-- windower.add_to_chat(8, 'Condensed: '..m.message..':'..r.message..' - '..m.effect..':'..r.effect..' - '..m.reaction..':'..r.reaction) + r.number = r.number + 1 + if not sumdamage then + if not r.cparam then + r.cparam = r.param + if condensecrits and r.message == 67 then + r.cparam = r.cparam..'!' + end + end + r.cparam = r.cparam..', '..m.param + if condensecrits and m.message == 67 then + r.cparam = r.cparam..'!' + end + end + r.param = m.param + r.param + if condensecrits and m.message == 67 then + r.message = m.message + r.effect = m.effect + end + m.message = 0 + else +-- windower.add_to_chat(8, 'Didn\'t condense: '..m.message..':'..r.message..' - '..m.effect..':'..r.effect..' - '..m.reaction..':'..r.reaction) + end + else +-- windower.add_to_chat(8, 'Didn\'t condense: '..m.message..':'..r.message..' - '..m.effect..':'..r.effect..' - '..m.reaction..':'..r.reaction) + end + else +-- windower.add_to_chat(8, 'Didn\'t condense: '..m.message..':'..r.message..' - '..m.effect..':'..r.effect..' - '..m.reaction..':'..r.reaction) + end + end + if m.has_add_effect and r.add_effect_message ~= 0 then + if m.add_effect_effect == r.add_effect_effect and m.add_effect_message == r.add_effect_message and m.add_effect_message ~= 0 then + r.add_effect_number = r.add_effect_number + 1 + if not sumdamage then + r.cadd_effect_param = (r.cadd_effect_param or r.add_effect_param)..', '..m.add_effect_param + end + r.add_effect_param = m.add_effect_param + r.add_effect_param + m.add_effect_message = 0 + end + end + if m.has_spike_effect and r.spike_effect_message ~= 0 then + if r.spike_effect_effect == r.spike_effect_effect and m.spike_effect_message == r.spike_effect_message and m.spike_effect_message ~= 0 then + r.spike_effect_number = r.spike_effect_number + 1 + if not sumdamage then + r.cspike_effect_param = (r.cspike_effect_param or r.spike_effect_param)..', '..m.spike_effect_param + end + r.spike_effect_param = m.spike_effect_param + r.spike_effect_param + m.spike_effect_message = 0 + end + end + end + end + end + else + local tempact = v.actions[1] + if res.action_messages[tempact.message] then tempact.fields = fieldsearch(res.action_messages[tempact.message][language]) end + if res.action_messages[tempact.add_effect_message] then tempact.add_effect_fields = fieldsearch(res.action_messages[tempact.add_effect_message][language]) end + if res.action_messages[tempact.spike_effect_message] then tempact.spike_effect_fields = fieldsearch(res.action_messages[tempact.spike_effect_message][language]) end + + + --if tempact.add_effect_fields and tempact.add_effect_fields.status then windower.add_to_chat(8,tostring(tempact.add_effect_fields.status)..' '..res.action_messages[tempact.add_effect_message][language]) end + + if not check_filter(act.actor,v.target[1],act.category,tempact.message) then + tempact.message = 0 + tempact.add_effect_message = 0 + end + if tempact.spike_effect_message ~= 0 and not check_filter(v.target[1],act.actor,act.category,tempact.message) then + tempact.spike_effect_message = 0 + end + tempact.number = 1 + if tempact.has_add_effect and tempact.message ~= 674 then + tempact.add_effect_number = 1 + end + if tempact.has_spike_effect then + tempact.spike_effect_number = 1 + end + if res.buffs[tempact.param] then -- and tempact.param ~= 0 then + tempact.status = res.buffs[tempact.param][language] + end + if res.buffs[tempact.add_effect_param] then -- and tempact.add_effect_param ~= 0 then + tempact.add_effect_status = res.buffs[tempact.add_effect_param][language] + end + if res.buffs[tempact.spike_effect_param] then -- and tempact.spike_effect_param ~= 0 then + tempact.spike_effect_status = res.buffs[tempact.spike_effect_param][language] + end + end + + if condensetargets and i > 1 then + for n=1,i-1 do + local m = act.targets[n] + --windower.add_to_chat(8,m.actions[1].message..' '..v.actions[1].message) + if (v.actions[1].message == m.actions[1].message and v.actions[1].param == m.actions[1].param) or + (message_map[m.actions[1].message] and message_map[m.actions[1].message]:contains(v.actions[1].message) and v.actions[1].param == m.actions[1].param) or + (message_map[m.actions[1].message] and message_map[m.actions[1].message]:contains(v.actions[1].message) and v.actions[1].param == m.actions[1].param) then + m.target[#m.target+1] = v.target[1] + v.target[1] = nil + v.actions[1].message = 0 + end + end + end + end + + for i,v in pairs(act.targets) do + for n,m in pairs(v.actions) do + if m.message ~= 0 and res.action_messages[m.message] ~= nil then + local col = res.action_messages[m.message].color + local targ = assemble_targets(act.actor,v.target,act.category,m.message) + local color = color_filt(col,v.target[1].id==Self.id) + if m.reaction == 11 and act.category == 1 then m.simp_name = 'parried by' + --elseif m.reaction == 12 and act.category == 1 then m.simp_name = 'blocked by' + elseif m.message == 1 and (act.category == 1 or act.category == 11) then m.simp_name = 'hit' + elseif m.message == 15 then m.simp_name = 'missed' + elseif m.message == 29 or m.message == 84 then m.simp_name = 'is paralyzed' + elseif m.message == 30 then m.simp_name = 'anticipated by' + elseif m.message == 31 then m.simp_name = 'absorbed by' + elseif m.message == 32 then m.simp_name = 'dodged by' + elseif m.message == 67 and (act.category == 1 or act.category == 11) then m.simp_name = 'critical hit' + elseif m.message == 106 then m.simp_name = 'intimidated by' + elseif m.message == 153 then m.simp_name = act.action.name..' fails' + elseif m.message == 244 then m.simp_name = 'Mug fails' + elseif m.message == 282 then m.simp_name = 'evaded by' + elseif m.message == 373 then m.simp_name = 'absorbed by' + elseif m.message == 352 then m.simp_name = 'RA' + elseif m.message == 353 then m.simp_name = 'critical RA' + elseif m.message == 354 then m.simp_name = 'missed RA' + elseif m.message == 576 then m.simp_name = 'RA hit squarely' + elseif m.message == 577 then m.simp_name = 'RA struck true' + elseif m.message == 157 then m.simp_name = 'Barrage' + elseif m.message == 76 then m.simp_name = 'No targets within range' + elseif m.message == 77 then m.simp_name = 'Sange' + elseif m.message == 360 then m.simp_name = act.action.name..' (JA reset)' + elseif m.message == 426 or m.message == 427 then m.simp_name = 'Bust! '..act.action.name + elseif m.message == 435 or m.message == 436 then m.simp_name = act.action.name..' (JAs)' + elseif m.message == 437 or m.message == 438 then m.simp_name = act.action.name..' (JAs and TP)' + elseif m.message == 439 or m.message == 440 then m.simp_name = act.action.name..' (SPs, JAs, TP, and MP)' + elseif T{252,265,268,269,271,272,274,275,379,650,747}:contains(m.message) then m.simp_name = 'Magic Burst! '..act.action.name + elseif not act.action then + m.simp_name = '' + act.action = {} + else m.simp_name = act.action.name or '' + end + + -- Debuff Application Messages + if simplify and message_map[82]:contains(m.message) then + if m.status == 'Evasion Down' then + m.message = 237 + end + if m.status == 'addle' then m.status = 'addled' + elseif m.status == 'bind' then m.status = 'bound' + elseif m.status == 'blindness' then m.status = 'blinded' + elseif m.status == 'Inundation' then m.status = 'inundated' + elseif m.status == 'paralysis' then m.status = 'paralyzed' + elseif m.status == 'petrification' then m.status = 'petrified' + elseif m.status == 'poison' then m.status = 'poisoned' + elseif m.status == 'silence' then m.status = 'silenced' + elseif m.status == 'sleep' then m.status = 'asleep' + elseif m.status == 'slow' then m.status = 'slowed' + elseif m.status == 'stun' then m.status = 'stunned' + elseif m.status == 'weight' then m.status = 'weighed down' + end + end + + -- Some messages uses the english log version of the buff + if not simplify and log_form_messages:contains(m.message) then + m.status = res.buffs[m.param].enl + end + + -- if m.message == 93 or m.message == 273 then m.status=color_it('Vanish',color_arr['statuscol']) end + + -- Special Message Handling + if m.message == 93 or m.message == 273 then + m.status=color_it('Vanish',color_arr['statuscol']) + elseif m.message == 522 and simplify then + targ = targ..' ('..color_it('stunned',color_arr['statuscol'])..')' + elseif m.message == 416 and simplify then + targ = targ..' ('..color_it('Magic Attack Boost and Magic Defense Boost',color_arr['statuscol'])..')' + elseif m.message == 1023 and simplify then + targ = targ..' ('..color_it('attacks and defenses enhanced',color_arr['statuscol'])..')' + elseif m.message == 762 and simplify then + targ = targ..' ('..color_it('all status parameters boosted',color_arr['statuscol'])..')' + elseif m.message == 779 and simplify then + targ = 'A barrier pulsates around '..targ + elseif m.message == 780 and simplify then + targ = 'Takes aim on '..targ + elseif T{158,188,245,324,592,658}:contains(m.message) and simplify then + -- When you miss a WS or JA. Relevant for condensed battle. + m.status = 'Miss' --- This probably doesn't work due to the if a==nil statement below. + elseif m.message == 653 or m.message == 654 then + m.status = color_it('Immunobreak',color_arr['statuscol']) + elseif m.message == 655 or m.message == 656 then + m.status = color_it('Completely Resists',color_arr['statuscol']) + elseif m.message == 85 or m.message == 284 then + if m.unknown == 2 then + m.status = color_it('Resists!',color_arr['statuscol']) + else + m.status = color_it('Resists',color_arr['statuscol']) + end + elseif m.message == 351 then + m.status = color_it('status ailments',color_arr['statuscol']) + m.simp_name = color_it('remedy',color_arr['itemcol']) + elseif T{75,114,156,189,248,283,312,323,336,355,408,422,423,425,659}:contains(m.message) then + m.status = color_it('No effect',color_arr['statuscol']) -- The status code for "No Effect" is 255, so it might actually work without this line + end + if m.message == 188 then + m.simp_name = m.simp_name..' (Miss)' + -- elseif m.message == 189 then + -- m.simp_name = m.simp_name..' (No Effect)' + elseif T{78,198,328}:contains(m.message) then + m.simp_name = '(Too Far)' + end + local msg,numb = simplify_message(m.message) + if not color_arr[act.actor.owner or act.actor.type] then windower.add_to_chat(123,'Battlemod error, missing filter:'..tostring(act.actor.owner)..' '..tostring(act.actor.type)) end + if m.fields.status then numb = m.status else numb = pref_suf((m.message == 674 and m.add_effect_param or m.cparam or m.param),m.message,act.actor.damage,col) end + + if msg and m.message == 70 and not simplify then -- fix pronoun on parry + if v.target[1].race == 0 then + msg = msg:gsub(' his ',' its ') + elseif female_races:contains(v.target[1].race) then + msg = msg:gsub(' his ',' her ') + end + end + + local count = '' + if m.message == 377 and act.actor_id == Self.id then + parse_quantity = true + item_quantity.id = act.action.item2_id + count = '${count}' + end + + if not simplify then + if col == 'D' or grammar_numb_msg:contains(m.message) then + msg = grammatical_number_fix(msg, (m.cparam or m.param), m.message) + end + if act.action.item_id or act.action.item2_id then + msg = item_article_fix(act.action.item_id,act.action.item2_id,msg) + end + if common_nouns:contains(act.actor.id) then + msg = actor_noun(msg) + end + if plural_entities:contains(act.actor.id) then + msg = plural_actor(msg, m.message) + end + if targets_condensed or plural_entities:contains(v.target[1].id) then + msg = plural_target(msg, m.message) + end + end + + local roll = showrollinfo and act.category == 6 and corsair_rolls[act.param] and corsair_rolls[act.param][m.param] or '' + local reaction_lookup = reaction_offsets[act.category] and (m.reaction - reaction_offsets[act.category]) or 0 + local has_line_break = string.find(res.action_messages[m.message].en, '${lb}') and true or false + local prefix = (not has_line_break or simplify) and get_prefix(act.category, m.effect, m.message, m.unknown, reaction_lookup) or '' + local prefix2 = has_line_break and get_prefix(act.category, m.effect, m.message, m.unknown, reaction_lookup) or '' + local message = prefix..make_condensedamage_number(m.number)..( clean_msg((msg or tostring(m.message)) + :gsub('${spell}',color_it(act.action.spell or 'ERROR 111',color_arr.spellcol)) + :gsub('${ability}',color_it(act.action.ability or 'ERROR 112',color_arr.abilcol)) + :gsub('${item}',color_it(act.action.item or 'ERROR 113',color_arr.itemcol)) + :gsub('${item2}',count..color_it(act.action.item2 or 'ERROR 121',color_arr.itemcol)) + :gsub('${weapon_skill}',color_it(act.action.weapon_skill or 'ERROR 114',color_arr.wscol)) + :gsub('${abil}',m.simp_name or 'ERROR 115') + :gsub('${numb}',numb..roll or 'ERROR 116') + :gsub('${actor}\'s',color_it(act.actor.name or 'ERROR 117',color_arr[act.actor.owner or act.actor.type])..'\'s'..act.actor.owner_name) + :gsub('${actor}',color_it(act.actor.name or 'ERROR 117',color_arr[act.actor.owner or act.actor.type])..act.actor.owner_name) + :gsub('${target}\'s',targ) + :gsub('${target}',targ) + :gsub('${lb}','\7'..prefix2) + :gsub('${number}',(act.action.number or m.param)..roll) + :gsub('${status}',m.status or 'ERROR 120') + :gsub('${gil}',m.param..' gil'), m.message)) + if m.message == 377 and act.actor_id == Self.id then + send_delayed_message:schedule(0.5,color,message) + else + windower.add_to_chat(color,message) + end + if not non_block_messages:contains(m.message) then + m.message = 0 + end + end + if m.has_add_effect and m.add_effect_message ~= 0 and add_effect_valid[act.category] then + local targ = assemble_targets(act.actor,v.target,act.category,m.add_effect_message) + local col = res.action_messages[m.add_effect_message].color + local color = color_filt(col,v.target[1].id==Self.id) + if m.add_effect_message > 287 and m.add_effect_message < 303 then m.simp_add_name = skillchain_arr[m.add_effect_message-287] + elseif m.add_effect_message > 384 and m.add_effect_message < 399 then m.simp_add_name = skillchain_arr[m.add_effect_message-384] + elseif m.add_effect_message > 766 and m.add_effect_message < 769 then m.simp_add_name = skillchain_arr[m.add_effect_message-752] + elseif m.add_effect_message > 768 and m.add_effect_message < 771 then m.simp_add_name = skillchain_arr[m.add_effect_message-754] + elseif m.add_effect_message == 603 then m.simp_add_name = 'AE: TH' + elseif m.add_effect_message == 605 then m.simp_add_name = 'AE: Death' + elseif m.add_effect_message == 776 then m.simp_add_name = 'AE: Chainbound' + else m.simp_add_name = 'AE' + end + local msg,numb = simplify_message(m.add_effect_message) + if not simplify then + if col == 'D' or grammar_numb_msg:contains(m.add_effect_message) then + msg = grammatical_number_fix(msg, (m.cparam or m.param), m.add_effect_message) + end + if common_nouns:contains(act.actor.id) then + msg = actor_noun(msg) + end + if plural_entities:contains(act.actor.id) then + msg = plural_actor(msg, m.add_effect_message) + end + if targets_condensed or plural_entities:contains(v.target[1].id) then + msg = plural_target(msg, m.add_effect_message) + end + end + if m.add_effect_fields.status then numb = m.add_effect_status else numb = pref_suf((m.cadd_effect_param or m.add_effect_param),m.add_effect_message,act.actor.damage,col) end + if not act.action then +-- windower.add_to_chat(color, 'act.action==nil : '..m.message..' - '..m.add_effect_message..' - '..msg) + else + windower.add_to_chat(color,make_condensedamage_number(m.add_effect_number)..(clean_msg(msg + :gsub('${spell}',act.action.spell or 'ERROR 127') + :gsub('${ability}',act.action.ability or 'ERROR 128') + :gsub('${item}',act.action.item or 'ERROR 129') + :gsub('${weapon_skill}',act.action.weapon_skill or 'ERROR 130') + :gsub('${abil}',m.simp_add_name or act.action.name or 'ERROR 131') + :gsub('${numb}',numb or 'ERROR 132') + :gsub('${actor}\'s',color_it(act.actor.name,color_arr[act.actor.owner or act.actor.type])..'\'s'..act.actor.owner_name) + :gsub('${actor}',color_it(act.actor.name,color_arr[act.actor.owner or act.actor.type])..act.actor.owner_name) + :gsub('${target}\'s',targ) + :gsub('${target}',targ) + :gsub('${lb}','\7') + :gsub('${number}',m.add_effect_param) + :gsub('${status}',m.add_effect_status or 'ERROR 178'), m.add_effect_message))) + if not non_block_messages:contains(m.add_effect_message) then + m.add_effect_message = 0 + end + end + end + if m.has_spike_effect and m.spike_effect_message ~= 0 and spike_effect_valid[act.category] then + local targ = assemble_targets(act.actor,v.target,act.category,m.spike_effect_message) + local col = res.action_messages[m.spike_effect_message].color + local color = color_filt(col,act.actor.id==Self.id) + + local actor = act.actor + if m.spike_effect_message == 14 then + m.simp_spike_name = 'from counter' + elseif T{33,606}:contains(m.spike_effect_message) then + m.simp_spike_name = 'counter' + actor = v.target[1] --Counter dmg is done by the target, fix for coloring the dmg + elseif m.spike_effect_message == 592 then + m.simp_spike_name = 'missed counter' + elseif m.spike_effect_message == 536 then + m.simp_spike_name = 'retaliation' + actor = v.target[1] --Retaliation dmg is done by the target, fix for coloring the dmg + elseif m.spike_effect_message == 535 then + m.simp_spike_name = 'from retaliation' + else + m.simp_spike_name = 'spikes' + actor = v.target[1] --Spikes dmg is done by the target, fix for coloring the dmg + end + + local msg = simplify_message(m.spike_effect_message) + if not simplify then + if col == 'D' or grammar_numb_msg:contains(m.spike_effect_message) then + msg = grammatical_number_fix(msg, (m.cparam or m.param), m.spike_effect_message) + end + if common_nouns:contains(act.actor.id) then + msg = actor_noun(msg) + end + if plural_entities:contains(act.actor.id) then + msg = plural_actor(msg, m.spike_effect_message) + end + if targets_condensed or plural_entities:contains(v.target[1].id) then + msg = plural_target(msg, m.spike_effect_message) + end + end + if m.spike_effect_fields.status then numb = m.spike_effect_status else numb = pref_suf((m.cspike_effect_param or m.spike_effect_param),m.spike_effect_message,actor.damage,col) end + windower.add_to_chat(color,make_condensedamage_number(m.spike_effect_number)..(clean_msg(msg + :gsub('${spell}',act.action.spell or 'ERROR 142') + :gsub('${ability}',act.action.ability or 'ERROR 143') + :gsub('${item}',act.action.item or 'ERROR 144') + :gsub('${weapon_skill}',act.action.weapon_skill or 'ERROR 145') + :gsub('${abil}',m.simp_spike_name or act.action.name or 'ERROR 146') + :gsub('${numb}',numb or 'ERROR 147') + :gsub('${actor}\'s',color_it(act.actor.name,color_arr[act.actor.owner or act.actor.type])..'\'s'..act.actor.owner_name) + :gsub((simplify and '${target}' or '${actor}'),color_it(act.actor.name,color_arr[act.actor.owner or act.actor.type])..act.actor.owner_name) + :gsub('${target}\'s',targ) + :gsub((simplify and '${actor}' or '${target}'),targ) + :gsub('${lb}','\7') + :gsub('${number}',m.spike_effect_param) + :gsub('${status}',m.spike_effect_status or 'ERROR 150'), m.spike_effect_message))) + if not non_block_messages:contains(m.spike_effect_message) then + m.spike_effect_message = 0 + end + end + end + end + + return act +end + +function pref_suf(param,msg_ID,actor_dmg,col) + local outstr = (col == 'D' or dmg_drain_msg:contains(msg_ID)) and color_it(tostring(param),color_arr[actor_dmg]) or tostring(param) + local msg = res.action_messages[msg_ID] or nil + if msg then + if msg.prefix then + outstr = msg.prefix..' '..outstr + end + if msg.suffix then + if msg.suffix == 'shadow' and param ~= 1 then + outstr = outstr..' shadows' + elseif msg.suffix == 'Petra' and param ~= 1 then + outstr = outstr..' Petras' + elseif msg.suffix == 'effects disappears' and param ~= 1 then + outstr = outstr..' effects disappear' + elseif msg_ID == 641 then + outstr = outstr..' 1 attribute drained' + elseif msg.suffix == 'attributes drained' and param == 1 then + outstr = outstr..' attribute drained' + elseif msg.suffix == 'status effect drained' and param ~= 1 then + outstr = outstr..' status effects drained' + elseif msg.suffix == 'status ailments disappears' and param ~= 1 then + outstr = outstr..' status ailments disappear' + elseif msg.suffix == 'status ailments absorbed' and param == 1 then + outstr = outstr..' status ailment absorbed' + elseif msg.suffix == 'status ailments healed' and param == 1 then + outstr = outstr..' status ailment healed' + elseif msg.suffix == 'status benefits absorbed' and param == 1 then + outstr = outstr..' status benefit absorbed' + elseif msg.suffix == 'status effects removed' and param == 1 then + outstr = outstr..' status effect removed' + elseif msg.suffix == 'magic effects drained' and param == 1 then + outstr = outstr..' magic effect drained' + elseif msg.suffix == 'magical effects received' and param == 1 then + outstr = outstr..' magical effect received' + elseif msg.suffix == 'magical effects copied' and param == 1 then + outstr = outstr..' magical effect copied' + else + outstr = outstr..' '..msg.suffix + end + end + end + return outstr +end + +function simplify_message(msg_ID) + local msg = res.action_messages[msg_ID][language] + local fields = fieldsearch(msg) + + if simplify and not T{23,64,133,204,210,211,212,213,214,350,442,516,531,557,565,582}:contains(msg_ID) then + if T{93,273,522,653,654,655,656,85,284,75,114,156,189,248,283,312,323,336,351,355,408,422,423,425,453,659,158,245,324,658}:contains(msg_ID) then + fields.status = true + end + if msg_ID == 31 or msg_ID == 798 or msg_ID == 799 then + fields.actor = true + end + if (msg_ID > 287 and msg_ID < 303) or (msg_ID > 384 and msg_ID < 399) or (msg_ID > 766 and msg_ID < 771) or + T{129,152,161,162,163,165,229,384,453,603,652,798}:contains(msg_ID) then + fields.ability = true + end + + if T{125,593,594,595,596,597,598,599}:contains(msg_ID) then + fields.ability = true + fields.item = true + end + + if T{129,152,153,160,161,162,163,164,165,166,167,168,229,244,652}:contains(msg_ID) then + fields.actor = true + fields.target = true + end + + if msg_ID == 139 then + fields.number = true + end + + local Despoil_msg = {[593] = 'Attack Down', [594] = 'Defense Down', [595] = 'Magic Atk. Down', [596] = 'Magic Def. Down', [597] = 'Evasion Down', [598] = 'Accuracy Down', [599] = 'Slow',} + if line_full and fields.number and fields.target and fields.actor then + msg = line_full + elseif line_aoebuff and fields.status and fields.target then --and fields.actor then -- and (fields.spell or fields.ability or fields.item or fields.weapon_skill) then + msg = line_aoebuff + elseif line_item and fields.item2 then + if fields.number then + msg = line_itemnum + else + msg = line_item + end + elseif line_steal and fields.item and fields.ability then + if T{593,594,595,596,597,598,599}:contains(msg_ID) then + msg = line_steal..''..string.char(0x07)..'AE: '..color_it(Despoil_msg[msg_ID],color_arr['statuscol']) + else + msg = line_steal + end + elseif line_nonumber and not fields.number then + msg = line_nonumber + elseif line_aoe and T{264}:contains(msg_ID) then + msg = line_aoe + elseif line_noactor and not fields.actor and (fields.spell or fields.ability or fields.item or fields.weapon_skill) then + msg = line_noactor + elseif line_noability and not fields.actor then + msg = line_noability + elseif line_notarget and fields.actor and fields.number then + if msg_ID == 798 then --Maneuver message + msg = line_notarget..'%' + elseif msg_ID == 799 then --Maneuver message with overload + msg = line_notarget..'% (${actor} overloaded)' + else + msg = line_notarget + end + end + end + return msg +end + +function assemble_targets(actor,targs,category,msg) + local targets = {} + local samename = {} + local total = 0 + for i,v in pairs(targs) do + -- Done in two loops so that the ands and commas don't get out of place. + -- This loop filters out unwanted targets. + if check_filter(actor,v,category,msg) or check_filter(v,actor,category,msg) then + if samename[v.name] and condensetargetname then + samename[v.name] = samename[v.name] + 1 + else + targets[#targets+1] = v + samename[v.name] = 1 + end + total = total + 1 + end + end + local out_str + if targetnumber and total > 1 then + out_str = '{'..total..'}: ' + else + out_str = '' + end + + for i,v in pairs(targets) do + local name = string.gsub(v.name,' ', string.char(0x81,0x3F)) --fix for ffxi chat splits on space + local article = common_nouns:contains(v.id) and (not simplify or msg == 206) and 'The ' or '' + local numb = condensetargetname and samename[v.name] > 1 and ' {'..samename[v.name]..'}' or '' + if i == 1 then + name = color_it(name,color_arr[v.owner or v.type])..v.owner_name + if samename[v.name] > 1 then + targets_condensed = true + else + if (not simplify or msg == 206) and #targets == 1 and string.find(res.action_messages[msg][language], '${target}\'s') then + name = color_it(name,color_arr[v.owner or v.type])..(plural_entities:contains(v.id) and '\'' or '\'s')..v.owner_name + end + targets_condensed = false + end + out_str = out_str..article..name..numb + else + targets_condensed = true + name = color_it(name,color_arr[v.owner or v.type])..v.owner_name + out_str = conjunctions(out_str,article..name..numb,#targets,i) + end + end + out_str = string.gsub(out_str,'-', string.char(0x81,0x7C)) --fix for ffxi chat splits on trusts with - + return out_str +end + +function make_condensedamage_number(number) + if swingnumber and condensedamage and 1 < number then + return '['..number..'] ' + else + return '' + end +end + +function player_info(id) + local player_table = windower.ffxi.get_mob_by_id(id) + local typ,dmg,owner,filt,owner_name + + if player_table == nil then + return {name=nil,id=nil,is_npc=nil,type='debug',owner=nil, owner_name=nil,race=nil} + end + + for i,v in pairs(windower.ffxi.get_party()) do + if type(v) == 'table' and v.mob and v.mob.id == player_table.id then + typ = i + if i == 'p0' then + filt = 'me' + dmg = 'mydmg' + elseif i:sub(1,1) == 'p' then + filt = 'party' + dmg = 'partydmg' + else + filt = 'alliance' + dmg = 'allydmg' + end + end + end + + if not filt then + if player_table.is_npc then + if player_table.index>1791 or player_table.charmed then + typ = 'other_pets' + filt = 'other_pets' + owner = 'other' + dmg = 'otherdmg' + for i,v in pairs(windower.ffxi.get_party()) do + if type(v) == 'table' and v.mob and v.mob.pet_index and v.mob.pet_index == player_table.index then + if i == 'p0' then + typ = 'my_pet' + filt = 'my_pet' + dmg = 'mydmg' + end + owner = i + owner_name = showownernames and ' ('..color_it(v.mob.name, color_arr[owner or typ])..')' + break + elseif type(v) == 'table' and v.mob and v.mob.fellow_index and v.mob.fellow_index == player_table.index then + if i == 'p0' then + typ = 'my_fellow' + filt = 'my_fellow' + dmg = 'mydmg' + end + owner = i + owner_name = showownernames and ' ('..color_it(v.mob.name, color_arr[owner or typ])..')' + break + end + end + else + typ = 'mob' + filt = 'monsters' + dmg = 'mobdmg' + + if filter.enemies then + for i,v in pairs(Self.buffs) do + if domain_buffs:contains(v) then + -- If you are in Domain Invasion, or a Reive, or various other places + -- then all monsters should be considered enemies. + filt = 'enemies' + break + end + end + + if filt ~= 'enemies' then + for i,v in pairs(windower.ffxi.get_party()) do + if type(v) == 'table' and nf(v.mob,'id') == player_table.claim_id then + filt = 'enemies' + break + end + end + end + end + end + else + typ = 'other' + filt = 'others' + dmg = 'otherdmg' + end + end + if not typ then typ = 'debug' end + return {name=player_table.monstrosity_name or player_table.name,id=id,is_npc = player_table.is_npc,type=typ,damage=dmg,filter=filt,owner=(owner or nil), owner_name=(owner_name or ''),race = player_table.race} +end + +function get_spell(act) + local spell, abil_ID, effect_val = {} + local msg_ID = act.targets[1].actions[1].message + + if T{7,8,9}:contains(act['category']) then + abil_ID = act.targets[1].actions[1].param + elseif T{3,4,5,6,11,13,14,15}:contains(act.category) then + abil_ID = act.param + effect_val = act.targets[1].actions[1].param + end + + if act.category == 1 then + spell.english = 'hit' + spell.german = spell.english + spell.japanese = spell.english + spell.french = spell.english + elseif act.category == 2 and act.category == 12 then + if msg_ID == 77 then + spell = res.job_abilities[171] -- Sange + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + end + elseif msg_ID == 157 then + spell = res.job_abilities[60] -- Barrage + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + end + else + spell.english = 'Ranged Attack' + spell.german = spell.english + spell.japanese = spell.english + spell.french = spell.english + end + else + if not res.action_messages[msg_ID] then + if T{4,8}:contains(act['category']) then + spell = res.spells[abil_ID] + elseif T{6,14,15}:contains(act['category']) or T{7,13}:contains(act['category']) and false then + spell = res.job_abilities[abil_ID] -- May have to correct for charmed pets some day, but I'm not sure there are any monsters with TP moves that give no message. + elseif T{3,7,11}:contains(act['category']) then + if abil_ID < 256 then + spell = res.weapon_skills[abil_ID] -- May have to correct for charmed pets some day, but I'm not sure there are any monsters with TP moves that give no message. + else + spell = res.monster_abilities[abil_ID] + end + elseif T{5,9}:contains(act['category']) then + spell = res.items[abil_ID] + else + spell = {none=tostring(msg_ID)} -- Debugging + end + return spell + end + + local fields = fieldsearch(res.action_messages[msg_ID][language]) + + if fields.spell then + spell = res.spells[abil_ID] + if spell then + spell.name = color_it(spell[language],color_arr.spellcol) + spell.spell = color_it(spell[language],color_arr.spellcol) + end + elseif fields.ability then + spell = res.job_abilities[abil_ID] + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + if msg_ID == 139 then + spell.number = 'Nothing' + end + end + elseif fields.weapon_skill then + if abil_ID > 256 then -- WZ_RECOVER_ALL is used by chests in Limbus + spell = res.monster_abilities[abil_ID] + if not spell then + spell = {english= 'Special Attack'} + end + elseif abil_ID <= 256 then + spell = res.weapon_skills[abil_ID] + end + if spell then + spell.name = color_it(spell[language],color_arr.wscol) + spell.weapon_skill = color_it(spell[language],color_arr.wscol) + end + elseif msg_ID == 303 then + spell = res.job_abilities[74] -- Divine Seal + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + end + elseif msg_ID == 304 then + spell = res.job_abilities[75] -- 'Elemental Seal' + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + end + elseif msg_ID == 305 then + spell = res.job_abilities[76] -- 'Trick Attack' + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + end + elseif msg_ID == 311 or msg_ID == 312 then + spell = res.job_abilities[79] -- 'Cover' + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + end + elseif msg_ID == 240 or msg_ID == 241 then + spell = res.job_abilities[43] -- 'Hide' + if spell then + spell.name = color_it(spell[language],color_arr.abilcol) + spell.ability = color_it(spell[language],color_arr.abilcol) + end + end + + if fields.item then + if T{125,593,594,595,596,597,598,599}:contains(msg_ID) then + local item_article = not simplify and add_item_article(effect_val) or '' + spell.item = color_it(item_article..res.items[effect_val]['english_log'], color_arr.itemcol) + spell.item_id = res.items[effect_val].id + else + spell = res.items[abil_ID] + local item_article = not simplify and add_item_article(spell.id) or '' + if spell then + spell.name = color_it(item_article..spell['english_log'],color_arr.itemcol) + spell.item = color_it(item_article..spell['english_log'],color_arr.itemcol) + spell.item_id = abil_ID + end + end + end + + if fields.item2 then + local item_article = not simplify and add_item_article(effect_val) or '' + local tempspell = (msg_ID == 377 or msg_ID == 674) and res.items_grammar[effect_val] and res.items_grammar[effect_val].plural or item_article..res.items[effect_val].english_log + spell.item2 = color_it(tempspell,color_arr.itemcol) + spell.item2_id = effect_val + if fields.number then + spell.number = act.targets[1].actions[1].add_effect_param + end + end + end + + if spell and not spell.name then spell.name = spell[language] end + return spell +end + + +function color_filt(col,is_me) + --Used to convert situational colors from the resources into real colors + --Depends on whether or not the target is you, the same as using in-game colors + -- Returns a color code for windower.add_to_chat() + -- Does not currently support a Debuff/Buff distinction + if col == 'D' then -- Damage + if is_me then + return 28 + else + return 20 + end + elseif col == 'M' then -- Misses + if is_me then + return 29 + else + return 21 + end + elseif col == 'H' then -- Healing + if is_me then + return 30 + else + return 22 + end + elseif col == 'B' then -- Beneficial effects + if is_me then + return 56 + else + return 60 + end + elseif col == 'DB' then -- Detrimental effects (I don't know how I'd split these) + if is_me then + return 57 + else + return 61 + end + elseif col == 'R' then -- Resists + if is_me then + return 59 + else + return 63 + end + else + return col + end +end + +function get_prefix(category, effect, message, unknown, reaction_lookup) + local prefix = S{1,3,4,6,11,13,14,15}:contains(category) and (bit.band(unknown,1)==1 and 'Cover! ' or '') + ..(bit.band(unknown,4)==4 and 'Magic Burst! ' or '') --Used on Swipe/Lunge MB + ..(bit.band(unknown,8)==8 and 'Immunobreak! ' or '') --Unused? Displayed directly on message + ..(showcritws and bit.band(effect,2)==2 and S{1,3,11}:contains(category) and message~=67 and 'Critical Hit! ' or '') --Unused? Crits have their own message + ..(showblocks and reaction_lookup == 4 and 'Blocked! ' or '') + ..(showguards and reaction_lookup == 2 and 'Guarded! ' or '') + ..(reaction_lookup == 3 and S{3,4,6,11,13,14,15}:contains(category) and 'Parried! ' or '') --Unused? They are send the same as missed + return prefix +end + +function condense_actions(action_array) + for i,v in pairs(action_array) do + local comb_table = {} + for n,m in pairs(v) do + if comb_table[m.primary.name] then + if m.secondary.name == 'number' then + comb_table[m.primary.name].secondary.name = tostring(tonumber(comb_table[m.primary.name].secondary.name)+tonumber(m.secondary.name)) + end + comb_table[m.primary.name].count = comb_table[m.primary.name].count + 1 + else + comb_table[m.primary.name] = m + comb_table[m.primary.name].count = 1 + end + m = nil -- Could cause next() error + end + for n,m in pairs(comb_table) do + v[#v+1] = m + end + end + return action_array +end + +function condense_targets(action_array) + local comb_table = {} + for i,v in pairs(action_array) do + local was_created = false + for n,m in pairs(comb_table) do + if table.equal(v,m,3) then -- Compares 3 levels deep + n[#n+1] = i[1] + was_created = true + end + end + if not was_created then + comb_table[{i[1]}] = v + end + end + return comb_table +end diff --git a/Data/DefaultContent/Libraries/addons/addons/battlemod/statics.lua b/Data/DefaultContent/Libraries/addons/addons/battlemod/statics.lua new file mode 100644 index 0000000..8abe43e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/battlemod/statics.lua @@ -0,0 +1,699 @@ + --Copyright © 2013, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. +language = 'english' +skillchain_arr = {'Light:','Darkness:','Gravitation:','Fragmentation:','Distortion:','Fusion:','Compression:','Liquefaction:','Induration:','Reverberation:','Transfixion:','Scission:','Detonation:','Impaction:','Radiance:','Umbra:'} +ratings_arr = {'TW','IEP','EP','DC','EM','T','VT','IT'} +current_job = 'NONE' +default_filt = false +parse_quantity = false +targets_condensed = false +common_nouns = T{} +plural_entities = T{} +item_quantity = {id = 0, count = ''} +rcol = string.char(0x1E,0x01) +non_block_messages = T{1,2,7,14,15,24,25,26,30,31,32,33,44,63,67,69,70,77,102,103,110,122,132,152,157,158,161,162,163,165,167,185,187,188,196,197,223,224,225,226,227,228,229,238,245,252,263,264,265,274,275,276,281,282,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,306,317,318,324,352,353,354,357,358,366,367,373,379,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,409,413,451,452,454,522,535,536,537,539,576,577,587,588,592,603,606,648,650,651,658,732,736,746,747,748,749,750,751,752,753,767,768,769,770,781} +passed_messages = T{4,5,6,16,17,18,20,34,35,36,38,40,47,48,49,53,62,64,72,78,87,88,89,90,94,97,112,116,154,170,171,172,173,174,175,176,177,178,191,192,198,204,206,215,217,218,219,234,246,249,251,307,308,313,315,328,336,350,523,530,531,558,561,563,575,584,601,609,562,610,611,612,613,614,615,616,617,618,619,620,625,626,627,628,629,630,631,632,633,634,635,636,643,660,661,662,679} +agg_messages = T{85,653,655,75,156,189,248,323,355,408,422,425,82,93,116,127,131,134,151,144,146,148,150,166,186,194,230,236,237,242,243,268,271,319,320,364,375,412,414,416,420,424,426,432,433,441,602,645,668,435,437,439} +color_redundant = T{26,33,41,71,72,89,94,109,114,164,173,181,184,186,70,84,104,127,128,129,130,131,132,133,134,135,136,137,138,139,140,64,86,91,106,111,175,178,183,81,101,16,65,87,92,107,112,174,176,182,82,102,67,68,69,170,189,15,208,18,25,32,40,163,185,23,24,27,34,35,42,43,162,165,187,188,30,31,14,205,144,145,146,147,148,149,150,151,152,153,190,13,9,253,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,284,285,286,287,292,293,294,295,300,301,301,303,308,309,310,311,316,317,318,319,324,325,326,327,332,333,334,335,340,341,342,343,344,345,346,347,348,349,350,351,355,357,358,360,361,363,366,369,372,374,375,378,381,384,395,406,409,412,415,416,418,421,424,437,450,453,456,458,459,462,479,490,493,496,499,500,502,505,507,508,10,51,52,55,58,62,66,80,83,85,88,90,93,100,103,105,108,110,113,122,168,169,171,172,177,179,180,12,11,37,291} -- 37 and 291 might be unique colors, but they are not gsubbable. +block_messages = T{12} +block_modes = T{20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,40,41,42,43,56,57,59,60,61,63,104,109,114,162,163,164,165,181,185,186,187,188} +black_colors = T{}--352,354,356,388,390,400,402,430,432,442,444,472,474,484,486} +dmg_drain_msg = T{132,161,187,227,274,281,736,748,749,802,803} +grammar_numb_msg = T{14,31,133,231,369,370,382,385,386,387,388,389,390,391,392,393,394,395,396,397,398,400,401,403,404,405,411,417,535,536,557,570,571,589,607,651,757,769,770,778,792} + +replacements_map = { + actor = { + hits = T{1,373}, + casts = T{2,7,42,82,83,85,86,93,113,114,227,228,230,236,237,252,268,271,274,275,309,329,330,331,332,333,334,335,341,342,430,431,432,433,454,533,534,570,572,642,647,653,655}, + starts = T{3,327,716}, + gains = T{8,54,105,166,253,371,372,718,735}, + apos = T{14,16,33,69,70,75,248,310,312,352,353,354,355,382,493,535,574,575,576,577,592,606,798,799}, + misses = T{15,63}, + learns = T{23,45,442}, + uses = T{28,77,100,101,102,103,108,109,110,115,116,117,118,119,120,121,122,123,125,126,127,129,131,133,134,135,136,137,138,139,140,141,142,143,144,146,148,150,153,156,157,158,159,185,186,187,188,189,194,197,221,224,225,226,231,238,242,243,245,303,304,305,306,317,318,319,320,321,322,323,324,360,362,364,369,370,375,376,377,378,379,399,400,401,402,405,406,407,408,409,412,413,414,416,417,418,420,422,424,425,426,435, + 437,439,441,451,452,453,519,520,521,522,526,527,528,529,532,539,560,585,591,593,594,595,596,597,598,599,602,607,608,644,645,646,657,658,663,664,667,668,670,671,672,674,730,734,736,737,738,743,746,747,748,750,752,754,755,758,762,763,764,765,766,778,779,780,792,802,803,804,805,1023}, + is = T{29,49,84,106,191}, + does = T{34,91,192}, + readies = T{43,326,675}, + earns = T{50,368,719}, + steals = T{125,133,453,593,594,595,596,597,598,599}, + recovers = T{152,167}, + butmissestarget = T{188,245,324,658}, + eats = T{600,604}, + leads = T{648,650,651}, + has = T{515,661,665,688}, + obtains = T{582,673}, + }, + target = { + takes = T{2,67,77,110,157,185,196,197,229,252,264,265,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,317,353,379,413,522,648,650,732,747,767,768,800}, + is = T{4,13,64,78,82,86,107,127,128,130,131,134,136,141,148,149,150,151,154,198,203,204,232,236,242,246,270,271,272,277,279,286,287,313,328,350,519,520,521,529,531,586,591,593,594,595,596,597,598,599,645,754,776}, + recovers = T{7,24,25,26,74,102,103,224,238,263,276,306,318,367,373,382,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,651,769,770}, + apos = T{31,38,44,53,73,83,106,112,116,120,121,123,132,159,168,206,221,231,249,285,308,314,321,322,329,330,331,332,333,334,335,341,342,343,344,351,360,361,362,363,364,365,369,374,378,383,399,400,401,402,403,405,407,409,417,418,430,431,435,436,437,438,439,440,459,530,533,534,537,570,571,572,585,606,607,641,642,644,647,676,730,743,756,757,762,792,805,806,1023}, + falls = T{20,113,406,605,646}, + uses = T{79,80}, + resists = T{85,197,284,653,654}, + vanishes = T{93,273}, + receives = T{142,144,145,146,147,237,243,267,268,269,278,320,375,412,414,415,416,420,421,424,432,433,441,532,557,602,668,672,739,755,804}, + seems = T{170,171,172,173,174,175,176,177,178}, + gains = T{186,194,205,230,266,280,319}, + regains = T{357,358,439,440,451,452,539,587,588}, + obtains = T{376,377,565,566,765,766}, + loses = T{426,427,652}, + was = T{97,564}, + has = T{589,684,763}, + compresists = T{655,656}, + }, + number = { + points = T{1,2,8,10,33,38,44,54,67,77,105,110,157,163,185,196,197,223,229,252,253,264,265,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,310,317,352,353,371,372,373,379,382,385,386,387,388,389,390,391,392,393,394,395,396,397,398,413,522,536,576,577,648,650,651,718,721,722,723,724,725,726,727,728,729,732,735,747,767,768,769,770,800}, + absorbs = T{14,31,535}, + disappears = T{14,31,231,400,401,405,535,570,571,607,757,792,}, + attributes = T{369,403,417}, + status = T{370,404}, + + }, + the = { + point = T{33,308,536,800}, + } +} + +corsair_rolls = { + [98] = {[5] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Fighter's Roll + [99] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Monk's Roll + [100] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Healer's Roll + [101] = {[5] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Wizard's Roll + [102] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Warlock's Roll + [103] = {[5] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Rogue's Roll + [104] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Gallant's Roll + [105] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Chaos Roll + [106] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Beast Roll + [107] = {[2] = ' (Lucky Roll!)', [6] = ' (Unlucky Roll!)'}, -- Choral Roll + [108] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Hunter's Roll + [109] = {[2] = ' (Lucky Roll!)', [6] = ' (Unlucky Roll!)'}, -- Samurai Roll + [110] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Ninja Roll + [111] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Drachen Roll + [112] = {[5] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Evoker's Roll + [113] = {[2] = ' (Lucky Roll!)', [6] = ' (Unlucky Roll!)'}, -- Magus's Roll + [114] = {[5] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Corsair's Roll + [115] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Puppet Roll + [116] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Dancer's Roll + [117] = {[2] = ' (Lucky Roll!)', [6] = ' (Unlucky Roll!)'}, -- Scholar's Roll + [118] = {[3] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Bolter's Roll + [119] = {[2] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Caster's Roll + [120] = {[3] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Courser's Roll + [121] = {[4] = ' (Lucky Roll!)', [9] = ' (Unlucky Roll!)'}, -- Blitzer's Roll + [122] = {[5] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Tactician's Roll + [302] = {[3] = ' (Lucky Roll!)', [10] = ' (Unlucky Roll!)'}, -- Allies' Roll + [303] = {[5] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Miser's Roll + [304] = {[2] = ' (Lucky Roll!)', [10] = ' (Unlucky Roll!)'}, -- Companion's Roll + [305] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Avenger's Roll + [390] = {[3] = ' (Lucky Roll!)', [7] = ' (Unlucky Roll!)'}, -- Naturalit's Roll + [391] = {[4] = ' (Lucky Roll!)', [8] = ' (Unlucky Roll!)'}, -- Runeist's Roll +} + +domain_buffs = S{ + 250, -- EF Badge + 257, -- Besieged + 267, -- Allied Tags + --292, -- Pennant? + --475, -- Voidwatcher + 511, -- Reive Mark + 603, -- Elvorseal + } -- EF BadElvorseal, Allied Tags, EF Badge? + +-- resists = {85,284} +-- immunobreaks = {653,654} +-- complete_resists = {655,656} +-- no_effects = {75,156,189,248,323,355,408,422,425,283,423,659} +-- receives = {82,116,127,131,134,151,144,146,148,150,166,186,194,230,236,237,242,243,268,271,319,320,364,375,412,414,416,420,424,426,432,433,441,602,645,668,203,205,266,270,272,277,279,280,285,145,147,149,151,267,269,278,286,287,365,415,421,427} +-- vanishes = {93,273} + +no_effect_map = T{248,355,189,75,408,156,0,0,0,0,189,0,189,156,156} +receives_map = T{0,0,186,82,375,116,0,0,0,0,186,0,127,116,116} +stat_ignore = T{66,69,70,71,444,445,446} +enfeebling = T{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,155,156,157,158,159,167,168,174,175,177,186,189,192,193,194,223,259,260,261,262,263,264,298,378,379,380,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,404,448,449,450,451,452,473,540,557,558,559,560,561,562,563,564,565,566,567} + +reaction_offsets = { + [1] = 8, + [3] = 24, + [4] = 0, + [6] = 16, + [11] = 24, + [13] = 24, + [14] = 16, + [15] = 24, +} + +female_races = T{2,4,6,7} +male_races = T{1,3,5,8} + +color_arr = {} +default_color_table = {mob=69,other=8, +p0=501,p1=204,p2=410,p3=492,p4=259,p5=260, +a10=205,a11=359,a12=167,a13=038,a14=125,a15=185, +a20=429,a21=257,a22=200,a23=481,a24=483,a25=208, +mobdmg=0,mydmg=0,partydmg=0,allydmg=0,otherdmg=0, +spellcol=0,abilcol=0,wscol=0,mobwscol=0,mobspellcol=0,statuscol=191,itemcol=256,enfeebcol=475} + +filter = {} +multi_targs = {} +multi_actor = {} +multi_msg = {} +line_aoe = 'AOE ${numb} '..string.char(129,168)..' ${target}' +line_aoebuff = '${actor} ${abil} '..string.char(129,168)..' ${target} (${status})' +line_full = '[${actor}] ${numb} ${abil} '..string.char(129,168)..' ${target}' +line_itemnum = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${numb} ${item2})' +line_item = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${item2})' +line_steal = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${item})' +line_noability = '${numb} '..string.char(129,168)..' ${target}' +line_noactor = '${abil} ${numb} '..string.char(129,168)..' ${target}' +line_nonumber = '[${actor}] ${abil} '..string.char(129,168)..' ${target}' +line_notarget = '[${actor}] ${abil} '..string.char(129,168)..' ${number}' +line_roll = '${actor} ${abil} '..string.char(129,168)..' ${target} '..string.char(129,170)..' ${number}' + +default_settings_table = {line_aoe = 'AOE ${numb} '..string.char(129,168)..' ${target}', + line_aoebuff = '${actor} ${abil} '..string.char(129,168)..' ${target} (${status})', + line_full = '[${actor}] ${numb} ${abil} '..string.char(129,168)..' ${target}', + line_itemnum = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${numb} ${item2})', + line_item = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${item2})', + line_steal = '[${actor}] ${abil} '..string.char(129,168)..' ${target} (${item})', + line_noability = '${numb} '..string.char(129,168)..' ${target}', + line_noactor = '${abil} ${numb} '..string.char(129,168)..' ${target}', + line_nonumber = '[${actor}] ${abil} '..string.char(129,168)..' ${target}', + line_notarget = '[${actor}] ${abil} '..string.char(129,168)..' ${number}', + line_roll = '${actor} ${abil} '..string.char(129,168)..' ${target} '..string.char(129,170)..' ${number}', + condensedamage=true,condensetargets=true,cancelmulti=true,oxford=true,commamode=false,targetnumber=true,condensetargetname=false,swingnumber=true,sumdamage=true,condensecrits=false,showownernames=false,crafting=true,showblocks=true,showguards=true,showcritws=false,showrollinfo=false} + +message_map = {} +for n=1,700,1 do + message_map[n] = T{} +end + +message_map[85] = T{284} -- resist +message_map[653] = T{654} -- immunobreak +message_map[655] = T{656} -- complete resist +message_map[93] = T{273} -- vanishes +-- message_map[75] = -- no effect spell +message_map[156] = T{156,323,422,425} -- no effect ability +message_map[75] = T{283} -- No Effect: Spell, Target +-- message_map[189] = -- no effect ws +-- message_map[408] = -- no effect item +message_map[248] = T{355} -- no ability of any kind +message_map['No effect'] = T{283,423,659} -- generic "no effect" messages for sorting by category +message_map[432] = T{433} -- Receives: Spell, Target +message_map[82] = T{230,236,237,267,268,271} -- Receives: Spell, Target, Status +message_map[230] = T{266} -- Receives: Spell, Target, Status +message_map[319] = T{266} -- Receives: Spell, Target, Status (Generic for avatar buff BPs) +message_map[134] = T{287} -- Receives: Spell, Target, Status +message_map[116] = T{131,134,144,146,148,150,364,414,416,441,602,668,285,145,147,149,151,286,287,365,415,421} -- Receives: Ability, Target +message_map[127]=T{319,320,645} -- Receives: Ability, Target, Status +message_map[420]=T{424} -- Receives: Ability, Target, Status, Number +message_map[375] = T{412}-- Receives: Item, Target, Status +-- message_map[166] = -- receives additional effect +message_map[186] = T{194,242,243}-- Receives: Weapon skill, Target, Status +message_map.Receives = T{203,205,270,272,277,279,280,266,267,269,278} +message_map[426] = T{427} -- Loses +message_map[320] = T{267} +message_map[414] = T{415} -- Dream Shroud +message_map[7] = T{263} +message_map[148] = T{149} +message_map[441] = T{421} +message_map[131] = T{286} +message_map[150] = T{151} +message_map[420] = T{421} +message_map[424] = T{421} +message_map[437] = T{438} +message_map[126] = T{676} +message_map[268] = T{269} +message_map[271] = T{272} +message_map[252] = T{265} +message_map[360] = T{361} +message_map[362] = T{363} +message_map[318] = T{263} -- Whispering Wind +message_map[323] = T{283} -- No effect Soothing Ruby +message_map[364] = T{365} -- Ecliptic Growl +message_map[146] = T{147} -- Ecliptic Howl +message_map[236] = T{270} +message_map[194] = T{280} +message_map[185] = T{264} +message_map[243] = T{278} +message_map[2] = T{264} +message_map[668] = T{669} -- Valiance +message_map[762] = T{365} -- Mix: Samson's Strength +message_map[242] = T{277} +message_map[238] = T{367} -- Phototrophic Blessing +message_map[188] = T{282} -- Misses +message_map[342] = T{344} -- Dispelga +message_map[369] = T{403} -- Ultimate Terror + +spike_effect_valid = {true,false,false,false,false,false,false,false,false,false,false,false,false,false,false} +add_effect_valid = {true,true,true,true,false,false,false,false,false,false,true,false,true,false,false} + +-- These are the debuffs that are expressed in their log form by battlemod (The status variable when using english log is code 14 while the other one is code 13 so it should be handled by messages) +--log_form_debuffs = T{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,28,29,30,31,134,135,155,156,157,168,176,177,259,260,261,262,263,264,309,474} +log_form_messages = T{64,73,82,127,128,130,141,203,204,236,242,270,271,272,277,279,350,374,531,645,754} + + +default_filters = [[ +<?xml version="1.0" ?> +<settings> +<!-- Filters are customizable based on the action user. So if you filter other pets, you're going + to eliminate all messages initiated by everyone's pet but your own. + True means "filter this" + False means "don't filter this" + + Generally, the outer tag is the actor and the inner tag is the action. + If the monster is the actor, then the inner tag is the target and the tag beyond that is the action.--> + <global> + <me> <!-- You're doing something --> + <melee>false</melee> <!-- Prevents your melee ("white") damage from appearing --> + <ranged>false</ranged> <!-- Prevents your ranged damage from appearing --> + <damage>false</damage> <!-- Prevents your damage from appearing --> + <healing>false</healing> <!-- Prevents your healing from appearing --> + <misses>false</misses> <!-- Prevents your misses from appearing --> + <items>false</items> <!-- Prevents your "Jim used an item. Jim gains the effect of Reraise." messages from appearing --> + <uses>false</uses> <!-- Prevents your "Jim uses an item." messages from appearing --> + <readies>false</readies> <!-- Prevents your "Jim readies ____" messages from appearing --> + <casting>false</casting> <!-- Prevents your "Jim begins casting ____" messages from appearing --> + <all>false</all> <!-- Prevents all of your messages from appearing --> + + <target>true</target> <!-- true = SHOW all actions where I am the target. --> + </me> + <party> <!-- A party member is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- An alliance member is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- Some guy nearby is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </others> + <my_pet> <!-- Your pet is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <my_fellow> <!-- Your adventuring fellow is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_fellow> + <other_pets> <!-- Someone else's pet is doing something --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </other_pets> + + <enemies> <!-- Monster that your party has claimed doing something with one of the below targets --> + <me> <!-- He's targeting you! --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- He's targeting a party member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- He's targeting an alliance member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- He's targeting some guy nearby --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </others> + <my_pet> <!-- He's targeting your pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <my_fellow> <!-- He's targeting your adventuring fellow --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_fellow> + <other_pets> <!-- He's targeting someone else's pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </other_pets> + <enemies> <!-- He's targeting himself or another monster your party has claimed --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </enemies> + <monsters> <!-- He's targeting another monster --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </monsters> + </enemies> + + <monsters> <!-- NPC not claimed to your party is doing something with one of the below targets --> + <me> <!-- He's targeting you! --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </me> + <party> <!-- He's targeting a party member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </party> + <alliance> <!-- He's targeting an alliance member --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </alliance> + <others> <!-- He's targeting some guy nearby --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </others> + <my_pet> <!-- He's targeting your pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_pet> + <my_fellow> <!-- He's targeting your adventuring fellow --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </my_fellow> + <other_pets> <!-- He's targeting someone else's pet --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </other_pets> + <enemies> <!-- He's targeting a monster your party has claimed --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </enemies> + <monsters> <!-- He's targeting himself or another monster --> + <melee>false</melee> + <ranged>false</ranged> + <damage>false</damage> + <healing>false</healing> + <misses>false</misses> + <items>false</items> + <uses>false</uses> + <readies>false</readies> + <casting>false</casting> + <all>false</all> + </monsters> + </monsters> + </global> +</settings> +]] + +default_filter_table = {me={melee=false,ranged=false,damage=false,healing=false,misses=false,items=false,uses=false,readies=false,casting=false,all=false,target=true}, +party={melee=false,ranged=false,damage=false,healing=false,misses=false,items=false,uses=false,readies=false,casting=false,all=false,target=false}, +alliance={melee=false,ranged=false,damage=false,healing=false,misses=false,items=false,uses=false,readies=false,casting=false,all=false,target=false}, +others={melee=false,ranged=false,damage=false,healing=false,misses=false,items=false,uses=false,readies=false,casting=false,all=false,target=false}, +my_pet={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false,target=false}, +my_fellow={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false,target=false}, +other_pets={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false,target=false}, +monsters = { +me={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +party={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +alliance={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +others={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +my_pet={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +my_fellow={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +other_pets={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}, +monsters={melee=false,ranged=false,damage=false,healing=false,misses=false,readies=false,casting=false,all=false}} } + +default_settings = [[ +<?xml version="1.0" ?> +<settings> +<!-- For the output customization lines, ${actor} denotes a value to be replaced. The options are actor, number, abil, and target. + Options for other modes are either "true" or "false". Other values will not be interpreted.--> + <global> + <condensedamage>true</condensedamage> + <condensetargets>true</condensetargets> + <cancelmulti>true</cancelmulti> + <oxford>true</oxford> + <commamode>false</commamode> + <targetnumber>true</targetnumber> + <condensetargetname>false</condensetargetname> + <swingnumber>true</swingnumber> + <sumdamage>true</sumdamage> + <condensecrits>false</condensecrits> + <tpstatuses>true</tpstatuses> + <simplify>true</simplify> + <showownernames>false</showownernames> + <crafting>true</crafting> + <showblocks>true</showblocks> + <showguards>true</showguards> + <showcritws>false</showcritws> + <showrollinfo>false</showrollinfo> + <line_aoe>AOE ${numb} ]]..string.char(129,168)..[[ ${target}</line_aoe> + <line_aoebuff>${actor} ${abil} ]]..string.char(129,168)..[[ ${target} (${status})</line_aoebuff> + <line_full>[${actor}] ${numb} ${abil} ]]..string.char(129,168)..[[ ${target}</line_full> + <line_item>[${actor}] ${abil} ]]..string.char(129,168)..[[ ${target} (${item2})</line_item> + <line_itemnum>[${actor}] ${abil} ]]..string.char(129,168)..[[ ${target} (${numb} ${item2})</line_itemnum> + <line_noability>${numb} ]]..string.char(129,168)..[[ ${target}</line_noability> + <line_noactor>${abil} ${numb} ]]..string.char(129,168)..[[ ${target}</line_noactor> + <line_nonumber>[${actor}] ${abil} ]]..string.char(129,168)..[[ ${target}</line_nonumber> + <line_notarget>[${actor}] ${abil} ]]..string.char(129,168)..[[ ${number}</line_notarget> + <line_roll>${actor} ${abil} ]]..string.char(129,168)..[[ ${target} ]]..string.char(129,170)..[[ ${number}</line_roll> + </global> +</settings> +]] + +default_colors = [[ +<? xml version="1.0" ?> +<settings> +<!-- Colors are customizable based on party / alliance position. Use the colortest command to view the available colors. + If you wish for a color to be unchanged from its normal color, set it to 0. --> + <global> + <mob>69</mob> + <other>8</other> + + <p0>501</p0> + <p1>204</p1> + <p2>410</p2> + <p3>492</p3> + <p4>259</p4> + <p5>260</p5> + + <a10>205</a10> + <a11>359</a11> + <a12>167</a12> + <a13>038</a13> + <a14>125</a14> + <a15>185</a15> + + <a20>429</a20> + <a21>257</a21> + <a22>200</a22> + <a23>481</a23> + <a24>483</a24> + <a25>208</a25> + + <mobdmg>0</mobdmg> + <mydmg>0</mydmg> + <partydmg>0</partydmg> + <allydmg>0</allydmg> + <otherdmg>0</otherdmg> + + <spellcol>0</spellcol> + <mobspellcol>0</mobspellcol> + <abilcol>0</abilcol> + <wscol>0</wscol> + <mobwscol>0</mobwscol> + <statuscol>0</statuscol> + <enfeebcol>501</enfeebcol> + <itemcol>256</itemcol> + </global> +</settings> +]] + +local item_lag_preventer = table.length(res.items) diff --git a/Data/DefaultContent/Libraries/addons/addons/blist/README.md b/Data/DefaultContent/Libraries/addons/addons/blist/README.md new file mode 100644 index 0000000..0ba021d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/blist/README.md @@ -0,0 +1,27 @@ +#Blist v1.2 +####written by Ragnarok.Ikonic + +More detailed blist with tiered display options. Allows for blist to be active on any or all of several chat types. + +####//Blist and //bl are both valid commands. +//bl help: Lists this menu. +//bl status: Shows current configuration. +//bl list: Displays blacklist. +//bl useblist|linkshell|party|tell|emote|say|shout|bazaar|examine : Toggles using Blist for said chat mode. +//bl mutedcolor #: Sets color for muted communication. Valid values 1-255. +//bl add|update name # hidetype reason : Adds to or updates a user on your blist. + name = name of person you want to blist + # = number of days to blist said person; 0 = forever + hidetype = how blacklisted you want said person to be; valid options: hard, soft, muted + hard = full blist, nothing gets through + soft = message saying conversation from name was blocked + muted = message comes through, but in a different color + reason = reason why you are adding said person to blist +//bl delete|remove name : Removes a user from your blist. +//bl qa name [reason] : Adds a user to your blist w/o requiring extra details (reason is optional). + +###Changelog: +* v0.0 06/08/13 Created addon. +* v1.0 06/08/13 Public release. +* v1.1 06/15/13 Added 'bl qa name' command, fixed some type-mismatch errors, and added command to tell all characters to update whenever a members.xml change was made. +* v1.2 07/01/13 Fixed issue with bazaar and emote not getting blocked. Fixed issue with member entries being erased. diff --git a/Data/DefaultContent/Libraries/addons/addons/blist/blist.lua b/Data/DefaultContent/Libraries/addons/addons/blist/blist.lua new file mode 100644 index 0000000..509d6c1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/blist/blist.lua @@ -0,0 +1,317 @@ +--[[ +Copyright (c) 2013, Ikonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Blist nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL IKONIC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'Blist' +_addon.author = 'Ragnarok.Ikonic' +_addon.version = '1.2.0.1' + +require 'tables' +require 'strings' +require 'chat/colors' +local config = require 'config' +require 'logger' + +local defaults = T{} +defaults.useblist = true +defaults.linkshell = true +defaults.party = true +defaults.tell = true +defaults.emote = true +defaults.say = true +defaults.shout = true +defaults.bazaar = true +defaults.examine = true +defaults.mutedcolor = 57 + +settings = T{} +members = T{} + +windower.register_event('load',function() + settings = config.load(defaults) + members = config.load("data/members.xml",members) + windower.send_command('alias blist lua command blist') + windower.send_command('alias bl lua command blist') + windower.add_to_chat(55, "Loading ".._addon.name.." v".._addon.version.." (written by ".._addon.author..")") + windower.add_to_chat(160,' Type '..string.color('//blist help',204,160)..' for a list of possible commands.') +end) + +windower.register_event('unload',function() + windower.send_command('unalias blist') + windower.send_command('unalias bl') + windower.add_to_chat(55, "Unloading ".._addon.name.." v".._addon.version..".") +end) + +windower.register_event('login',function (name) + settings = config.load(defaults) + windower.add_to_chat(160,"Loading "..string.color(_addon.name,55,160).." settings for "..windower.ffxi.get_player().name..".") +end) + +function addon_command(...) + local args = {...} + local dummysettings = table.copy(settings) + if args[1] ~= nil then + comm = args[1]:lower() + if comm == 'help' then + windower.add_to_chat(55,_addon.name.." v".._addon.version..' possible commands:') + windower.add_to_chat(160,' '..string.color('//Blist',204,160)..' and '..string.color('//bl',204,160)..' are both valid commands.') + windower.add_to_chat(160,' '..string.color('//bl help',204,160)..' : Lists this menu.') + windower.add_to_chat(160,' '..string.color('//bl status',204,160)..' : Shows current configuration.') + windower.add_to_chat(160,' '..string.color('//bl list',204,160)..' : Displays blacklist.') + windower.add_to_chat(160,' '..string.color('//bl useblist|linkshell|party|tell|emote|say|shout|bazaar|examine',204,160)..' : Toggles using '.._addon.name..' for said chat mode.') + windower.add_to_chat(160,' '..string.color('//bl mutedcolor #',204,160)..' : Sets color for muted communication. Valid values 1-255.') + windower.add_to_chat(160,' '..string.color('//bl add|update name # hidetype reason',204,160)..' : Adds to or updates a user on your blist.') + windower.add_to_chat(160,' '..string.color(' name',204,160)..' = name of person you want to blist') + windower.add_to_chat(160,' '..string.color(' #',204,160)..' = number of days to blist said person; 0 = forever') + windower.add_to_chat(160,' '..string.color(' hidetype',204,160)..' = how blacklisted you want said person to be; valid options: hard, soft, muted') + windower.add_to_chat(160,' '..string.color(' hard',204,160)..' = full blist, nothing gets through') + windower.add_to_chat(160,' '..string.color(' soft',204,160)..' = message saying conversation from name was blocked') + windower.add_to_chat(160,' '..string.color(' muted',204,160)..' = message comes through, but in a different color') + windower.add_to_chat(160,' '..string.color(' reason',204,160)..' = reason why you are adding said person to blist') + windower.add_to_chat(160,' '..string.color('//bl delete|remove name',204,160)..' : Removes a user from your blist.') + windower.add_to_chat(160,' '..string.color('//bl qa name [reason]',204,160)..' : Adds a user to your blist w/o requiring extra details (reason is optional).') + elseif comm == 'status' then + showStatus() + elseif comm == 'list' then + windower.add_to_chat(160,string.color("Name",56).." | ".. + string.color("TempTime",3).." | ".. + string.color("HideType",settings.mutedcolor).." | ".. + string.color("Reason",59).." | ".. + string.color("Date Added",29)) + for i,v in pairs(members) do + if members[i].hidetype ~= "delete" then + windower.add_to_chat(160,string.color(tostring(i),56) .. " | " .. + string.color(tostring(members[i].temptime),3) .. " | " .. + string.color(tostring(members[i].hidetype),settings.mutedcolor) .. " | " .. + string.color(tostring(members[i].reason),59) .. " | " .. + string.color(tostring(members[i].date),29) + ) + end + end + elseif comm == "add" or comm == "update" then + if type(args[2]:match("(%a+)")) ~= "string" + or type(tonumber(args[3]:match("(%d+)"))) ~= "number" + or S({'hard','soft','muted'}):contains(args[4]:lower()) ~= true + then + windower.add_to_chat(160,"Invalid format; use the following: "..string.color("//bl "..comm.." name temptime hidetype reason",204,160)) + else + com2 = args[2] -- name + com3 = tonumber(args[3]) -- temptime + com4 = args[4]:lower() -- hidetype + if args[5] ~= nil then + com5 = table.slice(args,5) + com5mess = tostring(table.sconcat(com5)) + else + com5mess = nil + end + + local addTemp = T{} + addTemp[com2] = {} + addTemp[com2].reason = com5mess + addTemp[com2].date = os.date("%x", date) + addTemp[com2].temptime = com3 + addTemp[com2].hidetype = com4 + + members = members:update(addTemp) + members:save('all') + windower.send_ipc_message("blist reload members") + windower.add_to_chat(160,"Updating "..string.color(args[2],56,160).." entry on "..string.color(_addon.name,55,160)..".") + end + elseif comm == "qa" then + if type(args[2]:match("(%a+)")) ~= "string" then + windower.add_to_chat(160,"Invalid format; use the following: "..string.color("//bl qa <name> %[reason%]",204,160)) + else + com2 = args[2] -- name + if args[3] ~= nil then + com3 = table.slice(args,3) + com3mess = tostring(table.sconcat(com3)) + else + com3mess = nil + end + + local addTemp = T{} + addTemp[com2] = {} + addTemp[com2].reason = com3mess + addTemp[com2].date = os.date("%x", date) + addTemp[com2].temptime = 0 + addTemp[com2].hidetype = "hard" + + members = members:update(addTemp) + members:save('all') + windower.send_ipc_message("blist reload members") + windower.add_to_chat(160,"Updating "..string.color(string.ucfirst(args[2]),56,160).." entry on "..string.color(_addon.name,55,160)..".") + end + elseif comm == "remove" or comm == "delete" then + if members[args[2]] ~= nil then + windower.add_to_chat(160,"Removing "..string.color(args[2],56,160).." from "..string.color(_addon.name,55,160)..".") + members[args[2]].hidetype = "delete" + members:save('all') + windower.send_ipc_message("blist reload members") + else + windower.add_to_chat(160,"User "..string.color(args[2],56,160).." not in "..string.color(_addon.name,55,160).." database; cannot remove.") + end + elseif comm == "mutedcolor" or comm == "color" then + com2num = tonumber(args[2]) + if (com2num ~= nil) and (com2num >= 1 and com2num <= 255) then + settings[comm] = com2num + else + settings[comm] = defaults[comm] + windower.add_to_chat(160," Invalid "..string.color(comm,settings.mutedcolor,160).." value; acceptable values: 1-255. Setting default.") + end + showStatus(comm) + if tostring(com2num) ~= tostring(dummysettings[comm]) then + settings:save() -- current character only + windower.add_to_chat(55,"Saving "..string.color(_addon.name,204,55).." settings.") + end + elseif S({'useblist','linkshell','party','tell','emote','say','shout','bazaar','examine'}):contains(comm) then + settings[comm] = not settings[comm] + showStatus(comm) + if tostring(com2) ~= tostring(dummysettings[comm]) then + settings:save() -- current character only + windower.add_to_chat(55,"Saving "..string.color(_addon.name,204,55).." settings.") + end + + elseif comm == "settings" then + settings:vprint() + + else + windower.add_to_chat(160, " Not a valid ".._addon.name.." v".._addon.version.." command. "..string.color('//bl help',204,160).." for a list of valid commands.") + return + end + else + addon_command('help') + end +end + +windower.register_event('addon command',addon_command) + +function showStatus(var) + if var == "mutedcolor" then + windower.add_to_chat(160," Muted"..string.color("Color",settings.mutedcolor,160)..": " .. string.color(tostring(settings.mutedcolor),204,160)) + elseif var == "useblist" then + windower.add_to_chat(160," UseBlist: " .. string.color(onOffPrint(settings[var]),204,160)) + elseif var ~= nul then + windower.add_to_chat(160," UseBlistOn"..var:ucfirst()..": " .. string.color(onOffPrint(settings[var]),204,160)) + else + windower.add_to_chat(160," UseBlist: " .. string.color(onOffPrint(settings.useblist),204,160)) + windower.add_to_chat(160," UseBlistOnLinkshell: " .. string.color(onOffPrint(settings.linkshell),204,160)) + windower.add_to_chat(160," UseBlistOnParty: " .. string.color(onOffPrint(settings.party),204,160)) + windower.add_to_chat(160," UseBlistOnTell: " .. string.color(onOffPrint(settings.tell),204,160)) + windower.add_to_chat(160," UseBlistOnEmote: " .. string.color(onOffPrint(settings.emote),204,160)) + windower.add_to_chat(160," UseBlistOnSay: " .. string.color(onOffPrint(settings.say),204,160)) + windower.add_to_chat(160," UseBlistOnShout: " .. string.color(onOffPrint(settings.shout),204,160)) + windower.add_to_chat(160," UseBlistOnBazaar: " .. string.color(onOffPrint(settings.bazaar),204,160)) + windower.add_to_chat(160," UseBlistOnExamine: " .. string.color(onOffPrint(settings.examine),204,160)) + windower.add_to_chat(160," Muted"..string.color("Color",settings.mutedcolor,160)..": " .. string.color(tostring(settings.mutedcolor),204,160)) + end +end + +function onOffPrint(bleh) + if (bleh ~= nul) then + if (bleh == 1) or (bleh == true) then + bleh = "ON"; + else + bleh = "OFF"; + end + else + bleh = "OFF"; + end + return bleh; +end + +windower.register_event('ipc message',function (msg) + if msg == "blist reload members" then + members = config.load("data/members.xml",members) +-- windower.add_to_chat(160, "Reloading members database.") + end +end) + +windower.register_event('incoming text',function (original, modified, mode) + if settings.useblist == true then + name = "blist" + if mode == 14 and settings.linkshell == true then -- linkshell (others) + a,z,name = string.find(original,'<(%a+)> ') + elseif mode == 13 and settings.party == true then -- party (others) + a,z,name = string.find(original,'%((%a+)%) ') + elseif mode == 12 and settings.tell == true then -- tell (in) + a,z,name = string.find(original,'(%a+)>> ') + elseif (mode == 15 or mode == 7) and settings.emote == true then -- emote + a,z,name = string.find(original,'(%a+) ') + elseif mode == 1 and settings.say == true then -- say + a,z,name = string.find(original,'(%a+) ') + elseif mode == 2 and settings.shout == true then -- shout + a,z,name = string.find(original,'(%a+) ') + elseif mode == 121 and settings.bazaar == true then -- bazaar + a,z,name,filler = string.find(original,'(%a+) (.*) bazaar%.') + elseif mode == 208 and settings.examine == true then -- examine + a,z,name = string.find(original,'(%a+) examines you%.') + else + name = "blist" + end + if name ~= nil then + name = name:lower() + else + name = "blist" + end + + if name ~= "blist" and + name ~= nil and + members[name] ~= nil and + members[name].hidetype ~= "delete" then + local pattern = "(%d+)%/(%d+)%/(%d+)" + local xmonth, xday, xyear = members[name].date:match(pattern) + local blah = tonumber(members[name].temptime) + if #xyear < 4 then + xyear = tonumber("20"..tostring(xyear)) + end + local convertedT = os.time({year = xyear, month = xmonth, day = xday+blah}) + local nowTime = os.time() + if nowTime > convertedT and members[name].temptime ~= 0 then + members[name].hidetype = "delete" + members:save('all') + windower.send_ipc_message("blist reload members") + end + + if members[name].hidetype == "delete" then + modified = original + elseif members[name].hidetype == "hard" or #members[name].hidetype == 0 then + modified = '' + elseif members[name].hidetype == "soft" then + modified = _addon.name.." blocked message from "..string.ucfirst(name).."." + elseif members[name].hidetype == "muted" then + modified = string.color(original:trim(),settings.mutedcolor) + else + members[name].hidetype = "hard" + members:save('all') + windower.send_ipc_message("blist reload members") + modified = '' + end + end + return modified + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/bluguide.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/bluguide.lua new file mode 100644 index 0000000..e541b98 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/bluguide.lua @@ -0,0 +1,290 @@ +traits = require ('res/traits') +buttons = require ('ui/buttons') +spellinfo = require ('res/spellinfo') +pages = require("ui/pages") +traitboxes = require("ui/traitboxes") +spellboxes = require("ui/spellboxes") +setspells = require("masterlist") + +_addon.version = '1.2' +_addon.name = 'bluGuide' +_addon.author = 'Anissa of Cerberus' + +local button_settings = { + text = { size = 15, font = 'Arial',}, + bg = { alpha = 200, red = 0, green = 100, blue = 100, visible = false }, +} + +windower.register_event('load',function () + + giftexempttraits = { + ['Double/Triple Attack'] = true, + ['Auto Refresh'] = true, + ['Gilfinder/Treasure Hunter'] = true, + } + lineheight = 14 + updatelist = {} + setspells.create(30, 50, update) + build_columns() + + local player = windower.ffxi.get_player() + job = player.main_job + sub = player.sub_job + + traitsbutton = buttons.new("Traits", button_settings) + traitsbutton.pos(80, 360) + traitsbutton.show() + traitsbutton.left_click = show_traits + traitsbutton.hover_on = function() traitsbutton.bg_visible(true) end + traitsbutton.hover_off = function() traitsbutton.bg_visible(false) end + + utilbutton = buttons.new("Utility", button_settings) + utilbutton.pos(80, 390) + utilbutton.show() + utilbutton.left_click = show_utilities + utilbutton.hover_on = function() utilbutton.bg_visible(true) end + utilbutton.hover_off = function() utilbutton.bg_visible(false) end + + scbutton = buttons.new("Damage", button_settings) + scbutton.pos(80, 420) + scbutton.show() + scbutton.left_click = show_skillchain + scbutton.hover_on = function() scbutton.bg_visible(true) end + scbutton.hover_off = function() scbutton.bg_visible(false) end + + procsbutton = buttons.new("Procs", button_settings) + procsbutton.pos(80, 450) + procsbutton.show() + procsbutton.left_click = show_procs + procsbutton.hover_on = function() procsbutton.bg_visible(true) end + procsbutton.hover_off = function() procsbutton.bg_visible(false) end + + closebutton = buttons.new("Close", button_settings) + closebutton.pos(80, 480) + closebutton.show() + closebutton.left_click = close + closebutton.hover_on = function() closebutton.bg_visible(true) end + closebutton.hover_off = function() closebutton.bg_visible(false) end +end) + +function update() + for _, v in pairs(updatelist) do + v:update() + end +end + +windower.register_event('job change', function(new, old) + jobchange = coroutine.schedule(check_job, 5) +end) + +function check_job() + local player = windower.ffxi.get_player() + local t = {} + if player.main_job == "BLU" then + if job ~= "BLU" then + print("Reloading bluGuide. Changed job/subjob combo.") + windower.send_command('lua reload bluguide') + end + elseif player.sub_job == "BLU" then + if sub ~= "BLU" then + print("Reloading bluGuide. Changed job/subjob combo.") + windower.send_command('lua reload bluguide') + end + else + print("Unloading bluGuide. Invalid job/subjob combo.") + windower.send_command('lua unload bluguide') + end +end + +function build_columns() + traitpage = pages.new(260, 50) + + traitpage:add(traitboxes.new(traits['Dual Wield'])) + traitpage:add(traitboxes.new(traits['Double/Triple Attack'])) + traitpage:add(traitboxes.new(traits['Attack Bonus'])) + traitpage:add(traitboxes.new(traits['Accuracy Bonus'])) + traitpage:add(traitboxes.new(traits['Store TP'])) + traitpage:add(traitboxes.new(traits['Skillchain Bonus'])) + traitpage:add(traitboxes.new(traits['Critical Attack Bonus'])) + traitpage:add(traitboxes.new(traits['Magic Attack Bonus'])) + traitpage:add(traitboxes.new(traits['Magic Accuracy Bonus'])) + traitpage:add(traitboxes.new(traits['Fast Cast'])) + traitpage:add(traitboxes.new(traits['Magic Burst Bonus'])) + traitpage:add(traitboxes.new(traits['Conserve MP'])) + traitpage:add(traitboxes.new(traits['Auto Refresh'])) + traitpage:add(traitboxes.new(traits['Clear Mind'])) + traitpage:add(traitboxes.new(traits['Max MP Boost'])) + traitpage:add(traitboxes.new(traits['Counter'])) + traitpage:add(traitboxes.new(traits['Defense Bonus'])) + traitpage:add(traitboxes.new(traits['Magic Defense Bonus'])) + traitpage:add(traitboxes.new(traits['Magic Evasion Bonus'])) + traitpage:add(traitboxes.new(traits['Evasion Bonus'])) + traitpage:add(traitboxes.new(traits['Inquartata'])) + traitpage:add(traitboxes.new(traits['Auto Regen'])) + traitpage:add(traitboxes.new(traits['Max HP Boost'])) + traitpage:add(traitboxes.new(traits['Tenacity'])) + traitpage:add(traitboxes.new(traits['Resist Gravity'])) + traitpage:add(traitboxes.new(traits['Resist Silence'])) + traitpage:add(traitboxes.new(traits['Resist Sleep'])) + traitpage:add(traitboxes.new(traits['Resist Slow'])) + traitpage:add(traitboxes.new(traits['Gilfinder/TH'])) + traitpage:add(traitboxes.new(traits['Beast Killer'])) + traitpage:add(traitboxes.new(traits['Lizard Killer'])) + traitpage:add(traitboxes.new(traits['Undead Killer'])) + traitpage:add(traitboxes.new(traits['Plantoid Killer'])) + traitpage:add(traitboxes.new(traits['Rapid Shot'])) + traitpage:add(traitboxes.new(traits['Zanshin'])) + + updatelist[#updatelist+1] = traitpage + traitpage:show() + + buffpage = pages.new(260, 50) + buffpage:add(spellboxes.new('Cure', function(s) return s.effect['Cure'] end)) + buffpage:add(spellboxes.new('Erase', function(s) return s.effect['Erase'] end)) + buffpage:add(spellboxes.new('Haste', function(s) return s.effect['Haste'] end)) + buffpage:add(spellboxes.new('Attack Boost', function(s) return s.effect['Attack Boost'] end)) + buffpage:add(spellboxes.new('Refresh', function(s) return s.effect['Refresh'] end)) + buffpage:add(spellboxes.new('Accuracy Boost', function(s) return s.effect['Accuracy Boost'] end)) + buffpage:add(spellboxes.new('Magic Attack Boost', function(s) return s.effect['Magic Attack Boost'] end)) + + buffpage:add(spellboxes.new('Blink', function(s) return s.effect['Blink'] end)) + buffpage:add(spellboxes.new('Defense Boost', function(s) return s.effect['Defense Boost'] end)) + buffpage:add(spellboxes.new('Phalanx', function(s) return s.effect['Phalanx'] end)) + buffpage:add(spellboxes.new('Stoneskin', function(s) return s.effect['Stoneskin'] end)) + buffpage:add(spellboxes.new('Spikes', function(s) return s.effect['Spikes'] end)) + buffpage:add(spellboxes.new('Magic Defense Boost', function(s) return s.effect['Magic Defense Boost'] end)) + buffpage:add(spellboxes.new('Evasion Boost', function(s) return s.effect['Evasion Boost'] end)) + buffpage:add(spellboxes.new('Regen', function(s) return s.effect['Regen'] end)) + buffpage:add(spellboxes.new('Counter', function(s) return s.effect['Counter'] end)) + + buffpage:add(spellboxes.new('Sleep', function(s) return s.effect['Sleep'] end)) + buffpage:add(spellboxes.new('Dispel', function(s) return s.effect['Dispel'] end)) + buffpage:add(spellboxes.new('Defense Down', function(s) return s.effect['Defense Down'] end)) + buffpage:add(spellboxes.new('Evasion Down', function(s) return s.effect['Evasion Down'] end)) + buffpage:add(spellboxes.new('Magic Defense Down', function(s) return s.effect['Magic Defense Down'] end)) + + buffpage:add(spellboxes.new('Aspir', function(s) return s.effect['Aspir'] end)) + buffpage:add(spellboxes.new('Drain', function(s) return s.effect['Drain'] end)) + buffpage:add(spellboxes.new('Poison', function(s) return s.effect['Poison'] end)) + buffpage:add(spellboxes.new('Stun', function(s) return s.effect['Stun'] end)) + buffpage:add(spellboxes.new('Terror', function(s) return s.effect['Terror'] end)) + + buffpage:add(spellboxes.new('Silence', function(s) return s.effect['Silence'] end)) + buffpage:add(spellboxes.new('Slow', function(s) return s.effect['Slow'] end)) + buffpage:add(spellboxes.new('Paralyze', function(s) return s.effect['Paralyze'] end)) + buffpage:add(spellboxes.new('Reduce TP', function(s) return s.effect['Reduce TP'] end)) + buffpage:add(spellboxes.new('Plague', function(s) return s.effect['Plague'] end)) + + buffpage:add(spellboxes.new('Bind', function(s) return s.effect['Bind'] end)) + buffpage:add(spellboxes.new('Gravity', function(s) return s.effect['Gravity'] end)) + buffpage:add(spellboxes.new('Petrify', function(s) return s.effect['Petrify'] end)) + buffpage:add(spellboxes.new('Flash', function(s) return s.effect['Flash'] end)) + buffpage:add(spellboxes.new('Blind', function(s) return s.effect['Blind'] end)) + buffpage:add(spellboxes.new('Accuracy Down', function(s) return s.effect['Accuracy Down'] end)) + + buffpage:add(spellboxes.new('Bio', function(s) return s.effect['Bio'] end)) + buffpage:add(spellboxes.new('Doom', function(s) return s.effect['Doom'] end)) + buffpage:add(spellboxes.new('Frost', function(s) return s.effect['Frost'] end)) + buffpage:add(spellboxes.new('Burn', function(s) return s.effect['Burn'] end)) + buffpage:add(spellboxes.new('Drown', function(s) return s.effect['Drown'] end)) + buffpage:add(spellboxes.new('Vit Down', function(s) return s.effect['Vit Down'] end)) + buffpage:add(spellboxes.new('Int Down', function(s) return s.effect['Int Down'] end)) + buffpage:add(spellboxes.new('Str Down', function(s) return s.effect['Str Down'] end)) + buffpage:add(spellboxes.new('Dex Down', function(s) return s.effect['Dex Down'] end)) + + updatelist[#updatelist+1] = buffpage + + procpage = pages.new(260, 50) + procpage:add(spellboxes.new('Voidwatch - Fire', function(s) return s.Voidwatch ~= nil and s.element == 0 end)) + procpage:add(spellboxes.new('Voidwatch - Ice', function(s) return s.Voidwatch and s.element == 1 end)) + procpage:add(spellboxes.new('Voidwatch - Wind', function(s) return s.Voidwatch and s.element == 2 end)) + procpage:add(spellboxes.new('Voidwatch - Earth', function(s) return s.Voidwatch and s.element == 3 end)) + procpage:add(spellboxes.new('Voidwatch - Thunder', function(s) return s.Voidwatch and s.element == 4 end)) + procpage:add(spellboxes.new('Voidwatch - Water', function(s) return s.Voidwatch and s.element == 5 end)) + procpage:add(spellboxes.new('Voidwatch - Light', function(s) return s.Voidwatch and s.element == 6 end)) + procpage:add(spellboxes.new('Voidwatch - Dark', function(s) return s.Voidwatch and s.element == 7 end)) + procpage:add(spellboxes.new('Abyssea', function(s) return s.Abyssea end)) + updatelist[#updatelist+1] = procpage + + scpage = pages.new(260, 50) + scpage:add(spellboxes.new('Gravitation', function(s) return s.SCA == "Gravitation" or s.SCB == "Gravitation" end, function(s) if s.SCB == "Gravitation" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Fusion', function(s) return s.SCA == "Fusion" or s.SCB == "Fusion" end, function(s) if s.SCB == "Fusion" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Distortion', function(s) return s.SCA == "Distortion" or s.SCB == "Distortion" end, function(s) if s.SCB == "Distortion" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Fragmentation', function(s) return s.SCA == "Fragmentation" or s.SCB == "Fragmentation" end, function(s) if s.SCB == "Fragmentation" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Transfixion', function(s) return s.SCA == "Transfixion" or s.SCB == "Transfixion" end, function(s) if s.SCB == "Transfixion" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Compression', function(s) return s.SCA == "Compression" or s.SCB == "Compression" end, function(s) if s.SCB == "Compression" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Reverberation', function(s) return s.SCA == "Reverberation" or s.SCB == "Reverberation" end, function(s) if s.SCB == "Reverberation" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Induration', function(s) return s.SCA == "Induration" or s.SCB == "Induration" end, function(s) if s.SCB == "Induration" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Impaction', function(s) return s.SCA == "Impaction" or s.SCB == "Impaction" end, function(s) if s.SCB == "Impaction" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Liquifaction', function(s) return s.SCA == "Liquifaction" or s.SCB == "Liquifaction" end, function(s) if s.SCB == "Liquifaction" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Detonation', function(s) return s.SCA == "Detonation" or s.SCB == "Detonation" end, function(s) if s.SCB == "Detonation" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + scpage:add(spellboxes.new('Scission', function(s) return s.SCA == "Scission" or s.SCB == "Scission" end, function(s) if s.SCB == "Scission" then return s.name.." ("..string.sub(s.SCA, 1, 3)..")" end end)) + + scpage:add(spellboxes.new("Fire", function(s) return s.Nuke and s.element == 0 end)) + scpage:add(spellboxes.new("Ice", function(s) return s.Nuke and s.element == 1 end)) + scpage:add(spellboxes.new("Wind", function(s) return s.Nuke and s.element == 2 end)) + scpage:add(spellboxes.new("Earth", function(s) return s.Nuke and s.element == 3 end)) + scpage:add(spellboxes.new("Thunder", function(s) return s.Nuke and s.element == 4 end)) + scpage:add(spellboxes.new("Water", function(s) return s.Nuke and s.element == 5 end)) + scpage:add(spellboxes.new("Light", function(s) return s.Nuke and s.element == 6 end)) + scpage:add(spellboxes.new("Dark", function(s) return s.Nuke and s.element == 7 end)) + updatelist[#updatelist+1] = scpage +end + +function show_traits() + traitpage:show() + buffpage:hide() + procpage:hide() + scpage:hide() +end + +function show_utilities() + traitpage:hide() + buffpage:show() + procpage:hide() + scpage:hide() +end + +function show_procs() + traitpage:hide() + buffpage:hide() + procpage:show() + scpage:hide() +end + +function show_skillchain() + traitpage:hide() + buffpage:hide() + procpage:hide() + scpage:show() +end + +function close() + windower.send_command('lua unload bluguide') +end + +--Copyright © 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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. diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/masterlist.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/masterlist.lua new file mode 100644 index 0000000..eaed220 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/masterlist.lua @@ -0,0 +1,192 @@ +texts = require("texts") +spellbuttons = require("ui/spellbuttons") +spelllist = {} +display = {} + +local function get_limits() + local player = windower.ffxi.get_player() + local slots = 0 + local points = 0 + local gifts = 0 + local level = 0 + + if player.main_job == "BLU" then + if player.main_job_level > 70 then + slots = 20 + else + slots = (math.floor((player.sub_job_level + 9) / 10) * 2) + 4 + end + + points = (math.floor((player.main_job_level + 9) / 10) * 5) + 5 + if player.main_job_level >= 75 then + points = points + player.merits.assimilation + if player.main_job_level == 99 then + points = points + player.job_points.blu.blue_magic_point_bonus + end + end + level = player.main_job_level + elseif player.sub_job == "BLU" then + points = (math.floor((player.sub_job_level + 9) / 10) * 5) + 5 + slots = (math.floor((player.sub_job_level + 9) / 10) * 2) + 4 + level = player.sub_job_level + end + + local jobpointsspent = player.job_points.blu.jp_spent + if jobpointsspent >= 1200 then + gifts = 2 + elseif jobpointsspent >= 100 then + gifts = 1 + end + + spelllist.limits = { slots = slots, points = points, level = level } + spelllist.gifts = gifts + spelllist.learned = windower.ffxi.get_spells() +end + +function spelllist.create(px, py, updatefn) + local textsettings = { + text = { size = 10, font = 'Lucida Console' }, + bg = { alpha = 120, red = 0, green = 0, blue = 0, visible = true }, + flags = { draggable = false }, + pos = {x = px, y = py} } + + slots = texts.new("Slots: 0/20 Points: 0/70", textsettings) + x = px + y = py + slots:pos(px, py) + slots:show() + + spelllist.slots = 0 + spelllist.points = 0 + spelllist.update = updatefn + get_limits() + + t = get_set_spells() + if not t.spells then return end + for _,v in pairs(t.spells) do + spelllist.add(v) + end +end + +function get_set_spells() + local player = windower.ffxi.get_player() + local t = {} + if player.main_job == "BLU" then + t = windower.ffxi.get_mjob_data() + elseif player.sub_job == "BLU" then + t = windower.ffxi.get_sjob_data() + end + return t +end + +function spelllist.add(spell) + spelllist[spell] = true + spelllist.slots = spelllist.slots + 1 + spelllist.points = spelllist.points + spellinfo[spell].cost + + local newspell = spellbuttons.new(string.format('%-24s %i', spellinfo[spell].name, spellinfo[spell].cost), spell, spellinfo[spell].cost, x, y + spelllist.slots * lineheight) + if spellinfo[spell].element == 0 then --fire + newspell:color(255, 0, 0) + elseif spellinfo[spell].element == 1 then --ice + newspell:color(180, 180, 255) + elseif spellinfo[spell].element == 2 then --wind + newspell:color(0, 255, 0) + elseif spellinfo[spell].element == 3 then --earth + newspell:color(255, 255, 0) + elseif spellinfo[spell].element == 4 then --thunder + newspell:color(255, 0, 255) + elseif spellinfo[spell].element == 5 then --water + newspell:color(100, 100, 255) + elseif spellinfo[spell].element == 6 then --light + newspell:color(235, 235, 255) + elseif spellinfo[spell].element == 7 then --dark + newspell:color(180, 180, 180) + elseif spellinfo[spell].element == 15 then --physical + newspell:color(200, 100, 0) + end + + newspell:show() + display[spell] = newspell + + spelllist.sort() + + local t = get_set_spells() + for i = 1, spelllist.limits.slots do + if t.spells[i] == nil then + windower.ffxi.set_blue_magic_spell(spell, i) + return + end + end +end + +function spelllist.remove(spell) + display[spell]:destroy() + display[spell] = nil + + spelllist[spell] = nil + + spelllist.slots = spelllist.slots - 1 + spelllist.points = spelllist.points - spellinfo[spell].cost + + spelllist.sort() + + + local t = get_set_spells() + for i = 1, spelllist.limits.slots do + if t.spells[i] == spell then + windower.ffxi.remove_blue_magic_spell(i) + + return + end + end + +end + +function spelllist.sort() + local pos = 1 + vspace = "\n" + for _, v in pairs(display) do + vspace = vspace.."\n" + v:pos(x, y + (pos*lineheight)) + v:update() + pos = pos + 1 + end + slots:text(string.format('Slots: %2i/%2i Points: %2i/%2i', spelllist.slots, spelllist.limits.slots, spelllist.points, spelllist.limits.points)..vspace) + spelllist.update() +end + +function spelllist.toggle(spell) + if spelllist[spell] then + spelllist.remove(spell) + else + spelllist.add(spell) + end +end + +return spelllist + +--Copyright © 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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. diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/res/spellinfo.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/res/spellinfo.lua new file mode 100644 index 0000000..092b0ef --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/res/spellinfo.lua @@ -0,0 +1,206 @@ +return { +[584] = { name = "Sheep Song", id = 584, level = 16, cost = 2, element = 6, effect = { ["Sleep"] = true, }, }, +[616] = { name = "Temporal Shift", id = 616, level = 73, cost = 5, element = 4, effect = { ["Stun"] = true, }, Voidwatch = true, }, +[648] = { name = "Regurgitation", id = 648, level = 69, cost = 1, element = 5, effect = { ["Bind"] = true, }, Nuke = true }, +[680] = { name = "Charged Whisker", id = 680, level = 88, cost = 5, element = 4, effect = {}, Voidwatch = true, Nuke = true }, +[712] = { name = "Rail Cannon", id = 712, level = 99, cost = 6, element = 6, effect = {}, Nuke = true }, +[521] = { name = "MP Drainkiss", id = 521, level = 42, cost = 4, element = 7, effect = { ["Aspir"] = true, }, }, +[585] = { name = "Ram Charge", id = 585, level = 73, cost = 4, element = 15, effect = {}, SCA = "Fragmentation"}, +[617] = { name = "Vertical Cleave", id = 617, level = 75, cost = 3, element = 15, effect = {}, SCA = "Gravitation"}, +[681] = { name = "Winds of Promy.", id = 681, level = 89, cost = 5, element = 6, effect = { ["Erase"] = true, }, }, +[713] = { name = "Diffusion Ray", id = 713, level = 99, cost = 6, element = 6, effect = {}, Nuke = true }, +[522] = { name = "Death Ray", id = 522, level = 34, cost = 2, element = 7, effect = {}, Voidwatch = true, Nuke = true }, +[554] = { name = "Death Scissors", id = 554, level = 60, cost = 5, element = 15, effect = {}, SCA = "Compression", SCB = "Reverberation"}, +[618] = { name = "Blastbomb", id = 618, level = 18, cost = 2, element = 0, effect = { ["Bind"] = true, }, Voidwatch = true, Nuke = true }, +[650] = { name = "Seedspray", id = 650, level = 61, cost = 4, element = 15, effect = { ["Defense Down"] = true, }, SCA = "Induration", SCB = "Detonation", }, +[682] = { name = "Delta Thrust", id = 682, level = 89, cost = 2, element = 15, effect = { ["Plague"] = true, }, SCA = "Liquifaction", SCB = "Detonation"}, +[714] = { name = "Sinker Drill", id = 714, level = 99, cost = 6, element = 15, effect = {}, SCA = "Gravitation", SCB = "Reverberation"}, +[555] = { name = "Magnetite Cloud", id = 555, level = 46, cost = 3, element = 3, effect = { ["Gravity"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[587] = { name = "Claw Cyclone", id = 587, level = 20, cost = 2, element = 15, effect = {}, SCA = "Scission"}, +[651] = { name = "Corrosive Ooze", id = 651, level = 66, cost = 4, element = 5, effect = { ["Defense Down"] = true, ["Attack Down"] = true, }, Voidwatch = true, Nuke = true }, +[683] = { name = "Evryone. Grudge", id = 683, level = 90, cost = 4, element = 7, effect = {}, Nuke = true }, +[715] = { name = "Molting Plumage", id = 715, level = 99, cost = 6, element = 2, effect = {}, Nuke = true }, +[524] = { name = "Sandspin", id = 524, level = 1, cost = 2, element = 3, effect = { ["Accuracy Down"] = true, }, Voidwatch = true, Nuke = true }, +[588] = { name = "Lowing", id = 588, level = 71, cost = 2, element = 0, effect = { ["Plague"] = true, }, }, +[620] = { name = "Battle Dance", id = 620, level = 12, cost = 3, element = 15, effect = { ["Dex Down"] = true, }, SCA = "Impaction"}, +[652] = { name = "Spiral Spin", id = 652, level = 60, cost = 3, element = 15, effect = { ["Accuracy Down"] = true, }, SCA = "Transfixion"}, +[684] = { name = "Reaving Wind", id = 684, level = 90, cost = 4, element = 2, effect = { ["Reduce TP"] = true, }, Voidwatch = true, }, +[716] = { name = "Nectarous Deluge", id = 716, level = 99, cost = 6, element = 5, effect = { ["Poison"] = true, }, Nuke = true }, +[557] = { name = "Eyes On Me", id = 557, level = 61, cost = 4, element = 7, effect = {}, Voidwatch = true, Abyssea = true, Nuke = true }, +[589] = { name = "Dimensional Death", id = 589, level = 60, cost = 5, element = 15, effect = {}, SCA = "Impaction"}, +[621] = { name = "Sandspray", id = 621, level = 66, cost = 2, element = 7, effect = { ["Blind"] = true, }, Voidwatch = true, }, +[653] = { name = "Asuran Claws", id = 653, level = 70, cost = 2, element = 15, effect = {}, SCA = "Liquifaction", SCB = "Impaction"}, +[685] = { name = "Barrier Tusk", id = 685, level = 91, cost = 3, element = 3, effect = { ["Phalanx"] = true, }, }, +[717] = { name = "Sweeping Gouge", id = 717, level = 99, cost = 6, element = 15, effect = { ["Defense Down"] = true, }, SCA = "Fragmentation", SCB = "Scission"}, +[622] = { name = "Grand Slam", id = 622, level = 30, cost = 2, element = 15, effect = {}, SCA = "Induration"}, +[654] = { name = "Sub-zero Smash", id = 654, level = 72, cost = 4, element = 15, effect = { ["Paralyze"] = true, }, SCA = "Fragmentation"}, +[686] = { name = "Mortal Ray", id = 686, level = 91, cost = 4, element = 7, effect = { ["Doom"] = true, }, }, +[718] = { name = "Atra. Libations", id = 718, level = 99, cost = 6, element = 7, effect = { ["Drain"] = true, }, Nuke = true }, +[527] = { name = "Smite of Rage", id = 527, level = 34, cost = 3, element = 15, effect = {}, SCA = "Detonation"}, +[591] = { name = "Heat Breath", id = 591, level = 71, cost = 4, element = 0, effect = {}, Voidwatch = true, Abyssea = true, Nuke = true }, +[623] = { name = "Head Butt", id = 623, level = 12, cost = 3, element = 15, effect = { ["Stun"] = true, }, SCA = "Impaction"}, +[655] = { name = "Triumphant Roar", id = 655, level = 71, cost = 3, element = 0, effect = { ["Attack Boost"] = true, }, }, +[687] = { name = "Water Bomb", id = 687, level = 92, cost = 2, element = 5, effect = { ["Silence"] = true, }, Nuke = true }, +[719] = { name = "Searing Tempest", id = 719, level = 99, cost = 8, element = 0, effect = { ["Burn"] = true, }, Nuke = true }, +[560] = { name = "Frenetic Rip", id = 560, level = 63, cost = 3, element = 15, effect = {}, SCA = "Induration"}, +[592] = { name = "Blank Gaze", id = 592, level = 38, cost = 2, element = 6, effect = { ["Dispel"] = true, }, Voidwatch = true, }, +[656] = { name = "Acrid Stream", id = 656, level = 77, cost = 3, element = 5, effect = { ["Magic Defense Down"] = true, }, Voidwatch = true, Nuke = true }, +[688] = { name = "Heavy Strike", id = 688, level = 92, cost = 2, element = 15, effect = {}, SCA = "Fragmentation", SCB = "Transfixion"}, +[720] = { name = "Spectral Floe", id = 720, level = 99, cost = 8, element = 1, effect = { ["Terror"] = true, }, Nuke = true }, +[529] = { name = "Bludgeon", id = 529, level = 18, cost = 2, element = 15, effect = {}, SCA = "Liquifaction"}, +[561] = { name = "Frightful Roar", id = 561, level = 50, cost = 3, element = 2, effect = { ["Defense Down"] = true, }, }, +[593] = { name = "Magic Fruit", id = 593, level = 58, cost = 3, element = 6, effect = { ["Cure"] = true, }, }, +[657] = { name = "Blazing Bound", id = 657, level = 80, cost = 3, element = 0, effect = {}, Nuke = true }, +[689] = { name = "Dark Orb", id = 689, level = 93, cost = 3, element = 7, effect = {}, Nuke = true }, +[721] = { name = "Anvil Lightning", id = 721, level = 99, cost = 8, element = 4, effect = { ["Stun"] = true, }, Nuke = true }, +[530] = { name = "Refueling", id = 530, level = 48, cost = 4, element = 2, effect = { ["Haste"] = true, }, }, +[594] = { name = "Uppercut", id = 594, level = 38, cost = 3, element = 15, effect = {}, SCA = "Liquifaction"}, +[626] = { name = "Bomb Toss", id = 626, level = 28, cost = 3, element = 0, effect = {}, Nuke = true }, +[658] = { name = "Plenilune Embrace", id = 658, level = 76, cost = 4, element = 6, effect = { ["Cure"] = true, }, }, +[690] = { name = "White Wind", id = 690, level = 94, cost = 5, element = 2, effect = { ["Cure"] = true, }, }, +[722] = { name = "Entomb", id = 722, level = 99, cost = 8, element = 3, effect = { ["Petrify"] = true, }, Nuke = true }, +[531] = { name = "Ice Break", id = 531, level = 50, cost = 3, element = 1, effect = { ["Bind"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[563] = { name = "Hecatomb Wave", id = 563, level = 54, cost = 3, element = 2, effect = { ["Blind"] = true, }, Voidwatch = true, Nuke = true }, +[595] = { name = "1000 Needles", id = 595, level = 62, cost = 5, element = 6, effect = {}, Nuke = true }, +[659] = { name = "Demoralizing Roar", id = 659, level = 80, cost = 4, element = 5, effect = {}, }, +[723] = { name = "Saurian Slide", id = 723, level = 99, cost = 7, element = 15, effect = { ["Attack Down"] = true, }, SCA = "Fragmentation", SCB = "Distortion"}, +[532] = { name = "Blitzstrahl", id = 532, level = 44, cost = 4, element = 4, effect = { ["Stun"] = true, }, Voidwatch = true, Nuke = true }, +[564] = { name = "Body Slam", id = 564, level = 62, cost = 4, element = 15, effect = {}, SCA = "Impaction"}, +[596] = { name = "Pinecone Bomb", id = 596, level = 36, cost = 2, element = 15, effect = { ["Sleep"] = true, }, SCA = "Liquifaction"}, +[628] = { name = "Frypan", id = 628, level = 63, cost = 3, element = 15, effect = { ["Stun"] = true, }, SCA = "Impaction"}, +[660] = { name = "Cimicine Discharge", id = 660, level = 78, cost = 3, element = 3, effect = { ["Slow"] = true, }, Voidwatch = true, }, +[692] = { name = "Sudden Lunge", id = 692, level = 95, cost = 4, element = 15, effect = { ["Stun"] = true, }, SCA = "Detonation", SCB = "Impaction"}, +[724] = { name = "Palling Salvo", id = 724, level = 99, cost = 7, element = 7, effect = { ["Bio"] = true, }, Nuke = true }, +[533] = { name = "Self-Destruct", id = 533, level = 50, cost = 3, element = 0, effect = {}, Nuke = true }, +[565] = { name = "Radiant Breath", id = 565, level = 54, cost = 4, element = 6, effect = { ["Slow"] = true, ["Silence"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[597] = { name = "Sprout Smack", id = 597, level = 4, cost = 2, element = 15, effect = { ["Slow"] = true, }, SCA = "Reverberation"}, +[629] = { name = "Flying Hip Press", id = 629, level = 58, cost = 3, element = 2, effect = {}, Nuke = true }, +[661] = { name = "Animating Wail", id = 661, level = 79, cost = 5, element = 2, effect = { ["Haste"] = true, }, }, +[693] = { name = "Quadrastrike", id = 693, level = 96, cost = 5, element = 15, effect = {}, SCA = "Liquifaction", SCB = "Scission"}, +[725] = { name = "Blinding Fulgor", id = 725, level = 99, cost = 8, element = 6, effect = { ["Flash"] = true, }, Nuke = true }, +[534] = { name = "Mysterious Light", id = 534, level = 40, cost = 4, element = 2, effect = { ["Gravity"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[598] = { name = "Soporific", id = 598, level = 24, cost = 4, element = 7, effect = { ["Sleep"] = true, }, }, +[662] = { name = "Battery Charge", id = 662, level = 79, cost = 3, element = 6, effect = { ["Refresh"] = true, }, }, +[694] = { name = "Vapor Spray", id = 694, level = 96, cost = 3, element = 5, effect = {}, Nuke = true }, +[726] = { name = "Scouring Spate", id = 726, level = 99, cost = 8, element = 5, effect = { ["Attack Down"] = true, }, Nuke = true }, +[535] = { name = "Cold Wave", id = 535, level = 52, cost = 1, element = 1, effect = { ["Frost"] = true, }, Voidwatch = true, }, +[567] = { name = "Helldive", id = 567, level = 16, cost = 2, element = 15, effect = {}, SCA = "Transfixion"}, +[599] = { name = "Queasyshroom", id = 599, level = 8, cost = 2, element = 15, effect = { ["Poison"] = true, }, SCA = "Compression"}, +[631] = { name = "Hydro Shot", id = 631, level = 63, cost = 3, element = 15, effect = {}, SCA = "Reverberation"}, +[663] = { name = "Leafstorm", id = 663, level = 77, cost = 4, element = 2, effect = {}, Voidwatch = true, Nuke = true }, +[695] = { name = "Thunder Breath", id = 695, level = 97, cost = 4, element = 4, effect = {}, Nuke = true }, +[727] = { name = "Silent Storm", id = 727, level = 99, cost = 8, element = 2, effect = { ["Silence"] = true, }, Nuke = true }, +[536] = { name = "Poison Breath", id = 536, level = 22, cost = 1, element = 5, effect = { ["Poison"] = true, }, Nuke = true }, +[632] = { name = "Diamondhide", id = 632, level = 67, cost = 3, element = 3, effect = { ["Stoneskin"] = true, }, }, +[664] = { name = "Regeneration", id = 664, level = 78, cost = 2, element = 6, effect = { ["Regen"] = true, }, }, +[696] = { name = "O. Counterstance", id = 696, level = 98, cost = 5, element = 0, effect = { ["Counter"] = true, }, }, +[728] = { name = "Tenebral Crush", id = 728, level = 99, cost = 8, element = 7, effect = { ["Defense Down"] = true, }, Nuke = true }, +[537] = { name = "Stinking Gas", id = 537, level = 44, cost = 2, element = 2, effect = { ["Vit Down"] = true, }, }, +[569] = { name = "Jet Stream", id = 569, level = 38, cost = 4, element = 15, effect = {}, SCA = "Impaction"}, +[633] = { name = "Enervation", id = 633, level = 67, cost = 5, element = 7, effect = { ["Defense Down"] = true, ["Magic Defense Down"] = true, }, }, +[665] = { name = "Final Sting", id = 665, level = 81, cost = 1, element = 15, effect = {}, SCA = "Fusion"}, +[697] = { name = "Amorphic Spikes", id = 697, level = 98, cost = 4, element = 15, effect = {}, SCA = "Gravitation", SCB = "Transfixion"}, +[538] = { name = "Memento Mori", id = 538, level = 62, cost = 4, element = 1, effect = { ["Magic Attack Boost"] = true, }, }, +[570] = { name = "Blood Drain", id = 570, level = 20, cost = 2, element = 7, effect = { ["Drain"] = true, }, Nuke = true }, +[634] = { name = "Light of Penance", id = 634, level = 58, cost = 5, element = 6, effect = { ["Blind"] = true, ["Bind"] = true, ["Reduce TP"] = true, }, Voidwatch = true, }, +[666] = { name = "Goblin Rush", id = 666, level = 81, cost = 3, element = 15, effect = {}, SCA = "Fusion"}, +[698] = { name = "Wind Breath", id = 698, level = 99, cost = 2, element = 2, effect = {}, Nuke = true }, +[539] = { name = "Terror Touch", id = 539, level = 40, cost = 3, element = 15, effect = { ["Attack Down"] = true, }, SCA = "Compression", SCB = "Reverberation"}, +[603] = { name = "Wild Oats", id = 603, level = 4, cost = 3, element = 15, effect = { ["Vit Down"] = true, }, SCA = "Transfixion"}, +[667] = { name = "Vanity Dive", id = 667, level = 82, cost = 2, element = 15, effect = {}, SCA = "Scission"}, +[699] = { name = "Barbed Crescent", id = 699, level = 99, cost = 2, element = 15, effect = { ["Accuracy Down"] = true, }, SCA = "Distortion", SCB = "Liquifaction"}, +[540] = { name = "Spinal Cleave", id = 540, level = 63, cost = 4, element = 15, effect = {}, SCA = "Scission", SCB = "Detonation"}, +[572] = { name = "Sound Blast", id = 572, level = 32, cost = 1, element = 0, effect = { ["Int Down"] = true, }, }, +[604] = { name = "Bad Breath", id = 604, level = 61, cost = 5, element = 3, effect = { ["Poison"] = true, ["Blind"] = true, ["Silence"] = true, ["Slow"] = true, ["Paralyze"] = true, ["Bind"] = true, ["Gravity"] = true, }, Voidwatch = true, }, +[636] = { name = "Warm-Up", id = 636, level = 68, cost = 4, element = 3, effect = { ["Accuracy Boost"] = true, ["Evasion Boost"] = true, }, }, +[668] = { name = "Magic Barrier", id = 668, level = 82, cost = 3, element = 7, effect = { ["Stoneskin"] = true, }, }, +[700] = { name = "Nat. Meditation", id = 700, level = 99, cost = 6, element = 0, effect = { ["Attack Boost"] = true, }, }, +[541] = { name = "Blood Saber", id = 541, level = 48, cost = 2, element = 7, effect = { ["Drain"] = true, }, Nuke = true }, +[573] = { name = "Feather Tickle", id = 573, level = 64, cost = 3, element = 2, effect = { ["Reduce TP"] = true, }, SCA = "Transfixion"}, +[605] = { name = "Geist Wall", id = 605, level = 46, cost = 3, element = 7, effect = { ["Dispel"] = true, }, }, +[637] = { name = "Firespit", id = 637, level = 68, cost = 5, element = 0, effect = {}, Voidwatch = true, Nuke = true }, +[669] = { name = "Whirl of Rage", id = 669, level = 83, cost = 2, element = 15, effect = { ["Stun"] = true, }, SCA = "Scission", SCB = "Detonation"}, +[701] = { name = "Tem. Upheaval", id = 701, level = 99, cost = 6, element = 2, effect = {}, Nuke = true }, +[542] = { name = "Digest", id = 542, level = 36, cost = 2, element = 7, effect = { ["Drain"] = true, }, Nuke = true }, +[574] = { name = "Feather Barrier", id = 574, level = 56, cost = 2, element = 2, effect = { ["Evasion Boost"] = true, }, }, +[606] = { name = "Awful Eye", id = 606, level = 46, cost = 2, element = 5, effect = { ["Str Down"] = true, }, }, +[638] = { name = "Feather Storm", id = 638, level = 12, cost = 3, element = 15, effect = { ["Poison"] = true, }, SCA = "Transfixion"}, +[670] = { name = "Benthic Typhoon", id = 670, level = 83, cost = 4, element = 15, effect = { ["Defense Down"] = true, }, SCA = "Gravitation", SCB = "Transfixion"}, +[702] = { name = "Rending Deluge", id = 702, level = 99, cost = 6, element = 5, effect = { ["Dispel"] = true, }, Nuke = true }, +[543] = { name = "Mandibular Bite", id = 543, level = 44, cost = 2, element = 15, effect = {}, SCA = "Induration"}, +[575] = { name = "Jettatura", id = 575, level = 48, cost = 4, element = 7, effect = { ["Terror"] = true, }, }, +[671] = { name = "Auroral Drape", id = 671, level = 84, cost = 4, element = 2, effect = { ["Silence"] = true, ["Blind"] = true, }, }, +[703] = { name = "Embalming Earth", id = 703, level = 99, cost = 6, element = 3, effect = { ["Slow"] = true, }, Nuke = true }, +[544] = { name = "Cursed Sphere", id = 544, level = 18, cost = 2, element = 5, effect = { ["Poison"] = true, }, Voidwatch = true, }, +[576] = { name = "Yawn", id = 576, level = 64, cost = 3, element = 6, effect = { ["Sleep"] = true, }, }, +[608] = { name = "Frost Breath", id = 608, level = 66, cost = 3, element = 1, effect = { ["Paralyze"] = true, }, Voidwatch = true, Nuke = true }, +[640] = { name = "Tail Slap", id = 640, level = 69, cost = 4, element = 15, effect = { ["Stun"] = true, }, SCA = "Reverberation"}, +[672] = { name = "Osmosis", id = 672, level = 84, cost = 5, element = 7, effect = { ["Drain"] = true, }, Nuke = true }, +[704] = { name = "Paralyzing Triad", id = 704, level = 99, cost = 6, element = 15, effect = { ["Paralyze"] = true, }, SCA = "Gravitation"}, +[513] = { name = "Venom Shell", id = 513, level = 42, cost = 3, element = 5, effect = { ["Poison"] = true, }, }, +[545] = { name = "Sickle Slash", id = 545, level = 48, cost = 4, element = 15, effect = {}, SCA = "Compression"}, +[577] = { name = "Foot Kick", id = 577, level = 1, cost = 2, element = 15, effect = {}, SCA = "Detonation"}, +[641] = { name = "Hysteric Barrage", id = 641, level = 69, cost = 5, element = 15, effect = {}, SCA = "Detonation"}, +[673] = { name = "Quad. Continuum", id = 673, level = 85, cost = 4, element = 15, effect = {}, SCA = "Gravitation"}, +[705] = { name = "Foul Waters", id = 705, level = 99, cost = 3, element = 5, effect = { ["Drown"] = true, }, Nuke = true }, +[578] = { name = "Wild Carrot", id = 578, level = 30, cost = 3, element = 6, effect = { ["Cure"] = true, }, }, +[610] = { name = "Infrasonics", id = 610, level = 65, cost = 4, element = 1, effect = { ["Evasion Down"] = true, }, Voidwatch = true, }, +[642] = { name = "Amplification", id = 642, level = 70, cost = 3, element = 5, effect = { ["Magic Attack Boost"] = true, }, }, +[674] = { name = "Fantod", id = 674, level = 85, cost = 1, element = 0, effect = { ["Attack Boost"] = true, }, }, +[706] = { name = "Glutinous Dart", id = 706, level = 99, cost = 2, element = 15, effect = {}, SCA = "Fragmentation"}, +[515] = { name = "Maelstrom", id = 515, level = 61, cost = 5, element = 5, effect = { ["Str Down"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[547] = { name = "Cocoon", id = 547, level = 8, cost = 1, element = 3, effect = { ["Defense Boost"] = true, }, }, +[579] = { name = "Voracious Trunk", id = 579, level = 64, cost = 4, element = 2, effect = { ["Dispel"] = true, }, }, +[611] = { name = "Disseverment", id = 611, level = 72, cost = 5, element = 15, effect = { ["Poison"] = true, }, SCA = "Distortion"}, +[643] = { name = "Cannonball", id = 643, level = 70, cost = 3, element = 15, effect = {}, SCA = "Fusion"}, +[675] = { name = "Thermal Pulse", id = 675, level = 86, cost = 3, element = 0, effect = { ["Blind"] = true, }, Voidwatch = true, Nuke = true }, +[707] = { name = "Retinal Glare", id = 707, level = 99, cost = 5, element = 6, effect = { ["Flash"] = true, }, Nuke = true }, +[548] = { name = "Filamented Hold", id = 548, level = 52, cost = 3, element = 3, effect = { ["Slow"] = true, }, }, +[612] = { name = "Actinic Burst", id = 612, level = 74, cost = 4, element = 6, effect = { ["Flash"] = true, }, Voidwatch = true, }, +[644] = { name = "Mind Blast", id = 644, level = 73, cost = 4, element = 4, effect = { ["Paralyze"] = true, }, Voidwatch = true, Abyssea = true, Nuke = true }, +[708] = { name = "Subduction", id = 708, level = 99, cost = 6, element = 2, effect = { ["Gravity"] = true, }, Nuke = true }, +[517] = { name = "Metallic Body", id = 517, level = 8, cost = 1, element = 3, effect = { ["Stoneskin"] = true, }, }, +[549] = { name = "Pollen", id = 549, level = 1, cost = 1, element = 6, effect = { ["Cure"] = true, }, }, +[581] = { name = "Healing Breeze", id = 581, level = 16, cost = 4, element = 2, effect = { ["Cure"] = true, }, }, +[613] = { name = "Reactor Cool", id = 613, level = 74, cost = 5, element = 1, effect = { ["Defense Boost"] = true, ["Spikes"] = true, }, }, +[645] = { name = "Exuviation", id = 645, level = 75, cost = 4, element = 0, effect = { ["Erase"] = true, }, }, +[677] = { name = "Empty Thrash", id = 677, level = 87, cost = 3, element = 15, effect = {}, SCA = "Compression", SCB = "Scission"}, +[709] = { name = "Thrashing Assault", id = 709, level = 99, cost = 7, element = 15, effect = {}, SCA = "Fusion"}, +[582] = { name = "Chaotic Eye", id = 582, level = 32, cost = 2, element = 2, effect = { ["Silence"] = true, }, }, +[614] = { name = "Saline Coat", id = 614, level = 72, cost = 3, element = 6, effect = { ["Magic Defense Boost"] = true, }, }, +[646] = { name = "Magic Hammer", id = 646, level = 74, cost = 4, element = 6, effect = { ["Aspir"] = true, }, Nuke = true }, +[678] = { name = "Dream Flower", id = 678, level = 87, cost = 3, element = 7, effect = { ["Sleep"] = true, }, }, +[710] = { name = "Erratic Flutter", id = 710, level = 99, cost = 6, element = 2, effect = { ["Haste"] = true, }, }, +[519] = { name = "Screwdriver", id = 519, level = 26, cost = 3, element = 15, effect = {}, SCA = "Transfixion", SCB = "Scission"}, +[551] = { name = "Power Attack", id = 551, level = 4, cost = 1, element = 15, effect = {}, SCA = "Reverberation"}, +[615] = { name = "Plasma Charge", id = 615, level = 75, cost = 5, element = 4, effect = { ["Spikes"] = true, }, }, +[647] = { name = "Zephyr Mantle", id = 647, level = 65, cost = 2, element = 2, effect = { ["Blink"] = true, }, }, +[679] = { name = "Occultation", id = 679, level = 88, cost = 3, element = 2, effect = { ["Blink"] = true, }, }, +[711] = { name = "Restoral", id = 711, level = 99, cost = 7, element = 6, effect = { ["Cure"] = true, }, }, +} + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/res/traits.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/res/traits.lua new file mode 100644 index 0000000..b4d8175 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/res/traits.lua @@ -0,0 +1,426 @@ +return { + ['Accuracy Bonus'] = { + name = "Accuracy Bonus", + spells = { + { name = "Dimensional Death", points = 4, cost = 5, id = 589 }, + { name = "Frenetic Rip", points = 4, cost = 3, id = 560 }, + { name = "Disseverment", points = 4, cost = 5, id = 611 }, + { name = "Vanity Dive", points = 4, cost = 2, id = 667 }, + { name = "Nat. Meditation", points = 8, cost = 6, id = 700 }, + { name = "Anvil Lightning", points = 8, cost = 8, id = 721 }, + }, + --tiers = { 8, 16, 24, 32 }, + tiers = { [8] = 10, [16] = 22, [24] = 35, [32] = 48, [40] = 60, [48] = 72 }, + subs = { ['DNC'] = 8, ['DRG'] = 8, ['RNG'] = 16 }, + }, + ['Attack Bonus'] = { + name = "Attack Bonus", + spells = { + { name = "Battle Dance", points = 4, cost = 3, id = 620 }, + { name = "Uppercut", points = 4, cost = 3, id = 594 }, + { name = "Death Scissors", points = 4, cost = 5, id = 554 }, + { name = "Spinal Cleave", points = 4, cost = 4, id = 540 }, + { name = "Temporal Shift", points = 4, cost = 5, id = 616 }, + { name = "Thermal Pulse", points = 4, cost = 3, id = 675 }, + { name = "Embalming Earth", points = 8, cost = 6, id = 703 }, + { name = "Searing Tempest", points = 8, cost = 8, id = 719 }, + }, + tiers = { [8] = 10, [16] = 22, [24] = 35, [32] = 48, [40] = 60, [48] = 72 }, + subs = { ['WAR'] = 8, ['DRG'] = 8, ['DRK'] = 16 }, + }, + ['Auto Refresh'] = { + name = "Auto Refresh", + spells = { + { name = "Stinking Gas", points = 1, cost = 2, id = 537 }, + { name = "Frightful Roar", points = 2, cost = 3, id = 561 }, + { name = "Self-Destruct", points = 2, cost = 3, id = 533 }, + { name = "Cold Wave", points = 1, cost = 1, id = 535 }, + { name = "Light of Penance", points = 2, cost = 5, id = 634 }, + { name = "Voracious Trunk", points = 3, cost = 4, id = 579 }, + { name = "Actinic Burst", points = 4, cost = 4, id = 612 }, + { name = "Plasma Charge", points = 4, cost = 5, id = 615 }, + { name = "Winds of Promy.", points = 4, cost = 5, id = 681 }, + }, + tiers = { [8] = 1 }, + subs = { ['PLD'] = 8, ['SMN'] = 8 }, + }, + ['Auto Regen'] = { + name = "Auto Regen", + spells = { + { name = "Sheep Song", points = 4, cost = 2, id = 584 }, + { name = "Healing Breeze", points = 4, cost = 4, id = 581 }, + { name = "White Wind", points = 4, cost = 5, id = 690 }, + }, + tiers = { [8] = 1, [16] = 2, [24] = 3 }, + tiers = { [8] = 1, [16] = 2, [24] = 3 }, + subs = { ['WHM'] = 8, ['RUN'] = 8 }, + }, + ['Beast Killer'] = { + name = "Beast Killer", + spells = { + { name = "Wild Oats", points = 4, cost = 3, id = 603 }, + { name = "Sprout Smack", points = 4, cost = 2, id = 597 }, + { name = "Seedspray", points = 4, cost = 4, id = 650 }, + { name = "1000 Needles", points = 4, cost = 5, id = 595 }, + { name = "Nectarous Deluge", points = 8, cost = 6, id = 716 }, + }, + tiers = { [8] = "I" }, + subs = { }, + }, + ['Clear Mind'] = { + name = "Clear Mind", + spells = { + { name = "Poison Breath", points = 4, cost = 1, id = 536 }, + { name = "Sopoforic", points = 4, cost = 4, id = 598 }, + { name = "Venom Shell", points = 4, cost = 3, id = 513 }, + { name = "Awful Eye", points = 4, cost = 2, id = 606 }, + { name = "Filamented Hold", points = 4, cost = 3, id = 548 }, + { name = "Maelstrom", points = 4, cost = 5, id = 515 }, + { name = "Feather Tickle", points = 4, cost = 3, id = 573 }, + { name = "Corrosive Ooze", points = 4, cost = 4, id = 651 }, + { name = "Sandspray", points = 4, cost = 2, id = 621 }, + { name = "Warm-Up", points = 4, cost = 4, id = 636 }, + { name = "Lowing", points = 4, cost = 2, id = 588 }, + { name = "Mind Blast", points = 4, cost = 4, id = 644 }, + }, + tiers = { [8] = 3, [16] = 6, [24] = 9, [32] = 12, [40] = 15 }, + subs = { ['SMN'] = 24, ['BLM'] = 24, ['SCH'] = 16, ['GEO'] = 16, ['RDM'] = 8, }, + }, + ['Conserve MP'] = { + name = "Conserve MP", + spells = { + { name = "Chaotic Eye", points = 4, cost = 2, id = 582 }, + { name = "Zephyr Mantle", points = 4, cost = 2, id = 647 }, + { name = "Frost Breath", points = 4, cost = 3, id = 608 }, + { name = "Firespit", points = 4, cost = 5, id = 637 }, + { name = "Water Bomb", points = 4, cost = 2, id = 687 }, + { name = "Retinal Glare", points = 8, cost = 6, id = 707 }, + }, + tiers = { [8] = 25, [16] = 28, [24] = 31, [32] = 34, [40] = 37 }, + subs = { ['SCH'] = 8, ['BLM'] = 16, ['GEO'] = 24, }, + }, + ['Counter'] = { + name = "Counter", + spells = { + { name = "Enervation", points = 4, cost = 5, id = 633 }, + { name = "Asuran Claws", points = 4, cost = 2, id = 653 }, + { name = "Dark Orb", points = 4, cost = 3, id = 689 }, + { name = "O. Counterstance", points = 4, cost = 5, id = 696 }, + }, + tiers = { [8] = 10, [16] = 12 }, + subs = { ['MNK'] = 8 }, + }, + ['Defense Bonus'] = { + name = "Defense Bonus", + spells = { + { name = "Grand Slam", points = 4, cost = 2, id = 622 }, + { name = "Terror Touch", points = 4, cost = 3, id = 539 }, + { name = "Saline Coat", points = 4, cost = 3, id = 614 }, + { name = "Vertical Cleave", points = 4, cost = 3, id = 617 }, + { name = "Atra. Libations", points = 8, cost = 6, id = 718 }, + { name = "Entomb", points = 8, cost = 8, id = 722 }, + }, + tiers = { [8] = 10, [16] = 22, [24] = 35, [32] = 48, [40] = 60, [48] = 72 }, + subs = { ['WAR'] = 8, ['PLD'] = 16 }, + }, + ['Double/Triple Attack'] = { + name = "Double/Triple Attack", + spells = { + { name = "Acrid Stream", points = 4, cost = 3, id = 656 }, + { name = "Demoralizing Roar", points = 4, cost = 4, id = 659 }, + { name = "Empty Thrash", points = 4, cost = 3, id = 677 }, + { name = "Heavy Strike", points = 4, cost = 2, id = 688 }, + { name = "Thrashing Assault", points = 8, cost = 7, id = 709 }, + }, + tiers = { [8] = "DA", [16] = "TA" }, + subs = { ['WAR'] = 8, ['THF'] = 16 }, + }, + ['Dual Wield'] = { + name = "Dual Wield", + spells = { + { name = "Animating Wail", points = 4, cost = 5, id = 661 }, + { name = "Blazing Bound", points = 4, cost = 3, id = 657 }, + { name = "Quad. Continuum", points = 4, cost = 4, id = 673 }, + { name = "Delta Thrust", points = 4, cost = 2, id = 682 }, + { name = "Mortal Ray", points = 4, cost = 4, id = 686 }, + { name = "Barbed Crescent", points = 4, cost = 2, id = 699 }, + { name = "Molting Plumage", points = 8, cost = 6, id = 715 }, + }, + tiers = { [8] = 10, [16] = 15, [24] = 25, [32] = 30, [40] = 35 }, + subs = { ['NIN'] = 24, ['DNC'] = 16 }, + }, + ['Evasion Bonus'] = { + name = "Evasion Bonus", + spells = { + { name = "Screwdriver", points = 4, cost = 3, id = 519 }, + { name = "Hysteric Barrage", points = 4, cost = 5, id = 641 }, + { name = "Occultation", points = 4, cost = 3, id = 679 }, + { name = "Tem. Upheaval", points = 8, cost = 6, id = 701 }, + { name = "Silent Storm", points = 8, cost = 8, id = 727 }, + }, + tiers = { [8] = 10, [16] = 22, [24] = 35, [32] = 48, [40] = 60 }, + subs = { ['THF'] = 16, ['DNC'] = 16, ['PUP'] = 8 }, + }, + ['Fast Cast'] = { + name = "Fast Cast", + spells = { + { name = "Bad Breath", points = 4, cost = 5, id = 604 }, + { name = "Sub-Zero Smash", points = 4, cost = 4, id = 654 }, + { name = "Auroral Drape", points = 4, cost = 4, id = 671 }, + { name = "Wind Breath", points = 4, cost = 2, id = 698 }, + { name = "Erratic Flutter", points = 8, cost = 6, id = 710 }, + }, + tiers = { [8] = 5, [16] = 10, [24] = 15, [32] = 20, [40] = 25 }, + subs = { ['RDM'] = 24 }, + }, + ['Gilfinder/TH'] = { + name = "Gilfinder/TH", + spells = { + { name = "Charged Whisker", points = 6, cost = 5, id = 680 }, + { name = "Evryone. Grudge", points = 6, cost = 4, id = 683 }, + { name = "Amorphic Spikes", points = 6, cost = 4, id = 697 }, + }, + tiers = { [8] = "GF", [16] = "TH" }, + subs = { ['THF'] = 16 }, + }, + ['Lizard Killer'] = { + name = "Lizard Killer", + spells = { + { name = "Foot Kick", points = 4, cost = 2, id = 577 }, + { name = "Claw Cyclone", points = 4, cost = 2, id = 587 }, + { name = "Ram Charge", points = 4, cost = 4, id = 585 }, + { name = "Sweeping Gouge", points = 8, cost = 6, id = 717 }, + }, + tiers = { [8] = "I" }, + subs = { ['BST'] = 8 }, + }, + ['Magic Attack Bonus'] = { + name = "Magic Attack Bonus", + spells = { + { name = "Cursed Sphere", points = 4, cost = 2, id = 544 }, + { name = "Sound Blast", points = 4, cost = 1, id = 572 }, + { name = "Eyes On Me", points = 4, cost = 4, id = 557 }, + { name = "Memento Mori", points = 4, cost = 4, id = 538 }, + { name = "Heat Breath", points = 4, cost = 4, id = 591 }, + { name = "Reactor Cool", points = 4, cost = 5, id = 613 }, + { name = "Magic Hammer", points = 4, cost = 4, id = 646 }, + { name = "Dream Flower", points = 4, cost = 3, id = 678 }, + { name = "Subduction", points = 8, cost = 6, id = 708 }, + { name = "Spectral Floe", points = 8, cost = 8, id = 720 }, + }, + tiers = { [8] = 20, [16] = 24, [24] = 28, [32] = 32, [40] = 36, [48] = 40 }, + subs = { ['BLM'] = 16, ['RDM'] = 16 }, + }, + ['Magic Burst Bonus'] = { + name = "Magic Burst Bonus", + spells = { + { name = "Leafstorm", points = 6, cost = 4, id = 663 }, + { name = "Cimicine Discharge", points = 6, cost = 3, id = 660 }, + { name = "Reaving Wind", points = 6, cost = 4, id = 684 }, + { name = "Rail Cannon", points = 8, cost = 6, id = 712 }, + }, + tiers = { [8] = 5, [16] = 7, [24] = 9, [32] = 11, [40] = 13 }, + subs = { ['BLM'] = 8 }, + }, + ['Magic Defense Bonus'] = { + name = "Magic Defense Bonus", + spells = { + { name = "Magnetite Cloud", points = 4, cost = 3, id = 555 }, + { name = "Ice Break", points = 4, cost = 3, id = 531 }, + { name = "Osmosis", points = 4, cost = 5, id = 672 }, + { name = "Rending Deluge", points = 8, cost = 6, id = 702 }, + { name = "Scouring Spate", points = 8, cost = 8, id = 726 }, + }, + tiers = { [8] = 10, [16] = 12, [24] = 14, [32] = 16, [40] = 18 }, + subs = { ['RUN'] = 16, ['WHM'] = 16, ['RDM'] = 16 }, + }, + ['Max HP Boost'] = { + name = "Max HP Boost", + spells = { + { name = "Flying Hip Press", points = 4, cost = 3, id = 629 }, + { name = "Body Slam", points = 4, cost = 4, id = 564 }, + { name = "Frypan", points = 4, cost = 3, id = 628 }, + { name = "Barrier Tusk", points = 4, cost = 3, id = 685 }, + { name = "Thunder Breath", points = 4, cost = 4, id = 695 }, + { name = "Glutinous Dart", points = 4, cost = 2, id = 706 }, + { name = "Restoral", points = 8, cost = 7, id = 711 }, + }, + tiers = { [8] = 30, [16] = 60, [24] = 120, [32] = 180, [40] = 240, [48] = 280 }, + subs = { ['RUN'] = 16, ['MNK'] = 16, ['NIN'] = 16, ['WAR'] = 8, ['PLD'] = 8 }, + }, + ['Max MP Boost'] = { + name = "Max MP Boost", + spells = { + { name = "Metallic Body", points = 4, cost = 1, id = 517 }, + { name = "Mysterious Light", points = 4, cost = 4, id = 534 }, + { name = "Hecatomb Wave", points = 4, cost = 3, id = 563 }, + { name = "Magic Barrier", points = 4, cost = 3, id = 668 }, + { name = "Vapor Spray", points = 4, cost = 3, id = 694 }, + }, + tiers = { [8] = 10, [16] = 20, [24] = 40, [32] = 60 }, + subs = { ['SMN'] = 16, ['GEO'] = 8, ['SCH'] = 8 }, + }, + ['Plantoid Killer'] = { + name = "Plantoid Killer", + spells = { + { name = "Power Attack", points = 4, cost = 1, id = 551 }, + { name = "Mandibular Bite", points = 4, cost = 2, id = 543 }, + { name = "Spiral Spin", points = 4, cost = 3, id = 652 }, + }, + tiers = { [8] = "I" }, + subs = { }, + }, + ['Rapid Shot'] = { + name = "Rapid Shot", + spells = { + { name = "Feather Storm", points = 4, cost = 3, id = 638 }, + { name = "Jet Stream", points = 4, cost = 4, id = 569 }, + { name = "Hydro Shot", points = 4, cost = 3, id = 631 }, + }, + tiers = { [8] = "I" }, + subs = { ['RNG'] = 8, ['COR'] = 8 }, + }, + ['Resist Silence'] = { + name = "Resist Silence", + spells = { + { name = "Foul Waters", points = 8, cost = 3, id = 705 }, + }, + tiers = { [8] = "I" }, + subs = { ['BRD'] = 8, ['SCH'] = 8 }, + }, + ['Resist Gravity'] = { + name = "Resist Gravity", + spells = { + { name = "Feather Barrier", points = 4, cost = 2, id = 574 }, + { name = "Regurgitation", points = 4, cost = 1, id = 648 }, + }, + tiers = { [8] = "I", [16] = "II", [24] = "III" }, + subs = { ['THF'] = 16 }, + }, + ['Resist Sleep'] = { + name = "Resist Sleep", + spells = { + { name = "Pollen", points = 4, cost = 1, id = 549 }, + { name = "Wild Carrot", points = 4, cost = 3, id = 578 }, + { name = "Magic Fruit", points = 4, cost = 3, id = 593 }, + { name = "Yawn", points = 4, cost = 3, id = 576 }, + { name = "Exuviation", points = 4, cost = 4, id = 645 }, + }, + tiers = { [8] = "I", [16] = "II", [24] = "III", [32] = "IV" }, + subs = { ['PLD'] = 16 }, + }, + ['Skillchain Bonus'] = { + name = "Skillchain Bonus", + spells = { + { name = "Goblin Rush", points = 6, cost = 3, id = 666 }, + { name = "Benthic Typhoon", points = 6, cost = 4, id = 670 }, + { name = "Quadrastrike", points = 6, cost = 5, id = 693 }, + { name = "Paralyzing Triad", points = 8, cost = 6, id = 704 }, + }, + tiers = { [8] = 8, [16] = 12, [24] = 16, [32] = 20, [40] = 23 }, + subs = { ['DNC'] = 8 }, + }, + ['Store TP'] = { + name = "Store TP", + spells = { + { name = "Sickle Slash", points = 4, cost = 4, id = 545 }, + { name = "Tail Slap", points = 4, cost = 4, id = 640 }, + { name = "Fantod", points = 4, cost = 1, id = 674 }, + { name = "Sudden Lunge", points = 4, cost = 4, id = 692 }, + { name = "Diffusion Ray", points = 8, cost = 6, id = 713 }, + }, + tiers = { [8] = 10, [16] = 15, [24] = 20, [32] = 25, [40] = 30 }, + subs = { ['SAM'] = 16 }, + }, + ['Undead Killer'] = { + name = "Undead Killer", + spells = { + { name = "Bludgeon", points = 4, cost = 2, id = 529 }, + { name = "Smite of Rage", points = 4, cost = 3, id = 527 }, + }, + tiers = { [8] = "I" }, + subs = { ['PLD'] = 8 }, + }, + ['Zanshin'] = { + name = "Zanshin", + spells = { + { name = "Final Sting", points = 4, cost = 1, id = 665 }, + { name = "Whirl of Rage", points = 4, cost = 2, id = 669 }, + }, + tiers = { [8] = 15, [16] = 25, [24] = 35, }, + subs = { ['SAM'] = 16 }, + }, + ['Critical Attack Bonus'] = { + name = "Critical Attack Bonus", + spells = { + { name = "Sinker Drill", points = 8, cost = 6, id = 714 }, + }, + tiers = { [8] = 5, [16] = 8, [24] = 11 }, + subs = { }, + }, + ['Inquartata'] = { + name = "Inquartata", + spells = { + { name = "Saurian Slide", points = 8, cost = 7, id = 723 }, + }, + tiers = { [8] = 5, [16] = 7, [24] = 9 }, + subs = { ['RUN'] = 24 }, + }, + ['Tenacity'] = { + name = "Tenacity", + spells = { + { name = "Palling Salvo", points = 8, cost = 7, id = 724 }, + }, + tiers = { [8] = 5, [16] = 7, [24] = 9 }, + subs = { ['RUN'] = 24 }, + }, + ['Magic Accuracy Bonus'] = { + name = "Magic Accuracy Bonus", + spells = { + { name = "Tenebral Crush", points = 8, cost = 8, id = 728 }, + }, + tiers = { [8] = "I", [16] = "II", [24] = "III" }, + subs = { }, + }, + ['Magic Evasion Bonus'] = { + name = "Magic Evasion Bonus", + spells = { + { name = "Blinding Fulgor", points = 8, cost = 8, id = 725 }, + }, + tiers = { [8] = "I", [16] = "II", [24] = "III" }, + subs = { }, + }, + ['Resist Slow'] = { + name = "Resist Slow", + spells = { + { name = "Refueling", points = 4, cost = 4, id = 530 }, + }, + tiers = { [8] = "I" }, + subs = { ['PUP'] = 16, ['BST'] = 16, ['SMN'] = 16, ['DNC'] = 8, }, + }, +} +--Copyright © 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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. diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/buttons.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/buttons.lua new file mode 100644 index 0000000..98c52ef --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/buttons.lua @@ -0,0 +1,119 @@ +local texts = require('texts') + +local buttons = {} +local buttonlist = {} + +function buttons.new(label, settings) + label = label or "" + settings = settings or {} + + settings.flags = settings.flags or {} + settings.flags.draggable = false + + local button = {} + + button.left_click = settings.left_click + button.hover_on = settings.hover_on + button.hover_off = settings.hover_off + + button.text = texts.new(label, settings) + button.destroy = function() buttons.destroy(button) end + + setmetatable(button, {__index = function(t, k) + if t.text[k] ~= nil then + return function(...) + return t.text[k](t.text, ...) + end + end + end }) + + buttonlist[#buttonlist +1] = button + return button +end + +function buttons.destroy(me) + for k, v in pairs(buttonlist) do + if v == me then + buttonlist[k] = nil + end + end + me.text.destroy(me.text) +end + +local mousemoved = true +local ignorerelease = false + +windower.register_event('mouse', function(eventtype, x, y, delta, blocked) + if blocked then + return + end + + -- Mouse drag + if eventtype == 0 then + mousemoved = true + + for _, button in pairs(buttonlist) do + if type(button.hover_on) == "function" and type(button.hover_off) == "function" then + if button.text:hover(x, y) then + button:hover_on() + else + button:hover_off() + end + end + end + + -- Mouse left click + elseif eventtype == 1 then + mousemoved = false + for _, button in pairs(buttonlist) do + if button.text:hover(x, y) then + ignorerelease = true + return true + end + end + ignorerelease = false + + -- Mouse left release + elseif eventtype == 2 then + for _, button in pairs(buttonlist) do + if button.text:hover(x, y) and button.text:visible() and not mousemoved and type(button.left_click) == "function" then + button:left_click() + return true + end + end + + if ignorerelease then + return true + end + end + + return false +end) + +return buttons + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/pages.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/pages.lua new file mode 100644 index 0000000..8e170b4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/pages.lua @@ -0,0 +1,67 @@ +local pgs = {} + +function pgs.new(x, y) + return setmetatable({ x = x, y = y, boxlist = {} }, {__index = pgs}) +end + +function pgs.add(me, newbox) + if newbox then + me.boxlist[#me.boxlist+1] = newbox + end + me:update() +end + +function pgs.update(me) + for i = 1, #me.boxlist do + me.boxlist[i]:update() + if i > 1 then + me.boxlist[i]:pos(me.boxlist[i - 1]:left(), me.boxlist[i - 1]:bottom() + lineheight) + local info = windower.get_windower_settings() + if me.boxlist[i]:bottom() > info.ui_y_res - 150 then + me.boxlist[i]:pos(me.boxlist[i]:left() + 240, me.y) + end + else + me.boxlist[i]:pos(me.x, me.y) + end + end +end + +function pgs.show(me) + for _, v in pairs(me.boxlist) do + v:show() + end +end + +function pgs.hide(me) + for _, v in pairs(me.boxlist) do + v:hide() + end +end + +return pgs + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellboxes.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellboxes.lua new file mode 100644 index 0000000..4f8b910 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellboxes.lua @@ -0,0 +1,157 @@ +local sbx = {} +buttons = require("ui/buttons") +texts = require("texts") +spellbuttons = require("ui/spellbuttons") + + +local default_settings = { + text = { size = 10, font = 'Lucida Console' }, + bg = { alpha = 120, red = 0, green = 0, blue = 0, visible = true }, + flags = {draggable = false} +} + +local header_settings = { + text = { size = 10, font = 'Lucida Console',}, + bg = { alpha = 200, red = 0, green = 100, blue = 100, visible = false }, +} + +function sbx.new(name, filter, namefn) + local me = { name = name, x = 0, y = 0, splist = {}, collapsed = false} + + me.bg = texts.new(" ", default_settings) + + me.header = buttons.new(string.format('- %-24s', me.name), header_settings) + me.header.bold(false) + me.header.color(255, 255, 255) + me.header.bg_color(0, 100, 100) + me.header.left_click = function() collapse(me) end + me.header.hover_on = function() show_bg(me) end + me.header.hover_off = function() hide_bg(me) end + + local linenum = 1 + local totalpoints = 0 + for k, v in pairs(spellinfo) do + if filter(v) and v.level <= setspells.limits.level then + local buttonname = nil + if namefn ~= nil then + buttonname = namefn(v) + end + buttonname = buttonname or v.name + me.splist[#me.splist+1] = spellbuttons.new(string.format(' %-22s %i', buttonname, v.cost), v.id, v.cost, me.x, me.y + (linenum * lineheight)) + me.splist[#me.splist]:update() + linenum = linenum + 1 + end + end + + if #me.splist > 0 then return setmetatable(me, {__index = sbx}) end +end + +function sbx.update(me) + local vspace = " " + for _, v in pairs(me.splist) do + v:update() + if not me.collapsed then + vspace = vspace.."\n " + end + end + + me.bg:text(vspace) +end + +function sbx.bottom(me) + if not me.collapsed then + return me.y + ((1 + #me.splist) * lineheight) + else + return me.y + lineheight + end +end + +function sbx.left(me) + return me.x +end + +function sbx.show(me) + if not me.collapsed then + for _, v in pairs(me.splist) do + v:show() + end + end + me.bg:show() + me.header.show() +end + +function sbx.pos(me, x, y) + me.x = x + me.y = y + me.bg:pos(x, y) + me.header.pos(x, y) + local by = 0 + for i = 1, #me.splist do + by = by + lineheight + me.splist[i]:pos(x, y+by) + end +end + +function sbx.hide(me) + for _, v in pairs(me.splist) do + v:hide() + end + me.header.hide() + me.bg:hide() +end + +function show_bg(me) + me.header.bg_visible(true) +end + +function hide_bg(me) + me.header.bg_visible(false) +end + +function collapse(me) + me.collapsed = not me.collapsed + if me.collapsed then + me.header.text:text(string.format('+ %-24s', me.name)) + me.header.bold(true) + for _, v in pairs(me.splist) do + v:hide() + end + me:update() + else + me.header.text:text(string.format('- %-24s', me.name)) + me.header.bold(false) + for _, v in pairs(me.splist) do + v:show() + end + end + update() +end + +return sbx + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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. (c) 2015, Anissa +--All rights reserved.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellbuttons.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellbuttons.lua new file mode 100644 index 0000000..c853921 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/spellbuttons.lua @@ -0,0 +1,102 @@ +local spbt = {} +buttons = require("ui/buttons") + +local default_settings = { + text = { size = 10, font = 'Lucida Console',}, + bg = { alpha = 200, red = 0, green = 100, blue = 100, visible = false }, +} + +function spbt.new(label, spell, cost, x, y) + local me = { spell = spell, cost = cost} + me.settings = default_settings + me.settings.pos = { x = x, y = y } + me.set = {red = 100, green = 255, blue = 100 } + me.disabled = true + me.button = buttons.new(label, me.settings) + me.button.left_click = function() toggle_spell(me) end + me.button.hover_on = function() hover_on(me) end + me.button.hover_off = function() hover_off(me) end + return setmetatable(me, {__index = spbt}) +end + +function spbt.update(me) + if setspells[me.spell] ~= nil then + me.button.color(me.set.red, me.set.green, me.set.blue) + me.disabled = false + elseif not setspells.learned[me.spell] then + me.button.color(150, 150, 150) + me.disabled = true + elseif setspells.limits.points - setspells.points < me.cost or setspells.slots == setspells.limits.slots then + me.button.color(255, 100, 100) + me.disabled = true + else + me.button.color(255, 255, 255) + me.disabled = false + end +end + +function spbt.show(me) + me.button.show() +end + +function spbt.hide(me) + me.button.hide() +end + +function spbt.destroy(me) + me.button.destroy(me.button) + me = nil +end + +function spbt.pos(me, x, y) + me.button.pos(x, y) +end + +function hover_on(me) + me.button.bg_visible(true) +end + +function hover_off(me) + me.button.bg_visible(false) +end + +function spbt.color(me,r,g,b) + me.button.color(r,g,b) + me.set.red = r + me.set.green = g + me.set.blue = b +end + +function toggle_spell(me) + if not me.disabled then + setspells.toggle(me.spell) + end +end + +return spbt + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/traitboxes.lua b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/traitboxes.lua new file mode 100644 index 0000000..85edb30 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/bluguide/ui/traitboxes.lua @@ -0,0 +1,204 @@ +local tbx = {} +buttons = require("ui/buttons") +texts = require("texts") +spellbuttons = require("ui/spellbuttons") + +local default_settings = { + text = { size = 10, font = 'Lucida Console' }, + bg = { alpha = 120, red = 0, green = 0, blue = 0, visible = false }, + flags = {draggable = false} +} + +local header_settings = { + text = { size = 10, font = 'Lucida Console',}, + bg = { alpha = 200, red = 0, green = 100, blue = 100, visible = false }, +} + +function tbx.new(trait) + local me = { trait = trait, x = 0, y = 0, splist = {}, collapsed = false} + + me.greybox = texts.new("", default_settings) + me.greenbox = texts.new("", default_settings) + me.greybox:color(190, 190, 190) + me.greenbox:color(50, 255, 50) + me.greenbox:bg_visible(true) + me.greybox:bg_visible(false) + + me.header = buttons.new(string.format('- %-24s', trait.name), header_settings) + me.header.bold(false) + me.header.color(255, 255, 255) + me.header.bg_color(0, 100, 100) + me.header.left_click = function() collapsetrait(me) end + me.header.hover_on = function() show_bg(me) end + me.header.hover_off = function() hide_bg(me) end + + local linenum = 1 + local totalpoints = 0 + for k, v in pairs(trait.spells) do + if spellinfo[v.id].level <= setspells.limits.level then + me.splist[#me.splist+1] = spellbuttons.new(string.format(' %-20s %i %i', v.name, v.cost, v.points), v.id, v.cost, me.x, me.y + (linenum * lineheight)) + me.splist[#me.splist]:update() + linenum = linenum + 1 + totalpoints = totalpoints + v.points + end + end + + if totalpoints >= 8 then return setmetatable(me, {__index = tbx}) end + + for _, v in pairs(me.splist) do + v:destroy() + end + return nil +end + +function tbx.update(me) + local vspace = " \n " + for _, v in pairs(me.splist) do + v:update() + if not me.collapsed then + vspace = vspace.."\n " + end + end + + local traitpoints = 0 + for k, v in pairs(me.trait.spells) do + if setspells[v.id] then + traitpoints = traitpoints + v.points + end + end + + if traitpoints > 7 and giftexempttraits[me.trait.name] == nil then + traitpoints = traitpoints + (8 * setspells.gifts) + end + + me.greybox:text(vspace) + me.greenbox:text(vspace) + + for v = 8, 48 do + if me.trait.tiers[v] ~= nil then + if me.trait.name == "Double/Triple Attack" then + if v == 8 then + if traitpoints == 8 or traitpoints == 12 or sub == 'WAR' then + me.greenbox:append(string.format('%s ', me.trait.tiers[v])) + me.greybox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + else + me.greybox:append(string.format('%s ', me.trait.tiers[v])) + me.greenbox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + end + elseif traitpoints >= 16 or sub == "THF" then + me.greenbox:append(string.format('%s ', me.trait.tiers[v])) + me.greybox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + else + me.greybox:append(string.format('%s ', me.trait.tiers[v])) + me.greenbox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + end + elseif v <= traitpoints or (me.trait.subs[sub] and me.trait.subs[sub] >= v) then + me.greenbox:append(string.format('%s ', me.trait.tiers[v])) + me.greybox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + else + me.greybox:append(string.format('%s ', me.trait.tiers[v])) + me.greenbox:append(string.gsub(me.trait.tiers[v], ".", " ").." ") + end + end + end +end + +function tbx.bottom(me) + if not me.collapsed then + return me.y + ((2 + #me.splist) * lineheight) + else + return me.y + (2 * lineheight) + end +end + +function tbx.left(me) + return me.x +end + +function tbx.show(me) + if not me.collapsed then + for _, v in pairs(me.splist) do + v:show() + end + end + me.greenbox:show() + me.greybox:show() + me.header.show() +end + +function tbx.pos(me, x, y) + me.x = x + me.y = y + me.greybox:pos(x, y) + me.greenbox:pos(x, y) + me.header.pos(x, y) + local by = 0 + for i = 1, #me.splist do + by = by + lineheight + me.splist[i]:pos(x, y+by) + end +end + +function tbx.hide(me) + for _, v in pairs(me.splist) do + v:hide() + end + me.greenbox:hide() + me.greybox:hide() + me.header.hide() +end + +function show_bg(me) + me.header.bg_visible(true) +end + +function hide_bg(me) + me.header.bg_visible(false) +end + +function collapsetrait(me) + me.collapsed = not me.collapsed + if me.collapsed then + me.header.text:text(string.format('+ %-24s', me.trait.name)) + me.header.bold(true) + for _, v in pairs(me.splist) do + v:hide() + end + me:update() + else + me.header.text:text(string.format('- %-24s', me.trait.name)) + me.header.bold(false) + for _, v in pairs(me.splist) do + v:show() + end + end + update() +end + +return tbx + +--Copyright 2015, Anissa +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of bluGuide nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL ANISSA 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.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/README b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/README new file mode 100644 index 0000000..a34f5b9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/README @@ -0,0 +1,12 @@ +This script is a treasure casket helper addon to be used with Windower +for Final Fantasy XI. + +You can report issues, get the latest version, and view the source at: + https://github.com/svanheulen/boxdestroyer-windower-addon + +Donations are welcome in the form of Alexandrite: + Acacia on Odin (http://www.ffxiah.com/player/Odin/Acacia) + +With this addon loaded, just click on any brown treasure casket and it +will display info about possible combinations, the best number to guess +and the worst case chance of guessing correctly. diff --git a/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/boxdestroyer.lua b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/boxdestroyer.lua new file mode 100644 index 0000000..af70616 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/boxdestroyer.lua @@ -0,0 +1,466 @@ +--[[ +Copyright (c) 2014, Seth VanHeulen +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. 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. +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR 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. +--]] + +-- addon information + +_addon.name = 'boxdestroyer' +_addon.version = '1.0.5' +_addon.command = 'boxdestroyer' +_addon.author = 'Seth VanHeulen (Acacia@Odin)' + +-- modules + +require('pack') +require('tables') +require('chat') + +-- load message constants + +require('messages') + +-- config + +config = require('config') +defaults = { + HighlightResult = false, + HighlightColor = 36, +} +settings = config.load(defaults) +-- global constants + +default = { + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 +} + +observed_default = { + ['second_even_odd'] = false, + ['first_even_odd'] = false, + ['range'] = false, + ['equal'] = false, + ['second_multiple'] = false, + ['first_multiple'] = false, + ['thief_tools_active'] = false +} + +thief_tools = {[1022] = true} + +-- global variables + +box = {} +observed = {} +zone_id = windower.ffxi.get_info().zone + +-- filter helper functions + +function greater_less(id, greater, num) + local new = {} + for _,v in pairs(box[id]) do + if greater and v > num or not greater and v < num then + table.insert(new, v) + end + end + return new +end + +function even_odd(id, div, rem) + local new = {} + for _,v in pairs(box[id]) do + if (math.floor(v / div) % 2) == rem then + table.insert(new, v) + end + end + return new +end + +function equal(id, first, num) + local new = {} + for _,v in pairs(box[id]) do + if first and math.floor(v / 10) == num or not first and (v % 10) == num then + table.insert(new, v) + end + end + return new +end + +function exclusive_mean(counts) + total = 0 + for _,v in pairs(counts) do + total = total + v + end + weighted_mean = 0 + for _,v in pairs(counts) do + weighted_mean = weighted_mean + (total - v) * v / total + end + return weighted_mean +end + +function calculate_odds(id,chances) + local reductions = {} + if not observed[id].first_even_odd then + local counter = {0} + counter[0] = 0 + for _,v in pairs(box[id]) do + counter[math.floor(v / 10) % 2] = counter[math.floor(v / 10) % 2] + 1 + end + reductions[#reductions+1] = exclusive_mean(counter) + end + if not observed[id].second_even_odd then + local counter = {0} + counter[0] = 0 + for _,v in pairs(box[id]) do + counter[v % 2] = counter[v % 2] + 1 + end + reductions[#reductions+1] = exclusive_mean(counter) + end + if not observed[id].range then + local new = {} + local reduction = 0 + for i,v in pairs(box[id]) do + new[i] = 0 + for _,m in pairs(box[id]) do + if m-v > 16 then break end + new[i] = new[i] + math.max(16-math.abs(m - v),0)^2/256 + end + reduction = reduction + (#box[id] - new[i])/#box[id] + end + + reductions[#reductions+1] = reduction + end + if not observed[id].equal then + local counter = {0,0,0,0,0,0,0,0,0} + counter[0] = 0 + local eliminated = {0,0,0,0,0,0,0,0,0} + eliminated[0] = 0 + for _,v in pairs(box[id]) do + counter[math.floor(v / 10)] = counter[math.floor(v / 10)] + 1/2 + counter[v % 10] = counter[v % 10] + 1/2 + for i = 0,9 do + if i ~= v % 10 and i ~= math.floor(v / 10) then + eliminated[i] = eliminated[i] + 1 + end + end + end + + reduction = 0 + for i,v in pairs(counter) do + reduction = reduction + eliminated[i] * v / #box[id] + end + + reductions[#reductions+1] = reduction + end + if not observed[id].second_multiple then + local counter = {0,0,0,0,0,0,0,0,0} + counter[0] = 0 + for _,v in pairs(box[id]) do + counter[v % 10] = counter[v % 10] + 1 + end + + local weights = { + counter[0] + counter[1]/2 + counter[2]/3, + counter[1]/2 + counter[2]/3 + counter[3]/3, + counter[2]/3 + counter[3]/3 + counter[4]/3, + counter[3]/3 + counter[4]/3 + counter[5]/3, + counter[4]/3 + counter[5]/3 + counter[6]/3, + counter[5]/3 + counter[6]/3 + counter[7]/3, + counter[6]/3 + counter[7]/3 + counter[8]/2, + counter[7]/3 + counter[8]/2 + counter[9] + } + + local eliminated = { + counter[3] + counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9], + counter[0] + counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9], + counter[0] + counter[1] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9], + counter[0] + counter[1] + counter[2] + counter[6] + counter[7] + counter[8] + counter[9], + counter[0] + counter[1] + counter[2] + counter[3] + counter[7] + counter[8] + counter[9], + counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[8] + counter[9], + counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[9], + counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[6] + } + + local reduction = 0 + for i,v in pairs(weights) do + reduction = reduction + eliminated[i] * v / #box[id] + end + + reductions[#reductions + 1] = reduction + end + if not observed[id].first_multiple then + local counter = {0,0,0,0,0,0,0,0,0} + for _,v in pairs(box[id]) do + counter[math.floor(v / 10)] = counter[math.floor(v / 10)] + 1 + end + + local weights = { + counter[1] + counter[2]/2 + counter[3]/3, + counter[2]/2 + counter[3]/3 + counter[4]/3, + counter[3]/3 + counter[4]/3 + counter[5]/3, + counter[4]/3 + counter[5]/3 + counter[6]/3, + counter[5]/3 + counter[6]/3 + counter[7]/3, + counter[6]/3 + counter[7]/3 + counter[8]/2, + counter[7]/3 + counter[8]/2 + counter[9] + } + + local eliminated = { + counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9], + counter[1] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9], + counter[1] + counter[2] + counter[6] + counter[7] + counter[8] + counter[9], + counter[1] + counter[2] + counter[3] + counter[7] + counter[8] + counter[9], + counter[1] + counter[2] + counter[3] + counter[4] + counter[8] + counter[9], + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[9], + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[6] + } + + local reduction = 0 + for i,v in pairs(weights) do + reduction = reduction + eliminated[i] * v / #box[id] + end + + reductions[#reductions + 1] = reduction + end + + local expected_examine_value = 0 + for _,v in pairs(reductions) do + expected_examine_value = expected_examine_value + v/#reductions + end + + local optimal_guess = math.ceil(#box[id] / 2) + + local expected_guess_value = 2*optimal_guess - 2*optimal_guess^2 / #box[id] + 2*optimal_guess/#box[id] - 1 / #box[id] + + return expected_examine_value, expected_guess_value +end + +-- display helper function + +function display(id, chances) + if #box[id] == 90 then + windower.add_to_chat(207, 'Possible combinations: 10~99') + else + windower.add_to_chat(207, 'Possible combinations: ' .. table.concat(box[id], ' ')) + end + local remaining = math.floor(#box[id] / math.pow(2, (chances - 1))) + if remaining == 0 then + remaining = 1 + end + + if chances == 1 and observed[id].equal then + -- The "equal" message (== "X") for X in 1..9 gives an unequal probability to the remaining options + -- because "XX" is twice as likely to be indicated by the "equal" message. + -- This is too annoying to propagate to the rest of the addon, although it should be some day. + local printed = false + for _,v in pairs(box[id]) do + if math.floor(v/10) == v%10 then + windower.add_to_chat(207, 'Best guess: %d (%d%%)':format(v, 1 / remaining * 100)) + printed = true + break + end + end + if not printed then + windower.add_to_chat(207, 'Best guess: %d (%d%%)':format(box[id][math.ceil(#box[id] / 2)], 1 / remaining * 100)) + end + else + windower.add_to_chat(207, 'best guess: %d (%d%%)':format(box[id][math.ceil(#box[id] / 2)], 1 / remaining * 100)) + local clue_value,guess_value = calculate_odds(id,chances) + local result = clue_value > guess_value and remaining ~= 1 and 'examining the chest' or 'guessing ' .. '%d':format(box[id][math.ceil(#box[id] / 2)]) + local formatted_result = settings.HighlightResult and result:color(settings.HighlightColor) or result + windower.add_to_chat(207, 'boxdestroyer recommends ' .. formatted_result .. '.') + end + +end + +-- ID obtaining helper function +function get_id(zone_id,str) + return messages[zone_id] + offsets[str] +end + +-- event callback functions + +function check_incoming_chunk(id, original, modified, injected, blocked) + if id == 0x0A then + zone_id = original:unpack('H', 49) + elseif messages[zone_id] then + if id == 0x0B then + box = {} + observed = {} + elseif id == 0x2A then + local box_id = original:unpack('I', 5) + local param0 = original:unpack('I', 9) + local param1 = original:unpack('I', 13) + local param2 = original:unpack('I', 17) + local message_id = original:unpack('H', 27) % 0x8000 + + if box[box_id] == nil then + box[box_id] = table.copy(default) + end + if observed[box_id] == nil then + observed[box_id] = table.copy(observed_default) + end + + if get_id(zone_id,'greater_less') == message_id then + box[box_id] = greater_less(box_id, param1 == 0, param0) + elseif get_id(zone_id,'second_even_odd') == message_id then + -- tells whether the second digit is even or odd + box[box_id] = even_odd(box_id, 1, param0) + observed[box_id].second_even_odd = true + elseif get_id(zone_id,'first_even_odd') == message_id then + -- tells whether the first digit is even or odd + box[box_id] = even_odd(box_id, 10, param0) + observed[box_id].first_even_odd = true + elseif get_id(zone_id,'range') == message_id then + if observed[box_id].thief_tools_active then + -- Thief tools are the same as normal ranges but with larger bounds. + -- lower bound (param0) = solution - RANDINT(8,32) + -- upper bound (param1) = solution + RANDINT(8,32) + -- param0 + 33 > solution > param0 + 7 + -- param1 - 7 > solution > param1 - 33 + -- if the bound is less than 11 or greater than 98, the message changes to "greater" or "less" respectively + box[box_id] = greater_less(box_id, true, math.max(param1-33,param0+7) ) + box[box_id] = greater_less(box_id, false, math.min(param0+33,param1-7) ) + observed[box_id].thief_tools_active = false + else + -- lower bound (param0) = solution - RANDINT(5,20) + -- upper bound (param1) = solution + RANDINT(5,20) + -- param0 + 21 > solution > param0 + 4 + -- param1 - 4 > solution > param1 - 21 + -- if the bound is less than 11 or greater than 98, the message changes to "greater" or "less" respectively + box[box_id] = greater_less(box_id, true, math.max(param1-21,param0+4) ) + box[box_id] = greater_less(box_id, false, math.min(param0+21,param1-4) ) + observed[box_id].range = true + end + elseif get_id(zone_id,'less') == message_id then + -- Less is a range with 9 as the lower bound + if observed[box_id].thief_tools_active then + box[box_id] = greater_less(box_id, true, math.max(9, param0-33) ) + box[box_id] = greater_less(box_id, false, math.min(10+33,param0-7) ) + observed[box_id].thief_tools_active = false + else + box[box_id] = greater_less(box_id, true, math.max(9, param0-21) ) + box[box_id] = greater_less(box_id, false, math.min(10+21,param0-4) ) + observed[box_id].range = true + end + elseif get_id(zone_id,'greater') == message_id then + -- Greater is a range with 100 as the upper bound + if observed[box_id].thief_tools_active then + box[box_id] = greater_less(box_id, true, math.max(99-33,param0+7) ) + box[box_id] = greater_less(box_id, false, math.min(100,param0+33) ) + observed[box_id].thief_tools_active = false + else + box[box_id] = greater_less(box_id, true, math.max(99-21,param0+4) ) + box[box_id] = greater_less(box_id, false, math.min(100,param0+21) ) + observed[box_id].range = true + end + elseif get_id(zone_id,'equal') == message_id then + -- single number that is either the first or second digit of the solution + local new = equal(box_id, true, param0) + local duplicate = param0 * 10 + param0 + for k,v in pairs(new) do + if v == duplicate then + table.remove(new, k) + end + end + for _,v in pairs(equal(box_id, false, param0)) do table.insert(new, v) end + table.sort(new) + box[box_id] = new + observed[box_id].equal = true + elseif get_id(zone_id,'second_multiple') == message_id then + -- three digit range including the second digit of the solution + local new = equal(box_id, false, param0) + for _,v in pairs(equal(box_id, false, param1)) do table.insert(new, v) end + for _,v in pairs(equal(box_id, false, param2)) do table.insert(new, v) end + table.sort(new) + box[box_id] = new + observed[box_id].second_multiple = true + elseif get_id(zone_id,'first_multiple') == message_id then + -- three digit range including the first digit of the solution + local new = equal(box_id, true, param0) + for _,v in pairs(equal(box_id, true, param1)) do table.insert(new, v) end + for _,v in pairs(equal(box_id, true, param2)) do table.insert(new, v) end + table.sort(new) + box[box_id] = new + observed[box_id].first_multiple = true + elseif get_id(zone_id,'success') == message_id or get_id(zone_id,'failure') == message_id then + box[box_id] = nil + elseif get_id(zone_id,'tool_failure') == message_id then + observed[box_id].thief_tools_active = false + end + elseif id == 0x34 then + local box_id = original:unpack('I', 5) + if windower.ffxi.get_mob_by_id(box_id).name == 'Treasure Casket' then + local chances = original:byte(9) + if box[box_id] == nil then + box[box_id] = table.copy(default) + observed[box_id] = table.copy(observed_default) + end + if chances > 0 and chances < 7 then + display(box_id, chances) + end + end + elseif id == 0x5B then + box[original:unpack('I', 17)] = nil + observed[original:unpack('I', 17)] = nil + end + end +end + +function watch_for_keys(id, original, modified, injected, blocked) + if blocked then + elseif (id == 0x036 or id == 0x037) and + windower.ffxi.get_mob_by_id(modified:unpack('I',0x05)).name == 'Treasure Casket' and + (windower.ffxi.get_player().main_job == 'THF' or windower.ffxi.get_player().sub_job == 'THF') then + + local box_id = modified:unpack('I',0x05) + if not box[box_id] then + box[box_id] = table.copy(default) + observed[box_id] = table.copy(observed_default) + end + + if id == 0x037 and thief_tools[windower.ffxi.get_items(modified:byte(0x11))[modified:byte(0xF)].id] then + observed[box_id].thief_tools_active = true + elseif id == 0x036 then + for i = 1,9 do + if thief_tools[windower.ffxi.get_items(0)[modified:byte(0x30+i)].id] then + observed[box_id].thief_tools_active = true + break + end + end + end + end +end + +-- register event callbacks + +windower.register_event('incoming chunk', check_incoming_chunk) + +windower.register_event('outgoing chunk', watch_for_keys) diff --git a/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/make_messages.py b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/make_messages.py new file mode 100644 index 0000000..932ea3b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/make_messages.py @@ -0,0 +1,77 @@ +# Python 3 +import array +import os +import struct +import winreg + +from settings import search, zones + + +def find_dat(dat_id): + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\WOW6432Node\\PlayOnlineUS\\InstallFolder') + ffxi_path = winreg.QueryValueEx(key, '0001')[0] + key.Close() + for i in range(1, 10): + vtable = None + if i == 1: + vtable = open(os.path.join(ffxi_path, 'VTABLE.DAT'), 'rb') + else: + vtable = open(os.path.join(ffxi_path, 'ROM{}'.format(i), 'VTABLE{}.DAT'.format(i)), 'rb') + vtable.seek(dat_id) + temp = vtable.read(1)[0] + vtable.close() + if temp != i: + continue + ftable = None + if i == 1: + ftable = open(os.path.join(ffxi_path, 'FTABLE.DAT'), 'rb') + else: + ftable = open(os.path.join(ffxi_path, 'ROM{}'.format(i), 'FTABLE{}.DAT'.format(i)), 'rb') + ftable.seek(dat_id * 2) + path = struct.unpack('H', ftable.read(2))[0] + ftable.close() + if i == 1: + return os.path.join(ffxi_path, 'ROM', '{}'.format(path >> 7), '{}.DAT'.format(path & 0x7f)) + else: + return os.path.join(ffxi_path, 'ROM{}'.format(i), '{}'.format(path >> 7), '{}.DAT'.format(path & 0x7f)) + return None + +def decipher_dialog(dat_file): + dat = open(dat_file, 'rb') + dat_size, first_entry = struct.unpack('II', dat.read(8)) + dat_size -= 0x10000000 + first_entry ^= 0x80808080 + dat.seek(4) + data = bytearray(dat.read()) + dat.close() + for i in range(len(data)): + data[i] ^= 0x80 + offsets = array.array('I', data[:first_entry]) + offsets.append(dat_size) + for i in range(len(offsets)): + offsets[i] -= first_entry + return offsets, bytes(data[first_entry:]) + +def search_dialog(zones, search): + messages = {} + for zone_id, dat_id in zones.items(): + offsets, data = decipher_dialog(find_dat(dat_id)) + for i in range(len(offsets) - 1): + message = data[offsets[i]:offsets[i+1]] + if message == search: + messages[zone_id] = str(i) + return messages + +def write_lua(messages): + o = open('messages.lua', 'w') + print('messages = { -- These dialogue IDs match "You were unable to enter a combination" for the associated zone IDs', file=o) + zone_ids = list(messages.keys()) + zone_ids.sort() + for zone_id in zone_ids: + line = messages[zone_id]+',' + print(" [{}] = {}".format(zone_id, line), file=o) + print('}',file=o) + print('offsets = {greater_less=1, failure=2, success=4, second_even_odd=5, first_even_odd=6, range=7, less=8, greater=9, equal=10, second_multiple=11, first_multiple=12, tool_failure=13}',file=o) + o.close() + +write_lua(search_dialog(zones, search)) diff --git a/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/settings.py b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/settings.py new file mode 100644 index 0000000..ccc6b1b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/make_messages/settings.py @@ -0,0 +1,190 @@ +search = b'You were unable to enter a combination.\x7f1\x00\x07' + +zones = { + #1: 6421, # phanauet channel + #2: 6422, # carpenters' landing + #3: 6423, # manaclipper + #4: 6424, # bibiki bay + #11: 6431, # oldton movalpolos + #15: 6435, # abyssea - konschtat + #24: 6444, # lufaise meadows + #25: 6445, # misareaux coast + #26: 6446, # tavnazian safehold + #27: 6447, # phomiuna aquaducts + #33: 6453, # al'taieu + #39: 6459, # dynamis - valkurm + #40: 6460, # dynamis - buburimu + #41: 6461, # dynamis - qufim + #42: 6462, # dynamis - tavnazia + #43: 6463, # diorama abdhaljs-ghelsba + #44: 6464, # abdhaljs isle-purgonorgo + #45: 6465, # abyssea - tahrongi + #46: 6466, # open sea route to al zahbi + #47: 6467, # open sea route to mhaura + #48: 6468, # al zahbi + #50: 6470, # aht urhgan whitegate + #51: 6471, # wajaom woodlands + #52: 6472, # bhaflau thickets + #53: 6473, # nashmau + #54: 6474, # arrapago reef + #55: 6475, # ilrusi atoll + #56: 6476, # periqia + #57: 6477, # talacca cove + #58: 6478, # silver sea route to nashmau + #59: 6479, # silver sea route to al zahbi + #60: 6480, # the ashu talif + #61: 6481, # mount zhayolm + #65: 6485, # mamook + #66: 6486, # mamool ja training grounds + #67: 6487, # jade sepulcher + #68: 6488, # aydeewa subterrane + #69: 6489, # leujaoam sanctum + #79: 6499, # caedarva mire + #81: 6501, # east ronfaure [s] + #82: 6502, # jugner forest [s] + #83: 6503, # vunkerl inlet [s] + #84: 6504, # batallia downs [s] + #85: 6505, # la vaule [s] + #86: 6506, # everbloom hollow + #87: 6507, # bastok markets [s] + #88: 6508, # north gustaberg [s] + #89: 6509, # grauberg [s] + #90: 6510, # pashhow marshlands [s] + #91: 6511, # rolanberry fields [s] + #93: 6513, # ruhotz silvermines + #94: 6514, # windurst waters [s] + #95: 6515, # west sarutabaruta [a] + #96: 6516, # fort karugo-narugo [s] + #99: 6519, # castle oztroja [s] + 100: 6520, # west ronfaure + 101: 6521, # east ronfaure + 102: 6522, # la theine plateau + 103: 6523, # valkurm dunes + 104: 6524, # jugner forest + 105: 6525, # batallia downs + 106: 6526, # north gustaberg + 107: 6527, # south gustaberg + 108: 6528, # konschtat highlands + 109: 6529, # pashhow marshlands + 110: 6530, # rolanberry fields + 111: 6531, # beaucedine glacier + 112: 6532, # xarcabard + 113: 6533, # cape teriggan + 114: 6534, # eastern altepa desert + 115: 6535, # west sarutabaruta + 116: 6536, # east sarutabaruta + 117: 6537, # tahrongi canyon + 118: 6538, # buburimu peninsula + 119: 6539, # meriphataud mountains + 120: 6540, # sauromugue champaign + 121: 6541, # the sanctuary of zi'tah + 122: 6542, # ro'maeve + 123: 6543, # yuhtunga jungle + 124: 6544, # yhoator jungle + 125: 6545, # western altepa desert + 126: 6546, # qufim island + 127: 6547, # behemoth's dominion + 128: 6548, # valley of sorrows + 130: 6550, # ru'aun gardens + #132: 6552, # abyssea - la theine + #134: 6554, # dynamis - beaucedine + #136: 6556, # beaucedine glacier [s] + #139: 6559, # horlais peak + #140: 6560, # ghelsba outpost + #141: 6561, # fort ghelsba + #142: 6562, # yughott grotto + #143: 6563, # palborough mines + #145: 6565, # giddeus + #148: 6568, # qulun dome + #149: 6569, # davoi + #151: 6571, # castle oztroja + 153: 6573, # the boyahda tree + #154: 6574, # dragon's aery + 157: 6577, # middle delkfutt's tower + 158: 6578, # upper delkfutt's tower + 159: 6579, # temple of uggalepih + 160: 6580, # den of rancor + 166: 6586, # ranguemont pass + 167: 6587, # bostaunieux oubliette + 169: 6589, # torimarai canal + 172: 6592, # zeruhn mines + 173: 6593, # korroloka tunnel + 174: 6594, # kuftal tunnel + 176: 6596, # sea serpent grotto + 177: 6597, # ve'lugannon palace + 178: 6598, # the shrine of ru'avitau + 184: 6604, # lower delkfutt's tower + #186: 6606, # dynamis - bastok + #187: 6607, # dynamis - windurst + 190: 6610, # king ranpere's tomb + 191: 6611, # dangruf wadi + 192: 6612, # inner horutoto ruins + 193: 6613, # ordelle's caves + 194: 6614, # outer horutoto ruins + 195: 6615, # eldieme necropolis + 196: 6616, # gusgan mines + 197: 6617, # crawler's nest + 198: 6618, # maze of shakrami + 200: 6620, # garlaige citadel + 204: 6624, # fei'yin + 205: 6625, # ifrit's cauldron + 208: 6628, # quicksand caves + 212: 6632, # gustav tunnel + 213: 6633, # labyrinth of onzozo + #216: 6636, # abyssea - misareaux + #217: 6637, # abyssea - vunkerl + #218: 6638, # abyssea - altepa + #220: 6640, # ship bound for selbina + #221: 6641, # ship bound for mhaura + #222: 6642, # provenance + #227: 6647, # ship bound for selbina + #228: 6648, # ship bound for mhaura + #231: 6651, # northern san d'oria + #232: 6652, # port san d'oria + #234: 6654, # bastok mines + #235: 6655, # bastok markets + #236: 6656, # port bastok + #237: 6657, # metalworks + #238: 6658, # windurst waters + #239: 6659, # windurst walls + #240: 6660, # port windurst + #241: 6661, # windurst woods + #242: 6662, # heavens tower + #245: 6665, # lower jeuno + #246: 6666, # port jeuno + #247: 6667, # rabao + #248: 6668, # selbina + #249: 6669, # mhaura + #250: 6670, # kazham + #251: 6671, # hall of the gods + #252: 6672, # norg + #253: 6473, # abyssea - uleguerand + #254: 6674, # abyssea - grauberg + #256: 85591, # western adoulin + #257: 85592, # eastern adoulin + #258: 85593, # rala waterways + #259: 85594, # rala waterways [u] + #260: 85595, # yahse hunting grounds + #261: 85596, # ceizak battlegrounds + #262: 85597, # foret de hennetiel + #263: 85598, # yorcia weald + #264: 85599, # yorcia weald [u] + #265: 85600, # morimar basalt fields + #266: 85601, # marjami ravine + #267: 85602, # kamihr drifts + #268: 85603, # sih gates + #269: 85604, # moh gates + #270: 85605, # cirdas caverns + #271: 85606, # cirdas caverns [u] + #272: 85607, # dho gates + #273: 85608, # woh gates + #274: 85609, # outer ra'kaznar + #275: 85610, # outer ra'kaznar [u] + #276: 85611, # ra'kaznar inner court + #277: 85612, # ra'kaznar turris + #280: 85615, # mog garden + #281: 85616, # leafallia + #288: 85623, # escha - zi'tah + #289: 85624, # escha - ru'aun + #291: 85626, # reisenjima +} diff --git a/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/messages.lua b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/messages.lua new file mode 100644 index 0000000..6624a95 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/boxdestroyer/messages.lua @@ -0,0 +1,63 @@ +messages = { -- These dialogue IDs match "You were unable to enter a combination" for the associated zone IDs + [100] = 8078, + [101] = 7509, + [102] = 7929, + [103] = 8104, + [104] = 8669, + [105] = 7720, + [106] = 8094, + [107] = 7555, + [108] = 7624, + [109] = 8487, + [110] = 7611, + [111] = 8588, + [112] = 8190, + [113] = 7932, + [114] = 7789, + [115] = 7885, + [116] = 7581, + [117] = 7588, + [118] = 8130, + [119] = 8367, + [120] = 7527, + [121] = 8107, + [122] = 7438, + [123] = 7890, + [124] = 7841, + [125] = 7651, + [126] = 8074, + [127] = 7355, + [128] = 7510, + [130] = 7574, + [153] = 11400, + [157] = 7380, + [158] = 7386, + [159] = 8449, + [160] = 7413, + [166] = 10582, + [167] = 10596, + [169] = 7543, + [172] = 7416, + [173] = 10521, + [174] = 11399, + [176] = 7608, + [177] = 11223, + [178] = 11403, + [184] = 8633, + [190] = 8257, + [191] = 8377, + [192] = 7413, + [193] = 8389, + [194] = 8269, + [195] = 7600, + [196] = 8309, + [197] = 7354, + [198] = 8275, + [200] = 7531, + [204] = 7519, + [205] = 11486, + [208] = 8288, + [212] = 10642, + [213] = 10452, +} +offsets = {greater_less=1, failure=2, success=4, second_even_odd=5, first_even_odd=6, range=7, less=8, greater=9, equal=10, second_multiple=11, first_multiple=12, tool_failure=13} diff --git a/Data/DefaultContent/Libraries/addons/addons/cBlock/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/cBlock/ReadMe.md new file mode 100644 index 0000000..e147ce4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cBlock/ReadMe.md @@ -0,0 +1,11 @@ +**Author:** Ricky Gall +**Version:** v 1.07 +**Description:** +Addon is to allow you to ignore people in ffochat. + +**Abbreviation:** //cblock + +**Commands:** + +1. //cblock delete <name> --Unignores <name> so you can see what they're saying again +2. //cblock ignore <name> --Ignores <name> adds then to your blacklist.txt so you don't see anything from them until you either remove them or delete blacklist.txt. diff --git a/Data/DefaultContent/Libraries/addons/addons/cBlock/cBlock.lua b/Data/DefaultContent/Libraries/addons/addons/cBlock/cBlock.lua new file mode 100644 index 0000000..2869948 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cBlock/cBlock.lua @@ -0,0 +1,114 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'CBlock' +_addon.version = '1.05' + +windower.register_event('addon command',function (...) + local term = table.concat({...}, ' ') + a,b,block = string.find(term,'ignore (.*)') + c,d,delete = string.find(term,'delete (.*)') + if block ~= nil then + ignore[#ignore+1] = block:lower() + local f = io.open(settingsFile,'a') + f:write(block.."\n") + windower.add_to_chat(55,"No longer seeing "..block.." speak in FFOchat.") + local q,r = io.close(f) + if not q then print(r) end + elseif delete ~= nil then + for u = 1, #ignore do + if ignore[u] == delete then + table.remove(ignore,u) + end + end + windower.add_to_chat(55,"Seeing "..delete.." speak in FFOchat again.") + local tmp = io.open(settingsPath..'tmp.txt',"w") + for line in io.lines(settingsFile) do + if line ~= delete then + tmp:write(line..'\n') + end + end + local q,w = io.close(tmp) + if not q then print(w) end + local r,es = os.rename(settingsFile,settingsPath..'tmp2.txt') + if not r then print(es) end + local e,rs = os.rename(settingsPath..'tmp.txt',settingsFile) + if not e then print(rs) end + local r,es = os.remove(settingsPath..'tmp2.txt') + if not r then print(es) end + end +end) + +function file_exists(name) + local f=io.open(name,"r") + if f~=nil then + local q,r = io.close(f) + if not q then print(r) end + return true + else + return false + end +end + +windower.register_event('load',function () + windower.send_command('alias cBlock lua c cBlock') + ignore = {} + settingsPath = windower.addon_path..'data/' + settingsFile = settingsPath..'blacklist.txt' + if not file_exists(settingsFile) then + local f,err = assert(io.open(settingsPath.."blacklist.txt","w")) + io.close(f) + else + fill_ignore() + end +end) + +windower.register_event('unload',function () + windower.send_command('unalias cblock') +end) + +function fill_ignore() + i = 1 + for line in io.lines(settingsFile) do + ignore[i] = line + i = i + 1 + end +end + +windower.register_event('incoming text',function (old,new,color) + for i=1,#ignore do + c,d,text = string.find(old,'%[%d+:#%w+%](.*):') + if text ~= nil then + if text:lower() == ignore[i]:lower() then + new = '' + end + end + end + return new, color -- must be here or errors will be thrown +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/cBlock/data/DeleteMe.txt b/Data/DefaultContent/Libraries/addons/addons/cBlock/data/DeleteMe.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cBlock/data/DeleteMe.txt @@ -0,0 +1 @@ + diff --git a/Data/DefaultContent/Libraries/addons/addons/cBlock/data/blacklist.txt b/Data/DefaultContent/Libraries/addons/addons/cBlock/data/blacklist.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cBlock/data/blacklist.txt @@ -0,0 +1 @@ + diff --git a/Data/DefaultContent/Libraries/addons/addons/cancel/cancel.lua b/Data/DefaultContent/Libraries/addons/addons/cancel/cancel.lua new file mode 100644 index 0000000..b8040fc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cancel/cancel.lua @@ -0,0 +1,57 @@ +--Copyright (c) 2013, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'Cancel' +_addon.version = '1.0' +_addon.author = 'Byrth' +_addon.commands = {'cancel'} + +res = require 'resources' + +name_index = {} +language = windower.ffxi.get_info().language:lower() + + +windower.register_event('addon command',function (...) + local command = table.concat({...},' ') + if not command then return end + local status_id_tab = command:split(',') + status_id_tab.n = nil + local ids = {} + local buffs = {} + for _,v in pairs(windower.ffxi.get_player().buffs) do + for _,r in pairs(status_id_tab) do + if windower.wc_match(res.buffs[v][language],r) or windower.wc_match(tostring(v),r) then + cancel(v) + break + end + end + end +end) + +function cancel(id) + windower.packets.inject_outgoing(0xF1,string.char(0xF1,0x04,0,0,id%256,math.floor(id/256),0,0)) -- Inject the cancel packet +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/README.md b/Data/DefaultContent/Libraries/addons/addons/capetrader/README.md new file mode 100644 index 0000000..f870836 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/README.md @@ -0,0 +1,159 @@ +# Cape Trader +Cape Trader is used to automate the process of augmenting ambuscade capes. The cape trader addon injects packets extensively so please take that into consideration and use this addon at you own risk. While there are some safeguards in this addon, there is still a risk that you can lose your abdhaljs thread,dust,sap and dye if you use this addon. Please read the usage notes and information on the prep and go commands carefully as well as the warnings section before deciding to use this addon. + +___ +### Usage notes + +Load the addon by using the following command: + + //lua load capetrader + +There are some conditions that you need to meet in order to use the important go and prep commands: + +1. You must be in Mhaura and be within a distance of 6 to the Gorpa-Masorpa npc. + +2. If you have recently zoned into Mhaura you will have to wait to use the go command until your inventory loads, or if you have only recently logged in. + +3. Make sure there is only one of the given cape you want to augment in your inventory. For example if you are intending to augment an ogma's cape, there should only be one ogma's cape in your inventory. + +4. The go command takes a number as an input that represents the number of times you wish to augment your cape. It is possible you will lose your thread,dust,sap and dye if you enter a number that would take your augment past its possible maximum. For example suppose you have an ogma's cape already augmented with Dex+5 from a thread item. After using the **//ct prep run thread dex** command you enter **//ct go 20** command. There is a safeguard that will stop the augmentation process after augmenting your cape 15 times. However it is highly recommended that you enter the exact number of times you need to max a particular augment path just in case. + +5. It is also possible to lose augment items if you try to augment a cape with a different path than is already present. Suppose again you have an ogma's cape augmented with DEX+5 via threads. If you enter the **//ct prep run thread str** and then the **//ct go 15** command intending to augment your cape with str, **you might lose these 15 threads**. There is now a safeguard to avoid this, but it is highly recommended you make sure your intended augment path already matches what is on the cape. + +6. Make sure you do not move items around your various inventory bags while the augmentation process is ongoing. You can mess around with your inventory after you get the ending message, otherwise you might interrupt the augmentation process and need to reload the addon. + +7. The augmentation process can occasionally stall. If this ever happens you can use the **//ct r** command to reload the addon and start the process over again. + + +Suppose you want to augment an ogma's cape from scratch with dex, accuracy and attack, and double attack. You can use the following steps: + +1. Enter //ct prep run thread dex + +2. Enter //ct go 20 + +3. Wait for the ending message + +4. Enter //ct prep run dust acc/atk + +5. Enter //ct go 20 + +6. Wait for the ending message. + +7. Enter //ct prep run sap doubleattack + +8. Enter //ct go 10 + +9. Upon receiving the completion message you can then consider augmenting another cape. + +___ + +### Commands + +The **help** command displays all of the possible commands of CapeTrader in your chat windower. Below are the equivalent ways of calling the command: + + //ct help + //ct h + //capetrader help + //capetrader h + +The **reload** command reloads the addon. Useful if the capetrader addon ever gets stuck during the augmentation process. The below are equivalent: + + //ct reload + //ct r + //capetrader reload + //capetrader r + +The **unload** command reloads the addon. Useful if the capetrader addon ever gets stuck during the augmentation process or you want to unload the addon quickly. The below are equivalent: + + //ct unload + //ct u + //capetrader unload + //capetrader u + +The **prep** command is one of the key components of this addon's function. This command tells the go command how to augment your cape. There are three inputs to the prep command. First is the 3 letter abbreviation of the job on the cape, for example: cor blm whm pup. The second is the type of augment item you need to use: thread, dust, sap and dye. Third is the augment path. Note that none of these inputs are case sensitive. Also in case you are not sure exactly what to input for the augment path, you can use the list command or use the following list for reference. Below are all of the possible combinations of valid augment paths: + + //ct prep war thread hp + //ct prep mnk thread mp + //ct prep whm thread str + //ct prep blm thread dex + //ct prep rdm thread vit + //ct prep thf thread agi + //ct prep pld thread int + //ct prep drk thread mnd + //ct prep bst thread chr + //ct prep brd thread petmelee + //ct prep rng thread petmagic + + //ct prep sam dust acc/atk + //ct prep nin dust racc/ratk + //ct prep drg dust macc/mdmg + //ct prep smn dust eva/meva + + //ct prep blu sap wsd + //ct prep cor sap critrate + //ct prep pup sap stp + //ct prep dnc sap doubleattack + //ct prep sch sap haste + //ct prep geo sap dw + //ct prep run sap enmity+ + //ct prep war sap enmity- + //ct prep mnk sap snapshot + //ct prep whm sap mab + //ct prep blm sap fc + //ct prep rdm sap curepotency + //ct prep thf sap waltzpotency + //ct prep pld sap petregen + //ct prep drk sap pethaste + + //ct prep bst dye hp + //ct prep brd dye mp + //ct prep rng dye str + //ct prep sam dye dex + //ct prep nin dye vit + //ct prep drg dye agi + //ct prep smn dye int + //ct prep blu dye mnd + //ct prep cor dye chr + //ct prep pup dye acc + //ct prep dnc dye atk + //ct prep sch dye racc + //ct prep geo dye ratk + //ct prep run dye macc + //ct prep war dye mdmg + //ct prep mnk dye eva + //ct prep whm dye meva + //ct prep blm dye petacc + //ct prep rdm dye petatk + //ct prep thf dye petmacc + //ct prep pld dye petmdmg + +The **go** command is the second key component of the CapeTrader addon and requires that you have used the prep command correctly beforehand. Remember again that using this command carries with it some risks as described in the usage notes section. If you do not provide an input the go command will by default only augment your cape once. Below are equivalent ways of augmenting your cape 20 times: + + //ct go 20 + //capetrader go 20 + +The **list** command is used as a reminder of what are valid inputs for the augmentpath input of the prep command. Below are the equivalent ways of calling this command: + + //ct list + //capetrader list + //ct l + //capetrader l +___ + + +### Warnings and notes on the relevant packets +There are four parts to the process of augmenting your ambuscade cape: + +1. Part 1: Targetting gorpa-masorpa plus putting together and trading the relevant items. This takes me about 10 seconds to complete manually. (Involves outgoing packet 0x036) + +2. Part 2: Waiting for the dialog menu to pop up and become usable. This takes about 1 second and can't be controlled by the player. (Involves the incoming 0x034 packet.) + +3. Part 3: Navigating the menu to confirm and augment your cape. This takes me 2-3 seconds to confirm manually. (Involves 2 outgoing 0x05B packets) + +4. Part 4: Waiting and receiving your newly augmented cape. This takes anywhere from 1 to 3 seconds and can't be controlled by the player. (Involves the incoming 0x01D packet) + +The cape trader addon uses packets in order to substantially speed up parts one and three from above at a speed that would not normally be possible. Therefore if you use this addon you could potentially look suspicious. Part 1 of the process takes only 1 second using this addon. If this makes you uncomfortable you can change the value of the dustSapThreadTradeDelay and dyeTradeDelay variables in the capeTrader.lua file to a more reasonable amount if you wish. Please note that the augmenting with dyes needs a bit longer of a delay to work. This addon does part 3 pretty much instantaneously once the incoming 0x034 packet is received from part 2. So once again you can look suspicious again during this stage. The time it takes for part 2 and 4 should not be that different from you augmenting capes manually. + +The possibility of the loss of augment items comes from part 3. When augmenting manually you will get denied by the npc if you try to trade an already maxed cape. Injecting the 0x036 packet and later the 0x05B packets bypasses this check but you lose your augment items but receive your cape back unchanged if you have already maxed an augment path. There are some safeguards to prevent this happening in this addon but it has not been tested on every single augment path. So please use the go command with caution. + +Please keep all of the above in mind before deciding to use this addon. If you do decide to use CapeTrader I hope you find it useful! diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/allAugPaths.lua b/Data/DefaultContent/Libraries/addons/addons/capetrader/allAugPaths.lua new file mode 100644 index 0000000..e8f337b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/allAugPaths.lua @@ -0,0 +1,87 @@ +--[[Copyright © 2016, Burntwaffle@Odin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of CapeTrader nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Lygre,Burntwaffle@Odin 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.]]-- + +return T{ + ['abdhaljs thread'] = { + [0] = 'HP', + [1] = 'MP', + [2] = 'STR', + [3] = 'DEX', + [4] = 'VIT', + [5] = 'AGI', + [6] = 'INT', + [7] = 'MND', + [8] = 'CHR', + [9] = 'PetMelee', + [10] = 'PetMagic' + }, + ['abdhaljs dust'] = { + [0] = 'Acc/Atk', + [1] = 'RAcc/RAtk', + [2] = 'MAcc/MDmg', + [3] = 'Eva/MEva' + }, + ['abdhaljs sap'] = { + [0] = 'WSD', + [1] = 'CritRate', + [2] = 'STP', + [3] = 'DoubleAttack', + [4] = 'Haste', + [5] = 'DW', + [6] = 'Enmity+', + [7] = 'Enmity-', + [8] = 'Snapshot', + [9] = 'MAB', + [10] = 'FC', + [11] = 'CurePotency', + [12] = 'WaltzPotency', + [13] = 'PetRegen', + [14] = 'PetHaste', + }, + ['abdhaljs dye'] = { + [0] = 'HP', + [1] = 'MP', + [2] = 'STR', + [3] = 'DEX', + [4] = 'VIT', + [5] = 'AGI', + [6] = 'INT', + [7] = 'MND', + [8] = 'CHR', + [9] = 'Acc', + [10] = 'Atk', + [11] = 'Racc', + [12] = 'Ratk', + [13] = 'MAcc', + [14] = 'MDmg', + [15] = 'Eva', + [16] = 'MEva', + [17] = 'PetAcc', + [18] = 'PetAtk', + [19] = 'PetMAcc', + [20] = 'PetMDmg', + }, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/capetrader.lua b/Data/DefaultContent/Libraries/addons/addons/capetrader/capetrader.lua new file mode 100644 index 0000000..40fbbf7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/capetrader.lua @@ -0,0 +1,521 @@ +--[[Copyright © 2016, Lygre, Burntwaffle@Odin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of CapeTrader nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Lygre, Burntwaffle@Odin BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]]-- + +_addon.name = 'CapeTrader' +_addon.author = 'Lygre, Burntwaffle' +_addon.version = '1.0.2' +_addon.commands = {'capetrader', 'ct'} + +require('luau') +require('pack') +require('sets') +require('tables') +require('functions') +require('strings') +local res = require('resources') +local packets = require('packets') +local augPaths = require('allAugPaths') +local maxAugMap = require('maxAugMap') +local extData = require('extdata') +local jobToCapeMap = require('jobToCapeMap') +local validItemNames = require('validItemNames') + +function getValidItems() + local allItems = res.items + local itemTable = T{} + local counter = 0 + local numberOfValidItems = validItemNames:length() + + for key, item in pairs(allItems) do + if counter >= numberOfValidItems then + break + end + + if validItemNames:contains(item.english) then + itemTable:update(T{ [item.english:lower()] = item }) + counter = counter + 1 + end + end + + return itemTable +end +local validItemTable = getValidItems() + +--The following variables need to be carefully tracked and updated throughout all parts of this file. +--TODO: Find ways to reduce the amount of global variables in this list +local currentCape = nil +local pathItem = nil +local pathName = nil +local pathIndex +local capeHasBeenPrepared = false +local currentlyAugmenting = false +local firstTimeAug = false +local timesAugmentedCount = 0 +local numberOfTimesToAugment = 0 +local tradeReady = false +local maxAugKey = nil +local zoneHasLoaded = true +local inventory = nil + +--The following are constants. +local dustSapThreadTradeDelay = 1 +local dyeTradeDelay = 2 +local endMessageDelay = 2 +local threadIndex = 1 +local dustIndex = 2 +local dyeIndex = 3 +local sapIndex = 4 +local inventoryBagNumber = 0 +local maxAmountDustAndThread = 20 +local maxAmountSapAndDye = 10 +local blueTextColor = 466 +local redTextColor = 123 +local greenTextColor = 158 + +--NOTE: It is possible that the correct values for the following variables can change after a version update. +local gorpaID = nil +local gorpaTargetIndex = nil +local gorpaMenuID = 0x183 +local mhauraID = res.zones:with('english','Mhaura').id + +windower.register_event('addon command', function(input, ...) + local cmd = string.lower(input) + local args = {...} + + updateGorpaID() + + if cmd == 'prep' then + if args[1] and args[2] and args[3] then + prepareCapeForAugments(args[2], args[1], args[3]) + else + windower.add_to_chat(redTextColor, "You are missing at least one input to the prep command.") + end + elseif cmd == 'go' then + if zoneHasLoaded and not currentlyAugmenting then + if args[1] then + if tonumber(args[1]) then + startAugmentingCape(args[1], true) + else + windower.add_to_chat(redTextColor, 'Error: Not given a numerical argument.') + end + else + startAugmentingCape(1, true) + end + elseif not zoneHasLoaded then + windower.add_to_chat(redTextColor, 'Your inventory has not yet loaded, please try the go command again when your inventory loads.') + elseif currentlyAugmenting then + windower.add_to_chat(redTextColor, 'You are currently still augmenting a cape, please wait until the process finishes.') + end + elseif cmd == 'list' or cmd == 'l' then + printAugList() + elseif cmd == 'help' or cmd == 'h' then + printHelp() + elseif cmd == 'unload' or cmd == 'u' then + windower.send_command('lua unload ' .. _addon.name) + elseif cmd == 'reload' or cmd == 'r' then + windower.send_command('lua reload ' .. _addon.name) + else + windower.add_to_chat(redTextColor, 'You entered an unknown command, enter //ct help if you forget your commands.') + end +end) + +function getItemIndex(capeOrAugItem) + for itemIndex, item in pairs(inventory) do + if item.id == capeOrAugItem.id then + return itemIndex + end + end +end + +function getItem(itemName) + return validItemTable[itemName:lower()] +end + +function buildTrade(capeIndex,augmentItemIndex) + local packet = packets.new('outgoing', 0x036, { + ["Target"] = gorpaID, + ["Target Index"] = gorpaTargetIndex, + ["Item Count 1"] = 1, + ["Item Count 2"] = 1, + ["Item Index 1"] = capeIndex, + ["Item Index 2"] = augmentItemIndex, + ["Number of Items"] = 2 + }) + packets.inject(packet) +end + +windower.register_event('incoming chunk', function(id, data, modified, injected, blocked) + if id == 0x00B or id == 0x00A then + zoneHasLoaded = false --NOTE: This idea was taken from Zohno's findall addon. + end + + if id == 0x034 or id == 0x032 then + if currentlyAugmenting then + + injectAugmentConfirmationPackets() + + timesAugmentedCount = timesAugmentedCount + 1 + if timesAugmentedCount <= tonumber(numberOfTimesToAugment) then + windower.add_to_chat(greenTextColor, timesAugmentedCount - 1 .. '/' .. numberOfTimesToAugment .. ' augments completed.') + tradeReady = true + else + capeHasBeenPrepared = false + currentlyAugmenting = false + currentCape = nil + pathItem = nil + tradeReady = false + windower.add_to_chat:schedule(endMessageDelay,blueTextColor,'You have finished augmenting your cape.') + end + + return true + end + end + + if id == 0x01D then + if not zoneHasLoaded then + zoneHasLoaded = true + end + if tradeReady then + tradeReady = false + + if not pathItem.english:lower():endswith(' dye') then + startAugmentingCape:schedule(dustSapThreadTradeDelay, numberOfTimesToAugment - timesAugmentedCount + 1, false) + else + startAugmentingCape:schedule(dyeTradeDelay, numberOfTimesToAugment - timesAugmentedCount + 1, false) + end + end + end +end) + +function checkDistanceToGorpa() + local zoneID = tonumber(windower.ffxi.get_info().zone) + if zoneID == mhauraID then + local gorpa = windower.ffxi.get_mob_by_id(gorpaID) + + if gorpa and gorpa.distance < 36 then + return true + else + windower.add_to_chat(redTextColor, "You're not close enough to Gorpa-Masorpa, please get closer!") + return false + end + else + windower.add_to_chat(redTextColor, "You are not in Mhaura!") + currentlyAugmenting = false + return false + end +end + +function checkAugItemCount(numberOfAugmentAttempts) + if pathItem then + local pathItemCount = 0 + + for key, itemTable in pairs(inventory) do + if key ~= 'max' and key ~= 'count' and key ~= 'enabled' then + if itemTable.id == pathItem.id then + pathItemCount = pathItemCount + itemTable.count + end + end + end + + if tonumber(numberOfAugmentAttempts) < 1 then + windower.add_to_chat(redTextColor, 'Please enter a number of 1 or greater.') + return false + elseif tonumber(numberOfAugmentAttempts) > maxAmountSapAndDye and (pathItem.english:lower():endswith(' dye') or pathItem.english:lower():endswith(' sap')) then + windower.add_to_chat(redTextColor, 'For sap or dye, the max number of times you can augment a cape is ' .. maxAmountSapAndDye .. ' times. You entered: ' .. numberOfAugmentAttempts) + return false + elseif tonumber(numberOfAugmentAttempts) > maxAmountDustAndThread and (pathItem.english:lower():endswith(' dust') or pathItem.english:lower():endswith(' thread')) then + windower.add_to_chat(redTextColor, 'For dust or thread, the max number of times you can augment a cape is ' .. maxAmountDustAndThread .. ' times. You entered: ' .. numberOfAugmentAttempts) + return false + elseif tonumber(numberOfAugmentAttempts) > pathItemCount then + local temp + if tonumber(numberOfAugmentAttempts) > 1 then + temp = ' times.' + elseif tonumber(numberOfAugmentAttempts) == 1 then + temp = ' time.' + end + + if pathItemCount ~= 0 then + windower.add_to_chat(redTextColor, 'You do not have enough ' .. pathItem.name .. ' to augment that cape ' .. numberOfAugmentAttempts .. temp ..' You only have ' .. pathItemCount .. ' in your inventory.') + else + windower.add_to_chat(redTextColor, 'You do not have enough ' .. pathItem.name .. ' to augment that cape ' .. numberOfAugmentAttempts .. temp ..' You have none in your inventory.') + end + return false + else + return true + end + end +end + +function checkCapeCount() + local capeCount = 0 + + if currentCape then + for key, itemTable in pairs(inventory) do + if key ~= 'max' and key ~= 'count' and key ~= 'enabled' then + if itemTable.id == currentCape.id then + capeCount = capeCount + 1 + end + end + end + + if capeCount > 1 then + windower.add_to_chat(redTextColor, 'You have multiple ' .. currentCape.name .. 's in your inventory! Please keep only the one you intend to augment in your inventory.') + return false + elseif capeCount == 0 then + windower.add_to_chat(redTextColor, 'You have zero ' .. currentCape.name .. 's in your inventory. Please find the one you intend to augment and move it to your inventory.') + return false + elseif capeCount == 1 then + return true + end + else + return false + end +end + +function prepareCapeForAugments(augItemType, jobName, augPath) + if not currentlyAugmenting then + local validArguments = true + local augItemTypeIsValid = false + + if not S{'sap', 'dye', 'thread', 'dust'}:contains(augItemType:lower()) then + windower.add_to_chat(redTextColor, 'Error with the type of augment item you entered. The second input should be sap or dye or thread or dust. You entered: ' .. augItemType:lower()) + validArguments = false + else + pathItem = getItem('abdhaljs ' .. augItemType) + augItemTypeIsValid = true + end + + if jobToCapeMap[jobName] then + currentCape = getItem(jobToCapeMap[jobName]) + else + windower.add_to_chat(redTextColor, 'The job name you entered is not valid. You entered: ' .. jobName) + validArguments = false + end + + if augPath and augItemTypeIsValid and validArguments then + local isValidPath = false + for i, v in pairs(augPaths[pathItem.english:lower()]) do + if augPath:lower() == v:lower() then + pathIndex = i + pathName = augPath + isValidPath = true + break + end + end + + if not isValidPath then + windower.add_to_chat(redTextColor, 'The augment path you entered is not valid. Please check the possible augment list for ' .. augItemType:lower() .. ' using the //ct list command. You entered: ' .. augPath) + validArguments = false + end + end + + if validArguments then + maxAugKey = string.lower(augItemType .. augPath) + capeHasBeenPrepared = true + timesAugmentedCount = 1 + windower.add_to_chat(greenTextColor, 'You can now augment your ' .. jobToCapeMap[jobName] .. ' with ' .. augPath:lower() .. ' using abdhaljs ' .. augItemType:lower() .. '.') + else + capeHasBeenPrepared = false + currentCape = nil + pathItem = nil + end + else + windower.add_to_chat(redTextColor, 'You can\'t setup another cape while you are currently augmenting one.') + end +end + +function startAugmentingCape(numberOfRepeats, firstAttempt) + inventory = windower.ffxi.get_items(inventoryBagNumber) + currentlyAugmenting = true + local augStatus = nil + local capeIndex + local augmentItemIndex + if capeHasBeenPrepared and checkCapeCount() and checkAugItemCount(numberOfRepeats) and checkDistanceToGorpa() then + augStatus = checkAugLimits():lower() + capeIndex = getItemIndex(currentCape) + augmentItemIndex = getItemIndex(pathItem) + end + + if firstAttempt and augStatus then + if augStatus ~= 'maxed' and augStatus ~= 'notmatching' then + if augStatus:lower() ~= 'empty' then + firstTimeAug = false + else + firstTimeAug = true + end + + local temp + if tonumber(numberOfRepeats) == 1 then + temp = 'time.' + else + temp = 'times.' + end + + numberOfTimesToAugment = numberOfRepeats + windower.add_to_chat(blueTextColor, 'Starting to augment your ' .. currentCape.name .. ' ' .. numberOfRepeats .. ' ' .. temp) + + tradeReady = false + buildTrade(capeIndex,augmentItemIndex) + else + currentlyAugmenting = false + tradeReady = false + end + elseif not firstAttempt and augStatus then + if augStatus and augStatus ~= 'maxed' then + tradeReady = false + buildTrade(capeIndex,augmentItemIndex) + else + capeHasBeenPrepared = false + currentlyAugmenting = false + tradeReady = false + pathItem = nil + currentCape = nil + windower.add_to_chat(blueTextColor, 'Your cape is currently maxed in that augment path, ending the augment process now.') + end + elseif not capeHasBeenPrepared then + currentlyAugmenting = false + windower.add_to_chat(redTextColor, 'You have not yet setup your cape and augment information with the //ct prep command!') + else + currentlyAugmenting = false + end +end + +function checkAugLimits() + local capeItem + for index, item in pairs(inventory) do + if index ~= 'max' and index ~= 'count' and index ~= 'enabled' then + if item.id == currentCape.id then + capeItem = item + break + end + end + end + + local augmentTable + if extData.decode(capeItem).augments then + augmentTable = extData.decode(capeItem).augments + end + + local augValue + if augmentTable then + if pathItem.english:lower():endswith(' thread') then + augValue = augmentTable[threadIndex] + elseif pathItem.english:lower():endswith(' dust') then + augValue = augmentTable[dustIndex] + elseif pathItem.english:lower():endswith(' dye') then + augValue = augmentTable[dyeIndex] + elseif pathItem.english:lower():endswith(' sap') then + augValue = augmentTable[sapIndex] + end + else + augValue = 'none' + end + + if augValue:lower() == 'none' or not augmentTable then + return 'empty' + end + + local max = maxAugMap[maxAugKey].max + if augValue:contains(max) then + windower.add_to_chat(redTextColor, 'You have augmented your ' .. currentCape.name .. ' to the max already with ' .. pathItem.name .. '.') + return 'maxed' + end + + local mustContainTable = maxAugMap[maxAugKey].mustcontain + for k, augmentString in pairs(mustContainTable) do + if not augValue:lower():contains(augmentString:lower()) then + windower.add_to_chat(redTextColor,'You can\'t augment your ' .. currentCape.name .. ' with ' .. pathName .. ' because it has already been augmented with: ' .. augValue .. ' using ' .. pathItem.name .. '.') + return 'notmatching' + end + end + + local cantContainTable = maxAugMap[maxAugKey].cantcontain + if table.length(cantContainTable) > 0 then + for k, augmentString in pairs(cantContainTable) do + if augValue:lower():contains(augmentString:lower()) then + windower.add_to_chat(redTextColor,'You can\'t augment your ' .. currentCape.name .. 'with ' .. pathName .. ' because it has already been augmented with: ' .. augValue .. ' using ' .. pathItem.name .. '.') + return 'notmatching' + end + end + end + + return 'allclear' +end + +function injectAugmentConfirmationPackets() + local optionIndex + if firstTimeAug then + firstTimeAug = false + optionIndex = 512 + else + optionIndex = 256 + end + + local augmentChoicePacket = packets.new('outgoing', 0x05B) + augmentChoicePacket["Target"] = gorpaID + augmentChoicePacket["Option Index"] = optionIndex + augmentChoicePacket["_unknown1"] = pathIndex + augmentChoicePacket["Target Index"] = gorpaTargetIndex + augmentChoicePacket["Automated Message"] = true + augmentChoicePacket["Zone"] = mhauraID + augmentChoicePacket["Menu ID"] = gorpaMenuID + packets.inject(augmentChoicePacket) + + augmentChoicePacket["Automated Message"] = false + packets.inject(augmentChoicePacket) + + local playerUpdatePacket = packets.new('outgoing', 0x016, { + ["Target Index"] = windower.ffxi.get_mob_by_target('me').index, + }) + packets.inject(playerUpdatePacket) +end + +function updateGorpaID() + if not gorpaID then + local zoneID = tonumber(windower.ffxi.get_info().zone) + if zoneID == mhauraID then + local gorpa = windower.ffxi.get_mob_by_name('Gorpa-Masorpa') + gorpaID = gorpa.id + gorpaTargetIndex = gorpa.index + end + end +end + +function printAugList() + windower.add_to_chat(blueTextColor, 'Thread: hp mp str dex vit agi int mnd chr petmelee petmagic') + windower.add_to_chat(blueTextColor, 'Dust: acc/atk racc/ratk macc/mdmg eva/meva') + windower.add_to_chat(blueTextColor, 'Sap: wsd critrate stp doubleattack haste dw enmity+ enmity- snapshot mab fc curepotency waltzpotency petregen pethaste') + windower.add_to_chat(blueTextColor, 'Dye: hp mp str dex vit agi int mnd chr acc atk racc ratk macc mdmg eva meva petacc petatk petmacc petmdmg') +end + +function printHelp() + windower.add_to_chat(blueTextColor, string.format('%s Version: %s Command Listing:', _addon.name, _addon.version)) + windower.add_to_chat(blueTextColor, ' reload|r Reload CapeTrader.') + windower.add_to_chat(blueTextColor, ' unload|u Unload CapeTrader.') + windower.add_to_chat(blueTextColor, ' prep <jobName> <augItem> <augPath> Prepares a given job\'s cape with augItem on augPath. Need to use this before using //ct go.') + windower.add_to_chat(blueTextColor, ' go <#repeats> Starts augmenting cape with the info gathered from the prep command. The repeats input defaults to one if not provided.') + windower.add_to_chat(blueTextColor, ' list|l Lists all possible augitems and their valid paths. Use to know what the valid inputs for //ct prep are.') +end diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/jobToCapeMap.lua b/Data/DefaultContent/Libraries/addons/addons/capetrader/jobToCapeMap.lua new file mode 100644 index 0000000..552923c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/jobToCapeMap.lua @@ -0,0 +1,50 @@ +--[[Copyright © 2016, Lygre, Burntwaffle@Odin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of CapeTrader nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Lygre, Burntwaffle@Odin 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.]]-- + +return T{ + ['war'] = "Cichol's Mantle", + ['mnk'] = "Segomo's Mantle", + ['whm'] = "Alaunus's Cape", + ['blm'] = "Taranus's Cape", + ['rdm'] = "Sucellos's Cape", + ['thf'] = "Toutatis's Cape", + ['pld'] = "Rudianos's Mantle", + ['drk'] = "Ankou's Mantle", + ['bst'] = "Artio's Mantle", + ['brd'] = "Intarabus's Cape", + ['rng'] = "Belenus's Cape", + ['sam'] = "Smertrios's Mantle", + ['nin'] = "Andartia's Mantle", + ['drg'] = "Brigantia's Mantle", + ['smn'] = "Campestres's Cape", + ['blu'] = "Rosmerta's Cape", + ['cor'] = "Camulus's Mantle", + ['pup'] = "Visucius's Mantle", + ['dnc'] = "Senuna's Mantle", + ['sch'] = "Lugh's Cape", + ['geo'] = "Nantosuelta's Cape", + ['run'] = "Ogma's cape", +} diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/maxAugMap.lua b/Data/DefaultContent/Libraries/addons/addons/capetrader/maxAugMap.lua new file mode 100644 index 0000000..c4d1737 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/maxAugMap.lua @@ -0,0 +1,81 @@ +--[[Copyright © 2016, Lygre, Burntwaffle@Odin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of CapeTrader nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY Lygre, Burntwaffle@Odin 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.]]-- + +return T{ + ['threadhp'] = {['max'] = '60', ['mustcontain'] = T{'hp'},['cantcontain'] = T{}}, + ['threadmp'] = {['max'] = '60', ['mustcontain'] = T{'mp'},['cantcontain'] = T{}}, + ['threadstr'] = {['max'] = '20', ['mustcontain'] = T{'str'},['cantcontain'] = T{}}, + ['threaddex'] = {['max'] = '20', ['mustcontain'] = T{'dex'},['cantcontain'] = T{}}, + ['threadvit'] = {['max'] = '20', ['mustcontain'] = T{'vit'},['cantcontain'] = T{}}, + ['threadagi'] = {['max'] = '20', ['mustcontain'] = T{'agi'},['cantcontain'] = T{}}, + ['threadint'] = {['max'] = '20', ['mustcontain'] = T{'int'},['cantcontain'] = T{}}, + ['threadmnd'] = {['max'] = '20', ['mustcontain'] = T{'mnd'},['cantcontain'] = T{}}, + ['threadchr'] = {['max'] = '20', ['mustcontain'] = T{'chr'},['cantcontain'] = T{}}, + ['threadpetmelee'] = {['max'] = '20', ['mustcontain'] = T{'acc','r.acc','atk.','r.atk'},['cantcontain'] = T{}}, + ['threadpetmagic'] = {['max'] = '20', ['mustcontain'] = T{'pet','m.acc.','m.dmg.'},['cantcontain'] = T{}}, + + ['dustacc/atk'] = {['max'] = '20', ['mustcontain'] = T{'accuracy','attack'},['cantcontain'] = T{}}, + ['dustracc/ratk'] = {['max'] = '20', ['mustcontain'] = T{'rng.acc','rng.atk'},['cantcontain'] = T{}}, + ['dustmacc/mdmg'] = {['max'] = '20', ['mustcontain'] = T{'mag. acc','mag. dmg.'},['cantcontain'] = T{}}, + ['dusteva/meva'] = {['max'] = '20', ['mustcontain'] = T{'eva.','mag. eva.'},['cantcontain'] = T{}}, + + ['sapwsd'] = {['max'] = '10', ['mustcontain'] = T{'weapon skill damage'},['cantcontain'] = T{}}, + ['sapcritrate'] = {['max'] = '10', ['mustcontain'] = T{'crit'},['cantcontain'] = T{}}, + ['sapstp'] = {['max'] = '10', ['mustcontain'] = T{'store tp'},['cantcontain'] = T{}}, + ['sapdoubleattack'] = {['max'] = '10', ['mustcontain'] = T{'dbl.atk.'},['cantcontain'] = T{}}, + ['saphaste'] = {['max'] = '10', ['mustcontain'] = T{'haste'},['cantcontain'] = T{'pet'}}, + ['sapdw'] = {['max'] = '10', ['mustcontain'] = T{'dual'},['cantcontain'] = T{}}, + ['sapenmity+'] = {['max'] = '10', ['mustcontain'] = T{'enmity','+'},['cantcontain'] = T{}}, + ['sapenmity-'] = {['max'] = '10', ['mustcontain'] = T{'enmity','-'},['cantcontain'] = T{}}, + ['sapsnapshot'] = {['max'] = '10', ['mustcontain'] = T{'snapshot'},['cantcontain'] = T{}}, + ['sapmab'] = {['max'] = '10', ['mustcontain'] = T{'mag.atk.bns.'},['cantcontain'] = T{}}, + ['sapfc'] = {['max'] = '10', ['mustcontain'] = T{'fast cast'},['cantcontain'] = T{}}, + ['sapcurepotency'] = {['max'] = '10', ['mustcontain'] = T{'cure'},['cantcontain'] = T{}}, + ['sapwaltzpotency'] = {['max'] = '10', ['mustcontain'] = T{'waltz'},['cantcontain'] = T{}}, + ['sappetregen'] = {['max'] = '10', ['mustcontain'] = T{'pet','regen'},['cantcontain'] = T{}}, + ['sappethaste'] = {['max'] = '10', ['mustcontain'] = T{'pet','haste'},['cantcontain'] = T{}}, + + ['dyehp'] = {['max'] = '20', ['mustcontain'] = T{'hp'},['cantcontain'] = T{}}, + ['dyemp'] = {['max'] = '20', ['mustcontain'] = T{'mp'},['cantcontain'] = T{}}, + ['dyestr'] = {['max'] = '10', ['mustcontain'] = T{'str'},['cantcontain'] = T{}}, + ['dyedex'] = {['max'] = '10', ['mustcontain'] = T{'dex'},['cantcontain'] = T{}}, + ['dyevit'] = {['max'] = '10', ['mustcontain'] = T{'vit'},['cantcontain'] = T{}}, + ['dyeagi'] = {['max'] = '10', ['mustcontain'] = T{'agi'},['cantcontain'] = T{}}, + ['dyeint'] = {['max'] = '10', ['mustcontain'] = T{'int'},['cantcontain'] = T{}}, + ['dyemnd'] = {['max'] = '10', ['mustcontain'] = T{'mnd'},['cantcontain'] = T{}}, + ['dyechr'] = {['max'] = '10', ['mustcontain'] = T{'chr'},['cantcontain'] = T{}}, + ['dyeacc'] = {['max'] = '10', ['mustcontain'] = T{'accuracy'},['cantcontain'] = T{'pet'}}, + ['dyeatk'] = {['max'] = '10', ['mustcontain'] = T{'attack'},['cantcontain'] = T{'pet'}}, + ['dyeracc'] = {['max'] = '10', ['mustcontain'] = T{'rng', 'acc'},['cantcontain'] = T{'pet'}}, + ['dyeratk'] = {['max'] = '10', ['mustcontain'] = T{'rng', 'atk'},['cantcontain'] = T{'pet'}}, + ['dyemacc'] = {['max'] = '10', ['mustcontain'] = T{'mag', 'acc'},['cantcontain'] = T{'pet'}}, + ['dyemdmg'] = {['max'] = '10', ['mustcontain'] = T{'magic', 'damage'},['cantcontain'] = T{'pet'}}, + ['dyeeva'] = {['max'] = '10', ['mustcontain'] = T{'evasion'}, ['cantcontain'] = T{'mag'}}, + ['dyemeva'] = {['max'] = '10', ['mustcontain'] = T{'mag', 'evasion'},['cantcontain'] = T{}}, + ['dyepetacc'] = {['max'] = '10', ['mustcontain'] = T{'pet','accuracy','rng', 'acc'},['cantcontain'] = T{}}, + ['dyepetatk'] = {['max'] = '10', ['mustcontain'] = T{'pet','attack','rng', 'atk'},['cantcontain'] = T{}}, + ['dyepetmacc'] = {['max'] = '10', ['mustcontain'] = T{'pet','mag' , 'acc'},['cantcontain'] = T{}}, + ['dyepetmdmg'] = {['max'] = '10', ['mustcontain'] = T{'pet','magic', 'damage'},['cantcontain'] = T{}}, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/capetrader/validItemNames.lua b/Data/DefaultContent/Libraries/addons/addons/capetrader/validItemNames.lua new file mode 100644 index 0000000..9e4fa5f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/capetrader/validItemNames.lua @@ -0,0 +1,54 @@ +--[[Copyright © 2016, Lygre, Burntwaffle@Odin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of CapeTrader nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Lygre, Burntwaffle@Odin 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.]]-- + +return T{ + "Cichol's Mantle", + "Segomo's Mantle", + "Alaunus's Cape", + "Taranus's Cape", + "Sucellos's Cape", + "Toutatis's Cape", + "Rudianos's Mantle", + "Ankou's Mantle", + "Artio's Mantle", + "Intarabus's Cape", + "Belenus's Cape", + "Smertrios's Mantle", + "Andartia's Mantle", + "Brigantia's Mantle", + "Campestres's Cape", + "Rosmerta's Cape", + "Camulus's Mantle", + "Visucius's Mantle", + "Senuna's Mantle", + "Lugh's Cape", + "Nantosuelta's Cape", + "Ogma's cape", + "Abdhaljs Thread", + "Abdhaljs Dust", + "Abdhaljs Dye", + "Abdhaljs Sap", +} diff --git a/Data/DefaultContent/Libraries/addons/addons/cellhelp/README.md b/Data/DefaultContent/Libraries/addons/addons/cellhelp/README.md new file mode 100644 index 0000000..6eb5004 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cellhelp/README.md @@ -0,0 +1,39 @@ +Authors: Balloon, Krizz + +Version: 2.0 + +Date: 20130429 + +Cell Helper, for old Salvage + +Abbreviation: //ch + +Commands: +* help - Shows a menu of commands in game +* pos <x> <y> - Positions the Lot Order box. Default location is 1000,250. +* hide - Hides the Lot Order box +* show - Shows the Lot Order box +* set [set id] - Loads set from settings file. Default is set1. +* mode [lots/nolots] - If set to nolots, ll will not lot cells. Default is lots. +* timer [start/stop] - Will start or stop the 100 minute zone timer. + +Other Information: +* If you need to remove a cell from the Lot Order box, type in "/echo [You] obtains a --[cellname] cell--." You must have the dashes around the cellname, and the period at the end. +* If you have gear you want to pass or lot, add it in the appropriate tags for your player. If not, leave the value as 0. Otherwise you will see errors. +* As with the base LL, items already in the pool cannot be lotted/passed when the profile is loaded. + + +To do: +* Support for more than 4 people +* Remove (or create a toggle) for the Item Counter. +* Enable xml creation if settings file is not found. +* Change zone comparison to use zone IDs. +* Ability to add cell back to list. +* Box parameter settings. +* Interface adjustments. Icons? + +Known Issues: +* Pass tag errors +* Cellhelp shows itself again after hide. + + diff --git a/Data/DefaultContent/Libraries/addons/addons/cellhelp/cellhelp.lua b/Data/DefaultContent/Libraries/addons/addons/cellhelp/cellhelp.lua new file mode 100644 index 0000000..e0a50a4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cellhelp/cellhelp.lua @@ -0,0 +1,273 @@ +--Copyright (c) 2013, Thomas Rogers / Balloon - Cerberus and Krizz +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of cellhelp nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL THOMAS ROGERS OR KRIZZ BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon = _addon or {} +_addon.name = 'cellhelp' +_addon.commands = {'cellhelp','ch'} +_addon.version = 0.1 + + +local config = require 'config' + + +require 'tables' +require 'strings' +require 'maths' +require 'logger' +----------------------------- + +local settingtab = nil +local settings_file = 'data\\settings.xml' +local settingtab = config.load(settings_file) +if settingtab == nil then + print('No settings file found. Ensure you have a file at data\\settings.xml') +end +--variables + lotorder = '' + set = "set1" + mode = "lot" + posx = 1000 + posy = 250 + if settingtab['posx'] ~= nil then + posx = settingtab['posx'] + posy = settingtab['posy'] + end + itemcount = 0 + salvage_cell_name = { + 'incus cell','castellanus cell','undulatus cell','cumulus cell','radiatus cell','virga cell','cirrocumulus cell','stratus cell','duplicatus cell','opacus cell', 'praecipitatio cell', 'humilus cell','spissatus cell', 'pannus cell', 'fractus cell','congestus cell', 'nimbus cell', 'velum cell','pileus cell', 'mediocris cell' + } + salvage_cell_name_short = { + 'incus','castellanus','undulatus','cumulus','radiatus','virga','cirrocumulus','stratus','duplicatus','opacus', 'praecipitatio', 'humilus','spissatus', 'pannus', 'fractus','congestus','nimbus','velum','pileus', 'mediocris', 'alex' + } + salvage_cell_ident = { + 'Weapons and Shields', 'Head and Neck', 'Ranged and Ammo', 'Body', 'Hand', 'Earring and Ring', 'Back and Waist', 'Legs and Feet', 'Support Job','Job and Weaponskill', 'Magic', 'HP', 'MP', 'STR', 'DEX', 'VIT', 'AGI', 'INT', 'MND', 'CHR' + } + cells_id = { + '5365','5366','5371','5367','5368','5372','5370','5369','5373','5374','5375','5383','5384','5376','5377','5378','5379','5380','5381','5382','2488,5735,5736' + } + cell_lots ={ + 'incus','castellanus','undulatus','cumulus','radiatus','virga','cirrocumulus','stratus','duplicatus','opacus', 'praecipitatio', 'humilus','spissatus', 'pannus', 'fractus','congestus','nimbus','velum','pileus', 'mediocris','alex' + } + players = {'player1', 'player2', 'player3', 'player4'} + +salvage_zones = S{73, 74, 75, 76} + +function settings_create() +-- get player's name + player = windower.ffxi.get_player()['name'] +-- dynamic players from settings + for i=1, #players do + playernumber = players[i] + players[players[i]] = settingtab[set][playernumber]['name'] + if players[players[i]] == player then + player_num = players[i] + if player_num == nil or player_num == '' then + player_num = 'player1' + end + end + end + +-- set lot positions + for i=1, #salvage_cell_name_short do + if salvage_cell_name_short[i] ~= nil then + item = salvage_cell_name_short[i] + cell_lots[item] = settingtab[set][item][player_num] + end + end +-- Populate lot order + orderlots() + +end + +windower.register_event('addon command',function (...) + local params = {...}; + if #params < 1 then + return + end + if params[1] then + if params[1]:lower() == "help" then + print('ch help : Shows help message') + print('ch pos <x> <y> : Positions the list') + print('ch hide : Hides the box') + print('ch show : Shows the box') + print('ch set [set id] : Loads set from settings file. Default is set1') + print('ch mode [lots/nolots] : If mode is changed to nolots, ll will not lot cells automatically.') + elseif params[1]:lower() == "pos" then + if params[3] then + local posx, posy = tonumber(params[2]), tonumber(params[3]) + windower.text.set_location('salvage_box', posx, posy) + end + elseif params[1]:lower() == "start" then + initialize() + elseif params[1]:lower() == "hide" then + windower.text.set_visibility('salvage_box', false) + elseif params[1]:lower() == "show" then + windower.text.set_visibility('salvage_box', true) + elseif params[1]:lower() == "set" then + if params[2] then + set = params[2]:lower() + --Set variables from settings file + settings_create() + --Populate lot order + orderlots() + --Populate initial LL + lightluggage() + windower.send_command('ll profile salvage-'..player..'.txt') + initialize() + end + elseif params[1]:lower() == "mode" then + if params[2] == "lots" then + mode = params[2]:lower() + print('Mode changed to: Cast lots') + lightluggage() + elseif params[2] == "nolots" then + mode = params[2]:lower() + print('Mode changed to: Do not cast lots') + lightluggage() + else print('Invalid mode option') + end + elseif params[1]:lower() == "timer" then + if params[2] == "start" then + windower.send_command('timers c Remaining 6000 up') + elseif params[2] == "stop" then + windower.send_command('timers d Remaining') + end + end + end +end) + +windower.register_event('load',function () + player = windower.ffxi.get_player()['name'] + print('CellHelp loaded. CellHelp Authors: Cerberus.Balloon and Bahamut.Krizz') + mode = settingtab["mode"] + --Initial lot setting + settings_create() + --Populate initial LL + lightluggage() + windower.send_command('ll profile salvage-'..player..'.txt') + initialize() +end ) + +windower.register_event('login', settings_create) + +windower.register_event('zone change', function(id) + if salvage_zones:contains(id) then + windower.send_command('timers c Remaining 6000 up') + else + windower.send_command('timers d Remaining') + end +end) + +function orderlots() + lotorder = " " + for i=1, #salvage_cell_name_short do + if salvage_cell_name_short[i] ~= 'alex' and cell_lots[salvage_cell_name_short[i]] ~= 0 then + item = salvage_cell_name_short[i] + if cell_lots[item] ~= nil and cell_lots[item] ~= 0 then + lotorder = (lotorder..item..': '..cell_lots[item]..' \n ') + end + elseif salvage_cell_name_short[i] == 'alex' and cell_lots[salvage_cell_name_short[i]] ~= 0 then + item = salvage_cell_name_short[i] + lotorder = (lotorder..item..' \n ') + end + end + --Temporary Item Counter to see if items are registering with filter. + lotorder = (lotorder.."\n Item Counter: "..itemcount) +end + +function lightluggage() + llprofile = "" + ll_lots = "" + ll_pass = "" + for i=1, #salvage_cell_name_short do + if salvage_cell_name_short[i] ~= nil then + if cell_lots[salvage_cell_name_short[i]] == 1 then + ll_lots = (ll_lots..cells_id[i]..',') + elseif cell_lots[salvage_cell_name_short[i]] == 0 then + if salvage_cell_name_short[i] ~= "alex" then + ll_pass = (ll_pass..cells_id[i]..',') + end + end + end + end + if mode == "lots" and ll_lots ~= "" then + llprofile = (llprofile..'if item is '..ll_lots..' then lot \n') + end + if ll_pass ~= "" then + llprofile = (llprofile..'if item is '..ll_pass..' then pass \n') + end + if settingtab[set][player_num]['pass'] ~= 0 then + llprofile = (llprofile.."if item is "..settingtab[set][player_num]['pass'].." then pass \n") + end + if settingtab[set][player_num]['lot'] ~= 0 then + llprofile = (llprofile.."if item is "..settingtab[set][player_num]['lot'].." then lot \n") + end + + io.open(windower.addon_path..'../../plugins/ll/salvage-'..player..'.txt',"w"):write(llprofile):close() +end + +function initialize() + windower.text.create('salvage_box') + windower.text.set_bg_color('salvage_box',200,30,30,30) + windower.text.set_color('salvage_box',255,200,200,200) + windower.text.set_location('salvage_box',posx,posy) + windower.text.set_visibility('salvage_box',1) + windower.text.set_bg_visibility('salvage_box',1) + windower.text.set_font('salvage_box','Arial',12) + windower.text.set_text('salvage_box',' Lot order: \n'..lotorder); +end + +windower.register_event('incoming text',function (original, new, color) + a,b,name,cell = string.find(original,'(%w+) obtains an? ..(%w+) cell..\46') + if cell ~= nil then + if name == player then + cell_lots[cell] = 0 + itemcount = itemcount + 1 + elseif name ~= player and cell_lots[cell] > 1 then + cell_lots[cell] = cell_lots[cell] - 1 + end + -- Populate lot order + orderlots() + -- Update lightluggage + lightluggage() + -- Update textbox + initialize() + return new, color + end + + a,b,cell2 = string.find(original,'You find an? ..(%w+)..') + if cell2 ~= nil then + if cell_lots[cell2] ~= 0 and cell_lots[cell2] ~= nil then + new = 'You find a '..string.char(31,158)..cell2..' cell.'..string.char(31,167)..' /Need/' + end + return new, color + end +end ) + +windower.register_event('unload',function () + windower.text.delete('salvage_box') + windower.send_command('timers d Remaining') +end ) diff --git a/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/example_settings.xml b/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/example_settings.xml new file mode 100644 index 0000000..5154181 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/example_settings.xml @@ -0,0 +1,306 @@ +<?xml version="1.0" ?> + +<settings> + + <global> + <posx>1025</posx> + <posy>250</posy> + <mode>lots</mode> + <set1> + <player1> + <name>Krizz</name> + <pass>14547, 14557, 14563, 14564, 14962, 14963, 14974, 14978, 14979, 14980, 15628, 15638, 15639, 15640, 15644, 15726, 15728, 16085, 16086, 16093, 16101</pass> + <lot>0</lot> + </player1> + <player2> + <name>Savannahlynn</name> + <pass>0</pass> + <lot>0</lot> + </player2> + <player3> + <name>Svedin</name> + <pass>0</pass> + <lot>0</lot> + </player3> + <player4> + <name>Bellanotte</name> + <pass>0</pass> + <lot>0</lot> + </player4> + <incus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </incus> + <castellanus> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </castellanus> + <undulatus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </undulatus> + <cumulus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </cumulus> + <radiatus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </radiatus> + <virga> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </virga> + <cirrocumulus> + <player1>3</player1> + <player2>2</player2> + <player3>1</player3> + <player4>4</player4> + </cirrocumulus> + <stratus> + <player1>4</player1> + <player2>2</player2> + <player3>1</player3> + <player4>3</player4> + </stratus> + <duplicatus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </duplicatus> + <opacus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </opacus> + <praecipitatio> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </praecipitatio> + <humilus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </humilus> + <spissatus> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </spissatus> + <pannus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </pannus> + <fractus> + <player1>4</player1> + <player2>1</player2> + <player3>3</player3> + <player4>2</player4> + </fractus> + <congestus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </congestus> + <nimbus> + <player1>4</player1> + <player2>2</player2> + <player3>3</player3> + <player4>1</player4> + </nimbus> + <velum> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </velum> + <pileus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </pileus> + <mediocris> + <player1>3</player1> + <player2>2</player2> + <player3>1</player3> + <player4>4</player4> + </mediocris> + <alex> + <player1>0</player1> + <player2>1</player2> + <player3>0</player3> + <player4>0</player4> + </alex> + </set1> + <set2> + <player1> + <name>Krizz</name> + <pass>14547, 14557, 14563, 14564, 14962, 14963, 14974, 14978, 14979, 14980, 15628, 15638, 15639, 15640, 15644, 15726, 15728, 16085, 16086, 16093, 16101</pass> + <lot>0</lot> + </player1> + <player2> + <name>Savannahlynn</name> + <pass>0</pass> + <lot>0</lot> + </player2> + <player3> + <name>Svedin</name> + <pass>0</pass> + <lot>0</lot> + </player3> + <player4> + <name>Bellanotte</name> + <pass>0</pass> + <lot>0</lot> + </player4> + <incus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </incus> + <castellanus> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </castellanus> + <undulatus> + <player1>2</player1> + <player2>3</player2> + <player3>1</player3> + <player4>4</player4> + </undulatus> + <cumulus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </cumulus> + <radiatus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </radiatus> + <virga> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </virga> + <cirrocumulus> + <player1>3</player1> + <player2>2</player2> + <player3>1</player3> + <player4>4</player4> + </cirrocumulus> + <stratus> + <player1>4</player1> + <player2>2</player2> + <player3>1</player3> + <player4>3</player4> + </stratus> + <duplicatus> + <player1>3</player1> + <player2>1</player2> + <player3>2</player3> + <player4>4</player4> + </duplicatus> + <opacus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </opacus> + <praecipitatio> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </praecipitatio> + <humilus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </humilus> + <spissatus> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </spissatus> + <pannus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </pannus> + <fractus> + <player1>4</player1> + <player2>1</player2> + <player3>3</player3> + <player4>2</player4> + </fractus> + <congestus> + <player1>4</player1> + <player2>1</player2> + <player3>2</player3> + <player4>3</player4> + </congestus> + <nimbus> + <player1>4</player1> + <player2>2</player2> + <player3>3</player3> + <player4>1</player4> + </nimbus> + <velum> + <player1>1</player1> + <player2>3</player2> + <player3>2</player3> + <player4>4</player4> + </velum> + <pileus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </pileus> + <mediocris> + <player1>3</player1> + <player2>2</player2> + <player3>1</player3> + <player4>4</player4> + </mediocris> + <alex> + <player1>0</player1> + <player2>1</player2> + <player3>0</player3> + <player4>0</player4> + </alex> + </set2> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/settings.xml new file mode 100644 index 0000000..d8fd035 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/cellhelp/data/settings.xml @@ -0,0 +1,158 @@ +<?xml version="1.0" ?> + +<settings> + + <global> + <posx>1025</posx> + <posy>250</posy> + <mode>lots</mode> + <set1> + <player1> + <name></name> + <pass>0</pass> + <lot>0</lot> + </player1> + <player2> + <name></name> + <pass>0</pass> + <lot>0</lot> + </player2> + <player3> + <name></name> + <pass>0</pass> + <lot>0</lot> + </player3> + <player4> + <name></name> + <pass>0</pass> + <lot>0</lot> + </player4> + <incus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </incus> + <castellanus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </castellanus> + <undulatus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </undulatus> + <cumulus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </cumulus> + <radiatus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </radiatus> + <virga> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </virga> + <cirrocumulus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </cirrocumulus> + <stratus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </stratus> + <duplicatus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </duplicatus> + <opacus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </opacus> + <praecipitatio> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </praecipitatio> + <humilus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </humilus> + <spissatus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </spissatus> + <pannus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </pannus> + <fractus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </fractus> + <congestus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </congestus> + <nimbus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </nimbus> + <velum> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </velum> + <pileus> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </pileus> + <mediocris> + <player1>1</player1> + <player2>2</player2> + <player3>3</player3> + <player4>4</player4> + </mediocris> + <alex> + <player1>0</player1> + <player2>0</player2> + <player3>0</player3> + <player4>0</player4> + </alex> + </set1> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/chars/README.md b/Data/DefaultContent/Libraries/addons/addons/chars/README.md new file mode 100644 index 0000000..77609ec --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/chars/README.md @@ -0,0 +1,34 @@ +# Chars # +This addon lets you input special chars using simple tags (e.g.: ```<note>``` for ♪). Using the pattern ```<j:text>``` any alphanumeric character will be replaced with their full-width version ("japanese style" characters). The available characters depend on the [data](https://github.com/Windower/Lua/blob/master/addons/libs/chat/chars.lua) gathered by the Windower team. If anything in there is incorrect or missing, open an issue on [Windower's Lua issue tracker](https://github.com/Windower/Lua/issues). + + + +## Commands ## + +``` +chars +``` + +Shows the available characters. + +---- + +## Changelog ## + +### v1.20141219 ### +* **fix:** Target-related tags were removed incorrectly + +### v1.20141218 ### +* **fix:** Adjusted to Windower's new Lua libs API + +### v1.20130529 ### +* **change:** Aligned to Windower's addon development guidelines. + +### v1.20130525 ### +* **fix:** ```<j:text>``` pattern wasn't working with some special chars. + +### v1.20130521 ### +* **add:** added the pattern to write using alphanumeric japanese characters. + +### v1.20130421 ### +* first release. diff --git a/Data/DefaultContent/Libraries/addons/addons/chars/chars.lua b/Data/DefaultContent/Libraries/addons/addons/chars/chars.lua new file mode 100644 index 0000000..6cc8d82 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/chars/chars.lua @@ -0,0 +1,61 @@ +--[[ +Copyright 2013-2014, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of chars nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require('lists') +require('logger') +require('strings') + +_addon.name = 'chars' +_addon.version = '1.20141219' +_addon.command = 'chars' + +chars = require('chat.chars') + +windower.register_event('addon command', function(...) + for code, char in pairs(chars) do + log('<%s>: %s':format(code, char)) + end + + log('Using the pattern <j:text> any alphanumeric character will be replaced with its full-width ("japanese style") version') +end) + +windower.register_event('outgoing text', function(_, modified) + return modified:psplit('<[^>]+>', nil, true):map(function(token) + if token:match('^<.*>$') then + if token:startswith('<j:') then + return token:sub(4, -2):map(function(char) + return chars['j' .. char] or char + end) + else + return chars[token:sub(2, -2)] or token + end + else + return token + end + end):concat() +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/chatPorter/README.md b/Data/DefaultContent/Libraries/addons/addons/chatPorter/README.md new file mode 100644 index 0000000..dc85964 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/chatPorter/README.md @@ -0,0 +1,66 @@ +#ChatPorter v1.39 +####written by Ikonic + +Displays tell, party, and linkshell chat to alternate character and optional textbox. +Also, allows you to reply from either character. + +Requires two characters to both be using addon for it to work. +Currently only tested and supporting two characters. + +####//ChatPorter and //cp are both valid commands. +//cp help : Lists this menu. +//cp status : Shows current configuration. +//cp textbox : Shows current textbox configurations. +//cp colors : Shows possible color codes. +//cp toggle : Toggles ChatPorter on/off. +//cp [l|p|t] [toggle|displaychat] : Toggles linkshell|party|tell messages from showing or not. +//cp [l|p|t] color # : Sets color of l|p|t text (acceptable values of 1-255). +//cp [l|p|t|f] show : Toggles l|p|t textboxes from showing. +//cp [l|p|t|f] [fontname|fn, lines, fontsize|fs, x, y, alpha|a, red|r, green|g, blue|b] # : Sets l|p|t textbox specifics. +//[l2|p2|t2 name|r2] message : Sends message from second character to linkshell|party|tell|reply. +//[f#|cp f#] message : Sends message from second character to ffochat channel. +//cp help detail : Shows detailed ChatPorter commands. +//cp help textbox : Shows detailed textbox commands. + +####ChatPorter detailed commands: +//l2 message : Sends message from second character to linkshell. +//p2 message : Sends message from second character to party. +//t2 name message : Sends message from second character to name in tell. +//r2 message : Sends reply message from second character. +//f# message : Sends message from second character to FFOChat channel #. Works for 1-5. +//cp f# message : Same as f#, but for any #. + +####ChatPorter textbox commands: +//cp [l|p|t|f] [toggle|displaychat] : Toggles linkshell|party|tell|ffochat messages from showing or not. +//cp [l|p|t] color # : Sets color of l|p|t text (acceptable values of 1-255). +//cp [l|p|t|f] show : Toggles l|p|t|f textboxes from showing. +//cp [l|p|t|f] clear : Clears l|p|t|f textbox. +//cp clear : Clears all textboxes. +//cp [l|p|t|f] lines # : Sets # of lines to show in textbox. +//cp [l|p|t|f] [fontname|fn] * : Sets fontname for textbox. +//cp [l|p|t|f] [fontsize|fs] # : Sets fontsize for textbox. +//cp [l|p|t|f] x # : Sets x coordinate for textbox (acceptable values: 10-1014). +//cp [l|p|t|f] y # : Sets y coordinate for textbox (acceptable values: 10-758). +//cp [l|p|t|f] [alpha|a] # : Sets alpha (transparency) for textbox (acceptable values: 1-255; 0=fully transparent, 255=fully visible). +//cp [l|p|t|f] [red|r] # : Sets red value for RGB color of text in textbox. +//cp [l|p|t|f] [green|g] # : Sets green value for RGB color of text in textbox. + +###Changelog: +* v0.0 05/20/13 Created addon. +* v1.0 05/22/13 Testing, variable setup, boolean conversion, added toggles. +* v1.1 05/25/13 Added l2/p2/t2/r2 options to chat on 2nd char from 1st. +* v1.11 05/25/13 Added support for FFOChat replying. +* v1.12 05/29/13 Removed testing code, removed unused functions. +* v1.13 05/29/13 More cleaning of code, added some color functions, variable formatting. +* v1.2 05/31/13 Added settings.xml data and ability to change/use it. +* v1.21 05/31/13 Added code to change colors and option to list colors. +* v1.3 06/06/13 Added textboxes and user settings for l/p/t chat. Redid help options. +* v1.31 06/07/13 Settings can now be set for each character and are only saved when a change is made. +* v1.32 06/09/13 Fixed bug where textboxes would vanish on first run through. Added clear option for textboxes. Fixed issue of textbox settings not always saving. +* v1.33 06/09/13 Added textbox support for ffochat. +* v1.34 08/16/13 Changed include 'colors' to include 'chat' to make it current. +* v1.35 12/21/13 Updated textboxes to fix after recent update. +* v1.36 12/22/13 Added some missing ffochat info. +* v1.37 01/09/14 Redid how some settings are displayed, fixed chatboxes so they display again, fixed colors display option (//cp colors), and removed some extra code. +* v1.38 01/18/14 Minor tell textbox fix. +* v1.39 11/30/15 Fixed issue preventing chat from others from showing in textboxes. diff --git a/Data/DefaultContent/Libraries/addons/addons/chatPorter/chatPorter.lua b/Data/DefaultContent/Libraries/addons/addons/chatPorter/chatPorter.lua new file mode 100644 index 0000000..744b659 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/chatPorter/chatPorter.lua @@ -0,0 +1,644 @@ +--[[ +Copyright (c) 2013, 2015, Ikonic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of ChatPorter nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL IKONIC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'ChatPorter' +_addon.version = '1.39' +_addon.author = 'Ikonic' +_addon.commands = {'ChatPorter','cp'} + +require('tables') +require('strings') +require('chat') +config = require('config') +require('logger') + +defaults = T{} +defaults.usechatporter = true + +defaults.linkshell = T{} +defaults.linkshell.displaychat = true +defaults.linkshell.color = 41 -- 41, 70, 158, 204 +defaults.linkshell.show = true +defaults.linkshell.lines = 8 +defaults.linkshell.alpha = 255 +defaults.linkshell.red = 152 +defaults.linkshell.green = 251 +defaults.linkshell.blue = 152 +defaults.linkshell.fontname = "Arial" +defaults.linkshell.fontsize = 8 +defaults.linkshell.x = 150 +defaults.linkshell.y = 505 + +defaults.party = T{} +defaults.party.displaychat = true +defaults.party.color = 207 -- 207 +defaults.party.show = true +defaults.party.lines = 8 +defaults.party.alpha = 255 +defaults.party.red = 0 +defaults.party.green = 191 +defaults.party.blue = 255 +defaults.party.fontname = "Arial" +defaults.party.fontsize = 8 +defaults.party.x = 150 +defaults.party.y = 390 + +defaults.tell = T{} +defaults.tell.displaychat = true +defaults.tell.color = 200 -- 208 +defaults.tell.show = true +defaults.tell.lines = 8 +defaults.tell.alpha = 255 +defaults.tell.red = 255 +defaults.tell.green = 0 +defaults.tell.blue = 255 +defaults.tell.fontname = "Arial" +defaults.tell.fontsize = 8 +defaults.tell.x = 150 +defaults.tell.y = 100 + +defaults.ffochat = T{} +defaults.ffochat.displaychat = false +defaults.ffochat.color = 167 +defaults.ffochat.show = false +defaults.ffochat.lines = 8 +defaults.ffochat.alpha = 255 +defaults.ffochat.red = 255 +defaults.ffochat.green = 0 +defaults.ffochat.blue = 0 +defaults.ffochat.fontname = "Arial" +defaults.ffochat.fontsize = 4 +defaults.ffochat.x = 380 +defaults.ffochat.y = 300 + +settings = config.load(defaults) + +showlinkshell = T{} +showparty = T{} +showtell = T{} +showffochat = T{} + +specialChar = "|" +lastTellFrom = "" + +playerResolution = T{} +playerResolution.x = windower.get_windower_settings().x_res +playerResolution.y = windower.get_windower_settings().y_res + +windower.register_event('load',function () + windower.text.create("showlinkshell") + windower.text.create("showparty") + windower.text.create("showtell") + windower.text.create("showffochat") + windower.send_command('alias l2 lua command ChatPorter l2') + windower.send_command('alias p2 lua command ChatPorter p2') + windower.send_command('alias t2 lua command ChatPorter t2') + windower.send_command('alias r2 lua command ChatPorter r2') + windower.send_command('alias f1 lua command ChatPorter f1') + windower.send_command('alias f2 lua command ChatPorter f2') + windower.send_command('alias f3 lua command ChatPorter f3') + windower.send_command('alias f4 lua command ChatPorter f4') + windower.send_command('alias f5 lua command ChatPorter f5') + windower.add_to_chat(160,' Type '..string.color('//cp help',204,160)..' for a list of possible commands.') +end) + +windower.register_event('load', 'login', 'linkshell change', function() + local player = windower.ffxi.get_player() + if player then + playerName = player.name + LSname = player.linkshell + end +end) + +windower.register_event('unload',function () + windower.text.delete("showlinkshell") + windower.text.delete("showparty") + windower.text.delete("showtell") + windower.text.delete("showffochat") + windower.send_command('unalias l2') + windower.send_command('unalias p2') + windower.send_command('unalias t2') + windower.send_command('unalias r2') + windower.send_command('unalias f1') + windower.send_command('unalias f2') + windower.send_command('unalias f3') + windower.send_command('unalias f4') + windower.send_command('unalias f5') +end) + +windower.register_event('login',function (name) + settings = config.load(defaults) + show("linkshell") + show("party") + show("tell") + show("ffochat") + LSname = windower.ffxi.get_player().linkshell; + playerName = windower.ffxi.get_player().name; +end) + +function addon_command(...) + local args = {...} + local dummysettings = table.copy(settings) + if args[1] ~= nil then + comm = args[1]:lower() + if comm == 'help' then + if args[2] == nil or (args[2] ~= "detail" and args[2] ~= "textbox") then + windower.add_to_chat(55,_addon.name.." v".._addon.version..' possible commands:') + windower.add_to_chat(160,' '..string.color('//ChatPorter',204,160)..' and '..string.color('//cp',204,160)..' are both valid commands.') + windower.add_to_chat(160,' '..string.color('//cp help',204,160)..' : Lists this menu.') + windower.add_to_chat(160,' '..string.color('//cp status',204,160)..' : Shows current configuration.') + windower.add_to_chat(160,' '..string.color('//cp textbox',204,160)..' : Shows current textbox configurations.') + windower.add_to_chat(160,' '..string.color('//cp colors',204,160)..' : Shows possible color codes.') + windower.add_to_chat(160,' '..string.color('//cp toggle',204,160)..' : Toggles ChatPorter on/off.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t] [toggle|displaychat]',204,160)..' : Toggles linkshell|party|tell messages from showing or not.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t] color #',204,160)..' : Sets color of l|p|t text (acceptable values of 1-255).') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] show',204,160)..' : Toggles l|p|t textboxes from showing.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [fontname|fn, lines, fontsize|fs, x, y, alpha|a, red|r, green|g, blue|b] #',204,160)..' : Sets l|p|t textbox specifics.') + windower.add_to_chat(160,' '..string.color('//[l2|p2|t2 name|r2] message',204,160)..' : Sends message from second character to linkshell|party|tell|reply.') + windower.add_to_chat(160,' '..string.color('//[f#|cp f#] message',204,160)..' : Sends message from second character to ffochat channel.') + windower.add_to_chat(160,' '..string.color('//cp help detail',204,160)..' : Shows detailed ChatPorter commands.') + windower.add_to_chat(160,' '..string.color('//cp help textbox',204,160)..' : Shows detailed textbox commands.') + elseif args[2] == "detail" then + windower.add_to_chat(55,' ChatPorter detailed commands:') + windower.add_to_chat(160,' '..string.color('//l2 message',204,160)..' : Sends message from second character to linkshell.') + windower.add_to_chat(160,' '..string.color('//p2 message',204,160)..' : Sends message from second character to party.') + windower.add_to_chat(160,' '..string.color('//t2 name message',204,160)..' : Sends message from second character to name in tell.') + windower.add_to_chat(160,' '..string.color('//r2 message',204,160)..' : Sends reply message from second character.') + windower.add_to_chat(160,' '..string.color('//f# message',204,160)..' : Sends message from second character to FFOChat channel #. Works for 1-5.') + windower.add_to_chat(160,' '..string.color('//cp f# message',204,160)..' : Same as f#, but for any #.') + elseif args[2] == "textbox" then + windower.add_to_chat(55,'ChatPorter textbox commands:') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [toggle|displaychat]',204,160)..' : Toggles linkshell|party|tell|ffochat messages from showing or not.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t] color #',204,160)..' : Sets color of l|p|t text (acceptable values of 1-255).') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] show',204,160)..' : Toggles l|p|t|f textboxes from showing.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] clear',204,160)..' : Clears l|p|t|f textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] lines #',204,160)..' : Sets # of lines to show in textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [fontname|fn] *',204,160)..' : Sets fontname for textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [fontsize|fs] #',204,160)..' : Sets fontsize for textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] x #',204,160)..' : Sets x coordinate for textbox (acceptable values: 10-'.. playerResolution.x-10 ..').') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] y #',204,160)..' : Sets y coordinate for textbox (acceptable values: 10-'.. playerResolution.y-10 ..').') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [alpha|a] #',204,160)..' : Sets alpha (transparency) for textbox (acceptable values: 1-255; 0=fully transparent, 255=fully visible).') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [red|r] #',204,160)..' : Sets red value for RGB color of text in textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [green|g] #',204,160)..' : Sets green value for RGB color of text in textbox.') + windower.add_to_chat(160,' '..string.color('//cp [l|p|t|f] [blue|b] #',204,160)..' : Sets blue value for RGB color of text in textbox.') + end + elseif comm == 'status' then + showStatus() + elseif comm == 'textbox' then + showStatus('textbox') + elseif comm == 'colors' then + showColors() + elseif comm == 'toggle' then + settings.usechatporter = not settings.usechatporter + showStatus('usechatporter') + elseif S({'l2','p2','t2','r2'}):contains(comm) or comm:match('^f%d%d?$') then + com2 = table.remove(args,1) + com2mess = table.sconcat(args) + com2mess = string.gsub(com2mess,"\n","\\\92\110") + if comm == 'l2' then + windower.send_ipc_message(specialChar.."l2:"..LSname..specialChar..playerName..specialChar..com2mess) + elseif comm == 'p2' then + windower.send_ipc_message(specialChar.."p2:"..""..specialChar..playerName..specialChar..com2mess) + elseif comm == 't2' then + windower.send_ipc_message(specialChar.."t2:"..playerName..specialChar..playerName..specialChar..com2mess) + elseif comm == 'r2' then + windower.send_ipc_message(specialChar.."r2:"..playerName..specialChar..playerName..specialChar..com2mess) + elseif string.first(comm, 1) == 'f' then + windower.send_ipc_message(specialChar.."f:"..string.at(comm,2)..specialChar..playerName..specialChar..com2mess) + end + elseif comm == "l" or comm == "p" or comm == "t" or comm == "f" then + com2 = args[2] + if com2 == nil then + com2 = "toggle" + end + if comm == "l" then + comm = "linkshell" + elseif comm == "p" then + comm = "party" + elseif comm == "t" then + comm = "tell" + elseif comm == "f" then + comm = "ffochat" + end + com3 = args[3] + com3num = tonumber(args[3]) + + if com2 == "toggle" or com2 == "displaychat" then + settings[comm].displaychat = not settings[comm].displaychat + showStatus('display'..comm..'chat') + elseif com2 == "show" then + settings[comm][com2] = not settings[comm][com2] + windower.add_to_chat(160," Setting "..comm.." textbox to display: "..string.color(onOffPrint(settings[comm][com2]),204,160)) + elseif com2 == "clear" then + _G['show'..comm] = {} + elseif com2 == "fontname" or com2 == "fn" then + if com3 ~= nil then + com3 = table.slice(args,3) + com3 = tostring(table.sconcat(com3)) + settings[comm].fontname = com3 + windower.add_to_chat(160," Setting fontname: "..string.color(com3,204,160)) + else + settings[comm].fontname = defaults[comm].fontname + windower.add_to_chat(160," No fontname specified; setting default fontname.") + end + elseif com2 == "lines" then + if com3num > 8 or com3num < 1 then + settings[comm][com2] = 8 + windower.add_to_chat(160," Invalid setting. Lines must be a value from 1-8. Setting max value.") + else + settings[comm][com2] = com3num + windower.add_to_chat(160," Setting lines for "..comm.." textbox: "..string.color(tostring(com3num),204,160)) + end + elseif com2 == "fontsize" or com2 == "fs" then + if com3num ~= nil and com3num >= 4 and com3num <= 144 then + settings[comm].fontsize = com3num + windower.add_to_chat(160," Setting fontsize for "..comm.." textbox: "..string.color(tostring(com3num),204,160)) + else + settings[comm].fontsize = defaults[comm].fontsize + windower.add_to_chat(160," Invalid fontsize; acceptable values: 4-144. Setting to default.") + end + elseif com2 == "x" or com2 == "y" then + if com3num ~= nil and com3num >= 10 and com3num <= playerResolution[com2] - 10 then + settings[comm][com2] = com3num + windower.add_to_chat(160," Setting "..com2.." value for "..comm.." textbox: "..string.color(tostring(com3num),204,160)) + else + settings[comm][com2] = defaults[comm][com2] + windower.add_to_chat(160," Invalid "..com2.." value; acceptable values: 10-".. playerResolution[com2]-10 ..". Setting to default.") + end + elseif S({'color','alpha','a','red','r','green','g','blue','b'}):contains(com2) then + if com2 == "a" then + com2 = "alpha" + elseif com2 == "r" then + com2 = "red" + elseif com2 == "g" then + com2 = "green" + elseif com2 == "b" then + com2 = "blue" + end + if (com3num ~= nil) and (com3num >= 1 and com3num <= 255) then + settings[comm][com2] = com3num + if com2 == "color" then + windower.add_to_chat(160," Setting "..com2.." for "..comm..": "..string.color(tostring(com3num),204,160)) + else + windower.add_to_chat(160," Setting "..com2.." value for "..comm.." textbox: "..string.color(tostring(com3num),204,160)) + end + else + settings[comm][com2] = defaults[comm][com2] + windower.add_to_chat(160," Invalid "..com2.." value; acceptable values: 1-255. Setting default.") + end +-- showStatus(_G['settings['..comm..']['..com2..']']) +-- _G['show'..tbName] + end + if comm == "linkshell" or comm == "party" or comm == "tell" or comm == "ffochat" then + show(comm) + if com3 ~= nil and tostring(com3) ~= tostring(dummysettings[comm][com2]) then +-- settings:save('all') -- all characters + settings:save() -- current character only + windower.add_to_chat(55,"Saving "..string.color('ChatPorter',204,55).." settings.") + elseif com2 == "show" or com2 == "toggle" or com2 == "displaychat" then + settings:save() -- current character only + windower.add_to_chat(55,"Saving "..string.color('ChatPorter',204,55).." settings.") + end + end + elseif comm:lower() == 'vprint' then + settings:vprint() + elseif comm:lower() == 'print' then + for key, value in pairs(settings) do + log(key, value) + end + elseif comm:lower() == 'dummy' then + dummysettings:vprint() + elseif comm:lower() == 'pt' then + showparty:vprint() + elseif comm:lower() == 'test' then + windower.add_to_chat(160, "") + windower.send_command("input /p party 1") + coroutine.sleep(1) + windower.send_command("input /p party 2") + coroutine.sleep(1) + windower.send_command("input /p party 3") + coroutine.sleep(1) + windower.send_command("input /p party 4") + coroutine.sleep(1) + windower.send_command("input /p party 5") + coroutine.sleep(1) + windower.send_command("input /p party 6") + coroutine.sleep(1) + windower.send_command("input /p party 7") + coroutine.sleep(1) + windower.send_command("input /p party 8") + coroutine.sleep(1) + windower.send_command("input /t <me> tell 1") + coroutine.sleep(1) + windower.send_command("input /t <me> tell 2") + coroutine.sleep(1) + windower.send_command("input /t <me> tell 3") + coroutine.sleep(1) + windower.send_command("input /t <me> tell 4") + else + windower.add_to_chat(160, " Not a valid ".._addon.name.." v".._addon.version.." command. "..string.color('//cp help',204,160).." for a list of valid commands.") + return + end + else + addon_command('help') + end +end + +windower.register_event('addon command',addon_command) + +windower.register_event('linkshell change',function (linkshell) + LSname = windower.ffxi.get_player().linkshell; +end) + +function showStatus(var) + if (var ~= nul) and var ~= "textbox" then + if var == "usechatporter" then + windower.add_to_chat(160," UseChatPorter: " .. string.color(onOffPrint(settings.usechatporter),204,160)) + elseif var == "displaylinkshellchat" then + windower.add_to_chat(160," DisplayLinkshellChat: " .. string.color(onOffPrint(settings.linkshell.displaychat),204,160)) + elseif var == "displaypartychat" then + windower.add_to_chat(160," DisplayPartyChat: " .. string.color(onOffPrint(settings.party.displaychat),204,160)) + elseif var == "displaytellchat" then + windower.add_to_chat(160," DisplayTellChat: " .. string.color(onOffPrint(settings.tell.displaychat),204,160)) + elseif var == "linkshellcolor" then + windower.add_to_chat(160," LinkshellColor: " .. string.color(tostring(settings.linkshell.color),204,160)) + elseif var == "partycolor" then + windower.add_to_chat(160," PartyColor: " .. string.color(tostring(settings.party.color),204,160)) + elseif var == "tellcolor" then + windower.add_to_chat(160," TellColor: " .. string.color(tostring(settings.tell.color),204,160)) + end + elseif var == "textbox" then + windower.add_to_chat(55, "ChatPorter textbox settings: "..string.color('linkshell',settings.linkshell.color,160).." | "..string.color('party',settings.party.color,160).." | "..string.color('tell',settings.tell.color,160).." | "..string.color('ffochat',settings.ffochat.color,160)) +-- windower.add_to_chat(160, " displaychat: "..string.color(onOffPrint(settings.linkshell.displaychat),settings.linkshell.color,160).." | "..string.color(onOffPrint(settings.party.displaychat),settings.party.color,160).." | "..string.color(onOffPrint(settings.tell.displaychat),settings.tell.color,160).." | "..string.color(onOffPrint(settings.ffochat.displaychat),settings.ffochat.color,160)) +-- windower.add_to_chat(160, " color: "..string.color(tostring(settings.linkshell.color),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.color),settings.party.color,160).." | "..string.color(tostring(settings.tell.color),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.color),settings.ffochat.color,160)) + windower.add_to_chat(160, " show: "..string.color(onOffPrint(settings.linkshell.show),settings.linkshell.color,160).." | "..string.color(onOffPrint(settings.party.show),settings.party.color,160).." | "..string.color(onOffPrint(settings.tell.show),settings.tell.color,160).." | "..string.color(onOffPrint(settings.ffochat.show),settings.ffochat.color,160)) + windower.add_to_chat(160, " lines: "..string.color(tostring(settings.linkshell.lines),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.lines),settings.party.color,160).." | "..string.color(tostring(settings.tell.lines),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.lines),settings.ffochat.color,160)) + windower.add_to_chat(160, " fontname: "..string.color(settings.linkshell.fontname,settings.linkshell.color,160).." | "..string.color(settings.party.fontname,settings.party.color,160).." | "..string.color(settings.tell.fontname,settings.tell.color,160).." | "..string.color(settings.ffochat.fontname,settings.ffochat.color,160)) + windower.add_to_chat(160, " fontsize: "..string.color(tostring(settings.linkshell.fontsize),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.fontsize),settings.party.color,160).." | "..string.color(tostring(settings.tell.fontsize),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.fontsize),settings.ffochat.color,160)) + windower.add_to_chat(160, " x: "..string.color(tostring(settings.linkshell.x),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.x),settings.party.color,160).." | "..string.color(tostring(settings.tell.x),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.x),settings.ffochat.color,160)) + windower.add_to_chat(160, " y: "..string.color(tostring(settings.linkshell.y),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.y),settings.party.color,160).." | "..string.color(tostring(settings.tell.y),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.y),settings.ffochat.color,160)) + windower.add_to_chat(160, " alpha: "..string.color(tostring(settings.linkshell.alpha),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.alpha),settings.party.color,160).." | "..string.color(tostring(settings.tell.alpha),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.alpha),settings.ffochat.color,160)) + windower.add_to_chat(160, " red: "..string.color(tostring(settings.linkshell.red),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.red),settings.party.color,160).." | "..string.color(tostring(settings.tell.red),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.red),settings.ffochat.color,160)) + windower.add_to_chat(160, " green: "..string.color(tostring(settings.linkshell.green),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.green),settings.party.color,160).." | "..string.color(tostring(settings.tell.green),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.green),settings.ffochat.color,160)) + windower.add_to_chat(160, " blue: "..string.color(tostring(settings.linkshell.blue),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.blue),settings.party.color,160).." | "..string.color(tostring(settings.tell.blue),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.blue),settings.ffochat.color,160)) + else +-- windower.add_to_chat(160," UseChatPorter: " .. string.color(onOffPrint(settings.usechatporter),204,160)) +-- windower.add_to_chat(160," DisplayLinkshellChat: " .. string.color(onOffPrint(settings.linkshell.displaychat),204,160)) +-- windower.add_to_chat(160," DisplayPartyChat: " .. string.color(onOffPrint(settings.party.displaychat),204,160)) +-- windower.add_to_chat(160," DisplayTellChat: " .. string.color(onOffPrint(settings.tell.displaychat),204,160)) +-- windower.add_to_chat(160," DisplayFFOChat: " .. string.color(onOffPrint(settings.ffochat.displaychat),204,160)) +-- windower.add_to_chat(160," LinkshellColor: " .. string.color(tostring(settings.linkshell.color),204,160)) +-- windower.add_to_chat(160," PartyColor: " .. string.color(tostring(settings.party.color),204,160)) +-- windower.add_to_chat(160," TellColor: " .. string.color(tostring(settings.tell.color),204,160)) +-- windower.add_to_chat(160," TellColor: " .. string.color(tostring(settings.ffochat.color),204,160)) + windower.add_to_chat(55, "ChatPorter status: "..string.color('linkshell',settings.linkshell.color,160).." | "..string.color('party',settings.party.color,160).." | "..string.color('tell',settings.tell.color,160).." | "..string.color('ffochat',settings.ffochat.color,160)) + windower.add_to_chat(160, " DisplayChat: "..string.color(onOffPrint(settings.linkshell.displaychat),settings.linkshell.color,160).." | "..string.color(onOffPrint(settings.party.displaychat),settings.party.color,160).." | "..string.color(onOffPrint(settings.tell.displaychat),settings.tell.color,160).." | "..string.color(onOffPrint(settings.ffochat.displaychat),settings.ffochat.color,160)) + windower.add_to_chat(160, " Color: "..string.color(tostring(settings.linkshell.color),settings.linkshell.color,160).." | "..string.color(tostring(settings.party.color),settings.party.color,160).." | "..string.color(tostring(settings.tell.color),settings.tell.color,160).." | "..string.color(tostring(settings.ffochat.color),settings.ffochat.color,160)) + windower.add_to_chat(160, " UseChatPorter: " .. string.color(onOffPrint(settings.usechatporter),204,160)) + end +end + +function onOffPrint(bleh) + if (bleh ~= nul) then + if (bleh == 1) or (bleh == true) then + bleh = "ON"; + else + bleh = "OFF"; + end + else + bleh = "OFF"; + end + return bleh; +end + +function showColors() + colors = {} + colors[1] = 'Menu > Font Colors > Chat > Immediate vicinity ("Say")' + colors[2] = 'Menu > Font Colors > Chat > Wide area ("Shout")' + colors[3] = 'Menu > Font Colors > Chat > Extremely wide area ("Yell")' + colors[4] = 'Menu > Font Colors > Chat > Tell target only ("Tell")' + colors[5] = 'Menu > Font Colors > Chat > All party members ("Party")' + colors[6] = 'Menu > Font Colors > Chat > Linkshell group ("Linkshell")' + colors[7] = 'Menu > Font Colors > Chat > Emotes' + colors[8] = 'Menu > Font Colors > System > Calls for help' + colors[17] = 'Menu > Font Colors > Chat > Messages ("Message")' + colors[142] = 'Menu > Font Colors > Chat > NPC Conversations' -- 143? + colors[20] = 'Menu > Font Colors > For Others > HP/MP others loose' + colors[21] = 'Menu > Font Colors > For Others > Actions others evade' + colors[22] = 'Menu > Font Colors > For Others > HP/MP others recover' + colors[60] = 'Menu > Font Colors > For Others > Beneficial effects others are granted' + colors[61] = 'Menu > Font Colors > For Others > Detrimental effects others receive' + colors[63] = 'Menu > Font Colors > For Others > Effects others resist' + colors[28] = 'Menu > Font Colors > For Self > HP/MP you loose' + colors[29] = 'Menu > Font Colors > For Self > Actions you evade' + colors[30] = 'Menu > Font Colors > For Self > HP/MP you recover' + colors[56] = 'Menu > Font Colors > For Self > Beneficial effects you are granted' + colors[57] = 'Menu > Font Colors > For Self > Detrimental effects you receive' + colors[59] = 'Menu > Font Colors > For Self > Effects you resist' + colors[8] = 'Menu > Font Colors > System > Calls for help' + colors[50] = 'Menu > Font Colors > System > Standard battle messages' + colors[121] = 'Menu > Font Colors > System > Basic system messages' + + UsedColors = {9,10,11,12,13,14,15,16,17,18,20,21,22,23,24,25,26,27,31,32,33,34,35,40,41,42,43,51,52,55,58,62,64,65,66,67,68,69,70,71,72,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,122,127,128,129,130,131,132,133,134,135,136,137,138,139,140,144,145,146,147,148,149,150,151,152,153,162,163,164,165,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,205,208,253,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,284,285,286,287,292,293,294,295,300,301,302,303,308,309,310,311,316,317,318,319,324,325,326,327,332,333,334,335,340,341,342,343,344,345,346,347,348,349,350,351,355,357,358,360,361,363,366,369,372,374,375,378,381,384,395,406,409,412,415,416,418,421,424,437,450,453,456,458,459,462,479,490,493,496,499,500,502,505,507,508} + makeArray = T{} + for v = 1, 255, 1 do + if colors[v] ~= nil then + windower.add_to_chat(v, string.rep(0,3-#tostring(v))..v.." - "..colors[v]) + else + if table.contains(UsedColors,v) ~= true then +-- makeArray[#makeArray+1] = "\x1F"..string.char(v)..string.rep(0,3-#tostring(v))..v.."\x1F"..string.char(160) + makeArray[#makeArray+1] = string.char(0x1F,v)..string.rep(0,3-#tostring(v))..v..string.char(0x1F,v)..string.char(160) + end + end + end + windower.add_to_chat(160,table.sconcat(makeArray)) +-- makeArray:vprint() +end + +function show(tbName) + if #_G['show'..tbName] > 8 then + table.remove(_G["show"..tbName],1) + end + windower.text.set_bg_color("show"..tbName, 200, 30, 30, 30) + if #_G['show'..tbName] == 0 then + windower.text.set_bg_visibility("show"..tbName, false) + else + windower.text.set_bg_visibility("show"..tbName, true) + end + windower.text.set_color("show"..tbName, settings[tbName].alpha, settings[tbName].red, settings[tbName].green, settings[tbName].blue) + windower.text.set_font("show"..tbName, settings[tbName].fontname) + windower.text.set_font_size("show"..tbName, settings[tbName].fontsize) + windower.text.set_location("show"..tbName, settings[tbName].x, settings[tbName].y) + windower.text.set_visibility("show"..tbName, settings[tbName].show) + if #_G['show'..tbName] <= settings[tbName].lines then + start = 1 + else + start = #_G['show'..tbName]-settings[tbName].lines+1 + end + windower.text.set_text("show"..tbName, " " ..table.concat(table.slice(_G['show'..tbName], start, #_G['show'..tbName]), '\n ')) + +-- windower.text.set_bg_border_size("show"..tbName, 1) +-- windower.text.set_bold("show"..tbName, false) +-- windower.text.set_italic("show"..tbName, false) +-- windower.text.set_right_justified("show"..tbName, false) +-- windower.text.set_stroke_color("show"..tbName, 255, 100, 100, 100) +-- windower.text.set_stroke_width("show"..tbName, 1) +end + +windower.register_event('ipc message',function (msg) + if (settings.usechatporter == true) then + if (string.find(msg, "|(%w+):(%w*)|(%a+)|(.+)")) then + a,b,chatMode,senderLSname,senderName,message = string.find(msg, "|(%w+):(%w*)|(%a+)|(.+)") + if (chatMode == "l") and (settings.linkshell.displaychat == true) then + if (senderLSname ~= LSname) then + windower.add_to_chat(settings.linkshell.color,"["..senderLSname.."] <"..senderName.."> "..message) + showlinkshell[#showlinkshell +1] = " ["..senderLSname.."] <"..senderName.."> "..message:strip_format().." " + show("linkshell") + end + elseif (chatMode == "t") and (settings.tell.displaychat == true) then + if (playerName ~= senderLSname) and (playerName ~= senderName) then + windower.add_to_chat(settings.tell.color,"[t] "..senderName..">>"..senderLSname.." "..message) + end + elseif (chatMode == "p") and (settings.party.displaychat == true) then + if (T(windower.ffxi.get_party()):with('name', senderName) == nil) then + windower.add_to_chat(settings.party.color," ("..senderName..") "..message) + showparty[#showparty +1] = " ("..senderName..") "..message:strip_format():trim().." " + show("party") + end + elseif (chatMode == "l2") then + windower.send_command("input /l "..message) + elseif (chatMode == "p2") then + windower.send_command("input /p "..message) + elseif (chatMode == "t2") then + windower.send_command("input /t "..message) + elseif (chatMode == "r2") then + windower.send_command("input /t "..lastTellFrom.." "..message) + elseif (chatMode == "f") then + windower.send_command("input /"..senderLSname.." "..message) + end + end + end +end) + +windower.register_event('incoming text',function (original, modified, mode) + if (playerName == nil) then + playerName = windower.ffxi.get_player().name + end + if (LSname == nil) then + LSname = windower.ffxi.get_player().linkshell + end + +--[[ + function inTable(tbl, item) + for key, value in pairs(tbl) do + if value == item then return key end + end + return false + end + + tbl = {12, 4, 13, 5, 14, 6, 214, 213, 212, 9, 1, 10, 2, 11, 3, 15, 7, 208, 121, 123, 204, 206, 90, 91, 127, 161, 138, 0, 207, 136, 160} + + if (mode ~= 206) then + if inTable(tbl, mode) then + else + windower.send_command("input /echo mode: " .. mode .. " message: " .. original) + print(' cp mode: '..mode..' message: '..original) + end + end +]]-- + + if (mode == 6) or (mode == 14) or (mode == 213) or (mode == 214) then -- linkshell + if (string.find(original:strip_format(), "%[(%d+)%]<(%a+)> (.+)")) then + a,b,lsnum,player,message = string.find(original:strip_format(), "%[(%d+)%]<(%a+)> (.+)") + windower.send_ipc_message(specialChar.."l:"..LSname..specialChar..player..specialChar..message) + showlinkshell[#showlinkshell +1] = " ["..lsnum.."]<"..player.."> "..message:strip_format():trim().." " + show("linkshell") + end + elseif (mode == 5) or (mode == 13) then -- party + if (string.find(original:strip_format(), "%((%a+)%) (.+)")) then + a,b,player,message = string.find(original:strip_format(), "%((%a+)%) (.+)") + windower.send_ipc_message(specialChar.."p:"..""..specialChar..player..specialChar..message) + showparty[#showparty +1] = " ("..player..") "..message:strip_format():trim().." " + show("party") + end + elseif (mode == 4) or (mode == 12) then -- tell + if (string.find(original:strip_format(), ">>(%a+) : (.+)")) then -- incoming + a,b,player,message = string.find(original:strip_format(), ">>(%a+) : (.+)") + lastTellFrom = player; + windower.send_ipc_message(specialChar.."t:"..player..specialChar..playerName..specialChar..message) + showtell[#showtell +1] = " >>"..player.." : "..message:strip_format():trim().." " + show("tell") + elseif (string.find(original:strip_format(), "(%a+)>> (.+)")) then -- outgoing + a,b,player,message = string.find(original:strip_format(), "(%a+)>> (.+)") + windower.send_ipc_message(specialChar.."t:"..playerName..specialChar..player..specialChar..message) + showtell[#showtell +1] = " "..player..">> "..message:strip_format():trim().." " + show("tell") + end + end + + if (string.find(original:strip_format(), "%[(%d+):#(%a+)%](.+): (.+)")) then + a,b,channum,chanchan,player,message = string.find(original:strip_format(), "%[(%d+):#(%a+)%](.+): (.+)") +-- windower.send_ipc_message(specialChar.."f:"..player..specialChar..playerName..specialChar..message) + showffochat[#showffochat +1] = " "..original:strip_format():trim().." " + show("ffochat") + end + +--[[ +tell: in 12, out 4 +party: in 13, out 5 +linkshell: in 14, out 6 +linkshell2: in 214, out 213 +unity: in 212, out 212 + +say: in 9, out 1 +shout: in 10, out 2 +yell: in 11, out 3 +emote: in 15, out 7 + +3: ffochat channel +208: examines +121: browse and leaving bazaar +123: no party members message +204: player comments (seacom) +206: echo +90, 91, 127: uses an item +110: synergy overload +52: starts casting warp +64: casts warp +161: conquest results +138: bought from bazaar +0: player title +136: logout message +200: server welcome message +217: ls2 message +205: ls message +157: command error + +--]] +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/checkparam/README.md b/Data/DefaultContent/Libraries/addons/addons/checkparam/README.md new file mode 100644 index 0000000..7b0a6bc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/checkparam/README.md @@ -0,0 +1,30 @@ +# checkparam + +## English +- `/check` OR `/c` (in-game command) + - Whenever you `/check` any player, displays the total of property of that players current equipments.(defined in `settings.xml`) +- `//checkparam` OR `//cp` (addon command) + - same as /check <me>. you can use this command in equipment menu. + +### data/settings.xml (auto-generated) +- Define the properties you want to be displayed for each job. + - `|` divide each property + - `pet: ` define properties for all pets, which means avatar: wyvern: automaton: luopan: +- `<levelfilter>` ignore players with below the level `<number>` when `/check`. default value is 99. + - **Tips:** if set `100`, ignore all players. you can still use `//cp` for yourself. +- If there’s something wrong,or something strange, +please tell me on Twitter [@from20020516](https://twitter.com/from20020516) **with simple English**. Thank you! + +## 日本語 +- `/check` または `/c`(ゲーム内コマンド) + - プレイヤーを「調べる」したとき、そのプレイヤーが装備しているアイテムの任意のプロパティを合計して表示します。(`settings.xml`で定義) +- `//checkparam` または `//cp`(アドオンコマンド) + - /check <me> と同様ですが、装備変更画面でも使用できます。 + +### data/settings.xml (自動生成) +- 表示させたいプロパティをジョブごとに定義します。 + - `|` 区切り記号 + - `pet: ` 召喚獣: 飛竜: オートマトン: 羅盤: は代わりに`pet:`で指定します。 +- `<levelfilter>` + -「調べる」時に対象のレベルが設定値未満なら結果を表示しません。(初期値99) + - **Tips:** `100`を設定すると「調べる」時の結果を表示しません。 diff --git a/Data/DefaultContent/Libraries/addons/addons/checkparam/checkparam.lua b/Data/DefaultContent/Libraries/addons/addons/checkparam/checkparam.lua new file mode 100644 index 0000000..7f31a57 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/checkparam/checkparam.lua @@ -0,0 +1,372 @@ +--[[ +Copyright © 2018, from20020516 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of checkparam nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL from20020516 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'Checkparam' +_addon.author = 'from20020516 & Kigen' +_addon.version = '1.3' +_addon.commands = {'cp','checkparam'} + +require('logger') +res = require('resources') +extdata = require('extdata') +config = require('config') +packets = require('packets') +require('math') + +defaults = { + WAR = 'store tp|double attack|triple attack|quadruple attack|weapon skill damage', + MNK = 'store tp|double attack|triple attack|quadruple attack|martial arts|subtle blow', + WHM = 'cure potency|cure potency ii|fast cast|quick cast|cure spellcasting time|enmity|healing magic casting time|divine benison|damage taken|physical damage taken|magic damage taken', + BLM = 'magic attack bonus|magic burst damage|magic burst damage ii|int|magic accuracy|magic damage|fast cast|elemental magic casting time', + RDM = 'magic attack bonus|magic burst damage|magic burst damage ii|magic accuracy|fast cast|quick cast|enfeebling magic skill|enhancing magic skill|store tp|dual wield', + THF = 'store tp|double attack|triple attack|quadruple attack|dual wield|critical hit rate|critical hit damage|haste|weapon skill damage|steal|sneak attack|trick attack', + PLD = 'enmity|damage taken|physical damage taken|magic damage taken|spell interruption rate|phalanx|cure potency|fastcast', + DRK = 'store tp|double attack|triple attack|quadruple attack|weapon skill damage', + BST = 'pet: double attack|pet: magic attack bonus|pet: damage taken', + BRD = 'all songs|song effect duration|fast cast|song spellcasting time|singing skill|wind skill|string skill', + RNG = 'store tp|snapshot|rapid shot|weapon skill damage', + SAM = 'store tp|double attack|triple attack|quadruple attack|weapon skill damage', + NIN = 'store tp|double attack|triple attack|quadruple attack|subtle blow', + DRG = 'store tp|double attack|triple attack|quadruple attack|weapon skill damage', + SMN = 'physical damage taken|magic damage taken|pet: physical damage taken|pet: magic damage taken|blood pact delay|blood pact delay ii|blood pact damage|avatar perpetuation cost|pet: magic attack bonus|pet: attack|pet: double attack|pet: accuracy|pet: magic accuracy|summoning magic skill|pet: blood pact damage|pet: magic damage', + BLU = 'physical damage taken|magic damage taken|haste|dual wield|store tp|double attack|triple attack|quadruple attack|critical hit rate|critical hit damage|weapon skill damage|fast cast|magic attack bonus|magic accuracy|cure potency', + COR = 'physical damage taken|magic damage taken|haste|dual wield|store tp|snapshot|rapid shot|fast cast|cure potency|magic accuracy|magic attack bonus|magic damage|weapon skill damage', + PUP = 'pet: hp|pet: damage taken|pet: regen|martial arts|store tp|double attack|triple attack|quadruple attack', + DNC = 'store tp|double attack|triple attack|quadruple attack', + SCH = 'magic attack bonus|magic burst damage|magic burst damage ii|magic accuracy|magic damage|fast cast|elemental magic casting time|cure potency|enh mag eff dur|enhancing magic effect duration', + GEO = 'pet: regen|pet: damage taken|indicolure effect duration|fast cast|magic evasion|handbell skill|geomancy skill|geomancy', + RUN = 'enmity|damage taken|physical damage taken|magic damage taken|spell interruption rate|phalanx|inquartata|fastcast', + levelfilter = 99, +} +settings = config.load(defaults) + +tbl = {} + +windower.register_event('addon command',function(arg) + local items = windower.ffxi.get_items + for i=0,#res.slots do + local slot = windower.regex.replace(string.lower(res.slots[i].english),' ','_') + local gear_set = items().equipment + local gear = items(gear_set[slot..'_bag'],gear_set[slot]) + if gear_set[slot] > 0 then + get_text(gear.id,gear.extdata) + end + end + local my = windower.ffxi.get_player() + show_results(my.name,my.main_job,my.sub_job) +end) + +windower.register_event('incoming chunk',function(id,data) + if id == 0x0C9 then + local p = packets.parse('incoming',data) + if p['Type'] == 3 then + local count = p['Count'] + if count == 1 then + get_text(p['Item'],p['ExtData']) + else + for i=1,count do + get_text(p['Item '..i],p['ExtData '..i]) + end + end + elseif p['Type'] == 1 then + local t = windower.ffxi.get_mob_by_index(p['Target Index']) + local mjob = res.jobs[p['Main Job']].english_short + local sjob = res.jobs[p['Sub Job']].english_short + if p['Main Job Level'] >= settings.levelfilter then + show_results(t.name,mjob,sjob) + else + tbl = {} + if mjob == 'NON' then + error('The target is in /anon state.') + end + end + end + end +end) + +function get_text(id,data) + config.reload(settings) + local descriptions = res.item_descriptions[id] + local helptext = descriptions and descriptions.english or '' --for 'vanilla' items. e.g. Moonshade Earring + local stats = windower.regex.split(helptext,'(Pet|Avatar|Automaton|Wyvern|Luopan): ') + for i,v in ipairs(windower.regex.split(stats[1],'\n')) do + split_text(id,v) + end + if stats[2] then + stats[2] = stats[2]:trim() + split_text(id,stats[2],'pet: ') + end + local ext = extdata.decode({id=id,extdata=data}) + if ext.augments then + for i,v in ipairs(ext.augments) do + local stats = windower.regex.split(v,'(Pet|Avatar|Automaton|Wyvern|Luopan): ') + if stats[2] then + stats[2] = stats[2]:trim() + split_text(id,stats[2],'pet: ') + else + split_text(id,v) + end + end + end + if enhanced[id] then + local stats = enhanced[id]:gsub('([+-:][0-9]+)',',%1'):split(',') + tbl[stats[1]] = tonumber(stats[2]) + (tbl[stats[1]] or 0) + if settings.debugmode then + log(id,res.items[id].english,stats[1],stats[2],tbl[stats[1]]) + end + end + tbl.sets = tbl.sets or {} + table.insert(tbl.sets,id) +end + +function split_text(id,text,arg) + for key,value in string.gmatch(text,'/?([%D]-):?([%+%-]?[0-9]+)%%?%s?') do + local key = windower.regex.replace(string.lower(key),'(\\"|\\.|\\s$)','') + local key = integrate[key] or key + local key = arg and arg..key or key + if key == "blood pact damage" then + key = "pet: blood pact damage" + elseif key == "pet: damage taken" then + tbl['pet: physical damage taken'] = tonumber(value)+(tbl['pet: physical damage taken'] or 0) + tbl['pet: magic damage taken'] = tonumber(value)+(tbl['pet: magic damage taken'] or 0) + elseif key == "damage taken" then + tbl['physical damage taken'] = tonumber(value)+(tbl['physical damage taken'] or 0) + tbl['magic damage taken'] = tonumber(value)+(tbl['magic damage taken'] or 0) + tbl['breath damage taken'] = tonumber(value)+(tbl['breath damage taken'] or 0) + else + tbl[key] = tonumber(value)+(tbl[key] or 0) + end + if settings.debugmode then + log(id,res.items[id].english,key,value,tbl[key]) + end + end +end + +function show_results(name,mjob,sjob) + local count = {} + for key,value in pairs(combination) do + for _,id in pairs(tbl.sets) do + if value.item[id] then + count[key] = (count[key] or 0)+1 + end + end + if count[key] and count[key] > 1 then + for stat,multi in pairs(value.stats) do + tbl[stat] = (tbl[stat] or 0)+multi*math.min((count[key]+value.type),5) + end + end + end + local stats = settings[mjob] + local head = '<'..mjob..'/'..(sjob or '')..'>' + windower.add_to_chat(160,string.color(name,1,160)..': '..string.color(head,160,160)) + for index,key in ipairs(windower.regex.split(stats,'[|]')) do + -- WA for blood pact damage showing when it is converted to pet: blood pact damage + -- WA for damage taken showing when it is converted to physical/magic damage taken + key = string.lower(key) + if key ~= 'blood pact damage' and key ~= 'damage taken' then + local value = tbl[key] + local color = {value and 1 or 160,value and 166 or 160, 106, 205, 61} + local stat_cap = caps[key] + local output_string = ' ['..string.color(key,color[1],160)..']' + if stat_cap == nil or value == nil then + output_string = output_string..' '..string.color(tostring(value),color[2],160) + elseif value == stat_cap then + output_string = output_string..' '..string.color(tostring(value),color[3],160)..'/'..string.color(tostring(stat_cap),155,160) + elseif math.abs(value) > math.abs(stat_cap) then + output_string = output_string..' '..string.color(tostring(value),color[4],160)..'/'..string.color(tostring(stat_cap),155,160) + else + output_string = output_string..' '..string.color(tostring(value),color[5],160)..'/'..string.color(tostring(stat_cap),155,160) + end + windower.add_to_chat(160,output_string) + end + end + tbl = {} +end + +integrate = { + --[[integrate same property.information needed for development. @from20020516]] + ['quad atk'] = 'quadruple attack', + ['quad attack'] = 'quadruple attack', + ['triple atk'] = 'triple attack', + ['double atk'] = 'double attack', + ['dblatk'] = 'double attack', + ['blood pact ability delay'] = 'blood pact delay', + ['blood pact ability delay ii'] = 'blood pact delay ii', + ['blood pact ab del ii'] = 'blood pact delay ii', + ['blood pact recast time ii'] = 'blood pact delay ii', + ['blood pact dmg'] = 'blood pact damage', + ['enhancing magic duration'] = 'enhancing magic effect duration', + ['eva'] = 'evasion', + ['indicolure spell duration'] = 'indicolure effect duration', + ['indi eff dur'] = 'indicolure effect duration', + ['mag eva'] = 'magic evasion', + ['magic atk bonus'] = 'magic attack bonus', + ['magatkbns'] = 'magic attack bonus', + ['mag atk bonus'] = 'magic attack bonus', + ['mag acc'] = 'magic accuracy', + ['m acc'] = 'magic accuracy', + ['r acc'] = 'ranged accuracy', + ['magic burst dmg'] = 'magic burst damage', + ['mag dmg'] = 'magic damage', + ['crithit rate'] = 'critical hit rate', + ['phys dmg taken'] = 'physical damage taken', + ['occ. quickens spellcasting']="quick cast", + ['occassionally quickens spellcasting']="quick cast", + ['song duration']="song effect duration", +} +enhanced = { + [10392] = 'cursna+10', --Malison Medallion + [10393] = 'cursna+15', --Debilis Medallion + [10394] = 'fast cast+5', --Orunmila's Torque + [10469] = 'fast cast+10', --Eirene's Manteel + [10752] = 'fast cast+2', --Prolix Ring + [10790] = 'cursna+10', --Ephedra Ring + [10791] = 'cursna+15', --Haoma's Ring + [10802] = 'fast cast+5', --Majorelle Shield + [10806] = 'potency of cure effects received+15', --Adamas + [10826] = 'fast cast+3', --Witful Belt + [10838] = 'dual wield+5', --Patentia Sash + [11000] = 'fast cast+3', --Swith Cape + [11001] = 'fast cast+4', --Swith Cape +1 + [11037] = 'stoneskin+10', --Earthcry Earring + [11051] = 'increases resistance to all status ailments+5', --Hearty Earring + [11544] = 'fast cast+1', --Veela Cape + [11602] = 'martial arts+10', --Cirque Necklace + [11603] = 'dual wield+3', --Charis Necklace + [11615] = 'fast cast+5', --Orison Locket + [11707] = 'fast cast+2', --Estq. Earring + [11711] = 'rewards+2', --Ferine Earring + [11715] = 'dual wield+1', --Iga Mimikazari + [11722] = 'sublimation+1', --Savant's Earring + [11732] = 'dual wield+5', --Nusku's Sash + [11734] = 'martial arts+10', --Shaolin Belt + [11735] = 'snapshot+3', --Impulse Belt + [11753] = 'aquaveil+1', --Emphatikos Rope + [11775] = 'occult acumen+20', --Oneiros Rope + [11856] = 'fast cast+10', --Anhur Robe + [13177] = 'stoneskin+30', --Stone Gorget + [14739] = 'dual wield+5', --Suppanomimi + [14812] = 'fast cast+2', --Loquac. Earring + [14813] = 'double attack+5', --Brutal Earring + [15857] = 'drain and aspir potency+5', --Excelsis Ring + [15960] = 'stoneskin+20', --Siegel Sash + [15962] = 'magic burst damage+5', --Static Earring + [16209] = 'snapshot+5', --Navarch's Mantle + [19062] = 'divine benison+1', --Yagrush80 + [19082] = 'divine benison+2', --Yagrush85 + [19260] = 'dual wield+3', --Raider's Bmrng. + [19614] = 'divine benison+3', --Yagrush90 + [19712] = 'divine benison+3', --Yagrush95 + [19821] = 'divine benison+3', --Yagrush99 + [19950] = 'divine benison+3', --Yagrush99+ + [20509] = 'counter+14', --Spharai119AG + [20511] = 'martial arts+55', --Kenkonken119AG + [21062] = 'divine benison+3', --Yagrush119 + [21063] = 'divine benison+3', --Yagrush119+ + [21078] = 'divine benison+3', --Yagrush119AG + [21201] = 'fast cast+2', --Atinian Staff +1 + [27279] = 'physical damage taken-6', --Eri. Leg Guards + [27280] = 'physical damage taken-7', --Eri. Leg Guards +1 + [21699] = 'potency of cure effects received+10', --Nibiru Faussar + [27768] = 'fast cast+5', --Cizin Helm + [27775] = 'fast cast+10', --Nahtirah Hat + [28054] = 'fast cast+7', --Gendewitha Gages + [28058] = 'snapshot+4', --Manibozho Gloves + [28184] = 'fast cast+5', --Orvail Pants +1 + [28197] = 'snapshot+9', --Nahtirah Trousers + [28206] = 'fast cast+10', --Geomancy Pants + [28335] = 'cursna+10', --Gende. Galoshes + [28459] = 'potency of cure effects received+5', --Chuq'aba Belt + [28484] = 'cure potency+3', --Nourish Earring + [28485] = 'cure potency+5', --Nourish Earring +1 + [28577] = 'potency of cure effects received+5', --Kunaji Ring + [28582] = 'magic burst damage+5', --Locus Ring + [28619] = 'cursna+15', --Mending Cape + [28631] = 'elemental siphon+30', --Conveyance Cape + [28637] = 'fast cast+7', --Lifestream Cape + [11618] = 'song effect duration+10', -- Aoidos' Matinee + [20629] = 'song effect duration+5', -- Legato Dagger +} +combination={ + ['af']={item=S{ + 23040,23041,23042,23043,23044,23045,23046,23047,23048,23049,23050,23051,23052,23053,23055,23056,23057,23058,23059,23060,23061,23062, + 23107,23108,23109,23110,23111,23112,23113,23114,23115,23116,23117,23118,23119,23120,23122,23123,23124,23125,23126,23127,23128,23129, + 23174,23175,23176,23177,23178,23179,23180,23181,23182,23183,23184,23185,23186,23187,23189,23190,23191,23192,23193,23194,23195,23196, + 23241,23242,23243,23244,23245,23246,23247,23248,23249,23250,23251,23252,23253,23254,23256,23257,23258,23259,23260,23261,23262,23263, + 23308,23309,23310,23311,23312,23313,23314,23315,23316,23317,23318,23319,23320,23321,23323,23324,23325,23326,23327,23328,23329,23330, + 23375,23376,23377,23378,23379,23380,23381,23382,23383,23384,23385,23386,23387,23388,23390,23391,23392,23393,23394,23395,23396,23397, + 23442,23443,23444,23445,23446,23447,23448,23449,23450,23451,23452,23453,23454,23455,23457,23458,23459,23460,23461,23462,23463,23464, + 23509,23510,23511,23512,23513,23514,23515,23516,23517,23518,23519,23520,23521,23522,23524,23525,23526,23527,23528,23529,23530,23531, + 23576,23577,23578,23579,23580,23581,23582,23583,23584,23585,23586,23587,23588,23589,23591,23592,23593,23594,23595,23596,23597,23598, + 23643,23644,23645,23646,23647,23648,23649,23650,23651,23652,23653,23654,23655,23656,23658,23659,23660,23661,23662,23663,23664,23665, + 26085,26191},stats={['accuracy']=15,['magic accuracy']=15,['ranged accuracy']=15},type=-1}, + ['af_smn']={item=S{23054,23121,23188,23255,23322,23389,23456,23523,23590,23657,26342}, + stats={['pet: accuracy']=15,['pet: magic accuracy']=15,['pet: ranged accuracy']=15},type=-1}, + ['adhemar']={item=S{25614,25687,27118,27303,27474},stats={['critical hit rate']=2},type=0}, + ['amalric']={item=S{25616,25689,27120,27305,27476},stats={['magic attack bonus']=10},type=0}, + ['apogee']={item=S{26677,26853,27029,27205,27381},stats={['pet: blood pact damage']=2},type=0}, + ['argosy']={item=S{26673,26849,27025,27201,27377},stats={['double attack']=2},type=0}, + ['emicho']={item=S{25610,25683,27114,27299,27470},stats={['double attack']=2},type=0}, + ['carmine']={item=S{26679,26855,27031,27207,27383},stats={['accuracy']=10},type=0}, + ['kaykaus']={item=S{25618,25691,27122,27307,27478},stats={['cure potency ii']=2},type=0}, + ['lustratio']={item=S{26669,26845,27021,27197,27373},stats={['weapon skill damage']=2},type=0}, + ['rao']={item=S{26675,26851,27027,27203,27379},stats={['matial arts']=2},type=0}, + ['ryuo']={item=S{25612,25685,27116,27301,27472},stats={['attack']=10},type=0}, + ['souveran']={item=S{26671,26847,27023,27199,27375},stats={['damage taken']=2},type=0}, + ['ayanmo']={item=S{25572,25795,25833,25884,25951},stats={['str']=8,['vit']=8,['mnd']=8},type=-1}, + ['flamma']={item=S{25569,25797,25835,25886,25953},stats={['str']=8,['dex']=8,['vit']=8},type=-1}, + ['mallquis']={item=S{25571,25799,25837,25888,25955},stats={['vit']=8,['int']=8,['mnd']=8},type=-1}, + ['Mummu']={item=S{25570,25798,25836,25887,25954},stats={['dex']=8,['agi']=8,['chr']=8},type=-1}, + ['tali\'ah']={item=S{25573,25796,25834,25885,25952},stats={['vit']=8,['dex']=8,['chr']=8},type=-1}, + ['Hizamaru']={item=S{25576,25792,25830,25881,25948},stats={['counter']=2},type=-1}, + ['Inyanga']={item=S{25577,25793,25831,25882,25949},stats={['refresh']=1},type=-1}, + ['jhakri']={item=S{25578,25794,25832,25883,25950},stats={['fast cast']=3},type=-1}, + ['meghanada']={item=S{25575,25791,25829,25880,25947},stats={['regen']=3},type=-1}, + ['Sulevia\'s']={item=S{25574,25790,25828,25879,25946},stats={['subtle blow']=5},type=-1}, + ['BladeFlashEarrings']={item=S{28520,28521},stats={['double attack']=7},type=-1}, + ['HeartDudgeonEarrings']={item=S{28522,28523},stats={['dual wield']=7},type=-1} +} + +caps={ + ['haste']=25, + ['subtle blow']=50, + ['cure potency']=50, + ['potency of cure effects received']=30, + ['quick cast']=10, + ['physical damage taken']=-50, + ['magic damage taken']=-50, + ['breath damage taken']=-50, + ['pet: physical damage taken']=-87.5, + ['pet: magic damage taken']=-87.5, + ['pet: haste']=25, + ['magic burst damage']=40, + ['blood pact delay']=-15, + ['blood pact delay ii']=-15, + ['save tp']=500, + ['fast cast']=80, + ['reward']=50 +} diff --git a/Data/DefaultContent/Libraries/addons/addons/craft/README.md b/Data/DefaultContent/Libraries/addons/addons/craft/README.md new file mode 100644 index 0000000..64f29a7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/craft/README.md @@ -0,0 +1,38 @@ +**Author:** Snaps<br> +**Version:** 1.1.3<br> +**Date:** June 13th, 2017<br> + +# craft # + +* A Final Fantasy XI Crafting Addon + +#### Commands: #### +1. help - Shows a menu of commands in game. +2. repeat [count] - Repeats synthesis (default 1) using the lastsynth command. +- repeat - Repeats 1 synthesis. +- r 13 - Repeats 13 synthesis. +3. make [recipe] [count] - Issue a synthesis command using a recipe name. +- make "Sheep Leather" - Makes 1 Sheep Leather. +- m "Sheep Leather" 5 - Makes 5 Sheep Leather. +4. put [bag] - Moves all copies of an item into available bags. +- put "Dragon Mask" - Moves all Dragon Masks in inventory to any available bags. +- put "Dragon Mask" satchel - Moves all Dragon Masks inventory to Mog Satchel. +- put "Dragon Mask" safe2 - Moves all Dragon Masks to Mog Safe 2 (if available). +5. delay - Sets the delay between crafting attempts (default 24s) +- delay 30 - Sets the delay between crafting to 30 seconds. +6. food [item] - Sets a food item that will be consumed automatically while crafting (default None.) +- food - Sets the auto food to None. +- food "Kitron Macaron" - Sets the auto food to Kitron Macaron. +7. pause - Pauses the addon. +8. resume - Resumes the addon. +9. clear - Clears all pending items in the queue. +10. jiggle [key] - Set a key that will be pressed between every queue item (default disabled.) +- jiggle - Disables the jiggle feature. +- jiggle escape - Sets the jiggle key to escape. +11. support - Toggles auto support/ionis (default off) +- Must be near an NPC that offers Ionis or advanced imagery support to work. +12. find [details] - Search for a recipe fromt the recipes list using a string. +- find "Pizza" - Finds and displays all recipes containing the string "Pizza". +- find "Pizza" details - Finds and displays all recipes containing the string "Pizza". The recipe ingredients and crystal are also displayed. +13. status - Display some information about the addon's current state. +14. display - Toggles whether outgoing crafting packets are displayed in the chat log. diff --git a/Data/DefaultContent/Libraries/addons/addons/craft/craft.lua b/Data/DefaultContent/Libraries/addons/addons/craft/craft.lua new file mode 100644 index 0000000..9751dd7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/craft/craft.lua @@ -0,0 +1,814 @@ +--[[ +Craft v1.1.3 + +Copyright © 2017 Mojo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of craft nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mojo BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'craft' +_addon.author = 'Mojo, Recipes provided by BG-Wiki.com' +_addon.version = '1.1.3' +_addon.commands = {'craft'} + +require('chat') +require('lists') +require('coroutine') +require('queues') +require('logger') +require('tables') +require('sets') +require('strings') + +local packets = require('packets') +local res = require('resources') +local recipes = require('recipes') +local queue = Q{} +local handlers = {} +local delay = 24 +local synth = 0 +local skip_delay = false +local busy = false +local paused = false +local display = false +local jiggle = false +local support = false +local zone = nil +local hqsynth = false + +local conditions = { + move = false, + sort = false, + crystal = false, + support = false, +} + +local food = false +local supported = false +local appropriated = {} +local inventory = {} + +local function filter_bag(v) + return not (v.name:match("Inventory") or + v.name:match("Temporary") or + v.name:match("Wardrobe")) +end + +local function get_bag_command(k) + return res.bags[k].command +end + +local function get_bag_id(bag) + return bag.id +end + +local bags = res.bags:filter(filter_bag):key_map(get_bag_command):map(get_bag_id) + +local support_npcs = { + {name = "Orechiniel", zone = 230, menu = 650, buff = 240}, + {name = "Greubaque", zone = 231, menu = 628, buff = 237}, + {name = "Ulycille", zone = 231, menu = 623, buff = 236}, + {name = "Azima", zone = 234, menu = 122, buff = 242}, + {name = "Fatimah", zone = 235, menu = 302, buff = 238}, + {name = "Wise Owl", zone = 237, menu = 103, buff = 237}, + {name = "Kipo-Opo", zone = 238, menu = 10015, buff = 243}, + {name = "Lih Pituu", zone = 241, menu = 10018, buff = 241}, + {name = "Terude-Harude", zone = 241, menu = 10013, buff = 239}, + {name = "Fleuricette", zone = 256, menu = 1201, buff = 512}, + {name = "Quiri-Aliri", zone = 257, menu = 1201, buff = 512}, +} + +local exceptions = { + ['Geo Crystal'] = 6509, + ['Fire Card'] = 9764, + ['Ice Card'] = 9765, + ['Wind Card'] = 9766, + ['Earth Card'] = 9767, + ['Lightning Card'] = 9768, + ['Water Card'] = 9769, + ['Light Card'] = 9770, + ['Dark Card'] = 9771, +} + +local clusters = { + ['Fire Crystal'] = 'Fire Cluster', + ['Ice Crystal'] = 'Ice Cluster', + ['Wind Crystal'] = 'Wind Cluster', + ['Earth Crystal'] = 'Earth Cluster', + ['Lightng. Crystal'] = 'Lightning Cluster', + ['Water Crystal'] = 'Water Cluster', + ['Light Crystal'] = 'Light Cluster', + ['Dark Crystal'] = 'Dark Cluster', +} + +local hqcrystal = { + ['Fire Crystal'] = 'Inferno Crystal', + ['Ice Crystal'] = 'Glacier Crystal', + ['Wind Crystal'] = 'Cyclone Crystal', + ['Earth Crystal'] = 'Terra Crystal', + ['Lightng. Crystal'] = 'Plasma Crystal', + ['Water Crystal'] = 'Torrent Crystal', + ['Light Crystal'] = 'Aurora Crystal', + ['Dark Crystal'] = 'Twilight Crystal', +} + +local help_commands = [[ +craft - Command List: +1. help - Displays this message. +2. repeat - Repeats synthesis (default 1) using the + lastsynth command. +* repeat - Repeats 1 synthesis +* repeat 13 - Repeats 13 synthesis +3. make - Issue a synthesis command using a recipe name +* make "Sheep Leather" - Makes 1 Sheep Leather +* make "Sheep Leather" 5 - Makes 5 Sheep Leather +4. put - Moves all copies of an item into available bags. +* put "Dragon Mask" - Moves all Dragon Masks in inventory + to any available bags. +* put "Dragon Mask" satchel - Moves all Dragon Masks in + inventory to Mog Satchel. +* put "Dragon Mask" safe2 - Moves all Dragon Masks to Mog + Safe 2 (if available). +5. delay - Sets the delay between crafting attempts + (default 24, minimum 17) +* delay 30 - Sets the delay between crafting to 30 + seconds.]] + +local help_commands_2 = [[ +6. food - Sets a food item that will automatically + be consumed while crafting. +* food - Sets the auto food to None. +* food "Kitron Macaron" - Sets the auto food + to Kitron Macaron. +7. pause - Pauses the addon. +8. resume - Resumes the addon. +9. clear - Clears all items in the queue. +10. jiggle - Set a key that will be pressed between every + queue item (default disabled.) +* jiggle - Disables the jiggle feature. +* jiggle escape - Sets the jiggle key to escape. +11. support - Toggles auto support/ionis (default off) +* Must be near an NPC that offers Ionis or advanced + imagery support to work. +* Determines whether items will be sold instantly or slowly. +12. status - Display some information about the + addon's current state. +13. find - Search for a recipe fromt the recipes list using + a string. +* find "Pizza" - Finds and displays all recipes containing + the string "Pizza". +* find "Pizza" details - Finds and displays all recipes + containing the string "Pizza" (ingredients/crystal are + also displayed.) +14. display - Toggles whether outgoing crafting + packets are displayed in the chat log. +15. hqcrystal - Toggle whether to use HQ Crystal +]] + +local help_notes = [[ +Notes: + Make commands will automatically pull items from + any available bags if they are not present in your + inventory. This includes all recipe ingredients + and the crystal. If a crystal cannot be found, + it will search for a cluster from your inventory + and available bags and use the cluster. These + features are not supported with the repeat command. + + The available recipes are stored in recipes.lua. + The order in which ingredients are entered matter. + To add new recipes, enable the display (//craft + display) and manually synthesis an item. The + packet will be printed to your chat log. Create + an entry similar to the recipes that are already + provided. Only add the actual ingredients and + crystal. Then save recipes.lua and reload the addon. + + Ingredients and food are case sensitive and use + the short english name. These are the ones + displayed on FFXIAH. +]] + +local function validate(npcs) + zone = windower.ffxi.get_info()['zone'] + local valid = false + for _, npc in pairs(npcs) do + if zone == npc.zone then + valid = true + local mob = windower.ffxi.get_mob_by_name(npc.name) + if mob then + if (math.sqrt(mob.distance) < 6) then + return mob, npc + end + end + end + end + if valid then + warning("Too far from away from NPC") + end +end + +local function get_support(id, data) + if (id == 0x34) and conditions['support'] then + local mob, npc = validate(support_npcs) + local p = packets.new('outgoing', 0x5b, { + ["Target"] = mob.id, + ["Option Index"] = 1, + ["Target Index"] = mob.index, + ["Automated Message"] = false, + ["Zone"] = zone, + ["Menu ID"] = npc.menu, + }) + packets.inject(p) + conditions['support'] = false + return true + end +end + +local function check_bag(bag, id) + if not inventory['enabled_%s':format(bag)] then + return false + end + local contents = inventory[bag] + for index = 1, inventory['max_%s':format(bag)] do + if contents[index].id == id then + conditions['sort'] = true + conditions['move'] = true + windower.ffxi.get_item(bags[bag], index, contents[index].count) + return true + end + end + return false +end + +local function check_bags(id) + if inventory['count_inventory'] == inventory['max_inventory'] then + return false + end + for bag, bag_id in pairs(bags) do + if check_bag(bag, id) then + return true + end + end + return false +end + +local function block_sort(id, data) + if (id == 0x3a) and conditions['sort'] then + return true + end +end + +local function busy_wait(block, timeout, message) + local start = os.time() + while conditions[block] and ((os.time() - start) < timeout) do + coroutine.sleep(.1) + end + if os.time() - start >= timeout then + conditions[block] = false + return "Timed out - %s":format(message) + else + inventory = windower.ffxi.get_items() + end +end + +local function poke_npc() + local mob, npc = validate(support_npcs) + if npc then + local player = windower.ffxi.get_player() + if S(player.buffs):contains(npc.buff) then + return + end + conditions['support'] = true + local p = packets.new('outgoing', 0x01a, { + ["Target"] = mob.id, + ["Target Index"] = mob.index, + ["Category"] = 0, + ["Param"] = 0, + ["_unknown1"] = 0, + }) + packets.inject(p) + return busy_wait('support', 10, "getting crafting buff") + end +end + +local function unblock_sort(id, data) + if id == 0x1d then + conditions['move'] = false + end +end + +local function unblock_item(id, data) + if (id == 0x20) then + p = packets.parse('incoming', data) + if p['Item'] == conditions['item'] then + conditions['item'] = false + end + end +end + +local function commence_jigglin() + windower.send_command('setkey %s down':format(jiggle)) + coroutine.sleep(.25) + windower.send_command('setkey %s up':format(jiggle)) +end + +local function consume_item(item) + windower.chat.input('/item \"%s\" <me>':format(item)) + coroutine.sleep(3.5) + inventory = windower.ffxi.get_items() +end + +local function fetch_ingredient(ingredient) + + local id, name + if exceptions[ingredient] then + id = exceptions[ingredient] + else + item = res.items:name(ingredient) + id, name = next(item, nil) + end + if id then + local contents = inventory['inventory'] + for index = 1, inventory['max_inventory'] do + if appropriated[index] == nil then + appropriated[index] = 0 + end + if (contents[index].id == id) and + (contents[index].count > appropriated[index]) then + appropriated[index] = appropriated[index] + 1 + return id, index + end + end + if check_bags(id) then + local status = busy_wait('move', 10, 'moving %s':format(ingredient)) + if status then + return status + else + return fetch_ingredient(ingredient) + end + end + if clusters[ingredient] then + local cluster = clusters[ingredient] + local cluster_id, cluster_index = fetch_ingredient(cluster) + if cluster_index then + conditions['sort'] = true + conditions['item'] = id + local start = os.time() + windower.chat.input('/item \"%s\" <me>':format(cluster)) + local status = busy_wait('item', 10, 'using %s':format(cluster)) + if status then + error(status) + end + coroutine.sleep(4 - (os.time() - start)) + inventory = windower.ffxi.get_items() + return fetch_ingredient(ingredient) + end + end + return "Unable to locate %s":format(ingredient) + else + return "Unknown item %s":format(ingredient) + end +end + +local function consume_food() + local player = windower.ffxi.get_player() + if S(player.buffs):contains(251) then + return + end + inventory = windower.ffxi.get_items() + local id, index = fetch_ingredient(food) + if index then + windower.chat.input('/item \"%s\" <me>':format(food)) + coroutine.sleep(3.5) + else + warning("Unable to consume %s":format(food)) + end +end + +local function fetch_recipe(item) + local item = item:lower() + for name, recipe in pairs(recipes) do + if item == name:lower() then + return recipe + end + end +end + +local function hash(crystal, item, count) + local c = ((crystal % 6506) % 4238) % 4096 + local m = (c + 1) * 6 + 77 + local b = (c + 1) * 42 + 31 + local m2 = (8 * c + 26) + (item - 1) * (c + 35) + return (m * item + b + m2 * (count - 1)) % 127 +end + +local function build_recipe(item) + if windower.ffxi.get_player().status ~= 0 then + return "You can't craft at the moment" + end + + local recipe = fetch_recipe(item) + + if recipe then + inventory = windower.ffxi.get_items() + appropriated = {} + local p = packets.new('outgoing', 0x096) + local crystal = recipe['crystal'] + if hqsynth then + crystal = hqcrystal[crystal] + end + local id, index = fetch_ingredient(crystal) + if not index then return id end + p['Crystal'] = id + p['Crystal Index'] = index + p['Ingredient count'] = #recipe['ingredients'] + for i, ingredient in pairs(recipe['ingredients']) do + id, index = fetch_ingredient(ingredient) + if not index then return id end + p["Ingredient %i":format(i)] = id + p["Ingredient Index %i":format(i)] = index + end + p['_unknown1'] = hash(p['Crystal'], p['Ingredient 1'], p['Ingredient count']) + return p + else + return "No recipe for %s":format(item) + end +end + +local function issue_synthesis(item) + local p = build_recipe(item) + if type(p) == 'string' then + skip_delay = true + conditions['sort'] = false + return "%s - %s":format(item, p) + else + packets.inject(p) + conditions['sort'] = false + end +end + +local function repeat_synthesis() + windower.chat.input('/lastsynth') +end + +local function put_items(bag, id) + local src = inventory['inventory'] + local dst = inventory[bag] + local empty = {} + for index = 1, inventory['max_%s':format(bag)] do + if dst[index].count == 0 then + empty[index] = true + end + end + local idx, status = next(empty, nil) + for index = 1, inventory['max_inventory'] do + if (src[index].id == id) and idx then + windower.ffxi.put_item(bags[bag], index, src[index].count) + dst[idx].id = id + dst[idx].count = src[index].count + src[index].id = 0 + src[index].count = 0 + idx, status = next(empty, idx) + delta = true + end + end +end + +local function put(args) + conditions['sort'] = true + delta = false + inventory = windower.ffxi.get_items() + if args['bag'] then + local bag = args['bag'] + if not inventory['enabled_%s':format(bag)] then + block = false + return "bag %s disabled":format(bag) + end + put_items(bag, args['id']) + else + for bag, bag_id in pairs(bags) do + if inventory['enabled_%s':format(bag)] then + put_items(bag, args['id']) + end + end + end + if delta then + delta = false + busy_wait('move', 10, 'moving %s':format(args['name'])) + end + conditions['sort'] = false + coroutine.sleep(3.5) + skip_delay = true +end + +local function check_queue() + if not queue:empty() then + if not paused then + if jiggle then + commence_jigglin() + end + if support then + poke_npc() + end + if food then + consume_food() + end + local fn, arg = unpack(queue:pop()) + local msg = fn(arg) + if msg then + error(msg) + end + if skip_delay then + coroutine.schedule(check_queue, 0) + skip_delay = false + else + coroutine.schedule(check_queue, delay) + end + end + else + busy = false + end +end + +local function process_queue() + if not busy then + busy = true + coroutine.schedule(check_queue, 0) + end +end + +local function handle_help() + windower.add_to_chat(100, help_commands) + windower.add_to_chat(100, help_commands_2) + windower.add_to_chat(100, help_notes) +end + +local function handle_status() + notice("delay", delay) + notice("paused", paused) + notice("display", display) + notice("auto food", food) + notice("auto support", support) + notice("jiggle", jiggle) + notice("queue size", queue:length()) + notice("hq crystal", hqsynth) +end + +local function handle_delay(seconds) + local n = tonumber(seconds) + if n == nil then + return "Invalid delay %s":format(seconds) + else + n = math.max(17, n) + notice("Setting delay to %d":format(n)) + delay = n + end +end + +local function handle_clear() + notice("Clearing queue") + queue = Q{} +end + +local function handle_pause() + notice("Pausing") + paused = true +end + +local function handle_resume() + notice("Resuming") + if paused then + paused = false + busy = false + process_queue() + end +end + +local function handle_jiggle(key) + if key then + notice("Setting jiggle to %s key":format(key)) + jiggle = key + else + notice("Removing jiggle") + jiggle = false + end +end + +local function handle_repeat(count) + local count = count or 1 + local n = tonumber(count) + if n == nil then + return "Invalid count %s":format(count) + end + notice("Adding %d repeat commands to the queue":format(count)) + for i = 1, count do + local item = {repeat_synthesis, nil} + queue:push(item) + end + process_queue() +end + +local function handle_make(item, count) + local count = count or 1 + local n = tonumber(count) + if n == nil then + return "Invalid count %s":format(count) + end + local recipe = fetch_recipe(item) + if not recipe then + return "No recipe for %s":format(item) + end + notice("Adding %d make %s commands to the queue":format(count, item)) + for i = 1, count do + local item = {issue_synthesis, item} + queue:push(item) + end + process_queue() +end + +local function handle_food(item) + if not item then + notice("Setting auto food to None") + food = false + else + local search = res.items:name(item) + local id, name = next(search, nil) + if id then + notice("Setting auto food to %s":format(name.en)) + food = name.en + else + return "Invalid food %s":format(item) + end + end +end + +local function handle_put(ingredient, bag) + if bag then + bag = bag:lower() + if not bags[bag] then + return "Unknown bag %s":format(bag) + end + end + local search = res.items:name(ingredient) + local id, name = next(search, nil) + if id then + local msg = nil + local args = { + ['id'] = id, + ['bag'] = bag, + ['name'] = name.english, + } + local item = {put, args} + if bag then + msg = "%s %s":format(ingredient, bag) + else + msg = ingredient + end + notice("Adding a put %s command to the queue":format(msg)) + queue:push(item) + process_queue() + else + return "Unknown item %s":format(ingredient) + end +end + +local function display_crafting_packet(id, data) + if id == 0x096 and display then + local p = packets.parse('outgoing', data) + log(p) + end +end + +local function handle_display() + if display then + notice("Disabling display") + display = false + else + notice("Enabling display") + display = true + end +end + +local function handle_support() + if support then + notice("Disabling support") + support = false + else + notice("Enabling support") + support = true + end +end + +local function handle_find(query, details) + local query = query:lower() + notice("Searching for recipes containing %s":format(query)) + for name, recipe in pairs(recipes) do + if string.find(name:lower(), query) then + notice("Found recipe - \"%s\"":format(name)) + if details then + notice(" %s":format(recipe['crystal'])) + for _, ingredient in pairs(recipe['ingredients']) do + notice(" %s":format(ingredient)) + end + end + end + end +end + +local function handle_hqsynth() + if hqsynth then + notice("Disabling HQ Crystal") + hqsynth = false + else + notice("Enabling HQ Crystal") + hqsynth = true + end +end + + +handlers['clear'] = handle_clear +handlers['repeat'] = handle_repeat +handlers['r'] = handle_repeat +handlers['delay'] = handle_delay +handlers['pause'] = handle_pause +handlers['resume'] = handle_resume +handlers['make'] = handle_make +handlers['m'] = handle_make +handlers['display'] = handle_display +handlers['put'] = handle_put +handlers['food'] = handle_food +handlers['status'] = handle_status +handlers['help'] = handle_help +handlers['jiggle'] = handle_jiggle +handlers['support'] = handle_support +handlers['find'] = handle_find +handlers['hqcrystal'] = handle_hqsynth + +local function handle_command(cmd, ...) + local cmd = cmd or 'help' + if handlers[cmd] then + local msg = handlers[cmd](unpack({...})) + if msg then + error(msg) + end + else + error("Unknown command %s":format(cmd)) + end +end + +-- This is here so if a player does a legitimate synth the result is not displayed twice, since results are only hidden on injected synthesis. +windower.register_event('outgoing chunk', function(id, original, modified, injected, blocked) + if id == 0x096 and injected then + injected_synth = true + end +end) + +windower.register_event('incoming chunk', function(id, original, modified, injected, blocked) + if id == 0x06F and injected_synth then + local p = packets.parse('incoming',original) + if p['Result'] == 0 or p['Result'] == 2 then + local item = res.items[p['Item']].english + windower.add_to_chat(121, 'You synthesized: \30\02%s\30\01.':format(item)) + injected_synth = false + end + if p['Result'] == 1 or p['Result'] == 5 then + windower.add_to_chat(121,'Your synthesis has failed and your crystal is lost.') + for i=1, 8 do + if p['Lost Item '..i] ~= 0 then + windower.add_to_chat(121, 'You lost: \30\02%s\30\01.':format(res.items[p['Lost Item '..i]].english)) + end + end + injected_synth = false + end + end +end) + +windower.register_event('addon command', handle_command) +windower.register_event('outgoing chunk', display_crafting_packet) +windower.register_event('outgoing chunk', block_sort) +windower.register_event('incoming chunk', unblock_sort) +windower.register_event('incoming chunk', unblock_item) +windower.register_event('incoming chunk', get_support) diff --git a/Data/DefaultContent/Libraries/addons/addons/craft/create_recipes.py b/Data/DefaultContent/Libraries/addons/addons/craft/create_recipes.py new file mode 100644 index 0000000..9a424ff --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/craft/create_recipes.py @@ -0,0 +1,244 @@ +import urllib2 +from bs4 import BeautifulSoup +from slpp import slpp as lua +import os +import platform + + +hdr = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', + 'Accept-Encoding': 'none', + 'Accept-Language': 'en-US,en;q=0.8', + 'Connection': 'keep-alive', +} + +sphere = None + +def get_recipe(row): + crystals = [ + 'Dark Crystal', + 'Light Crystal', + 'Earth Crystal', + 'Water Crystal', + 'Fire Crystal', + 'Wind Crystal', + 'Lightning Crystal', + 'Ice Crystal', + 'Pyre Crystal', + 'Frost Crystal', + 'Vortex Crystal', + 'Geo Crystal', + 'Terra II Crystal', + 'Bolt Crystal', + 'Fluid Crystal', + 'Glimmer Crystal', + 'Shadow Crystal', + ] + y, r, c, i = [ + td for td in row.findAll('td') + ] + name = str(y.findAll('a')[0]['title']) + crystal = None + ingredients = [] + for li in i.findAll('li'): + english = str(li.findAll('a')[0]['title']) + if english in crystals: + crystal = english + continue + if li.text[-1].isdigit() and not "Kit" in english: + for n in range(int(li.text[-1])): + ingredients.append(english) + else: + ingredients.append(english) + return [(name, crystal, ingredients)] + +def get_sphere_recipe(row): + spheres = [ + 'Liquefaction Sphere', + 'Transfixion Sphere', + 'Detonation Sphere', + 'Impaction Sphere', + 'Induration Sphere', + 'Reverberation Sphere', + 'Scission Sphere', + 'Compression Sphere', + ] + global sphere + cells = [td for td in row.findAll('td')] + if len(cells) > 4: + rare_ex, rare, ex, c, s = cells[:5] + if str(s.findAll('a')[0]['title']) in spheres: + sphere = str(s.findAll('a')[0]['title']) + else: + rare_ex, rare, ex, c = cells[:4] + recipes = [] + crystal = str(c.findAll('img')[0]['alt']).rstrip(' icon.png') + ingredients = [] + for cell in cells[:3]: + for a in cell.findAll('a'): + recipes.append((sphere, crystal, [str(a['title'])])) + return recipes + +def get_recipes_from_rows(rows, spheres=False): + recipes = {} + for row in rows: + if spheres: + subrecipes = get_sphere_recipe(row) + else: + subrecipes = get_recipe(row) + for (name, crystal, ingredients) in subrecipes: + while name in recipes.keys(): + if name[-1].isdigit(): + name = name[:-2] + (" %d" % (int(name[-1]) + 1)) + else: + name = name + " 2" + recipes[name] = [crystal, ingredients] + return recipes + +def get_recipes_from_soup(soup, spheres=False): + string = "Sphere Obtained" if spheres else "Synthesis Information" + lengths = [4, 5, 6, 7] if spheres else [4] + subtables = [ + descendant.parent.parent.parent + for descendant in soup.descendants + if string in descendant + ] + rows = [] + for subtable in subtables: + children = [ + row + for row in subtable.children + if (hasattr(row, 'findAll') and + len(row.findAll('td')) in lengths) + ] + rows.extend(children) + return get_recipes_from_rows(rows, spheres) + +def get_items_dictionary(): + if platform.system() == 'Windows': + path = 'C:\\Program Files (x86)\\Windower4\\res\\items.lua' + else: + path = os.path.join(os.path.expanduser("~"), 'Resources/lua/items.lua') + with open(path) as fd: + data = fd.read().replace('return', '', 1) + return lua.decode(data) + +def get_items(): + exceptions = { + 'geo crystal' : 6509, + 'terra ii crystal' : 6509, + 'broken single-hook fishing rod' : 472, + 'broken hume rod' : 1832, + 'broken bamboo rod' : 487, + 'dark adaman sheet' : 2001, + 'black chocobo fletchings' : 1254, + 'broken willow rod' : 485, + 'four-leaf korringan bud' : 1265, + 'broken fastwater rod' : 488, + 'h. q. coeurl hide' : 1591, + 'broken yew rod' : 486, + 'broken mithran rod' : 483, + 'broken tarutaru rod' : 484, + "broken lu shang's rod" : 489, + 'fire emblem card' : 9764, + 'ice emblem card' : 9765, + 'wind emblem card' : 9766, + 'earth emblem card' : 9767, + 'lightning emblem card': 9768, + 'water emblem card' : 9769, + 'light emblem card': 9770, + 'dark emblem card': 9771, + } + items = get_items_dictionary() + inverted = {} + for k, v in items.items(): + if not v['en'].lower() in inverted: + inverted[v['en'].lower()] = k + if not v['enl'].lower() in inverted: + inverted[v['enl'].lower()] = k + inverted.update(exceptions) + return items, inverted + +def get_item(ingredient, inverted): + results = [] + exceptions = { + 'behemoth leather' : 'square of behemoth leather', + 'puk fletchings' : 'bag of puk fletchings', + 'phrygian gold' : 'phrygian gold ingot', + 'smilodon leather' : 'square of smilodon leather', + 'chocobo fletchings' : 'bag of chocobo fletchings', + 'vermilion lacquer' : 'pot of vermilion lacquer', + } + if ingredient in exceptions: + return inverted[exceptions[ingredient]] + for name, iid in inverted.items(): + if ingredient in name: + return iid + +def fix_recipes(recipes): + items, inverted = get_items() + for name, (crystal, ingredients) in recipes.items(): + crystal = items[inverted[crystal.lower()]]['en'] + sorted = [] + for ingredient in ingredients: + ingredient = ingredient.lower() + if ingredient in inverted: + sorted.append(inverted[ingredient]) + else: + sorted.append(get_item(ingredient, inverted)) + sorted.sort() + ingredients = [ + items[ingredient]['en'] + for ingredient in sorted + ] + recipes[name] = [crystal, ingredients] + +def build_recipe_string(name, crystal, ingredients): + recipe = " [\"%s\"] = {\n [\"crystal\"] = \"%s\",\n [\"ingredients\"] = {\n" % (name, crystal) + for ingredient in ingredients: + recipe += " \"%s\",\n" % ingredient + recipe += " },\n },\n" + return recipe + +def save_recipes(recipes): + with open('recipes.lua', 'w') as fd: + fd.write("return {\n") + for key in sorted(recipes.iterkeys()): + fd.write(build_recipe_string(key, *recipes[key])) + fd.write("}\n") + +def get_recipes(craft, spheres=False): + base = "https://www.bg-wiki.com/bg/" + name = "%s.html" % craft + if not os.path.exists(name): + req = urllib2.Request(base + craft, headers=hdr) + try: + page = urllib2.urlopen(req).read() + except urllib2.HTTPError, e: + return + with open(name, 'w') as fd: + fd.write(page) + with open(name, 'r') as fd: + page = fd.read() + soup = BeautifulSoup(page, 'lxml') + return get_recipes_from_soup(soup, spheres) + +if __name__ == "__main__": + crafts = [ + 'Alchemy', + 'Bonecraft', + 'Clothcraft', + 'Cooking', + 'Goldsmithing', + 'Leathercraft', + 'Smithing', + 'Woodworking', + ] + recipes = {} + for craft in crafts: + recipes.update(get_recipes(craft)) + recipes.update(get_recipes('Category:Escutcheons', True)) + fix_recipes(recipes) + save_recipes(recipes) diff --git a/Data/DefaultContent/Libraries/addons/addons/craft/recipes.lua b/Data/DefaultContent/Libraries/addons/addons/craft/recipes.lua new file mode 100644 index 0000000..04b3f19 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/craft/recipes.lua @@ -0,0 +1,37402 @@ +return { + ["3-Drawer Almirah"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Ebony Lumber", + "Ancient Lumber", + "Gold Thread", + "Silk Cloth", + }, + }, + ["6-Drawer Almirah"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Ebony Lumber", + "Ancient Lumber", + "Gold Thread", + "Silk Cloth", + }, + }, + ["9-Drawer Almirah"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Ebony Lumber", + "Ancient Lumber", + "Gold Thread", + "Silk Cloth", + }, + }, + ["Aak'ab Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Mohbwa Cloth", + "Rhodium Ingot", + "Rhodium Ingot", + "Urunday Lumber", + "Umbril Ooze", + }, + }, + ["Aalak' Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Midrium Ingot", + "Urunday Lumber", + }, + }, + ["Abrasion Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mercury", + "Animal Glue", + "Belladonna Sap", + "Acuex Poison", + "Ra'Kaznar Ingot", + }, + }, + ["Abyss Scythe"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Dark Matter", + "Tartarian Chain", + "Cypress Log", + "Moldy Scythe", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Obsidian Crystal", + }, + }, + ["Abyssal Beads"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Obsidian Crystal", + "Moldy Necklace", + }, + }, + ["Accelerator"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Goblin Grease", + "Imperial Cermet", + "Myth.Gear Mach.", + "Wind Fan", + }, + }, + ["Accelerator II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Goblin Grease", + "Imperial Cermet", + "Golden Gear", + "Kilo Fan", + }, + }, + ["Accelerator III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Goblin Grease", + "Imperial Cermet", + "Platinum Gear", + "Mega Fan", + }, + }, + ["Accelerator IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Goblin Grease", + "Imperial Cermet", + "Ocl. Gearbox", + "Kilo Fan", + "Mega Fan", + }, + }, + ["Accura Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Velvet Cloth", + "Dahu Hair", + }, + }, + ["Acheron Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Adamantoise Shell", + "Adamantoise Shell", + }, + }, + ["Acid Baselard"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Mythril Baselard", + }, + }, + ["Acid Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Acid Bolt Heads", + }, + }, + ["Acid Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Acid Bolt Heads", + "Acid Bolt Heads", + "Acid Bolt Heads", + "Bundling Twine", + }, + }, + ["Acid Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Animal Glue", + "Vitriol", + }, + }, + ["Acid Claws"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Mythril Claws", + }, + }, + ["Acid Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Mythril Dagger", + }, + }, + ["Acid Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Mythril Knife", + }, + }, + ["Acid Kukri"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Mythril Kukri", + }, + }, + ["Acorn Cookie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Crawler Egg", + "Honey", + "Acorn", + "Acorn", + "Acorn", + "Acorn", + }, + }, + ["Acrobat's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "G. Bgd. Leather", + "Barbarian's Belt", + }, + }, + ["Adaman Barbuta"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Adaman Sheet", + "Gold Ingot", + "Sheep Leather", + "Mercury", + }, + }, + ["Adaman Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Adaman Ingot", + }, + }, + ["Adaman Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + }, + }, + ["Adaman Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Mandrel", + }, + }, + ["Adaman Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Adaman Sheet", + "Adaman Sheet", + "Gold Ingot", + "Tiger Leather", + "Tiger Leather", + "Mercury", + }, + }, + ["Adaman Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Adaman Sheet", + "Gold Ingot", + "Tiger Leather", + "Mercury", + }, + }, + ["Adaman Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Adaman Sheet", + "Gold Ingot", + "Mercury", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Adaman Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Adaman Ore", + "Adaman Ore", + "Adaman Ore", + }, + }, + ["Adaman Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ore", + "Adaman Nugget", + "Adaman Nugget", + "Adaman Nugget", + "Adaman Nugget", + "Adaman Nugget", + "Adaman Nugget", + }, + }, + ["Adaman Kilij"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Ebony Lumber", + "Aquamarine", + "Deathstone", + "Marid Leather", + "Scintillant Ingot", + "Scintillant Ingot", + }, + }, + ["Adaman Kris"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Gold Ingot", + "Deathstone", + }, + }, + ["Adaman Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Adaman Sheet", + "Gold Ingot", + "Tiger Leather", + "Tiger Leather", + "Mercury", + }, + }, + ["Adaman Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Adaman Ingot", + "Ebony Lumber", + "Gold Ingot", + "Gold Ingot", + "Mercury", + }, + }, + ["Adaman Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Adaman Sheet", + }, + }, + ["Adaman Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + }, + }, + ["Adaman Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Workshop Anvil", + }, + }, + ["Adamantoise Soup"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Chicken Bone", + "Fiend Blood", + "Rock Salt", + "Wild Onion", + "Distilled Water", + "Tav. Ram Meat", + "Diatryma Meat", + }, + }, + ["Adargas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Oak Lumber", + "Gold Ingot", + "Rheiyoh Leather", + }, + }, + ["Adder Jambiya"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Viper Potion", + "Khimaira Jambiya", + }, + }, + ["Adoulin Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Adoulinian Kelp", + "Adoulinian Kelp", + "Wild Onion", + "Distilled Water", + "Black Prawn", + }, + }, + ["Aero Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mtl. Mesh Sheet", + "Mufflers", + }, + }, + ["Aetosaur Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Squamous Hide", + "Raaz Leather", + "Leather Gloves", + }, + }, + ["Aetosaur Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Squamous Hide", + "Squamous Hide", + "Raaz Leather", + }, + }, + ["Aetosaur Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Squamous Hide", + "Squamous Hide", + "Raaz Leather", + "Raaz Leather", + }, + }, + ["Aetosaur Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Squamous Hide", + "Rhodium Sheet", + "Raaz Leather", + "Leather Highboots", + }, + }, + ["Aetosaur Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Squamous Hide", + "Squamous Hide", + "Raaz Leather", + "Leather Trousers", + }, + }, + ["Agility Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Phalaenopsis", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Ahkormaar Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Cloth", + "Coeurl Whisker", + "Urunday Lumber", + "Urunday Lumber", + "Chapuli Horn", + }, + }, + ["Aht Urhgan Brass Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Aht Urhgan Brass", + "Aht Urhgan Brass", + "Aht Urhgan Brass", + "Aht Urhgan Brass", + }, + }, + ["Aht Urhgan Brass Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "A.U. Brass Ingot", + }, + }, + ["Aht Urhgan Brass Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + }, + }, + ["Aht Urhgan Dart"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Animal Glue", + "Colibri Feather", + "Colibri Feather", + "Colibri Beak", + }, + }, + ["Aiming Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Wind Cell", + "Studded Gloves", + }, + }, + ["Air Rider"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Firesand", + "Firesand", + "Bast Parchment", + "Goblin Doll", + "Twinkle Powder", + }, + }, + ["Air Solea"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lizard Molt", + "Light Soleas", + }, + }, + ["Airborne"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Cloth", + "Bomb Ash", + "Firesand", + "Firesand", + "Bast Parchment", + "Goblin Doll", + }, + }, + ["Airmid's Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Silver Chain", + "Vivified Coral", + "Vivified Mythril", + }, + }, + ["Aisa"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Tama-Hagane", + "Elm Lumber", + "Thokcha Ingot", + "Behem. Leather", + "Khimaira Mane", + }, + }, + ["Akamochi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Sticky Rice", + "Cornstarch", + "Distilled Water", + "Azuki Bean", + }, + }, + ["Akaso Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Akaso Thread", + "Akaso Thread", + "Akaso Thread", + }, + }, + ["Akaso Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Akaso", + "Akaso", + }, + }, + ["Akaso Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spindle", + "Akaso", + "Akaso", + "Akaso", + "Akaso", + "Akaso", + "Akaso", + }, + }, + ["Aketon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Beetle Blood", + }, + }, + ["Akua Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Mercury", + "Rhodium Ingot", + "Rhodium Ingot", + "Urunday Lumber", + }, + }, + ["Al Zahbi Sash"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Gold Thread", + "Marid Hair", + "Marid Hair", + "Karakul Thread", + "Wamoura Silk", + }, + }, + ["Alacer Aketon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Wool Thread", + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Beetle Blood", + "Radiant Velvet", + }, + }, + ["Alchemist's Tools"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Parchment", + "Glass Sheet", + "Triturator", + "Colibri Feather", + "A.U. Brass Ingot", + "A.U Brass Sheet", + "F. Glass Sheet", + }, + }, + ["Alchemist's Water"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Dryad Root", + "Beastman Blood", + "Phil. Stone", + }, + }, + ["Alchemy Set 25"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Baselard", + }, + }, + ["Alchemy Set 45"] = { + ["crystal"] = "Bolt Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + }, + }, + ["Alchemy Set 65"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Baselard", + }, + }, + ["Alchemy Set 70"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Alchemy Set 75"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Kukri", + }, + }, + ["Alchemy Set 80"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Cermet Knife", + }, + }, + ["Alchemy Set 85"] = { + ["crystal"] = "Shadow Crystal", + ["ingredients"] = { + "Beastman Blood", + "Revival Root", + "Schlaeger", + }, + }, + ["Alchemy Set 90"] = { + ["crystal"] = "Shadow Crystal", + ["ingredients"] = { + "Beastman Blood", + "Beastman Blood", + "Revival Root", + "Darksteel Lance", + }, + }, + ["Alchemy Set 95"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Sage", + "Sage", + "Dragon Blood", + "Reishi Mushroom", + "Distilled Water", + }, + }, + ["Alluring Cotton Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Thread", + "Cotton Thread", + "Earth Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Altana's Repast"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Holy Water", + "Cursed Beverage", + "Cursed Soup", + "Orc Offering", + "Quadav Offering", + "Yagudo Offering", + "Goblin Offering", + }, + }, + ["Alumine Brayettes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Aluminum Chain", + "Linen Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Alumine Haubert"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Aluminum Sheet", + "Aluminum Ingot", + "Darksteel Chain", + "Darksteel Chain", + "Darksteel Chain", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Alumine Moufles"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Aluminum Sheet", + "Chain Mittens", + }, + }, + ["Alumine Salade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Aluminum Sheet", + "Darksteel Chain", + "Sheep Leather", + }, + }, + ["Alumine Solerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Aluminum Sheet", + "Greaves", + }, + }, + ["Aluminum Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aluminum Ingot", + "Aluminum Ingot", + }, + }, + ["Aluminum Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Mandrel", + }, + }, + ["Aluminum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Aluminum Ore", + "Aluminum Ore", + "Aluminum Ore", + "Aluminum Ore", + }, + }, + ["Aluminum Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Aluminum Ingot", + }, + }, + ["Aluminum Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Workshop Anvil", + }, + }, + ["Aluminum Sheet 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 50", + }, + }, + ["Amaltheia Lth."] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Amaltheia Hide", + "Distilled Water", + }, + }, + ["Amaltheia Lth. 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Amaltheia Hide", + "Distilled Water", + }, + }, + ["Amaltheia Lth. 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Amaltheia Hide", + "Amaltheia Hide", + "Amaltheia Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Amber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yellow Rock", + }, + }, + ["Amber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Soil Geode", + }, + }, + ["Amber Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amber", + "Silver Earring", + }, + }, + ["Amber Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amber", + "Silver Earring +1", + }, + }, + ["Amber Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amber", + "Silver Ring", + }, + }, + ["Amber Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amber", + "Silver Ring +1", + }, + }, + ["Amemet Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Lizard Molt", + "Amemet Skin", + }, + }, + ["Ames"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Urushi", + "Moonbow Stone", + "Ruthenium Ingot", + "Mistilteinn", + }, + }, + ["Amethyst"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Purple Rock", + }, + }, + ["Amethyst 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Thunder Geode", + }, + }, + ["Amethyst Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amethyst", + "Silver Earring", + }, + }, + ["Amethyst Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amethyst", + "Silver Earring +1", + }, + }, + ["Amethyst Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amethyst", + "Silver Ring", + }, + }, + ["Amethyst Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amethyst", + "Silver Ring +1", + }, + }, + ["Ametrine Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ametrine", + "Mythril Earring", + }, + }, + ["Ametrine Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ametrine", + "Mythril Earring +1", + }, + }, + ["Ametrine Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ametrine", + "Mythril Ring", + }, + }, + ["Ametrine Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ametrine", + "Mythril Ring +1", + }, + }, + ["Amiga Cactus"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Red Rock", + "Red Gravel", + "Cactus Arm", + "Karugo Clay", + "Humus", + }, + }, + ["Amigo Cactus"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Yellow Rock", + "Red Gravel", + "Cactus Arm", + "Karugo Clay", + "Humus", + }, + }, + ["Amir Bed"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bldwd. Lumber", + "Gold Thread", + "Gold Brocade", + "Karakul Cloth", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + "A.U. Brass Ingot", + }, + }, + ["Amood"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Gold Ingot", + "Turquoise", + "Scintillant Ingot", + "Scintillant Ingot", + }, + }, + ["Amphiptere Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Amphiptere Hide", + "Distilled Water", + }, + }, + ["Amphiptere Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Amphiptere Hide", + "Distilled Water", + }, + }, + ["Amphiptere Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Amphiptere Hide", + "Amphiptere Hide", + "Amphiptere Hide", + "Distilled Water", + }, + }, + ["Amplifier"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Ice Anima", + "Glass Sheet", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Amplifier II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Ice Anima", + "Plasma Oil", + "F. Glass Sheet", + "F. Glass Sheet", + }, + }, + ["Analyzer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Earth Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Anchovy"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Bay Leaves", + "Olive Oil", + "Rock Salt", + "Icefish", + "Icefish", + "Icefish", + "Icefish", + }, + }, + ["Anchovy 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Bay Leaves", + "Olive Oil", + "Rock Salt", + "Sandfish", + "Sandfish", + "Sandfish", + "Sandfish", + }, + }, + ["Anchovy Pizza"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Holy Basil", + "Pizza Dough", + "Pomodoro Sauce", + "Anchovy", + "Chalaimbille", + }, + }, + ["Anchovy Slice"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Holy Basil", + "Pizza Dough", + "Pomodoro Sauce", + "Anchovy", + "Chalaimbille", + "Pizza Cutter", + }, + }, + ["Ancient Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Petrified Log", + "Petrified Log", + "Petrified Log", + "Bundling Twine", + }, + }, + ["Ancient Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Petrified Log", + }, + }, + ["Anelace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Cockatrice Skin", + "Mercury", + }, + }, + ["Anelace 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 94", + }, + }, + ["Angel Skin Orb"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Angel Skin", + }, + }, + ["Angel's Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angelstone", + "Platinum Earring", + }, + }, + ["Angel's Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angelstone", + "Ptm. Earring +1", + }, + }, + ["Angel's Flute"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Parchment", + }, + }, + ["Angel's Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angelstone", + "Platinum Ring", + }, + }, + ["Angel's Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angelstone", + "Platinum Ring +1", + }, + }, + ["Angler Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fish Stock", + "Danceshroom", + "Distilled Water", + "Cotton Tofu", + "Cibol", + "Shungiku", + "Shirataki", + "Orobon Meat", + }, + }, + ["Angon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Yew Lumber", + "Yew Lumber", + "Yew Lumber", + "Wool Thread", + }, + }, + ["Animal Glue"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rabbit Hide", + "Bone Chip", + "Bone Chip", + "Distilled Water", + }, + }, + ["Animal Glue 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rabbit Hide", + "Rabbit Hide", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Triturator", + "Distilled Water", + }, + }, + ["Animator P"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Divine Lumber", + "Exalted Lumber", + "Exalted Lumber", + "Vulcanite Ore", + "Animator Z", + }, + }, + ["Animator P II"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Divine Lumber", + "Glass Fiber", + "Exalted Lumber", + "Exalted Lumber", + "Vulcanite Ore", + "Animator Z", + }, + }, + ["Antacid"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Maple Sugar", + "Chamomile", + "Dried Mugwort", + "Distilled Water", + }, + }, + ["Antica Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Antican Pauldron", + "Antican Robe", + "Antican Acid", + }, + }, + ["Antica Broth 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Antican Robe", + "Antican Robe", + "Antican Acid", + "Antican Acid", + }, + }, + ["Antidote"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Wijnruit", + "San d'Or. Grape", + "Distilled Water", + }, + }, + ["Antidote 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Wijnruit", + "Wijnruit", + "Wijnruit", + "Triturator", + "San d'Or. Grape", + "San d'Or. Grape", + "San d'Or. Grape", + "Distilled Water", + }, + }, + ["Antlion Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Antlion Jaw", + }, + }, + ["Antlion Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Antlion Jaw", + "Antlion Jaw", + "Antlion Jaw", + "Shagreen File", + }, + }, + ["Antlion Trap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Animal Glue", + "Antican Pauldron", + "Antican Robe", + "H.Q. Antlion Jaw", + "H.Q. Antlion Jaw", + }, + }, + ["Apaisante"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thokcha Ingot", + "Thokcha Ingot", + "Buffalo Horn", + }, + }, + ["Apkallu Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Apkallu Feather", + "Apkallu Feather", + }, + }, + ["Apkallu Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Apkallu Feather", + "Apkallu Feather", + "Apkallu Feather", + "Apkallu Feather", + "Apkallu Feather", + "Apkallu Feather", + }, + }, + ["Apple Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Faerie Apple", + "Faerie Apple", + "Faerie Apple", + }, + }, + ["Apple Juice 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cook. Kit 20", + }, + }, + ["Apple Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Lizard Egg", + "Faerie Apple", + }, + }, + ["Apple Pie 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Faerie Apple", + "Bird Egg", + }, + }, + ["Apple Pie 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 50", + }, + }, + ["Apple Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Apple au Lait", + "Apple au Lait", + "Apple au Lait", + "Apple au Lait", + }, + }, + ["Apple Vinegar"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Faerie Apple", + "Faerie Apple", + "Faerie Apple", + "Honey", + }, + }, + ["Apple au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Faerie Apple", + "Honey", + "Selbina Milk", + }, + }, + ["Aptitude Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Lizard Molt", + "Raaz Hide", + "Belinda's Hide", + }, + }, + ["Aput Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Akaso Thread", + "Cehuetzi Pelt", + }, + }, + ["Aqua Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Water Bead", + "Orichalcum Ring", + }, + }, + ["Aquamarine"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Scholar Stone", + }, + }, + ["Aquamarine 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Leviatite", + }, + }, + ["Aquamarine Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aquamarine", + "Gold Ring", + }, + }, + ["Aquamarine Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aquamarine", + "Gold Ring +1", + }, + }, + ["Aquamrne. Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aquamarine", + "Gold Earring", + }, + }, + ["Aquamrne. Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aquamarine", + "Gold Earring +1", + }, + }, + ["Aquan Slayer"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Bird Blood", + "Ameretat Vine", + "Darksteel Kilij", + }, + }, + ["Aqueous Orichalcum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Lightning Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Arachne Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Gold Thread", + "Arachne Thread", + }, + }, + ["Arachne Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Arachne Web", + "Arachne Web", + }, + }, + ["Arasy Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ebony Lumber", + "Deathstone", + "Electrum Ingot", + "Bismuth Ingot", + }, + }, + ["Arasy Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Carbon Fiber", + "Glass Fiber", + "Gua. Lumber", + "Akaso Cloth", + }, + }, + ["Arasy Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Ebony Lumber", + "Wyvern Skin", + "Bismuth Ingot", + }, + }, + ["Arasy Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Adaman Ingot", + "Ebony Lumber", + "Bismuth Ingot", + }, + }, + ["Arasy Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bldwd. Lumber", + "Bismuth Ingot", + }, + }, + ["Arasy Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ruby", + "Rhodium Ingot", + "Gua. Lumber", + }, + }, + ["Arasy Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Electrum Ingot", + "Bismuth Ingot", + }, + }, + ["Arasy Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ebony Lumber", + "Bismuth Ingot", + }, + }, + ["Arasy Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ruby", + "Rhodium Ingot", + "Akaso Cloth", + }, + }, + ["Arasy Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gua. Lumber", + "Raaz Tusk", + }, + }, + ["Arasy Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Wyvern Skin", + "Electrum Ingot", + "Bismuth Ingot", + }, + }, + ["Arasy Tabar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ebony Lumber", + "Electrum Ingot", + "Bismuth Ingot", + }, + }, + ["Arbalest"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mahogany Lbr.", + "Carbon Fiber", + }, + }, + ["Arcanic Cell"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Carbon Fiber", + "Light Anima", + "Glass Sheet", + "Plasma Oil", + }, + }, + ["Arcanic Cell II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Carbon Fiber", + "Light Anima", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Arcanoclutch"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Ice Bead", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Arcanoclutch II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Ice Bead", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Ardent Jadeite"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Jadeite", + "Fire Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Areion Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Serica Cloth", + "Strider Boots", + }, + }, + ["Argent Coat"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Thread", + "Silk Cloth", + "Eltoro Leather", + "Galateia", + "Baking Soda", + "Platinum Silk", + }, + }, + ["Argent Hose"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Silver Chain", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Sheep Leather", + "Eltoro Leather", + "Baking Soda", + "Platinum Silk", + }, + }, + ["Argute Staff"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Effluvium", + "Dark Matter", + "Khoma Thread", + "Moldy Staff", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Moonstone Crystal", + }, + }, + ["Argute Stole"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Bole", + "Dark Matter", + "Cypress Log", + "Moonstone Crystal", + "Moldy Stole", + }, + }, + ["Arhat's Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Steel Sheet", + "Rainbow Thread", + "Velvet Cloth", + "Silk Cloth", + "Rainbow Cloth", + }, + }, + ["Arhat's Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Arhat's Jinpachi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Arhat's Sune-Ate"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Arhat's Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Iron Chain", + "Linen Thread", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Arke Corazza"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Gabbrath Horn", + "Tartarian Chain", + "Tartarian Chain", + "Faulpie Leather", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Arke Cosciales"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Gabbrath Horn", + "Tartarian Chain", + "Faulpie Leather", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Arke Gambieras"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Gabbrath Horn", + "Tartarian Chain", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Arke Manopolas"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Gabbrath Horn", + "Tartarian Chain", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Arke Zuchetto"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Gabbrath Horn", + "Tartarian Chain", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Armoire"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Gold Ingot", + }, + }, + ["Armor Plate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Darksteel Sheet", + "Steel Sheet", + "Carbon Fiber", + "Imperial Cermet", + }, + }, + ["Armor Plate II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Steel Sheet", + "Carbon Fiber", + "Imperial Cermet", + }, + }, + ["Armor Plate III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Adaman Sheet", + "Carbon Fiber", + "Drk. Adm. Sheet", + "Imperial Cermet", + }, + }, + ["Armor Plate IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Carbon Fiber", + "Drk. Adm. Sheet", + "Imperial Cermet", + "Titanium Sheet", + }, + }, + ["Armored Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Taurus Horn", + }, + }, + ["Armored Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Taurus Horn", + "Taurus Horn", + "Taurus Horn", + "Shagreen File", + }, + }, + ["Armored Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tgh. Dhal. Lth.", + "Leather Ring", + }, + }, + ["Army Biscuit"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Rye Flour", + "Selbina Butter", + "Rock Salt", + "Simsim", + "Honey", + "Yogurt", + }, + }, + ["Arquebus"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Steel Ingot", + "Mahogany Lbr.", + }, + }, + ["Arrabbiata"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Holy Basil", + "Spaghetti", + "Dragon Meat", + "Pomodoro Sauce", + }, + }, + ["Arrowwood Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Arrowwood Log", + }, + }, + ["Arrowwood Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Arrowwood Log", + "Arrowwood Log", + "Arrowwood Log", + "Bundling Twine", + }, + }, + ["Artificial Lens"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Ash Clogs"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Sheep Leather", + }, + }, + ["Ash Club"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + }, + }, + ["Ash Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Log", + }, + }, + ["Ash Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Log", + "Ash Log", + "Ash Log", + "Bundling Twine", + }, + }, + ["Ash Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + }, + }, + ["Ash Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Bat Fang", + }, + }, + ["Ash Staff 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 10", + }, + }, + ["Ashijiro No Tachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Tama-Hagane", + "Ebony Lumber", + "Rainbow Thread", + "Wyvern Skin", + "Bismuth Ingot", + }, + }, + ["Ashura"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Thread", + "Uchigatana", + }, + }, + ["Ashura 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 75", + }, + }, + ["Aspis"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bronze Sheet", + "Ash Lumber", + }, + }, + ["Assailant's Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Wind Cell", + "Butterfly Axe", + }, + }, + ["Assassin's Gorget"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Pelt", + "Dark Matter", + "S. Faulpie Leather", + "Peridot Crystal", + "Moldy Gorget", + }, + }, + ["Assassin's Knife"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Horn", + "Dark Matter", + "Ruthenium Ore", + "Moldy Dagger", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Peridot Crystal", + }, + }, + ["Astragalos"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Femur", + "Black Ink", + "Beastman Blood", + }, + }, + ["Athenienne"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Sheet", + "Maple Lumber", + "A.U. Brass Ingot", + }, + }, + ["Atinian Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Belladonna Sap", + "Urunday Lumber", + "Gabbrath Horn", + }, + }, + ["Attacker's Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Molt", + "Wamoura Silk", + "Hahava's Mail", + "Squamous Hide", + }, + }, + ["Augmenting Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Earth Cell", + "Leather Belt", + }, + }, + ["Aumoniere"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Amaryllis", + "Ensang. Cloth", + "Scarlet Ribbon", + }, + }, + ["Aureous Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Mahogany Lbr.", + "Gold Ingot", + }, + }, + ["Aurgelmir Orb"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Black Ink", + "Beastman Blood", + "Cyan Coral", + "Cyan Coral", + "Cyan Coral", + "Wyrm Ash", + }, + }, + ["Aurora Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Tiger Hide", + "Tundra Mantle", + }, + }, + ["Auroral Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Distilled Water", + "Aurora Bass", + }, + }, + ["Austere Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Tarasque Skin", + "Yowie Skin", + "Velvet Cuffs", + }, + }, + ["Austere Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Sheep Leather", + "Ram Leather", + "Yowie Skin", + "Velvet Hat", + }, + }, + ["Austere Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Wool Cloth", + "Rainbow Cloth", + "Undead Skin", + "Ram Leather", + "Tarasque Skin", + "Yowie Skin", + "Velvet Robe", + }, + }, + ["Austere Sabots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Sheep Leather", + "Ram Leather", + "Lindwurm Skin", + "Ebony Sabots", + }, + }, + ["Austere Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Undead Skin", + "Wolf Hide", + "Lindwurm Skin", + "Velvet Slops", + }, + }, + ["Auto-Repair Kit"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Glass Fiber", + "Glass Sheet", + "Hi-Potion Tank", + "Kilo Pump", + }, + }, + ["Auto-Repair Kit II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Glass Fiber", + "Glass Sheet", + "Hi-Potion Tank", + "Kilo Pump", + }, + }, + ["Auto-Repair Kit III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Glass Fiber", + "Glass Sheet", + "Hi-Potion Tank", + "Mega Pump", + }, + }, + ["Auto-Repair Kit IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Glass Fiber", + "Glass Sheet", + "Hi-Potion Tank", + "Kilo Pump", + "Mega Pump", + }, + }, + ["Automaton Oil"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Olive Oil", + "Plasma Oil", + "Polyflan Paper", + }, + }, + ["Automaton Oil +1"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Olive Oil", + "Olive Oil", + "Plasma Oil", + "Polyflan Paper", + }, + }, + ["Automaton Oil +2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Olive Oil", + "Olive Oil", + "Olive Oil", + "Plasma Oil", + "Polyflan Paper", + }, + }, + ["Automaton Oil +3"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Olive Oil", + "Olive Oil", + "Olive Oil", + "Olive Oil", + "Plasma Oil", + "Polyflan Paper", + }, + }, + ["Awning"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Elm Lumber", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Ayran"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Rock Salt", + "Distilled Water", + "Yogurt", + }, + }, + ["Azure Cermet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Panacea", + "Azure Leaf", + }, + }, + ["Azure Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ore", + "Blue Text. Dye", + "Light Chest", + }, + }, + ["B.E.W. Pitaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rock Salt", + "Imperial Flour", + "Hard-boiled Egg", + "Distilled Water", + "Buffalo Jerky", + "Chalaimbille", + "Winterflower", + }, + }, + ["Baayami Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Waktza Rostrum", + "Plovid Flesh", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + }, + }, + ["Baayami Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Waktza Rostrum", + "Plovid Flesh", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + }, + }, + ["Baayami Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Waktza Rostrum", + "Plovid Flesh", + "Plovid Flesh", + "Khoma Thread", + "Khoma Cloth", + }, + }, + ["Baayami Sabots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Waktza Rostrum", + "Plovid Flesh", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + }, + }, + ["Baayami Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Waktza Rostrum", + "Plovid Flesh", + "Khoma Thread", + "Khoma Cloth", + }, + }, + ["Baghnakhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Sheet", + }, + }, + ["Bagua Charm"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Emerald Crystal", + "Moldy Charm", + }, + }, + ["Bagua Wand"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Dark Matter", + "Tartarian Chain", + "Cypress Log", + "Moldy Club", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Emerald Crystal", + }, + }, + ["Bahadur"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Jacaranda Lbr.", + }, + }, + ["Bahut"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + }, + }, + ["Bahut 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 40", + }, + }, + ["Baked Apple"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Cinnamon", + "Faerie Apple", + }, + }, + ["Baked Popoto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Popoto", + }, + }, + ["Baking Soda"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Rock Salt", + "Moval. Water", + }, + }, + ["Balder Earring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ruthenium Ingot", + "Ruthenium Ingot", + "Wyrm Ash", + }, + }, + ["Balik Sandvici"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Rock Salt", + "Kitron", + "White Bread", + "Wild Onion", + "Mithran Tomato", + "Uskumru", + }, + }, + ["Balik Sis"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Black Pepper", + "Rock Salt", + "Mithran Tomato", + "Kilicbaligi", + }, + }, + ["Balsam Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tanzanite Jewel", + "Urunday Lumber", + "Rockfin Tooth", + }, + }, + ["Bamboo Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Bamboo Rod", + }, + }, + ["Bamboo Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Grass Thread", + }, + }, + ["Bamboo Fishing Rod 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 15", + }, + }, + ["Banded Helm"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Sheet", + "Mythril Sheet", + "Mythril Chain", + "Sheep Leather", + }, + }, + ["Banded Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Mythril Chain", + "Mythril Chain", + "Mythril Chain", + "Mythril Chain", + "Linen Cloth", + }, + }, + ["Bandeiras Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Walnut Lumber", + "Midrium Ingot", + "Rhodium Ingot", + }, + }, + ["Bandit's Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Giant Femur", + }, + }, + ["Bannaret Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Chainmail", + }, + }, + ["Banquet Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "B. Table Blueprint", + "B. Table Fabric", + "B. Table Wood", + }, + }, + ["Barbarian's Belt"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Fiend Blood", + "Beastman Blood", + "Leather Belt", + }, + }, + ["Barbarity"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonbow Leather", + "Faulpie Leather", + "Juggernaut", + }, + }, + ["Barchha"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Ash Lumber", + "Ash Lumber", + "Gold Ingot", + }, + }, + ["Bard's Charm"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Alexandrite Crystal", + "Moldy Charm", + }, + }, + ["Bard's Knife"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Horn", + "Dark Matter", + "Ruthenium Ore", + "Moldy Dagger", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Alexandrite Crystal", + }, + }, + ["Barnacle"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Carrier Crab Crpc.", + }, + }, + ["Barnacle Paella"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Tarutaru Rice", + "Saffron", + "Wild Onion", + "Distilled Water", + "Mussel", + "Barnacle", + "Barnacle", + }, + }, + ["Barone Corazza"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Mlbd. Sheet", + "Gold Ingot", + "Ocl. Chain", + "Mercury", + "Water Bead", + "Buffalo Leather", + "Scarlet Linen", + }, + }, + ["Barone Cosciales"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Cloth", + "Sarcenet Cloth", + "Buffalo Leather", + "Buffalo Leather", + "H.Q. Bugard Skin", + }, + }, + ["Barone Gambieras"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Ocl. Sheet", + "Gold Ingot", + "Platinum Sheet", + "Mercury", + "Buffalo Leather", + "Buffalo Leather", + }, + }, + ["Barone Manopolas"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Adaman Sheet", + "Gold Ingot", + "Mercury", + "Buffalo Leather", + "Scarlet Linen", + "Scarlet Linen", + }, + }, + ["Barone Zucchetto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Ocl. Sheet", + "Gold Ingot", + "Sapphire", + "Sheep Leather", + "Mercury", + }, + }, + ["Barrier Module"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Glass Fiber", + "Mythril Coil", + "Myth.Gear Mach.", + }, + }, + ["Bascinet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Sheep Leather", + }, + }, + ["Baselard"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Steel Ingot", + }, + }, + ["Bass Meuniere"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Saruta Orange", + "Dark Bass", + }, + }, + ["Bast Parchment"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Elm Log", + "Moko Grass", + "Distilled Water", + }, + }, + ["Bastard Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ram Leather", + }, + }, + ["Bastokan Cap"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Legionnaire's Cap", + }, + }, + ["Bastokan Circlet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Lgn. Circlet", + }, + }, + ["Bastokan Crossbow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Lgn. Crossbow", + }, + }, + ["Bastokan Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Ctr. Cuisses", + }, + }, + ["Bastokan Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Decurion's Dagger", + }, + }, + ["Bastokan Finger Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Ctr. F. Gauntlets", + }, + }, + ["Bastokan Greataxe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Centurion's Axe", + }, + }, + ["Bastokan Greaves"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Ctr. Greaves", + }, + }, + ["Bastokan Hammer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Decurion's Hammer", + }, + }, + ["Bastokan Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Lgn. Harness", + }, + }, + ["Bastokan Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Lgn. Knuckles", + }, + }, + ["Bastokan Leggings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Lgn. Leggings", + }, + }, + ["Bastokan Leggings 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Lgn. Leggings", + }, + }, + ["Bastokan Mittens"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Lgn. Mittens", + }, + }, + ["Bastokan Scale Mail"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Ctr. Scale Mail", + }, + }, + ["Bastokan Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Lgn. Scythe", + }, + }, + ["Bastokan Sill"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Bastokan Staff"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Lgn. Staff", + }, + }, + ["Bastokan Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Lgn. Subligar", + }, + }, + ["Bastokan Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Centurion's Sword", + }, + }, + ["Bastokan Targe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Decurion's Shield", + }, + }, + ["Bastokan Tea Set"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Adaman Ingot", + "Silver Ingot", + }, + }, + ["Bastokan Visor"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Centurion's Visor", + }, + }, + ["Batagreen Saute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Batagreens", + }, + }, + ["Bataquiche"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Black Pepper", + "Rock Salt", + "Danceshroom", + "Selbina Milk", + "Stone Cheese", + "Batagreen Saute", + "Bird Egg", + }, + }, + ["Bathtub"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Angelstone", + "Marble", + "Sieglinde Putty", + "Kaolin", + "Kaolin", + "White Text. Dye", + }, + }, + ["Battery"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Tin Ingot", + "Cermet Chunk", + "Rock Salt", + "Lightning Cluster", + "Distilled Water", + }, + }, + ["Battle Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Ram Leather", + "Ram Leather", + "Tiger Leather", + }, + }, + ["Battle Boots 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 66", + }, + }, + ["Battle Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Chestnut Lumber", + "Silk Cloth", + "Scorpion Claw", + "Coeurl Whisker", + }, + }, + ["Battle Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Battle Fork"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Trident", + }, + }, + ["Battle Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Battle Jupon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Battle Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Walnut Lumber", + "Walnut Lumber", + }, + }, + ["Battleaxe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Holly Lumber", + }, + }, + ["Bavarois (Item)"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Maple Sugar", + "Gelatin", + "Apple Mint", + "Vanilla", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Bay Aquarium"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Oak Lumber", + "Coral Fragment", + "Sieglinde Putty", + "Glass Sheet", + "Saltwater Set", + "Bibikibo", + "Bibikibo", + "Bibikibo", + }, + }, + ["Beak Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cockatrice Skin", + "Cuir Gloves", + }, + }, + ["Beak Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Sheep Leather", + "Cockatrice Skin", + "Cockatrice Skin", + }, + }, + ["Beak Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Cockatrice Skin", + "Cockatrice Skin", + }, + }, + ["Beak Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Cockatrice Skin", + "Cuir Highboots", + }, + }, + ["Beak Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cockatrice Skin", + "Cockatrice Skin", + }, + }, + ["Beak Necklace"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "H.Q. Crab Shell", + "Oxblood", + "Colibri Beak", + "Colibri Beak", + "Colibri Beak", + "Colibri Beak", + "Mohbwa Thread", + "Wivre Maul", + }, + }, + ["Beak Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cockatrice Skin", + "Cockatrice Skin", + "Cuir Trousers", + }, + }, + ["Bean Daifuku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blue Peas", + "Maple Sugar", + "Sticky Rice", + "Cornstarch", + "Distilled Water", + "Azuki Bean", + }, + }, + ["Beast Horn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ram Horn", + "Ram Horn", + }, + }, + ["Beastmaster Collar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Topaz Crystal", + "Moldy Collar", + }, + }, + ["Beaugreen Saute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Beaugreens", + }, + }, + ["Beaugreen Saute 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 55", + }, + }, + ["Bee Spatha"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Beeswax", + "Beeswax", + "Spatha", + }, + }, + ["Beech Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beech Log", + "Beech Log", + "Beech Log", + "Bundling Twine", + }, + }, + ["Beech Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beech Log", + }, + }, + ["Beef Paella"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Tarutaru Rice", + "Saffron", + "Wild Onion", + "Gigant Squid", + "Distilled Water", + "Buffalo Meat", + "Mussel", + }, + }, + ["Beef Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Soy Stock", + "Distilled Water", + "Bird Egg", + "Buffalo Meat", + "Cotton Tofu", + "Cibol", + "Shungiku", + "Shirataki", + }, + }, + ["Beeswax"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Distilled Water", + }, + }, + ["Beeswax 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Triturator", + "Distilled Water", + }, + }, + ["Beeswax 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Peph. Hive Chip", + "Peph. Hive Chip", + "Distilled Water", + }, + }, + ["Beeswax 4"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Triturator", + "Peph. Hive Chip", + "Peph. Hive Chip", + "Peph. Hive Chip", + "Peph. Hive Chip", + "Distilled Water", + }, + }, + ["Beetle Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Chocobo Fltchg.", + "Beetle Arrowhd.", + }, + }, + ["Beetle Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Beetle Jaw", + }, + }, + ["Beetle Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Beetle Jaw", + "Beetle Jaw", + "Beetle Jaw", + "Shagreen File", + }, + }, + ["Beetle Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Silver Earring", + }, + }, + ["Beetle Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beetle Shell", + "Beetle Jaw", + "Beetle Jaw", + }, + }, + ["Beetle Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Lizard Skin", + "Lizard Skin", + "Beetle Shell", + "Beetle Shell", + }, + }, + ["Beetle Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Beetle Jaw", + }, + }, + ["Beetle Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Lizard Skin", + "Beetle Shell", + "Beetle Jaw", + }, + }, + ["Beetle Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Beetle Jaw", + }, + }, + ["Beetle Mask 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 30", + }, + }, + ["Beetle Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Lizard Skin", + "Beetle Shell", + }, + }, + ["Beetle Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beetle Jaw", + }, + }, + ["Beetle Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 25", + }, + }, + ["Beetle Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Lizard Skin", + "Beetle Jaw", + }, + }, + ["Behemoth Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Cesti", + }, + }, + ["Behemoth Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Behemoth Horn", + }, + }, + ["Behemoth Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Behemoth Hide", + "Distilled Water", + }, + }, + ["Behemoth Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Behemoth Hide", + "Distilled Water", + }, + }, + ["Behemoth Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Behemoth Hide", + }, + }, + ["Behemoth Mantle 2"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Leath. Kit 70", + }, + }, + ["Behemoth Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Behemoth Horn", + "Behemoth Horn", + }, + }, + ["Behemoth Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Popoto", + "Kitron", + "San d'Or. Carrot", + "Behemoth Meat", + }, + }, + ["Beryllium Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Beryllium Ingot", + }, + }, + ["Beryllium Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beryllium Ingot", + }, + }, + ["Beryllium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Beryllium Ore", + }, + }, + ["Beryllium Kris"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pearl", + "Beryllium Ingot", + "Ra'Kaznar Ingot", + }, + }, + ["Beryllium Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beryllium Ingot", + "Beryllium Ingot", + "Beryllium Ingot", + }, + }, + ["Beryllium Pick"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Urunday Lumber", + "Beryllium Ingot", + }, + }, + ["Beryllium Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Urunday Lumber", + "Raaz Leather", + "Beryllium Ingot", + "Beryllium Ingot", + "Beryllium Ingot", + }, + }, + ["Beryllium Tachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Urunday Lumber", + "Akaso Thread", + "Raaz Leather", + "Beryllium Ingot", + "Beryllium Ingot", + "Ra'Kaznar Ingot", + }, + }, + ["Beverage Barrel"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Water Barrel", + "Oak Lumber", + "Oak Lumber", + "Grape Juice", + "Grape Juice", + "Grape Juice", + }, + }, + ["Bewitched Ash Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Log", + "Fire Anima", + "Ice Anima", + "Light Anima", + }, + }, + ["Bewitched Breeches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "C. Breeches -1", + "Eschite Ore", + }, + }, + ["Bewitched Breeches 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Breeches", + "Warblade's Hide", + "Tartarian Chain", + "Eschite Ore", + }, + }, + ["Bewitched Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Cap -1", + "Eschite Ore", + }, + }, + ["Bewitched Cap 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Cap", + "Maliya. Coral", + "Largantua's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Celata"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Celata -1", + "Eschite Ore", + }, + }, + ["Bewitched Celata 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Celata", + "Warblade's Hide", + "Tartarian Chain", + "Eschite Ore", + }, + }, + ["Bewitched Crown"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Crown -1", + "Eschite Ore", + }, + }, + ["Bewitched Crown 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Crown", + "Hepatizon Ingot", + "Sif's Macrame", + "Eschite Ore", + }, + }, + ["Bewitched Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Cuirass -1", + "Eschite Ore", + }, + }, + ["Bewitched Cuirass 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Cuirass", + "Macuil Plating", + "Douma's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Cuisses"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Cuisses -1", + "Eschite Ore", + }, + }, + ["Bewitched Cuisses 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Cuisses", + "Arthro's Shell", + "Plovid Effluvium", + "Eschite Ore", + }, + }, + ["Bewitched Dalmatica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "C. Dalmatica -1", + "Eschite Ore", + }, + }, + ["Bewitched Dalmatica 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Dalmatica", + "Hepatizon Ingot", + "Sif's Macrame", + "Eschite Ore", + }, + }, + ["Bewitched Diechlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "C. Diechlings -1", + "Eschite Ore", + }, + }, + ["Bewitched Diechlings 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Diechlings", + "Macuil Plating", + "Douma's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Finger Gauntlets"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed F. Gnt. -1", + "Eschite Ore", + }, + }, + ["Bewitched Finger Gauntlets 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Fng. Gnt.", + "Arthro's Shell", + "Plovid Effluvium", + "Eschite Ore", + }, + }, + ["Bewitched Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Gloves -1", + "Eschite Ore", + }, + }, + ["Bewitched Gloves 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Gloves", + "Maliya. Coral", + "Immani. Hide", + "Eschite Ore", + }, + }, + ["Bewitched Gold Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Fire Anima", + "Ice Anima", + "Light Anima", + }, + }, + ["Bewitched Greaves"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Greaves -1", + "Eschite Ore", + }, + }, + ["Bewitched Greaves 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Greaves", + "Arthro's Shell", + "Plovid Effluvium", + "Eschite Ore", + }, + }, + ["Bewitched Haidate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Haidate -1", + "Eschite Ore", + }, + }, + ["Bewitched Haidate 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Haidate", + "Yggdreant Bole", + "Specter's Ore", + "Eschite Ore", + }, + }, + ["Bewitched Handschuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "C. Handschuhs -1", + "Eschite Ore", + }, + }, + ["Bewitched Handschuhs 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "C. Handschuhs", + "Macuil Plating", + "Douma's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Harness -1", + "Eschite Ore", + }, + }, + ["Bewitched Harness 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Harness", + "Maliya. Coral", + "Immani. Hide", + "Eschite Ore", + }, + }, + ["Bewitched Hauberk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Hauberk -1", + "Eschite Ore", + }, + }, + ["Bewitched Hauberk 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Hauberk", + "Sif's Macrame", + "Tartarian Chain", + "Eschite Ore", + }, + }, + ["Bewitched Kabuto"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Kabuto -1", + "Eschite Ore", + }, + }, + ["Bewitched Kabuto 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Kabuto", + "Yggdreant Bole", + "Specter's Ore", + "Eschite Ore", + }, + }, + ["Bewitched Kote"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cursed Kote -1", + "Eschite Ore", + }, + }, + ["Bewitched Kote 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cursed Kote", + "Yggdreant Bole", + "Specter's Ore", + "Eschite Ore", + }, + }, + ["Bewitched Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "C. Leggings -1", + "Eschite Ore", + }, + }, + ["Bewitched Leggings 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Leggings", + "Maliya. Coral", + "Immani. Hide", + "Eschite Ore", + }, + }, + ["Bewitched Mail"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Mail -1", + "Eschite Ore", + }, + }, + ["Bewitched Mail 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Mail", + "Arthro's Shell", + "Plovid Effluvium", + "Eschite Ore", + }, + }, + ["Bewitched Mask"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Mask -1", + "Eschite Ore", + }, + }, + ["Bewitched Mask 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Cursed Mask", + "Arthro's Shell", + "Plovid Effluvium", + "Eschite Ore", + }, + }, + ["Bewitched Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Mitts -1", + "Eschite Ore", + }, + }, + ["Bewitched Mitts 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Mitts", + "Hepatizon Ingot", + "Sif's Macrame", + "Eschite Ore", + }, + }, + ["Bewitched Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "C. Mufflers -1", + "Eschite Ore", + }, + }, + ["Bewitched Mufflers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Mufflers", + "Malatrix's Shard", + "Tartarian Chain", + "Eschite Ore", + }, + }, + ["Bewitched Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Pumps -1", + "Eschite Ore", + }, + }, + ["Bewitched Pumps 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Pumps", + "Sif's Macrame", + "Cehuetzi Pelt", + "Eschite Ore", + }, + }, + ["Bewitched Schaller"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Schaller -1", + "Eschite Ore", + }, + }, + ["Bewitched Schaller 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Schaller", + "Macuil Plating", + "Douma's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Schuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Schuhs -1", + "Eschite Ore", + }, + }, + ["Bewitched Schuhs 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cursed Schuhs", + "Macuil Plating", + "Douma's Shard", + "Eschite Ore", + }, + }, + ["Bewitched Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Slacks -1", + "Eschite Ore", + }, + }, + ["Bewitched Slacks 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Slacks", + "Hepatizon Ingot", + "Sif's Macrame", + "Eschite Ore", + }, + }, + ["Bewitched Sollerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "C. Sollerets -1", + "Eschite Ore", + }, + }, + ["Bewitched Sollerets 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Sollerets", + "Malatrix's Shard", + "Tartarian Chain", + "Eschite Ore", + }, + }, + ["Bewitched Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Subligar -1", + "Eschite Ore", + }, + }, + ["Bewitched Subligar 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cursed Subligar", + "Maliya. Coral", + "Immani. Hide", + "Eschite Ore", + }, + }, + ["Bewitched Sune-Ate"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "C. Sune-Ate -1", + "Eschite Ore", + }, + }, + ["Bewitched Sune-Ate 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cursed Sune-Ate", + "Yggdreant Bole", + "Specter's Ore", + "Eschite Ore", + }, + }, + ["Bewitched Togi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cursed Togi -1", + "Eschite Ore", + }, + }, + ["Bewitched Togi 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cursed Togi", + "Yggdreant Bole", + "Specter's Ore", + "Eschite Ore", + }, + }, + ["Bhakazi Sainti"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gua. Lumber", + "Titanium Ingot", + "Bismuth Ingot", + "Cehuetzi Claw", + "Cehuetzi Claw", + }, + }, + ["Bhuj"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Gold Ingot", + "Mercury", + }, + }, + ["Bihkah Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Peiste Leather", + "Midrium Ingot", + "Midrium Ingot", + }, + }, + ["Bilbo"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Silver Ingot", + }, + }, + ["Bird Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bird Feather", + "Bird Feather", + }, + }, + ["Bird Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Zephyr Thread", + }, + }, + ["Bishop's Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Linen Cloth", + "Priest's Robe", + }, + }, + ["Bismuth Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bismuth Ingot", + }, + }, + ["Bismuth Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tin Ore", + "Zinc Ore", + "Bismuth Ore", + "Bismuth Ore", + }, + }, + ["Bismuth Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bismuth Ingot", + }, + }, + ["Bismuth Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "Bismuth Ingot", + "Bismuth Ingot", + "Bismuth Ingot", + "Bismuth Ingot", + "Bismuth Ingot", + "Bismuth Ingot", + }, + }, + ["Bison Gamashes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Hair", + "Manticore Hair", + "Hippogryph Fthr.", + "Buffalo Leather", + "Buffalo Leather", + }, + }, + ["Bison Jacket"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Lth.", + "Manticore Hair", + "Manticore Hair", + "Buffalo Horn", + "Eft Skin", + "Buffalo Leather", + "Buffalo Leather", + }, + }, + ["Bison Kecks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Manticore Lth.", + "Manticore Hair", + "Manticore Hair", + "Manticore Hair", + "Manticore Hair", + "Buffalo Leather", + "Buffalo Leather", + }, + }, + ["Bison Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bay Leaves", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Rarab Tail", + "Buffalo Meat", + }, + }, + ["Bison Warbonnet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Hair", + "Manticore Hair", + "Hippogryph Fthr.", + "Hippogryph Fthr.", + "Eft Skin", + "Buffalo Leather", + }, + }, + ["Bison Wristbands"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Lth.", + "Manticore Hair", + "Manticore Hair", + "Buffalo Leather", + }, + }, + ["Bittern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Salinator", + "Distilled Water", + }, + }, + ["Black Adargas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Oak Lumber", + "Gold Ingot", + "Ovinnik Leather", + }, + }, + ["Black Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Teak Lumber", + "Blk. Bolt Heads", + }, + }, + ["Black Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bundling Twine", + "Teak Lumber", + "Teak Lumber", + "Teak Lumber", + "Blk. Bolt Heads", + "Blk. Bolt Heads", + "Blk. Bolt Heads", + }, + }, + ["Black Bolt 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 35", + }, + }, + ["Black Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Obsidian", + }, + }, + ["Black Bread"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rye Flour", + "Rock Salt", + "Distilled Water", + }, + }, + ["Black Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Black Chocobo Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Black C. Feather", + "Black C. Feather", + }, + }, + ["Black Chocobo Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Black C. Feather", + "Black C. Feather", + "Black C. Feather", + "Black C. Feather", + "Black C. Feather", + "Black C. Feather", + "Zephyr Thread", + }, + }, + ["Black Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Silk Cloth", + "Silk Cloth", + "Rainbow Cloth", + "Raxa", + "Raxa", + "Raxa", + }, + }, + ["Black Cotehardie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Malboro Fiber", + "Ram Leather", + "Tiger Leather", + "Mistletoe", + "Fiend Blood", + "Black Ink", + "Velvet Robe", + "Beak Jerkin", + }, + }, + ["Black Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Tiger Leather", + "Tiger Leather", + "Kunwu Sheet", + }, + }, + ["Black Curry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Curry Powder", + "Woozyshroom", + "Eggplant", + "Distilled Water", + "Lakerda", + "Ahtapot", + }, + }, + ["Black Curry 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Curry Powder", + "Nebimonite", + "Woozyshroom", + "Eggplant", + "Gugru Tuna", + "Distilled Water", + }, + }, + ["Black Curry Bun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Olive Oil", + "Black Curry", + "Bird Egg", + }, + }, + ["Black Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Mythril Earring", + }, + }, + ["Black Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Mythril Earring +1", + }, + }, + ["Black Gadlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Kunwu Sheet", + "Tiger Gloves", + }, + }, + ["Black Hobby Bo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Hickory Lumber", + "Dogwd. Lumber", + "Onyx", + "Onyx", + "Blk. Chocobo Dye", + }, + }, + ["Black Ink"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Nebimonite", + "Distilled Water", + }, + }, + ["Black Ink 10"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Gigant Squid", + "Distilled Water", + }, + }, + ["Black Ink 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Nebimonite", + "Distilled Water", + }, + }, + ["Black Ink 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Distilled Water", + "Cone Calamary", + "Cone Calamary", + }, + }, + ["Black Ink 4"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Distilled Water", + "Cone Calamary", + "Cone Calamary", + }, + }, + ["Black Ink 5"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Im. Tea Leaves", + "Distilled Water", + "Kalamar", + "Kalamar", + }, + }, + ["Black Ink 6"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Distilled Water", + "Kalamar", + "Kalamar", + }, + }, + ["Black Ink 7"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Grimmonite", + "Distilled Water", + }, + }, + ["Black Ink 8"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Im. Tea Leaves", + "Im. Tea Leaves", + "Distilled Water", + "Ahtapot", + }, + }, + ["Black Ink 9"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Alch. Kit 5", + }, + }, + ["Black Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Tiger Leather", + "Tiger Mantle", + }, + }, + ["Black Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Linen Cloth", + "Velvet Cloth", + "Saruta Cotton", + }, + }, + ["Black Mitts 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 60", + }, + }, + ["Black Pudding"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Cinnamon", + "Kitron", + "Saruta Orange", + "Buburimu Grape", + "Bird Egg", + "Royal Grape", + }, + }, + ["Black Puppet Turban"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Cotton Cloth", + "Velvet Cloth", + }, + }, + ["Black Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Mythril Ring", + }, + }, + ["Black Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Mythril Ring +1", + }, + }, + ["Black Sallet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Ocl. Ingot", + "Sheep Leather", + "Kunwu Sheet", + }, + }, + ["Black Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + }, + }, + ["Black Sollerets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Kunwu Sheet", + "Tiger Ledelsens", + }, + }, + ["Black Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Blackened Frog"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Rock Salt", + "Copper Frog", + }, + }, + ["Blackened Frog 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Rock Salt", + "Senroh Frog", + }, + }, + ["Blackened Newt"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Bay Leaves", + "Rock Salt", + "Elshimo Newt", + }, + }, + ["Blackened Newt 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Bay Leaves", + "Rock Salt", + "Phanauet Newt", + }, + }, + ["Blaze Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Incomb. Wool", + "Wool Hose", + }, + }, + ["Blenmot's Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oxblood", + "Vivified Coral", + "Holy Water", + "Holy Water", + "Hallowed Water", + }, + }, + ["Blessed Briault"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Rainbow Cloth", + "Galateia", + "Galateia", + }, + }, + ["Blessed Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Saruta Cotton", + "Galateia", + }, + }, + ["Blessed Mythril Sheet"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Holy Water", + }, + }, + ["Blessed Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Tiger Leather", + "Manticore Hair", + "Eltoro Leather", + }, + }, + ["Blessed Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Rainbow Cloth", + "Galateia", + }, + }, + ["Blind Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Bln. Bolt Heads", + }, + }, + ["Blind Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Bln. Bolt Heads", + "Bln. Bolt Heads", + "Bln. Bolt Heads", + "Bundling Twine", + }, + }, + ["Blind Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Animal Glue", + "Blinding Potion", + }, + }, + ["Blind Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Blinding Potion", + "Bronze Dagger", + }, + }, + ["Blind Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Blinding Potion", + "Bronze Knife", + }, + }, + ["Blinding Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Crying Mustard", + "Poison Flour", + "Sleepshroom", + }, + }, + ["Blinding Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Crying Mustard", + "Crying Mustard", + "Poison Flour", + "Poison Flour", + "Triturator", + "Sleepshroom", + "Sleepshroom", + }, + }, + ["Blinding Potion 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Mercury", + "Nebimonite", + }, + }, + ["Blink Band"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fine Linen Cloth", + "Flax Headband", + }, + }, + ["Blissful Chapeau"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Earth Cell", + "Trader's Chapeau", + }, + }, + ["Blizzard Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Frigid Skin", + "Raptor Gloves", + }, + }, + ["Blizzard Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Frgd. Orichalcum", + "Orichalcum Scythe", + }, + }, + ["Blood Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Fiend Blood", + "Leech Saliva", + "Lizard Blood", + "Bird Blood", + "Beast Blood", + }, + }, + ["Blood Broth 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Beastman Blood", + "Leech Saliva", + "Lizard Blood", + "Bird Blood", + "Beast Blood", + }, + }, + ["Blood Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Garnet", + "Mythril Earring", + }, + }, + ["Blood Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Garnet", + "Mythril Earring +1", + }, + }, + ["Blood Stone"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Thread", + "Giant Femur", + "Fiend Blood", + }, + }, + ["Bloodwood Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bloodwood Log", + }, + }, + ["Bloodwood Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bloodwood Log", + "Bloodwood Log", + "Bloodwood Log", + "Bundling Twine", + }, + }, + ["Bloody Aketon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Dragon Blood", + }, + }, + ["Bloody Blade"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Beastman Blood", + "Revival Root", + "Darksteel Falchion", + }, + }, + ["Bloody Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Bld. Bolt Heads", + }, + }, + ["Bloody Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Bld. Bolt Heads", + "Bld. Bolt Heads", + "Bld. Bolt Heads", + "Bundling Twine", + }, + }, + ["Bloody Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Beastman Blood", + "Revival Root", + }, + }, + ["Bloody Chocolate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Kukuru Bean", + "Kukuru Bean", + "Kukuru Bean", + "Kukuru Bean", + "Flytrap Leaf", + "Selbina Milk", + }, + }, + ["Bloody Lance"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Beastman Blood", + "Beastman Blood", + "Revival Root", + "Darksteel Lance", + }, + }, + ["Bloody Lance 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Alch. Kit 90", + }, + }, + ["Bloody Ram Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ram Skin", + "Earth Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Bloody Ram Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ram Skin", + "Earth Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Bloody Rapier"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Beastman Blood", + "Revival Root", + "Schlaeger", + }, + }, + ["Bloody Rapier 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Alch. Kit 85", + }, + }, + ["Bloody Sword"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Beastman Blood", + "Revival Root", + "Bastard Sword", + }, + }, + ["Blue 3-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "3-Drawer Almirah", + "Gold Thread", + "Blue Text. Dye", + }, + }, + ["Blue 6-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "6-Drawer Almirah", + "Gold Thread", + "Blue Text. Dye", + }, + }, + ["Blue 9-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "9-Drawer Almirah", + "Gold Thread", + "Blue Text. Dye", + }, + }, + ["Blue Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Cloth", + "Silk Cloth", + "Beetle Blood", + }, + }, + ["Blue Cotehardie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Malboro Fiber", + "Ram Leather", + "Tiger Leather", + "Mistletoe", + "Fiend Blood", + "Beetle Blood", + "Velvet Robe", + "Beak Jerkin", + }, + }, + ["Blue Hobby Bo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Hickory Lumber", + "Dogwd. Lumber", + "Onyx", + "Onyx", + "Blue Chocobo Dye", + }, + }, + ["Blue Mahogany Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mahogany Bed", + "Wool Thread", + "Blue Text. Dye", + }, + }, + ["Blue Noble's Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Noble's Bed", + "Gold Thread", + "Blue Text. Dye", + }, + }, + ["Blue Round Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Platinum Silk", + "Teak Lumber", + "Blue Text. Dye", + }, + }, + ["Blue Storm Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Brass Ingot", + "Silver Ingot", + "Silk Cloth", + "Glass Sheet", + "Sailcloth", + "Blue Text. Dye", + }, + }, + ["Blue Tarutaru Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tarutaru Desk", + "Blue Text. Dye", + }, + }, + ["Blue Tarutaru Standing Screen"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Taru F. Screen", + "Blue Text. Dye", + }, + }, + ["Blue Textile Dye"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dyer's Woad", + }, + }, + ["Blue Viola"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Blue Rock", + "Granite", + "Red Gravel", + "Wildgrass Seeds", + "Humus", + }, + }, + ["Blunderbuss"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Steel Ingot", + "Elm Lumber", + "Silver Ingot", + }, + }, + ["Blurred Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Tabarzin", + }, + }, + ["Blurred Bow"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Amazon Bow", + }, + }, + ["Blurred Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Dragon Claws", + }, + }, + ["Blurred Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Nagan", + }, + }, + ["Blurred Cleaver"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Toporok", + }, + }, + ["Blurred Crossbow"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Rpt. Crossbow", + }, + }, + ["Blurred Harp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Cythara Anglica", + }, + }, + ["Blurred Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Misericorde", + }, + }, + ["Blurred Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Dabo", + }, + }, + ["Blurred Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Scepter", + }, + }, + ["Blurred Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Death Scythe", + }, + }, + ["Blurred Shield"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Koenig Shield", + }, + }, + ["Blurred Staff"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Mythic Pole", + }, + }, + ["Blurred Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Adaman Kilij", + }, + }, + ["Blutkrallen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Bldwd. Lumber", + "Linen Thread", + "Scintillant Ingot", + "Meeble Claw", + "Meeble Claw", + "Meeble Claw", + }, + }, + ["Bodkin Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Blk. Chc. Fltchg.", + "Arm. Arrowhd.", + }, + }, + ["Boiled Barnacles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Distilled Water", + "Barnacle", + }, + }, + ["Boiled Cockatrice"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Ginger", + "Rock Salt", + "San d'Or. Carrot", + "Mithran Tomato", + "Cockatrice Meat", + "Coral Fungus", + "Distilled Water", + }, + }, + ["Boiled Crab"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bay Leaves", + "Rock Salt", + "Land Crab Meat", + "Distilled Water", + }, + }, + ["Boiled Crayfish"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Crayfish", + "Distilled Water", + }, + }, + ["Boiled Tuna Head"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ginger", + "Maple Sugar", + "Rock Salt", + "Treant Bulb", + "Grape Juice", + "Gugru Tuna", + "Distilled Water", + "Beaugreens", + }, + }, + ["Bokuto"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Blinding Potion", + "Shinobi-Gatana", + }, + }, + ["Bolt Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Sheep Leather", + "Brz. Bolt Heads", + "Brz. Bolt Heads", + "Leather Pouch", + }, + }, + ["Bone Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Bone Arrowhd.", + "Yagudo Fltchg.", + }, + }, + ["Bone Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + }, + }, + ["Bone Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + }, + }, + ["Bone Arrowheads 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 10", + }, + }, + ["Bone Axe"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Femur", + "Ram Horn", + "Ram Horn", + }, + }, + ["Bone Axe 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 20", + }, + }, + ["Bone Cudgel"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Cloth", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Giant Femur", + }, + }, + ["Bone Cudgel 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 40", + }, + }, + ["Bone Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bone Chip", + "Bone Chip", + }, + }, + ["Bone Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + }, + }, + ["Bone Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + "Pugil Scales", + "Bone Chip", + "Giant Femur", + }, + }, + ["Bone Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Giant Femur", + }, + }, + ["Bone Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + "Bone Chip", + "Giant Femur", + }, + }, + ["Bone Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Bone Chip", + "Giant Femur", + }, + }, + ["Bone Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + "Bone Chip", + "Bone Chip", + }, + }, + ["Bone Patas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Crab Shell", + "Giant Femur", + "Giant Femur", + "Carbon Fiber", + }, + }, + ["Bone Pick"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Giant Femur", + }, + }, + ["Bone Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Sheep Tooth", + }, + }, + ["Bone Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Giant Femur", + "Ram Horn", + "Ram Horn", + }, + }, + ["Bone Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bone. Kit 50", + }, + }, + ["Bone Scythe"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Grass Cloth", + "Sheep Tooth", + "Giant Femur", + "Giant Femur", + }, + }, + ["Bone Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Cloth", + "Sheep Leather", + "Sheep Leather", + "Bone Chip", + }, + }, + ["Bonecraft Set 25"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Beetle Jaw", + }, + }, + ["Bonecraft Set 45"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Crab Shell", + }, + }, + ["Bonecraft Set 65"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Ladybug Wing", + "Ladybug Wing", + }, + }, + ["Bonecraft Set 70"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Fish Scales", + "Demon Horn", + }, + }, + ["Bonecraft Set 75"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Ladybug Wing", + "Electrum Chain", + }, + }, + ["Bonecraft Set 80"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Bonecraft Set 85"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Imp Horn", + "Colibri Beak", + }, + }, + ["Bonecraft Set 90"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Dragon Talon", + "Dragon Talon", + }, + }, + ["Bonecraft Set 95"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ram Leather", + "Ram Leather", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + }, + }, + ["Bonfire"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beech Lumber", + "Beech Lumber", + "Firesand", + "Teak Lumber", + }, + }, + ["Book Holder"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Holly Lumber", + }, + }, + ["Bookshelf"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Tufa", + "Tufa", + "Tufa", + }, + }, + ["Bookstack"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Marble", + "Marble", + "Marble", + }, + }, + ["Boomerang"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Lumber", + "Cotton Thread", + }, + }, + ["Boscaiola"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Olive Oil", + "Spaghetti", + "Danceshroom", + "King Truffle", + "Wild Onion", + "Scream Fungus", + "Pomodoro Sauce", + }, + }, + ["Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Bracers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 25", + }, + }, + ["Brain Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Crying Mustard", + "Apple Vinegar", + "Gelatin", + "Honey", + "King Truffle", + "Yellow Globe", + "Distilled Water", + }, + }, + ["Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Sheep Leather", + }, + }, + ["Brass Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Axe", + }, + }, + ["Brass Baghnakhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Cat Baghnakhs", + }, + }, + ["Brass Cap"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Cap", + }, + }, + ["Brass Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + }, + }, + ["Brass Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Workshop Anvil", + }, + }, + ["Brass Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Brass Scales", + "Cotton Thread", + "Dhalmel Leather", + "Leather Trousers", + }, + }, + ["Brass Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Dagger", + }, + }, + ["Brass Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Brass Scales", + "Cotton Thread", + "Dhalmel Leather", + "Leather Gloves", + }, + }, + ["Brass Finger Gauntlets 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 30", + }, + }, + ["Brass Flowerpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Sheet", + "Brass Sheet", + "Brass Sheet", + }, + }, + ["Brass Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Brass Scales", + "Cotton Thread", + "Dhalmel Leather", + "Leather Highboots", + }, + }, + ["Brass Grip"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + "Mandrel", + }, + }, + ["Brass Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Ingot", + }, + }, + ["Brass Hammer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Hammer", + }, + }, + ["Brass Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Sheet", + "Bronze Harness", + }, + }, + ["Brass Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Ore", + "Copper Ore", + "Zinc Ore", + }, + }, + ["Brass Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Zinc Ore", + "Brass Nugget", + "Brass Nugget", + "Brass Nugget", + "Brass Nugget", + "Brass Nugget", + "Brass Nugget", + }, + }, + ["Brass Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 10", + }, + }, + ["Brass Jadagna"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Karakul Leather", + "A.U. Brass Ingot", + "Jadagna", + }, + }, + ["Brass Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Knuckles", + }, + }, + ["Brass Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Leggings", + }, + }, + ["Brass Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Dhalmel Leather", + "Faceguard", + }, + }, + ["Brass Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Mittens", + }, + }, + ["Brass Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + }, + }, + ["Brass Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Rod", + }, + }, + ["Brass Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Brass Scales", + "Brass Scales", + "Brass Scales", + "Cotton Thread", + "Dhalmel Leather", + "Leather Vest", + }, + }, + ["Brass Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Sheet", + }, + }, + ["Brass Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + }, + }, + ["Brass Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Workshop Anvil", + }, + }, + ["Brass Spear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Ingot", + "Ash Lumber", + "Linen Thread", + }, + }, + ["Brass Subligar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Subligar", + }, + }, + ["Brass Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Brass Sheet", + "Brass Sheet", + "Animal Glue", + }, + }, + ["Brass Xiphos"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Xiphos", + }, + }, + ["Brass Zaghnal"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bronze Zaghnal", + }, + }, + ["Brass Zaghnal 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 15", + }, + }, + ["Bread Crock"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Jadeite", + "Grass Thread", + "Grass Thread", + "Kaolin", + "Kaolin", + }, + }, + ["Bream Risotto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Selbina Butter", + "Tarutaru Rice", + "Black Pepper", + "Olive Oil", + "Sage", + "Bastore Bream", + "Distilled Water", + }, + }, + ["Bream Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Mercanbaligi", + }, + }, + ["Bream Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Bastore Bream", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Iron Sheet", + "Iron Sheet", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Breath Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Fr. Dhalmel Hide", + "Dhalmel Mantle", + }, + }, + ["Breeches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Mythril Chain", + "Linen Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Breeze Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wind Bead", + "Orichalcum Ring", + }, + }, + ["Breidox"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Thokcha Ingot", + "Thokcha Ingot", + "Dark Adaman", + }, + }, + ["Bretzel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Bretzel 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + "Distilled Water", + "Bird Egg", + }, + }, + ["Brigandine"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Brass Scales", + "Gold Sheet", + "Velvet Cloth", + "Velvet Cloth", + "Ram Leather", + "Lizard Jerkin", + }, + }, + ["Brigandine 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Iron Chain", + "Wool Thread", + "Linen Cloth", + "Velvet Cloth", + "Sheep Leather", + "Tiger Leather", + }, + }, + ["Brilliant Gold Thread"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Silk Thread", + "Water Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Brilliant Snow"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Sheep Leather", + "Glass Fiber", + "Rock Salt", + "Holy Water", + "Distilled Water", + }, + }, + ["Brimsand"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Sulfur", + "Fire Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Brimsand 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sulfur", + "Cluster Ash", + "Fire Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Briny Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Rhinochimera", + "Hamsi", + "Mercanbaligi", + }, + }, + ["Brioso Whistle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Thought Crystal", + "Cyan Coral", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Brioso Whistle 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hope Crystal", + "Cyan Coral", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Brioso Whistle 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fulfillment Crystal", + "Cyan Coral", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Brise-os"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Karakul Leather", + "Sahagin Gold", + "Jadagna", + }, + }, + ["Brisingamen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Chain", + "Platinum Chain", + "Platinum Chain", + "Freya's Tear", + "Jeweled Collar", + }, + }, + ["Broach Lance"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Obelisk Lance", + }, + }, + ["Broadsword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Lizard Skin", + }, + }, + ["Brocade Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Silver Thread", + "Gold Thread", + "Gold Thread", + }, + }, + ["Brocade Obi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 80", + }, + }, + ["Bronze Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Ash Lumber", + }, + }, + ["Bronze Bed"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + }, + }, + ["Bronze Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Brz. Bolt Heads", + }, + }, + ["Bronze Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Brz. Bolt Heads", + "Brz. Bolt Heads", + "Brz. Bolt Heads", + "Bundling Twine", + }, + }, + ["Bronze Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + }, + }, + ["Bronze Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Firesand", + }, + }, + ["Bronze Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Bronze Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Bronze Ingot", + }, + }, + ["Bronze Hammer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Chestnut Lumber", + }, + }, + ["Bronze Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bronze Sheet", + "Bronze Sheet", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Bronze Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beastcoin", + "Beastcoin", + "Beastcoin", + "Beastcoin", + }, + }, + ["Bronze Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Ore", + "Copper Ore", + "Tin Ore", + }, + }, + ["Bronze Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tin Ore", + "Bronze Nugget", + "Bronze Nugget", + "Bronze Nugget", + "Bronze Nugget", + "Bronze Nugget", + "Bronze Nugget", + }, + }, + ["Bronze Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Ash Lumber", + }, + }, + ["Bronze Knife 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 5", + }, + }, + ["Bronze Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Sheet", + "Bronze Sheet", + "Ash Lumber", + }, + }, + ["Bronze Leggings"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Leggings", + }, + }, + ["Bronze Leggings 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bronze Sheet", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Bronze Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + }, + }, + ["Bronze Mace 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 10", + }, + }, + ["Bronze Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Sheep Leather", + }, + }, + ["Bronze Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Bronze Ingot", + "Bronze Ingot", + }, + }, + ["Bronze Rose"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Im. Wootz Ingot", + "Drk. Adm. Sheet", + "Tr. Brz. Sheet", + "A.U. Brass Ingot", + }, + }, + ["Bronze Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Sheet", + }, + }, + ["Bronze Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + }, + }, + ["Bronze Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Workshop Anvil", + }, + }, + ["Bronze Spear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Ash Lumber", + "Grass Thread", + }, + }, + ["Bronze Subligar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Subligar", + }, + }, + ["Bronze Subligar 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Grass Cloth", + "Sheep Leather", + }, + }, + ["Bronze Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Sheep Leather", + }, + }, + ["Bronze Zaghnal"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Ash Lumber", + "Grass Cloth", + }, + }, + ["Bubble Chocolate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Kukuru Bean", + "Kukuru Bean", + "Kukuru Bean", + "Kukuru Bean", + "Selbina Milk", + }, + }, + ["Bubbling Carrion Broth"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Gelatin", + "Hare Meat", + "Distilled Water", + "Rotten Meat", + }, + }, + ["Buche au Chocolat"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Bay Leaves", + "Maple Sugar", + "Kukuru Bean", + "Selbina Milk", + "Bbl. Chocolate", + "Bird Egg", + }, + }, + ["Buckler"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Targe", + }, + }, + ["Buckler Plaque"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Silver Sheet", + "Mahogany Lbr.", + "Platinum Ingot", + "Platinum Sheet", + "Fluorite", + "Fluorite", + "Rainbow Velvet", + }, + }, + ["Budliqa"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Waktza Rostrum", + "Gua. Lumber", + "Bismuth Ingot", + }, + }, + ["Buffalo Jerky"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Rock Salt", + "Buffalo Meat", + }, + }, + ["Buffalo Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Buffalo Hide", + "Distilled Water", + }, + }, + ["Buffalo Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Buffalo Hide", + "Distilled Water", + }, + }, + ["Buffalo Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Buffalo Hide", + "Buffalo Hide", + "Buffalo Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Buffoon's Collar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Karakul Cloth", + }, + }, + ["Bug Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Lugworm", + "Shell Bug", + "Shell Bug", + }, + }, + ["Bugard Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Bugard Skin", + "Distilled Water", + }, + }, + ["Bugard Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Bugard Skin", + "Bugard Skin", + "Bugard Skin", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Bugard Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Bugard Skin", + "Distilled Water", + }, + }, + ["Bugard Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bugard Leather", + "Bugard Leather", + }, + }, + ["Bulky Coffer"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ore", + "Light Chest", + }, + }, + ["Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Firesand", + }, + }, + ["Bureau"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + }, + }, + ["Burning Carrion Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "G. Sheep Meat", + "Ruszor Meat", + }, + }, + ["Busuto"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Shinobi-Gatana", + }, + }, + ["Butachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Darksteel Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Tama-Hagane", + "Ancient Lumber", + "Cotton Thread", + "Manta Leather", + }, + }, + ["Butter Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Butterfly Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Iron Ingot", + "Maple Lumber", + }, + }, + ["Butterfly Cage"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Rattan Lumber", + "Rattan Lumber", + "Dogwd. Lumber", + "White Butterfly", + }, + }, + ["Butznar Shield"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Urunday Lumber", + "Midrium Sheet", + "Midrium Sheet", + }, + }, + ["Buzbaz Sainti"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Light Steel", + "Light Steel", + "Ruszor Fang", + "Ruszor Fang", + }, + }, + ["Buzdygan"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Mercury", + }, + }, + ["Byrnie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Dhalmel Leather", + "Ram Leather", + "Tiger Leather", + "Behem. Leather", + "Silver Mail", + }, + }, + ["Cabinet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Holly Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Cabinet 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 81", + }, + }, + ["Caisson"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bronze Sheet", + "Chestnut Lumber", + "Chestnut Lumber", + }, + }, + ["Caliginous Wolf Hide"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wolf Hide", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Candelabrum"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Beeswax", + "Beeswax", + "Beeswax", + }, + }, + ["Candle Holder"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + "Brass Ingot", + "Gold Ingot", + "Beeswax", + }, + }, + ["Cannon Shell"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Iron Ingot", + "Firesand", + "Firesand", + "Bomb Arm", + }, + }, + ["Cantarella"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Venom Dust", + "Paralysis Dust", + "Fresh Orc Liver", + "Distilled Water", + }, + }, + ["Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Carapace Breastplate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ram Leather", + "Ram Leather", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + }, + }, + ["Carapace Breastplate 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 95", + }, + }, + ["Carapace Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "H.Q. Crab Shell", + "H.Q. Crab Shell", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Carapace Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Crab Shell", + "Crab Shell", + }, + }, + ["Carapace Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Dhalmel Leather", + "Crab Shell", + "Crab Shell", + }, + }, + ["Carapace Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Sheep Leather", + "Ram Leather", + "H.Q. Crab Shell", + "H.Q. Crab Shell", + }, + }, + ["Carapace Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Dhalmel Leather", + "Fish Scales", + "Crab Shell", + }, + }, + ["Carapace Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Crab Shell", + }, + }, + ["Carapace Mask 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 45", + }, + }, + ["Carapace Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Fish Scales", + "Crab Shell", + }, + }, + ["Carapace Powder"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beetle Shell", + "Beetle Shell", + }, + }, + ["Carapace Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Crab Shell", + }, + }, + ["Carapace Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Dhalmel Leather", + "Crab Shell", + }, + }, + ["Carbon Dioxide"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Trumpet Shell", + }, + }, + ["Carbon Fiber"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + }, + }, + ["Carbon Fiber 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Alch. Kit 45", + }, + }, + ["Carbon Fiber 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Bomb Ash", + "Cluster Ash", + }, + }, + ["Carbon Fiber 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Djinn Ash", + }, + }, + ["Carbon Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Carbon Rod", + }, + }, + ["Carbon Fishing Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Glass Fiber", + }, + }, + ["Carbonara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Black Pepper", + "Holy Basil", + "Spaghetti", + "Selbina Milk", + "Stone Cheese", + "Bird Egg", + "Buffalo Jerky", + }, + }, + ["Cardinal Vest"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Steel Sheet", + "Gold Thread", + "Ram Leather", + "Tiger Leather", + "Tiger Leather", + "Beeswax", + "Manticore Lth.", + "Manticore Lth.", + }, + }, + ["Carmine Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Desk", + "Red Textile Dye", + }, + }, + ["Carp Sushi"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rock Salt", + "Forest Carp", + }, + }, + ["Carp Sushi 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rock Salt", + "Moat Carp", + }, + }, + ["Carrion Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "Rotten Meat", + }, + }, + ["Carrot Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Carrot", + "San d'Or. Carrot", + "San d'Or. Carrot", + "San d'Or. Carrot", + }, + }, + ["Carrot Paste"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "La Theine Millet", + "Lizard Egg", + "Distilled Water", + "Vomp Carrot", + "Vomp Carrot", + }, + }, + ["Cartonnier"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Bldwd. Lumber", + "Cassia Lumber", + "Cassia Lumber", + }, + }, + ["Cashmere Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Thrd.", + "Cashmere Thrd.", + "Cashmere Thrd.", + }, + }, + ["Cashmere Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Cashmere Wool", + "Cashmere Wool", + }, + }, + ["Cat Baghnakhs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Rabbit Hide", + "Bone Chip", + "Bone Chip", + "Bone Chip", + }, + }, + ["Catobl. Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Catoblepas Hide", + "Distilled Water", + }, + }, + ["Catobl. Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Catoblepas Hide", + "Distilled Water", + }, + }, + ["Catobl. Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Catoblepas Hide", + "Catoblepas Hide", + "Catoblepas Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Cehuetzi Snow Cone"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rolanberry", + "Thundermelon", + "Thundermelon", + "C. Ice Shard", + }, + }, + ["Cehuetzi Snow Cone 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rolanberry", + "Honey", + "Thundermelon", + "Thundermelon", + "C. Ice Shard", + }, + }, + ["Celata"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Steel Sheet", + "Darksteel Chain", + "Sheep Leather", + }, + }, + ["Celerity Salad"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gausebit Grass", + "Blue Peas", + "Cupid Worm", + "La Theine Millet", + "La Theine Cbg.", + "King Truffle", + "Shall Shell", + "Vomp Carrot", + }, + }, + ["Celerity Salad 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gausebit Grass", + "Blue Peas", + "Cupid Worm", + "La Theine Millet", + "La Theine Cbg.", + "King Truffle", + "Istiridye", + "Vomp Carrot", + }, + }, + ["Celerity Salad 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cook. Kit 75", + }, + }, + ["Celestial Globe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Comet Fragment", + "Puny Planet Kit", + "Cosmic Designs", + }, + }, + ["Cerberus Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bldwd. Lumber", + "Bldwd. Lumber", + "Coeurl Whisker", + "Cerberus Claw", + "Karakul Cloth", + }, + }, + ["Cerberus Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Cerberus Hide", + "Karakul Thread", + }, + }, + ["Cerberus Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cerberus Claw", + "Cerberus Claw", + }, + }, + ["Cermet Chunk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Magic Pot Shard", + "Magic Pot Shard", + "Magic Pot Shard", + "Magic Pot Shard", + }, + }, + ["Cermet Chunk 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Golem Shard", + "Golem Shard", + }, + }, + ["Cermet Chunk 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Doll Shard", + "Doll Shard", + }, + }, + ["Cermet Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Cermet Chunk", + }, + }, + ["Cermet Kilij"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Garnet", + "Sunstone", + "Marid Leather", + "Teak Lumber", + }, + }, + ["Cermet Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Oak Lumber", + "Cermet Chunk", + }, + }, + ["Cermet Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Cermet Chunk", + "Wyvern Skin", + }, + }, + ["Cermet Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Cermet Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Cerulean Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Desk", + "Blue Text. Dye", + }, + }, + ["Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Chai"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Im. Tea Leaves", + "Distilled Water", + }, + }, + ["Chain Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Chain", + "Silver Chain", + "Silver Chain", + }, + }, + ["Chain Choker"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Garnet", + }, + }, + ["Chain Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Silver Chain", + }, + }, + ["Chain Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Iron Chain", + "Linen Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Chain Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Iron Chain", + "Ram Leather", + }, + }, + ["Chainmail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Chain", + "Iron Chain", + "Iron Chain", + "Iron Chain", + "Ram Leather", + }, + }, + ["Chakram"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Ingot", + "Gold Ingot", + "Mercury", + }, + }, + ["Chalaimbille"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Rock Salt", + "Carbon Dioxide", + "Uleguerand Milk", + "Uleguerand Milk", + }, + }, + ["Chamomile Tea"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Chamomile", + "Chamomile", + "Honey", + "Distilled Water", + }, + }, + ["Chanar Xyston"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Midrium Ingot", + "Midrium Ingot", + "Urunday Lumber", + "Urunday Lumber", + }, + }, + ["Chapuli Arrowhd."] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Chapuli Horn", + }, + }, + ["Chapuli Arrowhd. 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Chapuli Horn", + "Chapuli Horn", + "Chapuli Horn", + }, + }, + ["Chapuli Fltchg."] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chapuli Wing", + "Chapuli Wing", + }, + }, + ["Chapuli Fltchg. 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Chapuli Wing", + "Chapuli Wing", + "Chapuli Wing", + "Chapuli Wing", + "Chapuli Wing", + "Chapuli Wing", + }, + }, + ["Charisma Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cattleya", + "Dried Mugwort", + "2Leaf Mandra Bud", + "Honey", + "Distilled Water", + }, + }, + ["Chasuble"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Silver Thread", + "Velvet Cloth", + "Velvet Cloth", + "Manticore Lth.", + "Eft Skin", + "Eltoro Leather", + "Eltoro Leather", + }, + }, + ["Cheese Sandwich"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Crying Mustard", + "Black Pepper", + "White Bread", + "Mithran Tomato", + "Chalaimbille", + "Graubg. Lettuce", + }, + }, + ["Chelona Blazer"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Carnelian", + "Karakul Skin", + "Beetle Blood", + "Silver Brocade", + "Karakul Cloth", + "Wamoura Silk", + "Amph. Leather", + "Wyrdstrand", + }, + }, + ["Chelona Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Thread", + "Karakul Cloth", + "Karakul Cloth", + "Karakul Cloth", + "Platinum Silk", + "Amph. Leather", + }, + }, + ["Chelona Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Karakul Cloth", + "Karakul Cloth", + "Wamoura Silk", + "Platinum Silk", + "Amph. Leather", + }, + }, + ["Chelona Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Carnelian", + "Beetle Blood", + "Silver Brocade", + "Wolf Felt", + "Wamoura Silk", + "Platinum Silk", + "Amph. Leather", + "Wyrdstrand", + }, + }, + ["Chelona Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wolf Felt", + "Wolf Felt", + "Imp. Silk Cloth", + "Platinum Silk", + "Amph. Leather", + }, + }, + ["Cherry Bavarois"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Maple Sugar", + "Gelatin", + "Vanilla", + "Yagudo Cherry", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Cherry Macaron"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Almond", + "Lizard Egg", + "Yagudo Cherry", + }, + }, + ["Cherry Muffin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Selbina Milk", + "Yagudo Cherry", + "Bird Egg", + }, + }, + ["Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Lauan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + }, + }, + ["Chestnut Club"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + }, + }, + ["Chestnut Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Log", + }, + }, + ["Chestnut Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Log", + "Chestnut Log", + "Chestnut Log", + "Bundling Twine", + }, + }, + ["Chestnut Sabots"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Sheep Leather", + }, + }, + ["Chestnut Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Bird Feather", + }, + }, + ["Cheviot Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Cheviot Cloth", + "Cheviot Cloth", + }, + }, + ["Chiffonier"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + }, + }, + ["Chimera Blood"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Lesser Chigoe", + }, + }, + ["Chimera Blood 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Triturator", + "Lesser Chigoe", + "Lesser Chigoe", + "Lesser Chigoe", + }, + }, + ["Chirich Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Plovid Effluvium", + "Macuil Plating", + "Defiant Sweat", + "Dark Matter", + }, + }, + ["Chirping Grasshopper Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Skull Locust", + "Skull Locust", + "La Theine Cbg.", + "Gysahl Greens", + "Little Worm", + "Shell Bug", + }, + }, + ["Chocobo Blinkers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Silk Cloth", + "Karakul Leather", + }, + }, + ["Chocobo Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Grass Cloth", + "Dhalmel Leather", + "Dhalmel Leather", + }, + }, + ["Chocobo Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chocobo Fthr.", + "Chocobo Fthr.", + }, + }, + ["Chocobo Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chocobo Fthr.", + "Chocobo Fthr.", + "Chocobo Fthr.", + "Chocobo Fthr.", + "Chocobo Fthr.", + "Chocobo Fthr.", + "Zephyr Thread", + }, + }, + ["Chocobo Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Dhalmel Leather", + "Lizard Skin", + }, + }, + ["Chocobo Hood"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Chocobo Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Wool Cloth", + "Sheep Leather", + }, + }, + ["Chocobo Jack Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Linen Cloth", + "Wool Cloth", + "Sheep Leather", + "Ram Leather", + "Ram Leather", + }, + }, + ["Chocobo Taping"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Chocolate Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Maple Sugar", + "Olive Oil", + "Selbina Milk", + "Heart Chocolate", + "Bird Egg", + "Apkallu Egg", + }, + }, + ["Chocolate Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Almond", + "Honey", + "Pamamas", + "Bbl. Chocolate", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Chocolate Rusk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Bbl. Chocolate", + "Bbl. Chocolate", + "Iron Bread", + "Bird Egg", + }, + }, + ["Chocolixir"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mistletoe", + "Revival Root", + "Gold Leaf", + "Royal Jelly", + "Distilled Water", + }, + }, + ["Chocomilk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Kukuru Bean", + "Honey", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Chocotonic"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Attohwa Ginseng", + "Distilled Water", + "Gysahl Greens", + }, + }, + ["Chocotonic 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Yellow Ginseng", + "Distilled Water", + "Gysahl Greens", + }, + }, + ["Chronos Tooth"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Colossal Skull", + }, + }, + ["Chrysoberyl"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Titanite", + }, + }, + ["Chrysoberyl Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chrysoberyl", + "Gold Ring", + }, + }, + ["Chrysoberyl Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chrysoberyl", + "Gold Ring +1", + }, + }, + ["Cilbir"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Selbina Butter", + "Rock Salt", + "Apkallu Egg", + "Apkallu Egg", + "Yogurt", + }, + }, + ["Cilice"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Dhalmel Hair", + "Dhalmel Hair", + }, + }, + ["Cinna-cookie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Cinnamon", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Cinna-cookie 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Cinnamon", + "Distilled Water", + "Bird Egg", + }, + }, + ["Circlet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Brass Ingot", + }, + }, + ["Cl. Wheat Broth"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Coriander", + "Imperial Flour", + "Distilled Water", + }, + }, + ["Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Beetle Jaw", + }, + }, + ["Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Ash Lumber", + "Lizard Skin", + }, + }, + ["Clear Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Clear Topaz", + "Silver Earring", + }, + }, + ["Clear Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Clear Topaz", + "Silver Earring +1", + }, + }, + ["Clear Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Clear Topaz", + "Silver Ring", + }, + }, + ["Clear Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Clear Topaz", + "Silver Ring +1", + }, + }, + ["Clear Topaz"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Translucent Rock", + }, + }, + ["Clear Topaz 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sparkling Stone", + }, + }, + ["Clear Topaz 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Snow Geode", + }, + }, + ["Clear Topaz 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gelid Aggregate", + }, + }, + ["Cleric's Torque"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Seedstone Crystal", + "Moldy Torque", + }, + }, + ["Cleric's Wand"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Dark Matter", + "Tartarian Chain", + "Cypress Log", + "Moldy Club", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Seedstone Crystal", + }, + }, + ["Clerisy Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Behemoth Hide", + "Bztavian Wing", + }, + }, + ["Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Clothcraft Set 25"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Clothcraft Set 45"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Chocobo Fthr.", + }, + }, + ["Clothcraft Set 64"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Wool Thread", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Clothcraft Set 70"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Gold Thread", + }, + }, + ["Clothcraft Set 75"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Imp. Silk Cloth", + "Battle Bracers", + }, + }, + ["Clothcraft Set 80"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Silver Thread", + "Gold Thread", + "Gold Thread", + }, + }, + ["Clothcraft Set 85"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Rainbow Cloth", + "Carbon Fiber", + }, + }, + ["Clothcraft Set 90"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Gold Thread", + }, + }, + ["Clothcraft Set 95"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Wool Thread", + "Rainbow Thread", + "Silver Thread", + "Gold Thread", + "Manticore Hair", + "Manticore Hair", + }, + }, + ["Clothespole"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Clothespole", + }, + }, + ["Clothespole 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Silk Thread", + }, + }, + ["Clown's Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Coral Fragment", + "Lindwurm Skin", + }, + }, + ["Coated Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Exor. Oak Lbr.", + "Oak Shield", + }, + }, + ["Coconut Rusk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Iron Bread", + "Bird Egg", + "Elshimo Coconut", + }, + }, + ["Coeurl Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Cesti", + }, + }, + ["Coeurl Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Cuir Gloves", + }, + }, + ["Coeurl Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Coeurl Hide", + }, + }, + ["Coeurl Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Leather", + "Sheep Leather", + }, + }, + ["Coeurl Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Coeurl Hide", + "Distilled Water", + }, + }, + ["Coeurl Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Coeurl Hide", + "Distilled Water", + }, + }, + ["Coeurl Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Coeurl Hide", + "Coeurl Hide", + "Coeurl Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Coeurl Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Mythril Sheet", + "Cuir Highboots", + }, + }, + ["Coeurl Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Coeurl Hide", + }, + }, + ["Coeurl Mantle 2"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Leath. Kit 85", + }, + }, + ["Coeurl Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Leather", + "Faceguard", + }, + }, + ["Coeurl Saute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Honey", + "Coeurl Meat", + "Wild Onion", + "Grape Juice", + }, + }, + ["Coeurl Saute 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Honey", + "Wild Onion", + "Grape Juice", + "Lynx Meat", + }, + }, + ["Coeurl Sub"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Selbina Butter", + "Crying Mustard", + "White Bread", + "La Theine Cbg.", + "Mithran Tomato", + "Coeurl Saute", + }, + }, + ["Coeurl Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Leather", + "Cuir Trousers", + }, + }, + ["Coffee Beans"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Coffee Cherries", + }, + }, + ["Coffee Macaron"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Coffee Beans", + "Almond", + "Lizard Egg", + }, + }, + ["Coffee Muffin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Coffee Powder", + "Selbina Milk", + "Bird Egg", + }, + }, + ["Coffee Powder"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rst. Coff. Beans", + }, + }, + ["Coffee Table"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Thread", + "Lancewood Lbr.", + "Lancewood Lbr.", + "Lancewood Lbr.", + "Silver Brocade", + }, + }, + ["Coffer"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Yew Lumber", + "Yew Lumber", + "Yew Lumber", + "Yew Lumber", + "Yew Lumber", + "Rosewood Lbr.", + }, + }, + ["Coiler II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Carbon Fiber", + "Super Cermet", + "Imperial Cermet", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Colibri Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Colibri Feather", + "Colibri Feather", + }, + }, + ["Colibri Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + "Colibri Feather", + }, + }, + ["Colichemarde"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Deathstone", + "Mailbreaker", + }, + }, + ["Colored Egg"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lizard Egg", + "La Theine Cbg.", + "San d'Or. Carrot", + "Distilled Water", + }, + }, + ["Colored Egg 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "La Theine Cbg.", + "San d'Or. Carrot", + "Distilled Water", + "Bird Egg", + }, + }, + ["Colossal Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Gigant Axe", + }, + }, + ["Comaa Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Thought Crystal", + "Hope Crystal", + "Fulfillment Crystal", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + }, + }, + ["Combat Caster's Axe +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Cmb.Cst. Axe", + }, + }, + ["Combat Caster's Boomerang +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Cmb.Cst. B'merang", + }, + }, + ["Combat Caster's Cloak +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Cmb.Cst. Cloak", + }, + }, + ["Combat Caster's Dagger +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Cmb.Cst. Dagger", + }, + }, + ["Combat Caster's Mitts +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Cmb.Cst. Mitts", + }, + }, + ["Combat Caster's Scimitar +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Cmb.Cst. Scimitar", + }, + }, + ["Combat Caster's Shoes +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Cmb.Cst. Shoes", + }, + }, + ["Combat Caster's Slacks +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Cmb.Cst. Slacks", + }, + }, + ["Combat Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Saruta Cotton", + "Ephemeral Cloth", + }, + }, + ["Commode"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + }, + }, + ["Commodore Charm"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Jadeite Crystal", + "Moldy Charm", + }, + }, + ["Commodore's Knife"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Horn", + "Dark Matter", + "Ruthenium Ore", + "Moldy Dagger", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Jadeite Crystal", + }, + }, + ["Composite Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Lumber", + "Ash Lumber", + "Wool Thread", + "Linen Cloth", + }, + }, + ["Composite Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Broken Comp. Rod", + }, + }, + ["Composite Fishing Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Cermet Chunk", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Compression Sphere"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Demon Pen", + }, + }, + ["Compression Sphere 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tonberry Board", + }, + }, + ["Compression Sphere 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Orcish Mail Scales", + }, + }, + ["Compression Sphere 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Colorful Hair", + }, + }, + ["Compression Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dark Card", + }, + }, + ["Console"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Dogwd. Lumber", + "Dogwd. Lumber", + "Dogwd. Lumber", + }, + }, + ["Cooking Set 25"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Bay Leaves", + "Rock Salt", + "La Theine Cbg.", + "Frost Turnip", + "Wild Onion", + "Eggplant", + "San d'Or. Carrot", + "Distilled Water", + }, + }, + ["Cooking Set 45"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Selbina Butter", + "Black Pepper", + "Rock Salt", + "Wild Onion", + "Mithran Tomato", + "Puk Egg", + "Puk Egg", + }, + }, + ["Cooking Set 65"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Bay Leaves", + "Olive Oil", + "Wild Onion", + "Eggplant", + "Mithran Tomato", + "Zucchini", + "Paprika", + }, + }, + ["Cooking Set 70"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Maple Sugar", + "Win. Tea Leaves", + "Sage", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Cooking Set 75"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Gausebit Grass", + "Blue Peas", + "Cupid Worm", + "La Theine Millet", + "La Theine Cbg.", + "King Truffle", + "Shall Shell", + "Vomp Carrot", + }, + }, + ["Cooking Set 80"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Selbina Butter", + "Bay Leaves", + "Rock Salt", + "Kazham Pineapl.", + "Shall Shell", + "Shall Shell", + "Distilled Water", + }, + }, + ["Cooking Set 85"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Popoto", + "Curry Powder", + "Turmeric", + "Coeurl Meat", + "Selbina Milk", + "Wild Onion", + "Distilled Water", + }, + }, + ["Cooking Set 90"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Faerie Apple", + "Rolanberry", + "Mithran Tomato", + "Red Terrapin", + }, + }, + ["Cooking Set 95"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Popoto", + "Bay Leaves", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Dragon Meat", + "San d'Or. Carrot", + "Rarab Tail", + }, + }, + ["Copper Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Firesand", + }, + }, + ["Copper Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + }, + }, + ["Copper Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Ore", + "Copper Ore", + "Copper Ore", + }, + }, + ["Copper Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Nugget", + "Copper Nugget", + "Copper Nugget", + "Copper Nugget", + "Copper Nugget", + "Copper Nugget", + }, + }, + ["Copper Nugget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Meteorite", + "Panacea", + }, + }, + ["Copper Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Copper Ingot", + }, + }, + ["Copper Ring 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 5", + }, + }, + ["Coral Bangles"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Coral Fragment", + "Coral Fragment", + "Giant Femur", + }, + }, + ["Coral Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coral Fragment", + "Darksteel Cap", + }, + }, + ["Coral Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Coral Fragment", + "Coral Fragment", + "Leather Trousers", + }, + }, + ["Coral Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Coral Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Coral Fragment", + "Coral Fragment", + "Leather Gloves", + }, + }, + ["Coral Gorget"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Coral Fragment", + }, + }, + ["Coral Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Coral Fragment", + "Coral Fragment", + "Leather Highboots", + }, + }, + ["Coral Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Pearl", + "Black Pearl", + "Coral Fragment", + }, + }, + ["Coral Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Leather", + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Coral Horn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oxblood", + }, + }, + ["Coral Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Leather", + "Wyvern Scales", + "Coral Fragment", + }, + }, + ["Coral Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Wyvern Scales", + "Coral Fragment", + }, + }, + ["Coral Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Coral Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 80", + }, + }, + ["Coral Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Sheep Leather", + "Coral Fragment", + "Coral Fragment", + "Coral Fragment", + "Coral Fragment", + "Leather Vest", + }, + }, + ["Coral Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Linen Cloth", + "Coral Fragment", + }, + }, + ["Coral Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coral Fragment", + "Coral Fragment", + "Coral Fragment", + "Coral Fragment", + "Katzbalger", + }, + }, + ["Coral Visor"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Cornette"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Ingot", + "Bone Chip", + }, + }, + ["Cornstarch"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Millioncorn", + "Millioncorn", + }, + }, + ["Corsair's Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Demon Horn", + }, + }, + ["Corsair's Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Marine Hat", + }, + }, + ["Corselet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Mercury", + "Lm. Bf. Leather", + "Ovinnik Leather", + "Marid Leather", + "Marid Hair", + "Scintillant Ingot", + "Star Sapphire", + }, + }, + ["Corsette"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Cloth", + "Dhalmel Leather", + "Coeurl Whisker", + "Waistbelt", + "Scarlet Ribbon", + }, + }, + ["Cotton Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + "Sheep Leather", + }, + }, + ["Cotton Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Thread", + "Cotton Thread", + }, + }, + ["Cotton Dogi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Cotton Gaiters"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Sheep Leather", + }, + }, + ["Cotton Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Cotton Hachimaki"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Headband"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Carbon Fiber", + }, + }, + ["Cotton Headband 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 20", + }, + }, + ["Cotton Headgear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Kyahan"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Linen Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + }, + }, + ["Cotton Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Cotton Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Cotton Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + "Spindle", + }, + }, + ["Cotton Tofu"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Bittern", + "Soy Milk", + }, + }, + ["Couse"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ash Lumber", + }, + }, + ["Crab Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fish Stock", + "Woozyshroom", + "Land Crab Meat", + "Coral Fungus", + "Distilled Water", + "Cotton Tofu", + "Cibol", + "Shungiku", + }, + }, + ["Crab Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Land Crab Meat", + "Distilled Water", + }, + }, + ["Cracker"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Cracker 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Alch. Kit 15", + }, + }, + ["Crayfish Ball"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Crayfish", + "Crayfish", + "Crayfish", + "Distilled Water", + }, + }, + ["Crayfish Ball 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Gold Lobster", + "Distilled Water", + }, + }, + ["Crayfish Ball 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Distilled Water", + "Istakoz", + }, + }, + ["Cream Puff"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Rock Salt", + "Vanilla", + "Distilled Water", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Credenza"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Teak Lumber", + "Teak Lumber", + "Teak Lumber", + "Teak Lumber", + "Jacaranda Lbr.", + }, + }, + ["Cricket Cage"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Rattan Lumber", + "Rattan Lumber", + "Dogwd. Lumber", + "Bell Cricket", + }, + }, + ["Crimson Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pigeon's Blood", + "Ocl. Earring", + }, + }, + ["Crimson Jelly"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Maple Sugar", + "Gelatin", + "Apple Mint", + "Clot Plasma", + "San d'Or. Grape", + "Distilled Water", + }, + }, + ["Crimson Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pigeon's Blood", + "Orichalcum Ring", + }, + }, + ["Crossbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Chestnut Lumber", + "Glass Fiber", + }, + }, + ["Crossbow Bolt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Ash Lumber", + }, + }, + ["Crow Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Black C. Feather", + "Bird Feather", + "Sheep Leather", + }, + }, + ["Crow Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Sheep Leather", + }, + }, + ["Crow Gaiters"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Ram Leather", + "Tiger Leather", + }, + }, + ["Crow Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Sheep Leather", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Crow Jupon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Sheep Leather", + }, + }, + ["Crumhorn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Demon Horn", + }, + }, + ["Crystal Rose"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scintillant Ingot", + "F. Glass Sheet", + "F. Glass Sheet", + "F. Glass Sheet", + }, + }, + ["Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Flint Stone", + "Flint Stone", + "Cotton Thread", + "Grass Cloth", + "Cotton Cloth", + }, + }, + ["Cuir Bandana"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Leather", + "Beeswax", + "Leather Bandana", + }, + }, + ["Cuir Bouilli"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Beeswax", + "Leather Vest", + }, + }, + ["Cuir Gloves"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Beeswax", + "Leather Gloves", + }, + }, + ["Cuir Highboots"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Beeswax", + "Leather Highboots", + }, + }, + ["Cuir Trousers"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Beeswax", + "Leather Trousers", + }, + }, + ["Cuir Trousers 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Leath. Kit 45", + }, + }, + ["Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Sheep Leather", + }, + }, + ["Culverin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Adaman Ingot", + "Mahogany Lbr.", + }, + }, + ["Cunning Brain Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "G. Sheep Meat", + "Cockatrice Meat", + }, + }, + ["Curdled Plasma Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Fiend Blood", + "Beastman Blood", + "Bird Blood", + }, + }, + ["Cursed Beverage"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Malboro Vine", + "Divine Sap", + "Kitron", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Cursed Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Ocl. Sheet", + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Cerber. Leather", + "Cerber. Leather", + "Scintillant Ingot", + "Rubber Harness", + }, + }, + ["Cursed Breeches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Adaman Chain", + "Linen Cloth", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Cursed Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angel Skin", + "Darksteel Cap", + }, + }, + ["Cursed Celata"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Brass Sheet", + "Adaman Sheet", + "Adaman Chain", + "Sheep Leather", + }, + }, + ["Cursed Clogs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Undead Skin", + "Marid Leather", + "Marid Leather", + "Netherpact Chain", + "Platinum Silk", + }, + }, + ["Cursed Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Marid Leather", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + "Netherfield Chain", + "Platinum Silk", + }, + }, + ["Cursed Crown"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Gold Ingot", + "Siren's Hair", + }, + }, + ["Cursed Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Angelstone", + "Angelstone", + "Velvet Cloth", + "Karakul Leather", + "Imp. Silk Cloth", + "Netherspirit Chain", + "Platinum Silk", + }, + }, + ["Cursed Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Ocl. Ingot", + "Platinum Sheet", + "Platinum Chain", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + "Darksteel Cuirass", + }, + }, + ["Cursed Cuishes"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Drk. Adm. Sheet", + "Marid Leather", + "Scintillant Ingot", + "Rubber Chausses", + }, + }, + ["Cursed Cuisses"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Dragon Blood", + "Dragon Cuisses", + }, + }, + ["Cursed Dalmatica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Chain", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Rainbow Cloth", + "Ram Leather", + "Behem. Leather", + "Siren's Macrame", + }, + }, + ["Cursed Diechlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Sheet", + "Cermet Chunk", + "Darksteel Cuisses", + }, + }, + ["Cursed Finger Gauntlets"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Dragon Blood", + "Dragon Fng. Gnt.", + }, + }, + ["Cursed Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Rubber Gloves", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Cursed Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Wyvern Scales", + "Angel Skin", + }, + }, + ["Cursed Greaves"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Dragon Blood", + "Dragon Greaves", + }, + }, + ["Cursed Haidate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Divine Lumber", + "Animal Glue", + "Urushi", + "Haidate", + }, + }, + ["Cursed Handschuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Cermet Chunk", + "Dst. Gauntlets", + }, + }, + ["Cursed Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Tiger Leather", + "Coral Fragment", + "Oxblood", + "Angel Skin", + }, + }, + ["Cursed Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Velvet Cloth", + "Tiger Leather", + "Marid Leather", + "Imp. Silk Cloth", + "Nethereye Chain", + "Platinum Silk", + }, + }, + ["Cursed Hauberk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Adaman Sheet", + "Adaman Chain", + "Gold Thread", + "Rainbow Cloth", + "Sheep Leather", + "Southern Pearl", + "Hauberk", + }, + }, + ["Cursed Helm"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Ocl. Sheet", + "Karakul Leather", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Rubber Cap", + }, + }, + ["Cursed Kabuto"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Divine Lumber", + "Animal Glue", + "Urushi", + "Zunari Kabuto", + }, + }, + ["Cursed Kote"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Divine Lumber", + "Animal Glue", + "Urushi", + "Kote", + }, + }, + ["Cursed Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Tiger Leather", + "Wyvern Scales", + "Angel Skin", + }, + }, + ["Cursed Mail"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Dragon Blood", + "Dragon Blood", + "Dragon Mail", + }, + }, + ["Cursed Mask"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Dragon Blood", + "Dragon Mask", + }, + }, + ["Cursed Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Wool Cloth", + "Rainbow Cloth", + "Saruta Cotton", + "Siren's Hair", + }, + }, + ["Cursed Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Adaman Sheet", + "Thick Mufflers", + }, + }, + ["Cursed Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Rainbow Cloth", + "Manticore Lth.", + "Siren's Macrame", + }, + }, + ["Cursed Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Marid Leather", + "Marid Leather", + "Scintillant Ingot", + "Rubber Soles", + }, + }, + ["Cursed Schaller"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Gold Ingot", + "Platinum Sheet", + "Platinum Chain", + "Sheep Leather", + "Cermet Chunk", + }, + }, + ["Cursed Schuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Cermet Chunk", + "Cermet Chunk", + "Dst. Sabatons", + }, + }, + ["Cursed Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Rainbow Cloth", + "Siren's Macrame", + }, + }, + ["Cursed Sollerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Adaman Sheet", + "Adaman Chain", + "Thick Sollerets", + }, + }, + ["Cursed Soup"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kitron", + "Persikos", + "Honey", + "Royal Jelly", + "Distilled Water", + }, + }, + ["Cursed Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Cloth", + "Tiger Leather", + "Angel Skin", + }, + }, + ["Cursed Sune-Ate"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Divine Lumber", + "Animal Glue", + "Urushi", + "Sune-Ate", + }, + }, + ["Cursed Togi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Divine Lumber", + "Divine Lumber", + "Tiger Leather", + "Animal Glue", + "Urushi", + "Hara-Ate", + }, + }, + ["Cursed Trews"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Velvet Cloth", + "Marid Leather", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + "Nethercant Chain", + "Platinum Silk", + }, + }, + ["Cutlass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Cutlet Sandwich"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + "Distilled Water", + "Pork Cutlet", + }, + }, + ["Cvl. Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wolf Hide", + "Sentinel's Mantle", + }, + }, + ["Cyan Orb"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Cyan Coral", + }, + }, + ["Cypress Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cypress Log", + }, + }, + ["Cypress Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Cypress Log", + "Cypress Log", + "Cypress Log", + }, + }, + ["Cythara Anglica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + "Ancient Lumber", + "Ancient Lumber", + "Coeurl Whisker", + }, + }, + ["Czar's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "T. Bgd. Leather", + "Koenigs Belt", + }, + }, + ["D. Fruit au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Honey", + "Selbina Milk", + "Dragon Fruit", + "Dragon Fruit", + }, + }, + ["Dabo"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ash Lumber", + "Ash Lumber", + "Scintillant Ingot", + "Dark Ixion Tail", + "Dk. Ixion Ferrule", + }, + }, + ["Dagger"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Dagger", + }, + }, + ["Dagger 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Iron Ingot", + }, + }, + ["Dakatsu-No-Nodowa"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silk Thread", + "Beryllium Ingot", + "Wyrm Blood", + }, + }, + ["Damage Gauge"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Light Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Damascus Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Relic Iron", + "Wootz Ore", + "Vanadium Ore", + "Vanadium Ore", + }, + }, + ["Dance Shoes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Moccasins", + }, + }, + ["Dancing Herbal Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Frost Turnip", + "Beaugreens", + "Beaugreens", + "Napa", + }, + }, + ["Dandy Spectacles"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "F. Glass Sheet", + }, + }, + ["Dark Adaman Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dark Adaman", + }, + }, + ["Dark Adaman Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Darksteel Ore", + "Darksteel Ore", + "Adaman Ore", + }, + }, + ["Dark Amood"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Silver Ingot", + "Garnet", + "Moblumin Ingot", + }, + }, + ["Dark Anima"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Malevolent Mem.", + "Malevolent Mem.", + "Malevolent Mem.", + "Malevolent Mem.", + }, + }, + ["Dark Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dark Ore", + }, + }, + ["Dark Bronze Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Ore", + "Tin Ore", + "Darksteel Ore", + }, + }, + ["Dark Bronze Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Bronze", + }, + }, + ["Dark Bronze Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Bronze", + "Dark Bronze", + "Dark Bronze", + "Dark Bronze", + "Dark Bronze", + "Dark Bronze", + "Workshop Anvil", + }, + }, + ["Dark Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Dark Cluster", + }, + }, + ["Dark Fewell"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Slime Oil", + "Black Rock", + "Catalytic Oil", + }, + }, + ["Dark Ixion Ferrule"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dark Ixion Horn", + }, + }, + ["Dark Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Dark Mezraq"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Platinum Ingot", + "Wamoura Silk", + }, + }, + ["Dark Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dark Bead", + "Orichalcum Ring", + }, + }, + ["Dark Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Dark Bead", + }, + }, + ["Darkling Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Revival Root", + "Imp Wing", + }, + }, + ["Darksteel Armet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Sheep Leather", + "Mercury", + }, + }, + ["Darksteel Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Oak Lumber", + }, + }, + ["Darksteel Baselard"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Darksteel Ingot", + }, + }, + ["Darksteel Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Yew Lumber", + "Dst. Bolt Heads", + }, + }, + ["Darksteel Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Yew Lumber", + "Yew Lumber", + "Yew Lumber", + "Dst. Bolt Heads", + "Dst. Bolt Heads", + "Dst. Bolt Heads", + "Bundling Twine", + }, + }, + ["Darksteel Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + }, + }, + ["Darksteel Breeches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Darksteel Chain", + "Linen Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Darksteel Buckler"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Sheet", + "Darksteel Sheet", + "Walnut Lumber", + }, + }, + ["Darksteel Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Darksteel Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + }, + }, + ["Darksteel Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Mandrel", + }, + }, + ["Darksteel Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Beetle Jaw", + }, + }, + ["Darksteel Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Chestnut Lumber", + "Ram Leather", + }, + }, + ["Darksteel Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + }, + }, + ["Darksteel Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Ram Leather", + }, + }, + ["Darksteel Falchion"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + }, + }, + ["Darksteel Falx"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Black Pearl", + "Buffalo Leather", + }, + }, + ["Darksteel Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Mercury", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Darksteel Gorget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Ram Leather", + }, + }, + ["Darksteel Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Darksteel Hexagun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Ebony Lumber", + "Silver Ingot", + }, + }, + ["Darksteel Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Darksteel Ore", + }, + }, + ["Darksteel Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ore", + "Dst. Nugget", + "Dst. Nugget", + "Dst. Nugget", + "Dst. Nugget", + "Dst. Nugget", + "Dst. Nugget", + }, + }, + ["Darksteel Jambiya"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Silver Ingot", + "Sunstone", + "Mercury", + "Ladybug Wing", + }, + }, + ["Darksteel Katars"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Iron Sheet", + "Tiger Leather", + }, + }, + ["Darksteel Kilij"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Platinum Ingot", + "Platinum Ingot", + "Ruby", + "Garnet", + "Marid Leather", + }, + }, + ["Darksteel Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Oak Lumber", + }, + }, + ["Darksteel Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Sheet", + "Oak Lumber", + }, + }, + ["Darksteel Kris"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Painite", + }, + }, + ["Darksteel Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Cockatrice Skin", + }, + }, + ["Darksteel Kukri 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 65", + }, + }, + ["Darksteel Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Ash Lumber", + "Ash Lumber", + }, + }, + ["Darksteel Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Darksteel Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 60", + }, + }, + ["Darksteel Mace 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + }, + }, + ["Darksteel Maul"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Mahogany Lbr.", + }, + }, + ["Darksteel Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Tiger Leather", + }, + }, + ["Darksteel Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Chain Mittens", + }, + }, + ["Darksteel Nodowa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + }, + }, + ["Darksteel Pick"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Elm Lumber", + }, + }, + ["Darksteel Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + }, + }, + ["Darksteel Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + }, + }, + ["Darksteel Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Platinum Ingot", + "Platinum Ingot", + "Mercury", + }, + }, + ["Darksteel Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + }, + }, + ["Darksteel Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Yew Lumber", + "Grass Cloth", + }, + }, + ["Darksteel Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + }, + }, + ["Darksteel Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Workshop Anvil", + }, + }, + ["Darksteel Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Ash Lumber", + "Wyvern Scales", + "Wyvern Scales", + "Wyvern Scales", + "Wyvern Scales", + }, + }, + ["Darksteel Sollerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Greaves", + }, + }, + ["Darksteel Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Linen Cloth", + "Tiger Leather", + }, + }, + ["Darksteel Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Raptor Skin", + }, + }, + ["Darksteel Tabar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Oak Lumber", + }, + }, + ["Darksteel Voulge"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Gold Ingot", + }, + }, + ["Dart"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chocobo Fthr.", + "Chocobo Fthr.", + "Bat Fang", + "Animal Glue", + }, + }, + ["Dashing Subligar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Stinky Subligar", + }, + }, + ["Date Tea"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Im. Tea Leaves", + "Distilled Water", + "Date", + }, + }, + ["Dawn Mulsum"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Holy Water", + "Grape Juice", + "White Honey", + }, + }, + ["Death Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Deathstone", + "Platinum Earring", + }, + }, + ["Death Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Deathstone", + "Ptm. Earring +1", + }, + }, + ["Death Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Deathstone", + "Platinum Ring", + }, + }, + ["Death Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Deathstone", + "Platinum Ring +1", + }, + }, + ["Death Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Yew Lumber", + "Grass Cloth", + "Fiend Blood", + }, + }, + ["Deathbone Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Bone Knife", + }, + }, + ["Debahocho"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Willow Lumber", + }, + }, + ["Decaying Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Flan Meat", + "Distilled Water", + "Rotten Meat", + }, + }, + ["Deductive Brocade Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brl. Gold Thread", + "Brocade Obi", + }, + }, + ["Deductive Gold Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brl. Gold Thread", + "Gold Obi", + }, + }, + ["Deep-Fried Shrimp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "White Bread", + "Bird Egg", + "Black Prawn", + "Black Prawn", + }, + }, + ["Deepbed Soil"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Beech Log", + "Woozyshroom", + "Sleepshroom", + "Danceshroom", + }, + }, + ["Degen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Silver Ingot", + }, + }, + ["Demon Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Demon Arrowhd.", + "Blk. Chc. Fltchg.", + }, + }, + ["Demon Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Demon Horn", + }, + }, + ["Demon Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Demon Horn", + "Demon Horn", + "Demon Horn", + "Shagreen File", + }, + }, + ["Demon Helm"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheep Leather", + "Demon Skull", + "Demon Horn", + "Demon Horn", + }, + }, + ["Demon Slayer"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Dragon Blood", + "Ameretat Vine", + "Adaman Kilij", + }, + }, + ["Demon's Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Behem. Leather", + "Demon Skull", + "Demon Skull", + }, + }, + ["Demon's Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Demon Horn", + }, + }, + ["Demon's Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fish Scales", + "Demon Horn", + }, + }, + ["Demon's Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 70", + }, + }, + ["Deodorizer"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Olive Oil", + "Chamomile", + "Sage", + }, + }, + ["Deodorizer 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Olive Oil", + "Olive Oil", + "Chamomile", + "Chamomile", + "Sage", + "Sage", + "Triturator", + }, + }, + ["Deodorizer 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Alch. Kit 10", + }, + }, + ["Desert Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Sheep Leather", + "Sheep Leather", + "Yowie Skin", + }, + }, + ["Desert Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Yowie Skin", + "Yowie Skin", + }, + }, + ["Desk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Elm Lumber", + "Linen Cloth", + }, + }, + ["Detonation Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Shoalweed", + }, + }, + ["Detonation Sphere 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Fetich Legs", + }, + }, + ["Detonation Sphere 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Soiled Letter", + }, + }, + ["Detonation Sphere 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beryl Memosphere", + }, + }, + ["Detonation Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wind Card", + }, + }, + ["Devotee's Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Silk Cloth", + "Zealot's Mitts", + }, + }, + ["Dexterity Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Malboro Vine", + "Sweet William", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Dhalmel Hair"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dhalmel Hide", + "Dhalmel Hide", + "Dhalmel Hide", + }, + }, + ["Dhalmel Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Dhalmel Hide", + "Distilled Water", + }, + }, + ["Dhalmel Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Dhalmel Hide", + "Dhalmel Hide", + "Dhalmel Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Dhalmel Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Dhalmel Hide", + "Distilled Water", + }, + }, + ["Dhalmel Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Dhalmel Hide", + }, + }, + ["Dhalmel Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Pie Dough", + "Black Pepper", + "Rock Salt", + "Dhalmel Meat", + "Wild Onion", + "San d'Or. Carrot", + "Coral Fungus", + }, + }, + ["Dhalmel Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Olive Oil", + "Dhalmel Meat", + }, + }, + ["Dhalmel Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Popoto", + "Cinnamon", + "Rock Salt", + "Dhalmel Meat", + "Wild Onion", + "Mithran Tomato", + "Distilled Water", + }, + }, + ["Diamond Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Diamond", + "Platinum Earring", + }, + }, + ["Diamond Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Diamond", + "Ptm. Earring +1", + }, + }, + ["Diamond Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Mahogany Lbr.", + "Platinum Sheet", + "Diamond", + "Diamond", + }, + }, + ["Diamond Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Diamond", + "Platinum Ring", + }, + }, + ["Diamond Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Diamond", + "Platinum Ring +1", + }, + }, + ["Diamond Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Platinum Sheet", + "Diamond", + "Diamond", + "Diamond", + "Diamond", + "Kite Shield", + }, + }, + ["Dija Sword"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Dragon Blood", + "Yggdreant Root", + "Bastard Sword", + }, + }, + ["Dispel Couse"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bwtch. Ash Lbr.", + "Couse", + }, + }, + ["Distilled Water"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Tahrongi Cactus", + }, + }, + ["Divine Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Divine Log", + "Divine Log", + "Divine Log", + "Bundling Twine", + }, + }, + ["Divine Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Divine Log", + }, + }, + ["Divine Sap"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Divine Log", + }, + }, + ["Divine Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Holy Water", + "Broadsword", + }, + }, + ["Dmc. Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Damascus Ingot", + }, + }, + ["Dogwood Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dogwood Log", + }, + }, + ["Dogwood Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dogwood Log", + "Dogwood Log", + "Dogwood Log", + "Bundling Twine", + }, + }, + ["Dominus Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sanctified Lbr.", + "Numinous Shield", + }, + }, + ["Donderbuss"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Ormolu Ingot", + "Rockfin Tooth", + }, + }, + ["Dorado Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Noble Lady", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Doublet 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 10", + }, + }, + ["Dragon Blood"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Dragon Heart", + }, + }, + ["Dragon Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dragon Bone", + "Darksteel Cap", + }, + }, + ["Dragon Claws"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Dragon Talon", + "Dragon Talon", + }, + }, + ["Dragon Claws 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 90", + }, + }, + ["Dragon Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Wyvern Scales", + "Wyvern Scales", + "Leather Trousers", + }, + }, + ["Dragon Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Wyvern Scales", + "Wyvern Scales", + "Leather Gloves", + }, + }, + ["Dragon Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Wyvern Scales", + "Wyvern Scales", + "Leather Highboots", + }, + }, + ["Dragon Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Buffalo Leather", + "Dragon Bone", + "Wyrm Horn", + }, + }, + ["Dragon Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wyvern Scales", + "Buffalo Leather", + "Buffalo Leather", + "Dragon Bone", + }, + }, + ["Dragon Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Silver Thread", + "Wyvern Scales", + "Wyvern Scales", + "Wyvern Scales", + "Dragon Scales", + "Leather Vest", + }, + }, + ["Dragon Mask"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Wyvern Scales", + "Wyvern Scales", + }, + }, + ["Dragon Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wyvern Scales", + "Buffalo Leather", + "Dragon Bone", + }, + }, + ["Dragon Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dragon Talon", + "Dragon Talon", + }, + }, + ["Dragon Slayer"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Ameretat Vine", + "Demon Blood", + "Adaman Kilij", + }, + }, + ["Dragon Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Ginger", + "Popoto", + "Black Pepper", + "Dragon Meat", + "Frost Turnip", + "Wild Onion", + "Distilled Water", + }, + }, + ["Dragon Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Bay Leaves", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Dragon Meat", + "San d'Or. Carrot", + "Rarab Tail", + }, + }, + ["Dragon Steak 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 95", + }, + }, + ["Dragon Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sarcenet Cloth", + "Buffalo Leather", + "Dragon Bone", + }, + }, + ["Dragon Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "D. Fruit au Lait", + "D. Fruit au Lait", + "D. Fruit au Lait", + "D. Fruit au Lait", + }, + }, + ["Dragoon's Collar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Andalusite Crystal", + "Moldy Collar", + }, + }, + ["Dresser"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Gold Ingot", + "Gold Thread", + "Velvet Cloth", + }, + }, + ["Dried Berry"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Rolanberry", + "Rolanberry", + "Rolanberry", + }, + }, + ["Dried Date"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Date", + }, + }, + ["Dried Marjoram"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + "Fresh Marjoram", + }, + }, + ["Dried Mugwort"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + "Fresh Mugwort", + }, + }, + ["Drk. Adm. Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Adaman", + }, + }, + ["Drk. Adm. Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Adaman", + "Dark Adaman", + "Dark Adaman", + "Dark Adaman", + "Dark Adaman", + "Dark Adaman", + "Workshop Anvil", + }, + }, + ["Duelist's Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Dark Matter", + "Khoma Thread", + "Moldy Sword", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Pyrope Crystal", + }, + }, + ["Duelist's Torque"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Pyrope Crystal", + "Moldy Torque", + }, + }, + ["Dull Gold Thread"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Silk Thread", + "Ice Anima", + "Ice Anima", + "Light Anima", + }, + }, + ["Durium Chain"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Durium Ingot", + "Durium Ingot", + }, + }, + ["Durium Chain 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Mandrel", + }, + }, + ["Durium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ore", + "Durium Ore", + "Durium Ore", + "Durium Ore", + }, + }, + ["Durium Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Durium Ingot", + }, + }, + ["Durium Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Workshop Anvil", + }, + }, + ["Dusk Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Tiger Gloves", + }, + }, + ["Dusk Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Behem. Leather", + "Wyvern Skin", + "Northern Jerkin", + }, + }, + ["Dusk Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Tiger Ledelsens", + }, + }, + ["Dusk Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Coeurl Mask", + }, + }, + ["Dusk Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Tiger Trousers", + }, + }, + ["Dux Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Sheep Leather", + "Dragon Scales", + "Turtle Shell", + "Hahava's Mail", + "Squamous Hide", + }, + }, + ["Dux Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Dragon Scales", + "Turtle Shell", + "Squamous Hide", + "Leather Gloves", + }, + }, + ["Dux Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Dragon Scales", + "Turtle Shell", + "Squamous Hide", + "Leather Highboots", + }, + }, + ["Dux Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Dragon Scales", + "Turtle Shell", + "Turtle Shell", + "Turtle Shell", + "Hahava's Mail", + "Squamous Hide", + "Leather Vest", + }, + }, + ["Dux Visor"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dragon Scales", + "Turtle Shell", + "Hahava's Mail", + "Squamous Hide", + }, + }, + ["Dweomer Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Jacaranda Lbr.", + "Dweomer Steel", + }, + }, + ["Dweomer Maul"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Dweomer Steel", + }, + }, + ["Dweomer Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Yew Lumber", + "Grass Cloth", + "Fiend Blood", + "Dweomer Steel", + }, + }, + ["Dweomer Steel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Swamp Ore", + }, + }, + ["Dynamic Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Behem. Leather", + "Mercury", + "Umbril Ooze", + }, + }, + ["Dynamo II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Sieglinde Putty", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Dynamo III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Sieglinde Putty", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Dyrnwyn"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonbow Urushi", + "Cypress Lumber", + "Balmung", + }, + }, + ["Ea Cuffs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Sheet", + "Bztavian Stinger", + "Defiant Scarf", + "Niobium Ore", + "Niobium Ore", + "Niobium Ore", + }, + }, + ["Ea Hat"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Bztavian Stinger", + "Defiant Scarf", + "Niobium Ore", + "Niobium Ore", + "Niobium Ore", + }, + }, + ["Ea Houppelande"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Bztavian Stinger", + "Defiant Scarf", + "Defiant Scarf", + "Niobium Ore", + "Niobium Ingot", + }, + }, + ["Ea Pigaches"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Bztavian Stinger", + "Defiant Scarf", + "Niobium Ore", + "Niobium Ore", + "Niobium Ore", + }, + }, + ["Ea Slops"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Bztavian Stinger", + "Defiant Scarf", + "Niobium Ore", + "Niobium Ingot", + }, + }, + ["Earth Anima"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Profane Memory", + "Profane Memory", + "Profane Memory", + "Profane Memory", + }, + }, + ["Earth Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Puk Fletching", + "Earth Arrowhds.", + }, + }, + ["Earth Arrowheads"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Marid Tusk", + }, + }, + ["Earth Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Earth Ore", + }, + }, + ["Earth Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Earth Cluster", + }, + }, + ["Earth Fewell"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Yellow Rock", + "Catalytic Oil", + }, + }, + ["Earth Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lithic W. Scale", + "Dragon Greaves", + }, + }, + ["Earth Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Earth Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Earth Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Earth Bead", + }, + }, + ["Ebon Armet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Scintillant Ingot", + "Iridium", + }, + }, + ["Ebon Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Scintillant Ingot", + "Ruszor Leather", + "Cambric", + "Cambric", + }, + }, + ["Ebon Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mlbd. Sheet", + "Platinum Ingot", + "Amph. Leather", + "Shagreen", + }, + }, + ["Ebon Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Amph. Leather", + "Shagreen", + "Studded Trousers", + }, + }, + ["Ebon Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Ocl. Sheet", + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Scintillant Ingot", + "Iridium", + }, + }, + ["Ebon Clogs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Tiger Leather", + "Tiger Leather", + "Molybden. Ingot", + "Cambric", + "Cambric", + }, + }, + ["Ebon Frock"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Chain", + "Behem. Leather", + "Taffeta Cloth", + "Scintillant Ingot", + "Ruszor Leather", + "Cambric", + "Cambric", + }, + }, + ["Ebon Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Iridium", + "Studded Gloves", + }, + }, + ["Ebon Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Amph. Leather", + "Amph. Leather", + "Shagreen", + }, + }, + ["Ebon Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mlbd. Sheet", + "Platinum Sheet", + "Angel Skin", + "Red Textile Dye", + "Amph. Leather", + "Amph. Leather", + "Shagreen", + }, + }, + ["Ebon Hose"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Iridium", + }, + }, + ["Ebon Leggings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Wool Thread", + "Ram Leather", + "Drk. Adm. Sheet", + "Drk. Adm. Sheet", + "Scintillant Ingot", + "Iridium", + }, + }, + ["Ebon Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mlbd. Sheet", + "Amph. Leather", + "Shagreen", + }, + }, + ["Ebon Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Scintillant Ingot", + "Garglle. Shank", + "Ruszor Leather", + "Cambric", + "Cambric", + }, + }, + ["Ebon Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Scintillant Ingot", + "Cambric", + "Cambric", + }, + }, + ["Ebonite"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Sulfur", + "Flan Meat", + "Flan Meat", + }, + }, + ["Ebony Harp"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + "Coeurl Whisker", + }, + }, + ["Ebony Harp 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 71", + }, + }, + ["Ebony Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Log", + }, + }, + ["Ebony Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Log", + "Ebony Log", + "Ebony Log", + "Bundling Twine", + }, + }, + ["Ebony Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + }, + }, + ["Ebony Sabots"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Sheep Leather", + }, + }, + ["Ebony Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Giant Bird Fthr.", + }, + }, + ["Echidna's Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bldwd. Lumber", + "Bldwd. Lumber", + "Damascene Cloth", + "Simian Mane", + "Gabbrath Horn", + }, + }, + ["Echo Drops"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Honey", + "Distilled Water", + }, + }, + ["Echo Drops 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Sage", + "Triturator", + "Honey", + "Honey", + "Honey", + "Distilled Water", + }, + }, + ["Ecru Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Desk", + "White Text. Dye", + }, + }, + ["Eel Kabob"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Black Eel", + }, + }, + ["Eel Kabob 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Yilanbaligi", + }, + }, + ["Egg Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Rock Salt", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Egg Soup 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Rock Salt", + "Distilled Water", + "Bird Egg", + }, + }, + ["Eight-Sided Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Walnut Lumber", + "Walnut Lumber", + }, + }, + ["Eisenbrust"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bronze Sheet", + "Iron Sheet", + "Iron Sheet", + "Silver Ingot", + "Sheep Leather", + "Mercury", + }, + }, + ["Eisendiechlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Iron Sheet", + "Dhalmel Leather", + "Sheep Leather", + }, + }, + ["Eisenhentzes"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Iron Sheet", + "Silver Ingot", + "Mercury", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Eisenschaller"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Sheet", + "Iron Sheet", + "Silver Ingot", + "Sheep Leather", + "Mercury", + }, + }, + ["Eisenschuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Silver Ingot", + "Dhalmel Leather", + "Sheep Leather", + "Mercury", + }, + }, + ["Ej Necklace"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Oxblood", + "Siren's Macrame", + "Wivre Maul", + "Carrier Crab Crpc.", + "Rockfin Tooth", + "Rockfin Tooth", + }, + }, + ["Elan Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cerberus Hide", + "Cehuetzi Pelt", + }, + }, + ["Eldritch Bone Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wail. Bone Chip", + "Bone Hairpin", + }, + }, + ["Eldritch Horn Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wailing Ram Horn", + "Horn Hairpin", + }, + }, + ["Electrified Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ladybug Wing", + "Gnat Wing", + "Panopt Tears", + "Snap. Secretion", + }, + }, + ["Electrum Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Firesand", + "Electrum Ingot", + }, + }, + ["Electrum Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Electrum Ingot", + "Electrum Ingot", + }, + }, + ["Electrum Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mandrel", + "Electrum Ingot", + "Electrum Ingot", + "Electrum Ingot", + "Electrum Ingot", + "Electrum Ingot", + "Electrum Ingot", + }, + }, + ["Electrum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ore", + "Gold Ore", + "Gold Ore", + "Gold Ore", + }, + }, + ["Elite Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Phoenix Feather", + "War Beret", + }, + }, + ["Elixir"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mistletoe", + "Revival Root", + "Dragon Blood", + "Royal Jelly", + "Distilled Water", + }, + }, + ["Elixir Tank"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sheep Leather", + "Brass Tank", + "Elixir", + "Elixir", + "Elixir", + "Elixir", + }, + }, + ["Elixir Vitae"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mistletoe", + "Revival Root", + "Imp Wing", + "Chimera Blood", + "Distilled Water", + }, + }, + ["Elm Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Log", + }, + }, + ["Elm Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Log", + "Elm Log", + "Elm Log", + "Bundling Twine", + }, + }, + ["Elm Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Lumber", + "Elm Lumber", + }, + }, + ["Elm Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Elm Lumber", + "Elm Lumber", + }, + }, + ["Elm Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Lumber", + "Ram Horn", + }, + }, + ["Elshena"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Olive Oil", + "Imperial Flour", + "Woozyshroom", + "Scream Fungus", + "Puffball", + "Bird Egg", + }, + }, + ["Elshimo Palm"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Willow Log", + "Rattan Lumber", + "Red Gravel", + "Elshimo Coconut", + "Humus", + }, + }, + ["Emerald Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Emerald", + "Platinum Earring", + }, + }, + ["Emerald Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Emerald", + "Ptm. Earring +1", + }, + }, + ["Emerald Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Emerald", + "Platinum Ring", + }, + }, + ["Emerald Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Emerald", + "Platinum Ring +1", + }, + }, + ["Emperor Roe"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Morinabaligi", + }, + }, + ["Emperor Roe 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Emperor Fish", + }, + }, + ["Empwr. Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Enhancing Mantle", + }, + }, + ["Engetsuto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Dogwd. Lumber", + "Scintillant Ingot", + }, + }, + ["Enju"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Ancient Lumber", + "Cotton Thread", + "Raptor Skin", + "Midrium Ingot", + "Mantid Carapace", + }, + }, + ["Enriching Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonbow Stone", + "Niobium Ingot", + "Enhancing Sword", + }, + }, + ["Enthralling Brocade Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sh. Gold Thread", + "Brocade Obi", + }, + }, + ["Enthralling Gold Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sh. Gold Thread", + "Gold Obi", + }, + }, + ["Epee"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mythril Ingot", + "Gold Ingot", + "Platinum Ingot", + "Angelstone", + "Mercury", + }, + }, + ["Ephedra Ring"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Holy Water", + "Hallowed Water", + "Mythril Ring", + }, + }, + ["Eremite's Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Tooth", + "Hermit's Ring", + }, + }, + ["Eremite's Wand"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Willow Lumber", + "Hermit's Wand", + }, + }, + ["Eris' Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Seashell", + "Nemesis Earring", + }, + }, + ["Erlking's Blade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Dweomer Steel", + }, + }, + ["Erlking's Kheten"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Rosewood Lbr.", + "Dweomer Steel", + }, + }, + ["Erlking's Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Peiste Leather", + "Dweomer Steel", + }, + }, + ["Erlking's Tabar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Oak Lumber", + "Dweomer Steel", + }, + }, + ["Errant Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Raxa", + "Peace Cape", + }, + }, + ["Errant Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turquoise", + "Turquoise", + "Gold Thread", + "Silk Cloth", + "Rainbow Cloth", + "Sheep Leather", + }, + }, + ["Errant Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pearl", + "Rainbow Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Errant Houppelande"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Chain", + "Rainbow Thread", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + "Rainbow Cloth", + "Rainbow Cloth", + "Ram Leather", + }, + }, + ["Errant Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Velvet Cloth", + "Undead Skin", + "Ram Leather", + "Ram Leather", + }, + }, + ["Errant Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + "Ram Leather", + }, + }, + ["Espadon"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Ether"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Bat Wing", + "Bat Wing", + "Dryad Root", + "Distilled Water", + }, + }, + ["Ether 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 50", + }, + }, + ["Ether Cotton"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dried Marjoram", + "Cotton Cloth", + "Ahriman Wing", + "Treant Bulb", + "Distilled Water", + }, + }, + ["Ether Drop"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Bat Wing", + "Bat Wing", + "Dryad Root", + "Distilled Water", + }, + }, + ["Ether Holly"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dried Marjoram", + "Holly Lumber", + "Ahriman Wing", + "Treant Bulb", + "Distilled Water", + }, + }, + ["Ether Leather"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dried Marjoram", + "Ram Leather", + "Ahriman Wing", + "Treant Bulb", + "Distilled Water", + }, + }, + ["Ether Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Brass Tank", + "Ether", + "Ether", + "Ether", + "Ether", + }, + }, + ["Ethereal Oak Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Log", + "Ice Anima", + "Wind Anima", + "Dark Anima", + }, + }, + ["Ethereal Vermilion Lacquer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sulfur", + "Lightning Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Etoile Gorget"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Pelt", + "Dark Matter", + "S. Faulpie Leather", + "Sunstone Crystal", + "Moldy Gorget", + }, + }, + ["Etoile Knife"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Horn", + "Dark Matter", + "Ruthenium Ore", + "Moldy Dagger", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Sunstone Crystal", + }, + }, + ["Etourdissante"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "P. Brass Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Durium Ingot", + "Buffalo Leather", + "Paralyze Potion", + }, + }, + ["Evader Earring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Ingot", + "P. Brass Ingot", + }, + }, + ["Exactitude Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Lizard Skin", + "Immortal Molt", + }, + }, + ["Exalted Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Akaso Thread", + "Akaso Cloth", + "Exalted Lumber", + "Exalted Lumber", + }, + }, + ["Exalted Crossbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Akaso Thread", + "Bismuth Ingot", + "Exalted Lumber", + }, + }, + ["Exalted Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Exalted Log", + }, + }, + ["Exalted Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Exalted Log", + "Exalted Log", + "Exalted Log", + }, + }, + ["Exalted Spear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Akaso Thread", + "Exalted Lumber", + "Ra'Kaznar Ingot", + }, + }, + ["Exalted Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Exalted Lumber", + "Exalted Lumber", + }, + }, + ["Exorcismal Oak Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Ice Anima", + "Earth Anima", + "Light Anima", + }, + }, + ["Eye Drops"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Chamomile", + "Ahriman Tears", + "Distilled Water", + }, + }, + ["Eye Drops 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Chamomile", + "Chamomile", + "Ahriman Tears", + "Ahriman Tears", + "Triturator", + "Distilled Water", + }, + }, + ["Eyeball Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Apple Vinegar", + "Beastman Blood", + "Hecteyes Eye", + "Gelatin", + "Frost Turnip", + "Distilled Water", + }, + }, + ["Faceguard"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Sheep Leather", + }, + }, + ["Fairweather Fetish"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Saruta Cotton", + "Bast Parchment", + }, + }, + ["Falchion"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + }, + }, + ["Falsiam Vase"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Darksteel Ingot", + "Tin Ingot", + }, + }, + ["Falx"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Mythril Ingot", + "Holly Lumber", + "Pearl", + "Dhalmel Leather", + }, + }, + ["Fane Baselard"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Adaman", + "Dweomer Steel", + }, + }, + ["Fane Hexagun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ebony Lumber", + "Ebony Lumber", + "Silver Ingot", + "Dweomer Steel", + }, + }, + ["Fang Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Yagudo Fltchg.", + "Fang Arrowhd.", + }, + }, + ["Fang Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Blk. Tiger Fang", + }, + }, + ["Fang Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Blk. Tiger Fang", + "Blk. Tiger Fang", + "Blk. Tiger Fang", + "Shagreen File", + }, + }, + ["Fang Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Blk. Tiger Fang", + "Blk. Tiger Fang", + }, + }, + ["Fang Necklace"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Bone Chip", + "Bone Chip", + "Blk. Tiger Fang", + "Bat Fang", + "Bat Fang", + "Bat Fang", + "Bat Fang", + }, + }, + ["Fang Necklace 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 15", + }, + }, + ["Fastwater Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Fast. Rod", + }, + }, + ["Fastwater Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 55", + }, + }, + ["Fastwater Fishing Rod 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Lumber", + "Wool Thread", + }, + }, + ["Faulpie Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Distilled Water", + "S. Faulpie Leather", + }, + }, + ["Faulpie Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Distilled Water", + "S. Faulpie Leather", + }, + }, + ["Faussar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mahogany Lbr.", + "Lizard Skin", + }, + }, + ["Fay Crozier"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gargouille Eye", + "Feywld. Lumber", + }, + }, + ["Fay Gendawa"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Rainbow Cloth", + "Rafflesia Vine", + "Gargouille Horn", + "Feywld. Lumber", + }, + }, + ["Fay Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Ash Lumber", + "Feywld. Lumber", + }, + }, + ["Fay Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Turquoise", + "Gargouille Horn", + "Feywld. Lumber", + }, + }, + ["Feasting Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Teak Lumber", + "Teak Lumber", + "Jacaranda Lbr.", + }, + }, + ["Feather Collar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Cloth", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + "Bird Feather", + }, + }, + ["Federal Mercenary's Hammock"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Lauan Lumber", + "Holly Lumber", + "Holly Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Wisteria Lumber", + }, + }, + ["Felicifruit Gelatin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Maple Sugar", + "Gelatin", + "San d'Or. Grape", + "Distilled Water", + "Felicifruit", + "Felicifruit", + }, + }, + ["Feyweald Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Feyweald Log", + }, + }, + ["Fictile Pot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Holly Lumber", + "Kaolin", + "Kaolin", + }, + }, + ["Field Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Grass Cloth", + "Ram Leather", + "Ram Leather", + }, + }, + ["Field Boots 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 40", + }, + }, + ["Field Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Dhalmel Leather", + "Ram Leather", + }, + }, + ["Field Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + }, + }, + ["Field Tunica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + "Sheep Leather", + "Ram Leather", + }, + }, + ["Fighting Fish Tank"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Lauan Lumber", + "Crawler Calculus", + "Sieglinde Putty", + "Glass Sheet", + "Goldfish Set", + "Lamp Marimo", + "Betta", + }, + }, + ["Fin Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Kalkanbaligi", + "Ground Wasabi", + }, + }, + ["Fine Linen Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Thread", + "Linen Thread", + "Wind Anima", + "Earth Anima", + "Light Anima", + }, + }, + ["Fine Parchment"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Parchment", + "Pumice Stone", + }, + }, + ["Finesse Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coquecigr. Skin", + "Beak Gloves", + }, + }, + ["Fire Anima"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Burning Memory", + "Burning Memory", + "Burning Memory", + "Burning Memory", + }, + }, + ["Fire Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Fire Arrowheads", + "Bird Fletchings", + }, + }, + ["Fire Arrowheads"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Iron Ingot", + "Grass Cloth", + }, + }, + ["Fire Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fire Ore", + }, + }, + ["Fire Biscuit"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Kazham Peppers", + "Selbina Butter", + "Crawler Egg", + "Honey", + "Acorn", + "Acorn", + "Acorn", + }, + }, + ["Fire Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Incomb. Wool", + "Wool Bracers", + }, + }, + ["Fire Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Fire Cluster", + }, + }, + ["Fire Fewell"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Red Rock", + "Catalytic Oil", + }, + }, + ["Fire Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fire Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Fire Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Fire Bead", + }, + }, + ["Fire Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Firesand", + "Iron Sword", + }, + }, + ["Firesand"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Yuhtunga Sulfur", + }, + }, + ["Firesand 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Sulfur", + }, + }, + ["Firesand 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Sulfur", + "Sulfur", + "Triturator", + }, + }, + ["Firesand 4"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Alch. Kit 40", + }, + }, + ["Firesand 5"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sulfur", + "Cluster Ash", + }, + }, + ["Firesand 6"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sulfur", + "Djinn Ash", + }, + }, + ["Firnaxe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Thokcha Ingot", + "Heavy Dst. Axe", + }, + }, + ["Fish Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bastore Sardine", + "Bastore Sardine", + "Bastore Sardine", + "Bastore Sardine", + }, + }, + ["Fish Broth 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Hamsi", + "Hamsi", + "Hamsi", + "Hamsi", + }, + }, + ["Fish Broth 3"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Senroh Sardine", + "Senroh Sardine", + "Senroh Sardine", + "Senroh Sardine", + }, + }, + ["Fish Broth 4"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Uskumru", + "Uskumru", + }, + }, + ["Fish Broth 5"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bluetail", + "Bluetail", + }, + }, + ["Fish Broth 6"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Uskumru", + "Uskumru", + }, + }, + ["Fish Broth 7"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Lakerda", + }, + }, + ["Fish Broth 8"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gugru Tuna", + }, + }, + ["Fish Mithkabob"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bastore Sardine", + "Nebimonite", + "Bluetail", + "Shall Shell", + "Shall Shell", + }, + }, + ["Fish Mithkabob 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hamsi", + "Uskumru", + "Ahtapot", + "Istiridye", + "Istiridye", + }, + }, + ["Fish Scales"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Gavial Fish", + }, + }, + ["Fish and Chips"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Popoto", + "Apple Vinegar", + "Olive Oil", + "Tiger Cod", + "Bird Egg", + "Moval. Water", + }, + }, + ["Fish and Chips 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Popoto", + "Apple Vinegar", + "Olive Oil", + "Blindfish", + "Bird Egg", + "Moval. Water", + }, + }, + ["Fisherman's Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Grass Cloth", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Fisherman's Boots 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 20", + }, + }, + ["Fisherman's Feast"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tonosama R.Ball", + "Sausage", + "Sausage", + "Salmon Rice Ball", + "Apkallu Egg", + "Graubg. Lettuce", + "Winterflower", + }, + }, + ["Fisherman's Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Fisherman's Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Fisherman's Tunica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + "Sheep Leather", + }, + }, + ["Fisherman's Tunica 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 30", + }, + }, + ["Fizzy Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Millioncorn", + "Pine Nuts", + "Lucerewe Meat", + }, + }, + ["Flamberge"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Mahogany Lbr.", + "Cockatrice Skin", + }, + }, + ["Flame Blade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Firesand", + "Falchion", + }, + }, + ["Flame Claymore"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Iron Ingot", + "Claymore", + }, + }, + ["Flame Degen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Firesand", + "Degen", + }, + }, + ["Flame Holder"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Cotton Thread", + "Beeswax", + "Wyvern Skin", + "Super Cermet", + "Cluster Arm", + }, + }, + ["Flame Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fire Bead", + "Orichalcum Ring", + }, + }, + ["Flanged Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Darksteel Ingot", + "Mahogany Lbr.", + "Gold Ingot", + "Smildn. Leather", + }, + }, + ["Flashbulb"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Glass Fiber", + "Sieglinde Putty", + "Glass Sheet", + "Lamp Marimo", + "Water Tank", + }, + }, + ["Flax Headband"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Carbon Fiber", + }, + }, + ["Flaxseed Oil"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Flax Flower", + "Flax Flower", + }, + }, + ["Flete Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Kapor Lumber", + "Kapor Lumber", + "Scintillant Ingot", + }, + }, + ["Fleuret"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Silver Ingot", + "Light Opal", + }, + }, + ["Flexible Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Earth Cell", + "Holly Pole", + }, + }, + ["Flint Caviar"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Rock Salt", + "Emperor Roe", + }, + }, + ["Flint Glass Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Shell Powder", + "Silica", + "Silica", + "Silica", + "Silica", + "Minium", + "Minium", + }, + }, + ["Floral Nightstand"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Ebony Lumber", + "Gold Ingot", + }, + }, + ["Flounder Meuniere"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Selbina Milk", + "Dil", + }, + }, + ["Flounder Meuniere 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Selbina Milk", + "Black Sole", + }, + }, + ["Flower Stand"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Yew Lumber", + }, + }, + ["Fluorite"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ramuite", + }, + }, + ["Fluorite Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fluorite", + "Gold Ring", + }, + }, + ["Fluorite Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fluorite", + "Gold Ring +1", + }, + }, + ["Fluoro-flora"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tufa", + "Red Gravel", + "Orobon Lure", + "Wildgrass Seeds", + "Super Ether", + "Humus", + }, + }, + ["Flute"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Lumber", + "Parchment", + }, + }, + ["Fly Lure"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chocobo Fthr.", + "Bat Fang", + "Animal Glue", + }, + }, + ["Focus Collar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Flawed Garnet", + }, + }, + ["Foppish Tunica"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Thread", + "Silk Cloth", + "Celaeno's Cloth", + "Penelope's Cloth", + "Defiant Scarf", + "Defiant Scarf", + }, + }, + ["Foulard"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Rainbow Thread", + "Rainbow Thread", + "Arachne Thread", + }, + }, + ["Foulweather Frog"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Caedarva Frog", + }, + }, + ["Foulweather Frog 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Elshimo Frog", + }, + }, + ["Fowler's Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Woolly Pelage", + }, + }, + ["Fragrant Dhalmel Hide"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dhalmel Hide", + "Wind Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Fragrant Ram Skin"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ram Skin", + "Wind Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Freshwater Aquarium"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Petrified Log", + "Oak Lumber", + "Sieglinde Putty", + "Glass Sheet", + "Freshwater Set", + "Pipira", + "Pipira", + "Pipira", + }, + }, + ["Freshwater Set"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Silica", + "River Foliage", + "Desalinator", + "Holy Water", + }, + }, + ["Fried Pototo"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Olive Oil", + "White Bread", + "Bird Egg", + }, + }, + ["Frigid Core"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Adaman Ore", + "Adaman Ore", + "Adaman Ore", + "Ice Anima", + "Ice Anima", + "Dark Anima", + }, + }, + ["Frigid Orichalcum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Ice Anima", + "Ice Anima", + "Dark Anima", + }, + }, + ["Frigid Skin"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Raptor Skin", + "Ice Anima", + "Ice Anima", + "Dark Anima", + }, + }, + ["Frizzante Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "Karakul Meat", + "Ziz Meat", + }, + }, + ["Frog Lure"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Glass Fiber", + "Copper Frog", + }, + }, + ["Frosty Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brilliant Snow", + "Snowman Cap", + }, + }, + ["Fruit Parfait"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Sugar", + "Apple Mint", + "Faerie Apple", + "Saruta Orange", + "Kazham Pineapl.", + "Yagudo Cherry", + "Pamamas", + "Uleguerand Milk", + }, + }, + ["Fuma Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Mythril Sheet", + "Gold Ingot", + "Mercury", + }, + }, + ["Furusumi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Kapor Log", + "Animal Glue", + "Distilled Water", + "Soot", + }, + }, + ["Fusion Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Aluminum Ingot", + }, + }, + ["Futhark Claymore"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Defiant Scarf", + "Dark Matter", + "Niobium Ore", + "Moldy G. Sword", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Apatite Crystal", + }, + }, + ["Futhark Torque"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Apatite Crystal", + "Moldy Torque", + }, + }, + ["Gaia Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Earth Doublet", + }, + }, + ["Gaia Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cockatrice Skin", + "Earth Mantle", + }, + }, + ["Gaiters"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Gallipot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Walnut Log", + "Bamboo Stick", + "Silica", + "Djinn Ash", + "Karugo Clay", + "Karugo Clay", + "Karugo Clay", + "Karugo Clay", + }, + }, + ["Gambison"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Garden Bangles"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Dryad Root", + "Fiend Blood", + "Grt. Boyahda Moss", + "4Lf. Korrin Bud", + }, + }, + ["Gargouille Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Gargouille Horn", + }, + }, + ["Gargouille Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Gargouille Horn", + "Gargouille Horn", + "Gargouille Horn", + }, + }, + ["Garish Crown"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Molt", + "Beetle Jaw", + "Coeurl Whisker", + "Bloodthread", + }, + }, + ["Garish Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Saruta Cotton", + "Sheep Leather", + "Scarlet Linen", + "Bloodthread", + }, + }, + ["Garish Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Scarlet Linen", + "Scarlet Linen", + "Bloodthread", + "Bloodthread", + }, + }, + ["Garish Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Bloodthread", + }, + }, + ["Garish Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Scarlet Linen", + "Scarlet Linen", + "Scarlet Linen", + "Bloodthread", + }, + }, + ["Garlic Cracker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Tarutaru Rice", + "Rock Salt", + "Distilled Water", + }, + }, + ["Garnet Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Garnet", + "Mythril Ring", + }, + }, + ["Garnet Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Garnet", + "Mythril Ring +1", + }, + }, + ["Gateau aux Fraises"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Rolanberry", + "Rolanberry", + "Selbina Milk", + "Distilled Water", + "Bird Egg", + }, + }, + ["Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Gavial Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "H.Q. Pugil Scls.", + "Leather Trousers", + }, + }, + ["Gavial Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "H.Q. Pugil Scls.", + "Leather Gloves", + }, + }, + ["Gavial Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "H.Q. Pugil Scls.", + "Leather Highboots", + }, + }, + ["Gavial Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Sheep Leather", + "Titanictus Shell", + "Titanictus Shell", + "H.Q. Pugil Scls.", + "H.Q. Pugil Scls.", + "Leather Vest", + }, + }, + ["Gavial Mask"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Titanictus Shell", + "H.Q. Pugil Scls.", + }, + }, + ["Gefechtbrust"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Mercury", + "Cerber. Leather", + "Largantua's Shard", + "Largantua's Shard", + "Malatrix's Shard", + "Malatrix's Shard", + }, + }, + ["Gefechtdiechlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Cerber. Leather", + "Largantua's Shard", + "Malatrix's Shard", + }, + }, + ["Gefechthentzes"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Mercury", + "Largantua's Shard", + "Malatrix's Shard", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Gefechtschaller"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Mercury", + "Cerber. Leather", + "Scintillant Ingot", + "Umbril Ooze", + "Largantua's Shard", + "Malatrix's Shard", + }, + }, + ["Gefechtschuhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Mercury", + "Buffalo Leather", + "Cerber. Leather", + "Largantua's Shard", + "Malatrix's Shard", + }, + }, + ["Gelatin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Chicken Bone", + "Chicken Bone", + "Distilled Water", + }, + }, + ["Gelatin 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Distilled Water", + }, + }, + ["Gelatin 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Giant Femur", + "Distilled Water", + }, + }, + ["Gemshorn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Femur", + "Beetle Jaw", + }, + }, + ["Gendawa"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Rainbow Cloth", + "Coeurl Whisker", + "Taurus Horn", + }, + }, + ["General's Shield"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Platinum Sheet", + "Sheep Leather", + "Mercury", + }, + }, + ["Gerdr Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ocl. Chain", + "Faulpie Leather", + "Faulpie Leather", + "Wyrm Ash", + }, + }, + ["Ghillie Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Ruszor Fang", + "Ruszor Fang", + }, + }, + ["Ghost Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Velvet Cloth", + "Spectral Serum", + }, + }, + ["Giant Bird Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "G. Bird Plume", + "G. Bird Plume", + }, + }, + ["Giant Bird Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "G. Bird Plume", + "G. Bird Plume", + "G. Bird Plume", + "G. Bird Plume", + "G. Bird Plume", + "G. Bird Plume", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + }, + }, + ["Gilded Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Gold Ingot", + }, + }, + ["Gilded Shelf"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Gimlet Spear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Bronze Spear", + }, + }, + ["Ginger Cookie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Ginger", + "Maple Sugar", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Ginger Cookie 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Ginger", + "Maple Sugar", + "Distilled Water", + "Bird Egg", + }, + }, + ["Girandola"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Beeswax", + "Beeswax", + }, + }, + ["Gladius"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Ram Horn", + }, + }, + ["Glass Fiber"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Goblin Mask", + }, + }, + ["Glass Fiber 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Moblin Mask", + }, + }, + ["Glass Fiber 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Flint Stone", + }, + }, + ["Glass Fiber 4"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Meteorite", + }, + }, + ["Glass Fiber Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Glass Rod", + }, + }, + ["Glass Fiber Fishing Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cotton Thread", + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Glass Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Shell Powder", + "Silica", + "Silica", + "Silica", + "Silica", + "Silica", + "Silica", + }, + }, + ["Glossy Bugard Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Bugard Skin", + "Wind Anima", + "Wind Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Glossy Bugard Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Bugard Skin", + "Wind Anima", + "Wind Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Glowfly Cage"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Rattan Lumber", + "Rattan Lumber", + "Dogwd. Lumber", + "Glowfly", + }, + }, + ["Gnat Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gnat Wing", + "Gnat Wing", + }, + }, + ["Gnat Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Gnat Wing", + "Gnat Wing", + "Gnat Wing", + "Gnat Wing", + "Gnat Wing", + "Gnat Wing", + }, + }, + ["Gnole Sainti"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Gnole Claw", + "Gnole Claw", + "Teak Lumber", + }, + }, + ["Goblin Bread"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Horo Flour", + "Bone Chip", + "Rock Salt", + "Poison Flour", + "Crawler Egg", + "Puffball", + "Distilled Water", + }, + }, + ["Goblin Bug Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Rotten Meat", + "Lugworm", + "Shell Bug", + "Shell Bug", + }, + }, + ["Goblin Chocolate"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Kukuru Bean", + "Kukuru Bean", + "Kukuru Bean", + "Wijnruit", + "Honey", + "Selbina Milk", + "Cobalt Jellyfish", + "Sunflower Seeds", + }, + }, + ["Goblin Coif"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Linen Cloth", + "Undead Skin", + "Sheep Leather", + "Artificial Lens", + "Artificial Lens", + "Moblin Thread", + "Goblin Cutting", + }, + }, + ["Goblin Drink"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "La Theine Cbg.", + "Frost Turnip", + "Wild Onion", + "San d'Or. Carrot", + "Watermelon", + "Distilled Water", + "Gysahl Greens", + "Elshimo Newt", + }, + }, + ["Goblin Mushpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Rock Salt", + "Sleepshroom", + "Danceshroom", + "Scream Fungus", + "Distilled Water", + "Sobbing Fungus", + "Deathball", + }, + }, + ["Goblin Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Ginger", + "Black Pepper", + "Crawler Egg", + "Honey", + "Eggplant", + "Crayfish", + "Sunflower Seeds", + }, + }, + ["Goblin Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Horo Flour", + "Chicken Bone", + "Fish Bones", + "Fiend Blood", + "Rock Salt", + "Puffball", + "Distilled Water", + "Rotten Meat", + }, + }, + ["Goblin Stir-Fry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Ginger", + "Olive Oil", + "Trilobite", + "Hare Meat", + "La Theine Cbg.", + "Eggplant", + "Rarab Tail", + }, + }, + ["Gold Algol"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Divine Lumber", + "Gold Ingot", + "Gold Ingot", + "Super Cermet", + "Super Cermet", + "Super Cermet", + "Super Cermet", + "Scintillant Ingot", + }, + }, + ["Gold Armet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Gold Ingot", + "Sheep Leather", + "Mercury", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Gold Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Yagudo Fltchg.", + "Gold Arrowheads", + }, + }, + ["Gold Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Gold Ingot", + }, + }, + ["Gold Bangles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Gold Bangles 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 70", + }, + }, + ["Gold Brocade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Rainbow Thread", + "Rainbow Thread", + "Gold Thread", + "Gold Thread", + }, + }, + ["Gold Buckler"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Targe", + }, + }, + ["Gold Buckler 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 80", + }, + }, + ["Gold Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Firesand", + }, + }, + ["Gold Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Gold Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Mandrel", + }, + }, + ["Gold Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Gold Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Mercury", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Gold Dust"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Brass Ingot", + }, + }, + ["Gold Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Gold Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Cermet Chunk", + "Cermet Chunk", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Gold Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + }, + }, + ["Gold Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Beastcoin", + "Gold Beastcoin", + "Gold Beastcoin", + "Gold Beastcoin", + }, + }, + ["Gold Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Gold Ore", + }, + }, + ["Gold Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ore", + "Gold Nugget", + "Gold Nugget", + "Gold Nugget", + "Gold Nugget", + "Gold Nugget", + "Gold Nugget", + }, + }, + ["Gold Nugget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Auric Sand", + "Auric Sand", + "Auric Sand", + "Auric Sand", + }, + }, + ["Gold Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Gold Thread", + }, + }, + ["Gold Obi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 70", + }, + }, + ["Gold Patas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Patas", + }, + }, + ["Gold Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Gold Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Gold Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + }, + }, + ["Gold Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Workshop Anvil", + }, + }, + ["Gold Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Mercury", + "Saber", + }, + }, + ["Gold Thread"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Silk Thread", + }, + }, + ["Gold Thread 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Spindle", + }, + }, + ["Golden Coil"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Mandrel", + }, + }, + ["Golden Gear"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Gold Sheet", + }, + }, + ["Golden Spear"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Partisan", + }, + }, + ["Goldfish Bowl"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Lauan Lumber", + "Crawler Calculus", + "Sieglinde Putty", + "Glass Sheet", + "Goldfish Set", + "Tiny Goldfish", + "Blk. Bubble-Eye", + }, + }, + ["Goldsmithing Set 25"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Silver Ingot", + "Lizard Belt", + }, + }, + ["Goldsmithing Set 45"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Peridot", + "Mythril Earring", + }, + }, + ["Goldsmithing Set 65"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Moonstone", + "Gold Earring", + }, + }, + ["Goldsmithing Set 70"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Goldsmithing Set 75"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Thread", + "Uchigatana", + }, + }, + ["Goldsmithing Set 80"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Targe", + }, + }, + ["Goldsmithing Set 85"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + }, + }, + ["Goldsmithing Set 90"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Gold Ingot", + "Emerald", + "Ruby", + "Diamond", + "Topaz", + "Sapphire", + "Spinel", + "Torque", + }, + }, + ["Goldsmithing Set 94"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Ph. Gold Ingot", + "Ph. Gold Ingot", + }, + }, + ["Gorget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Sheep Leather", + }, + }, + ["Gorkhali Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Thokcha Ingot", + "Griffon Leather", + }, + }, + ["Goshenite Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Goshenite", + "Mythril Earring", + }, + }, + ["Goshenite Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Goshenite", + "Mythril Earring +1", + }, + }, + ["Goshenite Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Goshenite", + "Mythril Ring", + }, + }, + ["Goshenite Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Goshenite", + "Mythril Ring +1", + }, + }, + ["Goulash"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Coeurl Meat", + "Mithran Tomato", + "Distilled Water", + "Paprika", + }, + }, + ["Goulash 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Mithran Tomato", + "Distilled Water", + "Lynx Meat", + "Paprika", + }, + }, + ["Gracile Grip"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mandrel", + "Rhodium Ingot", + "Waktza Crest", + }, + }, + ["Grape Daifuku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Sticky Rice", + "Cornstarch", + "Distilled Water", + "Royal Grape", + "Royal Grape", + "Azuki Bean", + }, + }, + ["Grape Juice"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "San d'Or. Grape", + "San d'Or. Grape", + "San d'Or. Grape", + "San d'Or. Grape", + }, + }, + ["Grass Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Thread", + "Grass Thread", + }, + }, + ["Grass Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Moko Grass", + "Moko Grass", + "Moko Grass", + "Moko Grass", + "Moko Grass", + "Moko Grass", + "Spindle", + }, + }, + ["Grass Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Moko Grass", + "Moko Grass", + }, + }, + ["Grasshopper Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Skull Locust", + "Skull Locust", + "King Locust", + "Mushrm. Locust", + "La Theine Cbg.", + "Gysahl Greens", + }, + }, + ["Great Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Chestnut Lumber", + "Velvet Cloth", + "Scorpion Claw", + "Coeurl Whisker", + }, + }, + ["Great Club"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Mahogany Lbr.", + }, + }, + ["Greataxe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Mythril Ingot", + "Holly Lumber", + }, + }, + ["Greatsword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Holly Lumber", + "Dhalmel Leather", + }, + }, + ["Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Chain", + "Ram Leather", + }, + }, + ["Green 3-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "3-Drawer Almirah", + "Gold Thread", + "Green Text. Dye", + }, + }, + ["Green 6-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "6-Drawer Almirah", + "Gold Thread", + "Green Text. Dye", + }, + }, + ["Green 9-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "9-Drawer Almirah", + "Gold Thread", + "Green Text. Dye", + }, + }, + ["Green Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Cloth", + "Silk Cloth", + "Chocobo Fthr.", + "Giant Bird Fthr.", + }, + }, + ["Green Curry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blue Peas", + "Bay Leaves", + "Curry Powder", + "Holy Basil", + "Crayfish", + "Distilled Water", + "Beaugreens", + }, + }, + ["Green Curry Bun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Olive Oil", + "Green Curry", + "Bird Egg", + }, + }, + ["Green Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Jadeite", + "Gold Earring", + }, + }, + ["Green Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Jadeite", + "Gold Earring +1", + }, + }, + ["Green Hobby Bo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Hickory Lumber", + "Dogwd. Lumber", + "Onyx", + "Onyx", + "Grn. Chocobo Dye", + }, + }, + ["Green Mahogany Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mahogany Bed", + "Wool Thread", + "Green Text. Dye", + }, + }, + ["Green Noble's Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Noble's Bed", + "Gold Thread", + "Green Text. Dye", + }, + }, + ["Green Quiche"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Rock Salt", + "Danceshroom", + "Selbina Milk", + "Stone Cheese", + "Bird Egg", + "Beaugr. Saute", + }, + }, + ["Green Quiche 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 60", + }, + }, + ["Green Ribbon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silk Cloth", + }, + }, + ["Green Round Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Platinum Silk", + "Teak Lumber", + "Green Text. Dye", + }, + }, + ["Green Storm Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Brass Ingot", + "Silver Ingot", + "Silk Cloth", + "Glass Sheet", + "Sailcloth", + "Green Text. Dye", + }, + }, + ["Green Tarutaru Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tarutaru Desk", + "Green Text. Dye", + }, + }, + ["Green Tarutaru Standing Screen"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Taru F. Screen", + "Green Text. Dye", + }, + }, + ["Green Textile Dye"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Im. Tea Leaves", + }, + }, + ["Grenade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Ash Lumber", + "Bomb Ash", + "Yuhtunga Sulfur", + "Firesand", + }, + }, + ["Grenade 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Ash Lumber", + "Bomb Ash", + "Firesand", + "Sulfur", + }, + }, + ["Grenade 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Ash Lumber", + "Firesand", + "Firesand", + "Cluster Ash", + }, + }, + ["Griffon Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Griffon Hide", + "Distilled Water", + }, + }, + ["Griffon Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Griffon Hide", + "Distilled Water", + }, + }, + ["Griffon Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Griffon Hide", + "Griffon Hide", + "Griffon Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Grilled Hare"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Hare Meat", + }, + }, + ["Griot Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Silver Chain", + "Qdv. Silv. Ingot", + }, + }, + ["Guatambu Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Guatambu Log", + }, + }, + ["Guatambu Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Guatambu Log", + "Guatambu Log", + "Guatambu Log", + }, + }, + ["Gueridon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Rosewood Lbr.", + "Gold Ingot", + }, + }, + ["Gully"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Mahogany Lbr.", + }, + }, + ["Gully 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 91", + }, + }, + ["Gust Claymore"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Iron Sheet", + "Claymore", + }, + }, + ["Gust Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Steel Sheet", + "Greatsword", + }, + }, + ["Gust Tongue"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Darksteel Sheet", + "Flamberge", + }, + }, + ["Gysahl Bomb"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Bast Parchment", + "Gysahl Greens", + }, + }, + ["Hachimaki"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Hachiman Domaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Darksteel Chain", + "Darksteel Chain", + "Gold Thread", + "Tiger Leather", + "Viridian Urushi", + "Dark Scales", + "Dark Scales", + }, + }, + ["Hachiman Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Darksteel Chain", + "Gold Thread", + "Velvet Cloth", + "Scarlet Linen", + "Kejusu Satin", + }, + }, + ["Hachiman Jinpachi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Sheet", + "Adaman Chain", + "Silk Cloth", + "Urushi", + "Kejusu Satin", + }, + }, + ["Hachiman Kote"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Darksteel Chain", + "Gold Ingot", + "Tiger Leather", + "Viridian Urushi", + }, + }, + ["Hachiman Sune-Ate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Darksteel Sheet", + "Darksteel Sheet", + "Gold Ingot", + "Rainbow Thread", + "Velvet Cloth", + "Tiger Leather", + "Mercury", + }, + }, + ["Hades Sainti"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Bldwd. Lumber", + "Cerberus Claw", + "Cerberus Claw", + }, + }, + ["Haidate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Silk Thread", + "Silk Cloth", + "Sheep Leather", + }, + }, + ["Hailstorm Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Monsoon Tekko", + }, + }, + ["Hajduk Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Khimaira Horn", + "Khimaira Horn", + }, + }, + ["Hakutaku Eye Cluster"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Fiend Blood", + "Wooden Hktk. Eye", + "Burning Hktk. Eye", + "Earthen Hktk. Eye", + "Golden Hktk. Eye", + "Damp Hktk. Eye", + }, + }, + ["Halberd"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Ingot", + "Ash Lumber", + "Wool Thread", + }, + }, + ["Halcyon Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Halcyon Rod", + }, + }, + ["Halcyon Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Half Partition"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + }, + }, + ["Hallowed Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Divine Sword", + }, + }, + ["Hallowed Water"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Fire Anima", + "Ice Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Halo Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Mythril Claymore", + }, + }, + ["Ham and Ch. Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Mhaura Garlic", + "Mithran Tomato", + "Bird Egg", + "Sausage", + "Chalaimbille", + "Uleguerand Milk", + }, + }, + ["Hammermill"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Coeurl Whisker", + "Goblin Grease", + "Myth.Gear Mach.", + "Warhammer", + "Warhammer", + "Wind Fan", + }, + }, + ["Hanafubuki"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Sakurafubuki", + }, + }, + ["Handgonne"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Thokcha Ingot", + "Wyrm Horn", + }, + }, + ["Hanger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Happo Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Mercury", + "Waktza Rostrum", + "Bismuth Sheet", + }, + }, + ["Hara-Ate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Iron Sheet", + "Iron Chain", + "Iron Chain", + "Silk Thread", + "Silk Cloth", + "Sheep Leather", + }, + }, + ["Hard Leather Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tiger Leather", + }, + }, + ["Hard Leather Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Leath. Kit 60", + }, + }, + ["Hard-boiled Egg"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lizard Egg", + "Distilled Water", + }, + }, + ["Hard-boiled Egg 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Distilled Water", + "Bird Egg", + }, + }, + ["Harp"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Chestnut Lumber", + "Coeurl Whisker", + }, + }, + ["Harp Stool"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Mahogany Lbr.", + "Wolf Felt", + }, + }, + ["Harpoon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Grass Thread", + "Sheep Tooth", + }, + }, + ["Haruspex Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Chain", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + "Akaso Cloth", + "Raaz Leather", + }, + }, + ["Haruspex Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Silk Cloth", + "Star Sapphire", + "Star Sapphire", + "Akaso Cloth", + "Raaz Leather", + }, + }, + ["Haruspex Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Silver Thread", + "Gold Thread", + "Raxa", + "Akaso Cloth", + "Raaz Leather", + }, + }, + ["Haruspex Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Raxa", + "Akaso Cloth", + "Raaz Hide", + "Raaz Leather", + }, + }, + ["Haruspex Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Thread", + "Raxa", + "Raxa", + "Akaso Cloth", + "Raaz Leather", + }, + }, + ["Haste Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Ram Lth.", + "Waistbelt", + }, + }, + ["Hatchet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Maple Lumber", + }, + }, + ["Hatzoaar Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Peiste Leather", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Urunday Lumber", + }, + }, + ["Haubergeon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Darksteel Chain", + "Darksteel Chain", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Hauberk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Darksteel Chain", + "Velvet Cloth", + "Silk Cloth", + "Haubergeon", + }, + }, + ["Haunted Muleta"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Spectral Crimson", + "Spect. Goldenrod", + }, + }, + ["Hawker's Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fiend Blood", + "Beastman Blood", + "Dragon Blood", + "Archer's Knife", + }, + }, + ["Hawkeye"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Ingot", + "Yagudo Feather", + "Yagudo Feather", + "Animal Glue", + }, + }, + ["Hayabusa"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Im. Wootz Ingot", + "Manta Leather", + "Scintillant Ingot", + }, + }, + ["Headgear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Headgear 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cloth. Kit 5", + }, + }, + ["Healing Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vivio Femur", + "Bone Harness", + }, + }, + ["Healing Justaucorps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vi. Scorpion Claw", + "Justaucorps", + }, + }, + ["Healing Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vi. Wyv. Scale", + "Dragon Mail", + }, + }, + ["Healing Vest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vi. Sh. Leather", + "Studded Vest", + }, + }, + ["Heat Capacitor II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Fire Anima", + "Golden Coil", + "Golden Coil", + "Ocl. Gearbox", + }, + }, + ["Heat Seeker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Lightning Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Heater Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 55", + }, + }, + ["Heater Shield 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Kite Shield", + }, + }, + ["Heatsink"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Sieglinde Putty", + "Myth.Gear Mach.", + "Water Tank", + "Hydro Pump", + }, + }, + ["Heavy Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Oak Lumber", + }, + }, + ["Heavy Crossbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Ebony Lumber", + "Giant Femur", + "Carbon Fiber", + }, + }, + ["Heavy Darksteel Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Mahogany Lbr.", + }, + }, + ["Hedgehog Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Pie Dough", + "Blue Peas", + "Rock Salt", + "Lizard Egg", + "King Truffle", + "San d'Or. Carrot", + "Buffalo Meat", + }, + }, + ["Heko Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Hellfire"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Walnut Lumber", + "Gold Ingot", + }, + }, + ["Hellish Bugle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Imp Horn", + "Colibri Beak", + }, + }, + ["Hellish Bugle 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 85", + }, + }, + ["Hellsteak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Bay Leaves", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Wild Onion", + "San d'Or. Carrot", + "Cerberus Meat", + }, + }, + ["Hemolele Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Urunday Lumber", + "Chapuli Horn", + }, + }, + ["Hemp Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + }, + }, + ["Hepatizon Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Urunday Lumber", + "Hepatizon Ingot", + "Hepatizon Ingot", + "Hepatizon Ingot", + }, + }, + ["Hepatizon Baghnakhs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Raaz Leather", + "Maliya. Coral Orb", + "Maliya. Coral Orb", + "Maliya. Coral Orb", + "Hepatizon Ingot", + }, + }, + ["Hepatizon Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ore", + "Mythril Ore", + "Mythril Ore", + "Hepatizon Ore", + }, + }, + ["Hepatizon Rapier"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Mercury", + "Star Sapphire", + "Hepatizon Ingot", + "Hepatizon Ingot", + }, + }, + ["Hepatizon Sapara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Hepatizon Ingot", + "Hepatizon Ingot", + }, + }, + ["Herb Crawler Eggs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Ginger", + "Maple Sugar", + "Olive Oil", + "Crawler Egg", + "Wild Onion", + "San d'Or. Carrot", + "Distilled Water", + }, + }, + ["Herb Paste"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Toko. Wildgrass", + "La Theine Millet", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Herb Quus"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Dried Marjoram", + "Bay Leaves", + "Black Pepper", + "Rock Salt", + "Quus", + "Quus", + "Quus", + }, + }, + ["Herbal Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Frost Turnip", + "Frost Turnip", + "Beaugreens", + "Beaugreens", + }, + }, + ["Hermes Quencher"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Mugwort", + "Flytrap Leaf", + "Honey", + "Jacknife", + "Moval. Water", + }, + }, + ["Hermes' Sandals"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Karakul Leather", + "Lynx Leather", + "Dark Ixion Tail", + }, + }, + ["Heroic Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Ram Leather", + "Ram Leather", + "Lindwurm Skin", + }, + }, + ["Hexagun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Walnut Lumber", + "Bldwd. Lumber", + "Gold Ingot", + "Mercury", + }, + }, + ["Hexed Bliaut"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Velvet Cloth", + "Kukulkan's Skin", + "Muculent Ingot", + "Serica Cloth", + "Serica Cloth", + }, + }, + ["Hexed Bonnet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Malboro Fiber", + "Muculent Ingot", + "Penelope's Cloth", + }, + }, + ["Hexed Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Kukulkan's Skin", + "Lynx Leather", + "Serica Cloth", + }, + }, + ["Hexed Coif"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Ethereal Squama", + "Belladonna Sap", + "Velvet Hat", + }, + }, + ["Hexed Coronet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Freya's Tear", + "Cerber. Leather", + "A.U. Brass Ingot", + "A.U Brass Sheet", + }, + }, + ["Hexed Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Red Grs. Thread", + "Lynx Leather", + "Muculent Ingot", + "Serica Cloth", + }, + }, + ["Hexed Domaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Scarletite Ingot", + "Gold Ingot", + "Taffeta Cloth", + "Urushi", + "Sealord Leather", + }, + }, + ["Hexed Doublet"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Ethereal Squama", + "Belladonna Sap", + "Belladonna Sap", + "Velvet Robe", + }, + }, + ["Hexed Gages"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Ethereal Squama", + "Belladonna Sap", + "Velvet Cuffs", + }, + }, + ["Hexed Gamashes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Malboro Fiber", + "Marid Leather", + "Befouled Silver", + "Staghorn Coral", + "Staghorn Coral", + }, + }, + ["Hexed Gambieras"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Ormolu Ingot", + "A.U. Brass Ingot", + "Pelt of Dawon", + "Black Sollerets", + }, + }, + ["Hexed Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "A.U. Brass Ingot", + "Pelt of Dawon", + "Black Gadlings", + }, + }, + ["Hexed Hakama"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Gold Ingot", + "Gold Thread", + "Taffeta Cloth", + "Red Grass Cloth", + "Sealord Leather", + "Sealord Leather", + }, + }, + ["Hexed Haubert"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Ormolu Ingot", + "Ormolu Ingot", + "Freya's Tear", + "Bloodthread", + "A.U. Brass Ingot", + "Plastron", + }, + }, + ["Hexed Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Behem. Leather", + "Pelt of Dawon", + "Sealord Leather", + "Sealord Leather", + }, + }, + ["Hexed Jacket"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Malboro Fiber", + "Befouled Silver", + "Penelope's Cloth", + "Staghorn Coral", + "Sealord Leather", + }, + }, + ["Hexed Kecks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Malboro Fiber", + "Taffeta Cloth", + "Penelope's Cloth", + "Staghorn Coral", + "Sealord Leather", + }, + }, + ["Hexed Mitra"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Kukulkan's Skin", + "Muculent Ingot", + "Serica Cloth", + }, + }, + ["Hexed Nails"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Ethereal Squama", + "Belladonna Sap", + "Ebony Sabots", + }, + }, + ["Hexed Slops"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Revival Root", + "Ethereal Squama", + "Belladonna Sap", + "Velvet Slops", + }, + }, + ["Hexed Somen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Gold Ingot", + "Taffeta Cloth", + "Urushi", + "Sealord Leather", + }, + }, + ["Hexed Sune-Ate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Gold Sheet", + "Taffeta Cloth", + "Amph. Leather", + "Sealord Leather", + }, + }, + ["Hexed Tekko"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Gold Sheet", + "Durium Chain", + "Taffeta Cloth", + "Urushi", + "Sealord Leather", + }, + }, + ["Hexed Tights"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Silver Thread", + "Red Grs. Thread", + "Shagreen", + "Ethereal Squama", + "Serica Cloth", + }, + }, + ["Hexed Wristbands"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Velvet Cloth", + "Marid Leather", + "Befouled Silver", + "Staghorn Coral", + "Sealord Leather", + }, + }, + ["Heyoka Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Yggdreant Root", + "Macuil Horn", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Heyoka Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Yggdreant Root", + "Macuil Horn", + "Macuil Horn", + "Faulpie Leather", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Heyoka Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Yggdreant Root", + "Macuil Horn", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Heyoka Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Yggdreant Root", + "Macuil Horn", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Heyoka Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Yggdreant Root", + "Macuil Horn", + "Faulpie Leather", + "Faulpie Leather", + "Faulpie Leather", + }, + }, + ["Hi-Ether"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Bat Wing", + "Bat Wing", + "Bat Wing", + "Bat Wing", + "Dryad Root", + "Distilled Water", + }, + }, + ["Hi-Ether 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dryad Root", + "Soulfl. Tentacle", + "Distilled Water", + }, + }, + ["Hi-Ether Drop"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Bat Wing", + "Bat Wing", + "Bat Wing", + "Bat Wing", + "Dryad Root", + "Distilled Water", + }, + }, + ["Hi-Ether Drop 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dryad Root", + "Soulfl. Tentacle", + "Distilled Water", + }, + }, + ["Hi-Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Malboro Vine", + "Distilled Water", + }, + }, + ["Hi-Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Ameretat Vine", + "Distilled Water", + }, + }, + ["Hi-Potion 3"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Rafflesia Vine", + "Distilled Water", + }, + }, + ["Hi-Potion 4"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 60", + }, + }, + ["Hi-Potion Drop"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Malboro Vine", + "Distilled Water", + }, + }, + ["Hi-Potion Drop 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Ameretat Vine", + "Distilled Water", + }, + }, + ["Hi-Potion Drop 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Rafflesia Vine", + "Distilled Water", + }, + }, + ["Hibari"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Lizard Skin", + }, + }, + ["Hickory Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Hickory Lumber", + "Hickory Lumber", + }, + }, + ["Hien"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Lizard Skin", + }, + }, + ["Hien 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 70", + }, + }, + ["High Breath Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Fragrant Ram Skin", + "Ram Mantle", + }, + }, + ["High Ebonite"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Sulfur", + "Flan Meat", + "Flan Meat", + "Flan Meat", + }, + }, + ["High Healing Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vivio Crab Shell", + "Carapace Harness", + }, + }, + ["High Mana Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mag. Silk Cloth", + "White Cloak", + }, + }, + ["High Mana Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "M. Ches. Lumber", + "Chestnut Wand", + }, + }, + ["Himantes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Raptor Skin", + "Lizard Cesti", + }, + }, + ["Himesama R. Ball"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rock Salt", + "Distilled Water", + "Buffalo Meat", + "Burdock", + }, + }, + ["Hiraishin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Silver Ingot", + }, + }, + ["Hirenjaku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Tama-Hagane", + "Elm Lumber", + "Ocl. Ingot", + "Ruby", + "Silk Thread", + "Manta Leather", + }, + }, + ["Holly Clogs"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Lumber", + "Sheep Leather", + }, + }, + ["Holly Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Log", + }, + }, + ["Holly Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Log", + "Holly Log", + "Holly Log", + "Bundling Twine", + }, + }, + ["Holly Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Lumber", + "Holly Lumber", + }, + }, + ["Holly Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Lumber", + "Sheep Tooth", + }, + }, + ["Holy Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Holy Bolt Heads", + }, + }, + ["Holy Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Holy Bolt Heads", + "Holy Bolt Heads", + "Holy Bolt Heads", + "Bundling Twine", + }, + }, + ["Holy Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Holy Water", + "Holy Water", + }, + }, + ["Holy Breastplate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Blsd. Mtl. Sheet", + "Blsd. Mtl. Sheet", + "Blsd. Mtl. Sheet", + "Silver Ingot", + "Sheep Leather", + "Ram Leather", + "Ram Leather", + }, + }, + ["Holy Degen"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Mythril Degen", + }, + }, + ["Holy Lance"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Holy Water", + "Mythril Lance", + }, + }, + ["Holy Leather"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Ram Leather", + "Holy Water", + }, + }, + ["Holy Mace"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Mythril Mace", + }, + }, + ["Holy Maul"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Maul", + }, + }, + ["Holy Shield"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mana Barrel", + "Holy Water", + "Holy Water", + "Hard Shield", + }, + }, + ["Holy Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Mythril Sword", + }, + }, + ["Holy Wand"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Mythic Wand", + }, + }, + ["Holy Water"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Distilled Water", + }, + }, + ["Holy Water 2"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Triturator", + "Distilled Water", + "Distilled Water", + "Distilled Water", + }, + }, + ["Homura"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Toad Oil", + "Firesand", + "Nodachi", + }, + }, + ["Honey"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + "Beehive Chip", + }, + }, + ["Honeyed Egg"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Honey", + "Bird Egg", + }, + }, + ["Hoplon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Chestnut Lumber", + "Walnut Lumber", + "Ram Leather", + }, + }, + ["Horn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Ram Horn", + }, + }, + ["Horn Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Bird Fletchings", + "Horn Arrowheads", + }, + }, + ["Horn Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Ram Horn", + }, + }, + ["Horn Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Ram Horn", + "Ram Horn", + "Ram Horn", + "Shagreen File", + }, + }, + ["Horn Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ram Horn", + }, + }, + ["Horn Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fish Scales", + "Ram Horn", + }, + }, + ["Horn Trophy"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Buffalo Horn", + "Buffalo Horn", + "Sheep Chammy", + "Teak Lumber", + }, + }, + ["Hornet Fleuret"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Pearl", + "Moonstone", + "Giant Femur", + "Giant Stinger", + }, + }, + ["Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Dhalmel Leather", + }, + }, + ["Hosodachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Ash Lumber", + "Silver Ingot", + "Silver Thread", + "Raptor Skin", + "Cermet Chunk", + }, + }, + ["Hume Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Hume Rod", + }, + }, + ["Hume Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Silver Thread", + }, + }, + ["Hume Fishing Rod 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 74", + }, + }, + ["Humidified Velvet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Wool Thread", + "Wool Thread", + "Earth Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Humidified Velvet 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Cotton Thread", + "Cotton Thread", + "Earth Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Humpty Dumpty"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silk Cloth", + "Rock Salt", + "Pine Nuts", + "Simsim", + "Asphodel", + "Kazham Pineapl.", + "Apkallu Egg", + "Burdock", + }, + }, + ["Humus"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Bay Leaves", + "Elm Log", + }, + }, + ["Hunter's Cotton"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Thread", + "Cotton Thread", + "Carap. Powder", + "Distilled Water", + }, + }, + ["Hunting Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Dhalmel Leather", + }, + }, + ["Hushed Baghnakhs"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Muting Potion", + "Slc. Baghnakhs", + }, + }, + ["Hushed Baghnakhs 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 20", + }, + }, + ["Hushed Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Muting Potion", + "Silence Dagger", + }, + }, + ["Hydra Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "Hydra Scale", + "Leather Trousers", + }, + }, + ["Hydra Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "Hydra Scale", + "Leather Gloves", + }, + }, + ["Hydra Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Titanictus Shell", + "Hydra Scale", + "Leather Highboots", + }, + }, + ["Hydra Kofte"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Imperial Rice", + "Imperial Flour", + "Wild Onion", + "Hydra Meat", + "Apkallu Egg", + }, + }, + ["Hydra Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Karakul Leather", + "Titanictus Shell", + "Titanictus Shell", + "Hydra Scale", + "Hydra Scale", + "Leather Vest", + }, + }, + ["Hydra Mask"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Karakul Leather", + "Titanictus Shell", + "Hydra Scale", + }, + }, + ["Hydro Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Greataxe", + }, + }, + ["Hydro Baghnakhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Brass Baghnakhs", + }, + }, + ["Hydro Chopper"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Heavy Dst. Axe", + }, + }, + ["Hydro Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Claws", + }, + }, + ["Hydro Cutter"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Heavy Axe", + }, + }, + ["Hydro Patas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Animal Glue", + "Bone Patas", + }, + }, + ["Hydro Pump"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Chestnut Lumber", + "Coeurl Whisker", + "Carbon Fiber", + "Animal Glue", + "Water Cluster", + }, + }, + ["Hyo"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Cotton Thread", + "Velvet Cloth", + }, + }, + ["Hyper Ether"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Treant Bulb", + "Taurus Wing", + "Taurus Wing", + "Moval. Water", + }, + }, + ["Hyper Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Hecteyes Eye", + "Flytrap Leaf", + "Moval. Water", + }, + }, + ["Ic Pilav"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Black Pepper", + "Rock Salt", + "Pine Nuts", + "Imperial Rice", + "Buburimu Grape", + "Distilled Water", + "Ziz Meat", + }, + }, + ["Icarus Wing"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Bird Fthr.", + "Giant Bird Fthr.", + "Giant Bird Fthr.", + "Giant Bird Fthr.", + "Giant Bird Fthr.", + "Giant Bird Fthr.", + "Beeswax", + "Beeswax", + }, + }, + ["Ice Anima"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Bitter Memory", + "Bitter Memory", + "Bitter Memory", + "Bitter Memory", + }, + }, + ["Ice Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Ice Arrowheads", + "Insect Fltchg.", + }, + }, + ["Ice Arrowheads"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Copper Ingot", + "Cermet Chunk", + }, + }, + ["Ice Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ice Ore", + }, + }, + ["Ice Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Ice Cluster", + }, + }, + ["Ice Fewell"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Slime Oil", + "Translucent Rock", + "Catalytic Oil", + }, + }, + ["Ice Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ice Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Ice Lance"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Rock Salt", + "Distilled Water", + "Cermet Lance", + }, + }, + ["Ice Maker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Super Cermet", + "Flan Meat", + "Karakul Wool", + "Snoll Arm", + "Mega Battery", + }, + }, + ["Ice Shield"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Rock Salt", + "Distilled Water", + "Diamond Shield", + }, + }, + ["Ice Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ice Bead", + }, + }, + ["Ice Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Frigid Skin", + "Raptor Trousers", + }, + }, + ["Icecap Rolanberry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Gelatin", + "Vanilla", + "Crawler Egg", + "Rolanberry", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Icecap Rolanberry 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Gelatin", + "Crawler Egg", + "Rolanberry", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Iga Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Gold Ingot", + "P. Brass Sheet", + "Mercury", + }, + }, + ["Igqira Huaraches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Whisker", + "Bugard Tusk", + "H.Q. Bugard Skin", + "Harajnite Shell", + }, + }, + ["Igqira Lappa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Coeurl Whisker", + "Manticore Lth.", + "Uragnite Shell", + "H.Q. Bugard Skin", + "M-bugard Tusk", + }, + }, + ["Igqira Manillas"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Garnet", + "Manticore Hair", + "Uragnite Shell", + "Bugard Tusk", + "Avatar Blood", + }, + }, + ["Igqira Tiara"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Garnet", + "Coral Fragment", + "Coral Fragment", + "Coral Fragment", + "Manticore Hair", + }, + }, + ["Igqira Weskit"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Lapis Lazuli", + "Dhalmel Leather", + "Dragon Talon", + "Manticore Hair", + "Bugard Tusk", + "Avatar Blood", + "H.Q. Bugard Skin", + }, + }, + ["Ikra Gunkan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rice Vinegar", + "Distilled Water", + "Salmon Roe", + }, + }, + ["Impaction Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Frayed Arrow", + }, + }, + ["Impaction Sphere 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Papyrus", + }, + }, + ["Impaction Sphere 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Star Spinel", + }, + }, + ["Impaction Sphere 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Test Answers", + }, + }, + ["Impaction Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lightning Card", + }, + }, + ["Imperial Cermet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + "Cermet Chunk", + "Silica", + }, + }, + ["Imperial Coffee"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Coffee Powder", + "Distilled Water", + }, + }, + ["Imperial Egg"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Platinum Ingot", + "Ocl. Ingot", + "Deathstone", + "Angelstone", + "Mercury", + "Pigeon's Blood", + "Bird Egg", + }, + }, + ["Imperial Silk Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Gold Thread", + "Wamoura Silk", + }, + }, + ["Imperial Tapestry"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Turquoise", + "Gold Thread", + "Gold Thread", + "Marid Hair", + "Wamoura Cloth", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + }, + }, + ["Imperial Wootz Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Khroma Ore", + "Khroma Ore", + "Wootz Ore", + }, + }, + ["Incombustible Wool"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Thread", + "Wool Thread", + "Fire Anima", + "Ice Anima", + "Dark Anima", + }, + }, + ["Indurated Gold Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Earth Anima", + "Earth Anima", + "Light Anima", + }, + }, + ["Induration Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Rusty Key", + }, + }, + ["Induration Sphere 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Giant Fish Bones", + }, + }, + ["Induration Sphere 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Exoray Mold", + }, + }, + ["Induration Sphere 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Ice Card", + }, + }, + ["Induration Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Teal Memosphere", + }, + }, + ["Inferno Axe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Firesand", + "Butterfly Axe", + }, + }, + ["Inferno Core"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Adaman Ore", + "Adaman Ore", + "Adaman Ore", + "Fire Anima", + "Fire Anima", + "Dark Anima", + }, + }, + ["Inferno Sabots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Namtar Bone", + "Ebony Sabots", + }, + }, + ["Inferno Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Firesand", + "Two-Hand. Sword", + }, + }, + ["Inhibitor"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Fire Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Inhibitor II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Fire Anima", + "Glass Sheet", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Insect Ball"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Millioncorn", + "Distilled Water", + "Little Worm", + }, + }, + ["Insect Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Insect Wing", + "Insect Wing", + }, + }, + ["Insect Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Insect Wing", + "Insect Wing", + "Insect Wing", + "Insect Wing", + "Insect Wing", + "Insect Wing", + "Zephyr Thread", + }, + }, + ["Insipid Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Apkallufa", + "Yorchete", + "Senroh Sardine", + }, + }, + ["Intelligence Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Coeurl Whisker", + "Olive Flower", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Invisible Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Calig. Wolf Hide", + "Wolf Mantle", + }, + }, + ["Invitriol"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Fire Anima", + "Water Anima", + "Dark Anima", + }, + }, + ["Ioskeha Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "P. Brass Chain", + "Wyrm Blood", + "Plovid Flesh", + }, + }, + ["Iqonde Crossbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Ebony Lumber", + "Carbon Fiber", + "Craklaw Pincer", + "Yggdreant Bole", + }, + }, + ["Irenic Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Behemoth Hide", + "Rockfin Fin", + }, + }, + ["Irmik Helvasi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Semolina", + "Pine Nuts", + "Selbina Milk", + "White Honey", + "White Honey", + "White Honey", + }, + }, + ["Iron Arrow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Ash Lumber", + "Chocobo Fthr.", + "Chocobo Fthr.", + }, + }, + ["Iron Arrow 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Iron Arrowheads", + "Chocobo Fltchg.", + }, + }, + ["Iron Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + }, + }, + ["Iron Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Smith. Kit 20", + }, + }, + ["Iron Bread"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Rock Salt", + "Distilled Water", + }, + }, + ["Iron Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Firesand", + }, + }, + ["Iron Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + }, + }, + ["Iron Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Mandrel", + }, + }, + ["Iron Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Iron Scales", + "Cotton Thread", + "Leather Trousers", + }, + }, + ["Iron Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Iron Scales", + "Cotton Thread", + "Leather Gloves", + }, + }, + ["Iron Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Iron Scales", + "Cotton Thread", + "Leather Highboots", + }, + }, + ["Iron Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Iron Ore", + }, + }, + ["Iron Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Nugget", + "Iron Nugget", + "Iron Nugget", + "Iron Nugget", + "Iron Nugget", + "Iron Nugget", + }, + }, + ["Iron Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Iron Sheet", + }, + }, + ["Iron Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Lizard Skin", + }, + }, + ["Iron Musketeer's Armet +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Irn.Msk. Armet", + }, + }, + ["Iron Musketeer's Cuirass +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Irn.Msk. Cuirass", + }, + }, + ["Iron Musketeer's Cuisses +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Irn.Msk. Cuisses", + }, + }, + ["Iron Musketeer's Gambison +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Irn.Msk. Gambison", + }, + }, + ["Iron Musketeer's Gauntlets +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Irn.Msk. Gauntlets", + }, + }, + ["Iron Musketeer's Sabatons +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Irn.Msk. Sabatons", + }, + }, + ["Iron Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Scales", + "Iron Scales", + "Iron Scales", + "Iron Scales", + "Cotton Thread", + "Sheep Leather", + "Leather Vest", + }, + }, + ["Iron Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Sheet", + }, + }, + ["Iron Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + }, + }, + ["Iron Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Workshop Anvil", + }, + }, + ["Iron Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Cotton Cloth", + "Lizard Skin", + }, + }, + ["Iron Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Lizard Skin", + }, + }, + ["Iron Visor"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Scales", + "Sheep Leather", + }, + }, + ["Iron-splitter"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Walnut Lumber", + "Walnut Lumber", + }, + }, + ["Isula Sideboard"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + }, + }, + ["Ivory Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Platinum Ingot", + "Ram Leather", + "Oversized Fang", + }, + }, + ["Iyo Scale"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + }, + }, + ["Izayoi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Im. Wootz Ingot", + "Elm Lumber", + "Rainbow Thread", + "Manta Leather", + "A.U. Brass Ingot", + }, + }, + ["Jacaranda Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Jacaranda Log", + }, + }, + ["Jacaranda Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Jacaranda Log", + "Jacaranda Log", + "Jacaranda Log", + }, + }, + ["Jack-o'-Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ogre Pumpkin", + "Beeswax", + }, + }, + ["Jack-o'-Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Ogre Pumpkin", + "Selbina Milk", + "Distilled Water", + "Apkallu Egg", + }, + }, + ["Jadagna"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tr. Brz. Ingot", + "Tr. Brz. Ingot", + "Jadagna -1", + }, + }, + ["Jadeite"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Garudite", + }, + }, + ["Jadeite Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Jadeite", + "Gold Ring", + }, + }, + ["Jadeite Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Jadeite", + "Gold Ring +1", + }, + }, + ["Jagdplaute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Platinum Ingot", + "Emerald", + "Ruby", + "Topaz", + "Mercury", + "Manticore Fang", + "Darksteel Sword", + }, + }, + ["Jamadhars"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Iron Sheet", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Jambiya"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mercury", + "Pigeon's Blood", + "Marid Tusk", + "Scintillant Ingot", + }, + }, + ["Januwiyah"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tr. Brz. Sheet", + "Tr. Brz. Sheet", + "Januwiyah -1", + }, + }, + ["Jaridah Bazubands"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Steel Sheet", + "Karakul Leather", + "Marid Hair", + "Mohbwa Cloth", + }, + }, + ["Jaridah Khud"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Marid Leather", + "Marid Hair", + "Karakul Cloth", + }, + }, + ["Jaridah Nails"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Karakul Leather", + "Marid Leather", + "Marid Hair", + "Karakul Cloth", + }, + }, + ["Jaridah Peti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Sheet", + "Darksteel Chain", + "Brass Chain", + "Velvet Cloth", + "Karakul Leather", + "Marid Leather", + "Karakul Cloth", + }, + }, + ["Jaridah Salvars"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Karakul Leather", + "Marid Hair", + "Karakul Cloth", + "Karakul Cloth", + }, + }, + ["Jester's Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Cloth", + "Silk Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Jester's Headband"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Linen Cloth", + "Dodge Headband", + }, + }, + ["Jet Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Mandibular Sickle", + }, + }, + ["Jeunoan Armoire"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Rosewood Lbr.", + "Ebony Lumber", + "Ebony Lumber", + "Gold Ingot", + }, + }, + ["Jeunoan Dresser"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Rosewood Lbr.", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Gold Ingot", + }, + }, + ["Jeweled Collar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Emerald", + "Ruby", + "Diamond", + "Topaz", + "Sapphire", + "Spinel", + "Torque", + }, + }, + ["Jeweled Collar 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 90", + }, + }, + ["Jindachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Raptor Skin", + }, + }, + ["Jinko"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Aquilaria Log", + }, + }, + ["Jolt Axe"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Bone Axe", + }, + }, + ["Jolt Counter"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonbow Stone", + "Ruthenium Ingot", + "Cross-Counters", + }, + }, + ["Juji Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Iron Sheet", + }, + }, + ["Junhanshi Habaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Rainbow Cloth", + "Rainbow Cloth", + "Manta Leather", + }, + }, + ["Junior Musketeer's Chakram +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Jr.Msk. Chakram", + }, + }, + ["Junior Musketeer's Tuck +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Jr.Msk. Tuck", + }, + }, + ["Junkenshi Habaki"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Manta Leather", + }, + }, + ["Junrenshi Habaki"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Manta Leather", + }, + }, + ["Jusatsu"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Black Ink", + "Beastman Blood", + "Bast Parchment", + }, + }, + ["Justaucorps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Beetle Shell", + "Scorpion Claw", + "Scorpion Claw", + "Wool Robe", + }, + }, + ["Kabenro"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beeswax", + "Water Lily", + "Water Lily", + }, + }, + ["Kabura Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Ram Horn", + "Kari. Arrowhd.", + "G. Bird Fltchg.", + }, + }, + ["Kabutowari"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Darksteel Ingot", + "Tama-Hagane", + "Elm Lumber", + "Cotton Thread", + "Raptor Skin", + }, + }, + ["Kaginawa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Grass Thread", + "Manticore Hair", + }, + }, + ["Kaman"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Elm Lumber", + "Silk Thread", + "Wool Cloth", + }, + }, + ["Kamayari"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Tama-Hagane", + "Oak Lumber", + "Silk Thread", + }, + }, + ["Kanesada +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Rosewood Lbr.", + "Silver Thread", + "Kanesada", + }, + }, + ["Kapor Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Kapor Log", + }, + }, + ["Kapor Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Kapor Log", + "Kapor Log", + "Kapor Log", + "Bundling Twine", + }, + }, + ["Karakul Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Thread", + "Karakul Thread", + "Karakul Thread", + }, + }, + ["Karakul Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Karakul Skin", + "Im. Tea Leaves", + "Distilled Water", + }, + }, + ["Karakul Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Karakul Skin", + "Karakul Skin", + "Karakul Skin", + "Tanning Vat", + "Im. Tea Leaves", + "Im. Tea Leaves", + "Im. Tea Leaves", + "Distilled Water", + }, + }, + ["Karakul Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Karakul Wool", + "Karakul Wool", + }, + }, + ["Karakul Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spindle", + "Karakul Wool", + "Karakul Wool", + "Karakul Wool", + "Karakul Wool", + "Karakul Wool", + "Karakul Wool", + }, + }, + ["Karakul Wool"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Karakul Skin", + "Karakul Skin", + }, + }, + ["Karimata Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Tama-Hagane", + }, + }, + ["Karni Yarik"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Wild Onion", + "Eggplant", + "Mithran Tomato", + "Karakul Meat", + }, + }, + ["Kaskara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Bugard Skin", + }, + }, + ["Katars"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Bronze Sheet", + "Ram Leather", + }, + }, + ["Katzbalger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Raptor Skin", + "Broadsword", + }, + }, + ["Katzbalger 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 80", + }, + }, + ["Kawahori-Ogi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Animal Glue", + "Bast Parchment", + }, + }, + ["Kazaridachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Ancient Lumber", + "Gold Ingot", + "Gold Thread", + "Wyvern Skin", + }, + }, + ["Keen Zaghnal"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Earth Cell", + "Brass Zaghnal", + }, + }, + ["Kendatsuba Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Cehuetzi Pelt", + "Defiant Sweat", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Kendatsuba Jinpachi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Cehuetzi Pelt", + "Defiant Sweat", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Kendatsuba Samue"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Cloth", + "Cehuetzi Pelt", + "Defiant Sweat", + "Defiant Sweat", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Kendatsuba Sune-Ate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Cehuetzi Pelt", + "Defiant Sweat", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Kendatsuba Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Cehuetzi Pelt", + "Defiant Sweat", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Kenpogi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Keppu"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Darksteel Sheet", + "Muketsu", + }, + }, + ["Keppu 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smith. Kit 76", + }, + }, + ["Kheten"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Darksteel Ingot", + "Rosewood Lbr.", + }, + }, + ["Khimaira Bonnet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Hippogryph Fthr.", + "Hippogryph Fthr.", + "Eft Skin", + "Buffalo Leather", + "Khimaira Mane", + "Khimaira Mane", + }, + }, + ["Khimaira Gamash."] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Hippogryph Fthr.", + "Buffalo Leather", + "Buffalo Leather", + "Khimaira Mane", + "Khimaira Mane", + }, + }, + ["Khimaira Jacket"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Lth.", + "Buffalo Horn", + "Eft Skin", + "Buffalo Leather", + "Buffalo Leather", + "Khimaira Mane", + "Khimaira Mane", + }, + }, + ["Khimaira Jambiya"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Gold Ingot", + "Mercury", + "Star Sapphire", + "Khimaira Horn", + }, + }, + ["Khimaira Kecks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Manticore Lth.", + "Buffalo Leather", + "Buffalo Leather", + "Khimaira Mane", + "Khimaira Mane", + "Khimaira Mane", + "Khimaira Mane", + }, + }, + ["Khimaira Wrist."] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Manticore Lth.", + "Buffalo Leather", + "Khimaira Mane", + "Khimaira Mane", + }, + }, + ["Khoma Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + }, + }, + ["Khromated Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Karakul Skin", + "Khroma Nugget", + "Distilled Water", + }, + }, + ["Khromated Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Karakul Skin", + "Karakul Skin", + "Karakul Skin", + "Tanning Vat", + "Khroma Nugget", + "Khroma Nugget", + "Khroma Nugget", + "Distilled Water", + }, + }, + ["Kilij"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Ebony Lumber", + "Gold Ingot", + "Gold Ingot", + "Lapis Lazuli", + "Turquoise", + "Marid Leather", + }, + }, + ["Killedar Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adamantoise Shell", + "Eltoro Leather", + "Gabbrath Horn", + }, + }, + ["Killer's Kilij"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Fiend Blood", + "Dragon Blood", + "Avatar Blood", + "Beast Blood", + "Chimera Blood", + "Ameretat Vine", + "Demon Blood", + "Adaman Kilij", + }, + }, + ["Kilo Battery"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tin Ingot", + "Silver Ingot", + "Cermet Chunk", + "Rock Salt", + "Lightning Cluster", + "Distilled Water", + }, + }, + ["Kilo Fan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Crab Shell", + "Giant Femur", + "Beeswax", + "Bat Wing", + "Wind Cluster", + }, + }, + ["Kilo Pump"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Chestnut Lumber", + "Coeurl Whisker", + "Coeurl Whisker", + "Carbon Fiber", + "Animal Glue", + "Water Cluster", + }, + }, + ["Kinesis Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ratatoskr Pelt", + "Nomad's Mantle", + }, + }, + ["Kinkobo"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ethrl. Vermilion", + "Primate Staff", + }, + }, + ["Kite Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Darksteel Sheet", + "Ash Lumber", + }, + }, + ["Kite Shield 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smith. Kit 50", + }, + }, + ["Kitron Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kitron", + }, + }, + ["Kitron Macaron"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Almond", + "Kitron", + "Lizard Egg", + }, + }, + ["Klouskap Sash"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Wool", + "Penelope's Cloth", + "Defiant Scarf", + }, + }, + ["Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Elm Lumber", + }, + }, + ["Knight's Beads"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Diamond Crystal", + "Moldy Necklace", + }, + }, + ["Knight's Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Ram Leather", + }, + }, + ["Kodachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Mythril Ingot", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Lizard Skin", + }, + }, + ["Kodoku"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Elshimo Frog", + "Lugworm", + "Shell Bug", + }, + }, + ["Kodoku 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Caedarva Frog", + "Lugworm", + "Shell Bug", + }, + }, + ["Koen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Toad Oil", + "Brimsand", + "Homura", + }, + }, + ["Koenig Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Gold Ingot", + "Gold Sheet", + "General's Shield", + }, + }, + ["Koenigs Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Chain", + "Manticore Lth.", + "Manticore Lth.", + }, + }, + ["Koenigs Belt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 90", + }, + }, + ["Koenigs Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Gold Ingot", + "Mercury", + "Diamond Knuckles", + }, + }, + ["Koga Shinobi-Gatana"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Plating", + "Dark Matter", + "Ruthenium Ore", + "Moldy Katana", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Tanzanite Crystal", + }, + }, + ["Kohlrouladen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Black Pepper", + "Rock Salt", + "La Theine Cbg.", + "Selbina Milk", + "Ruszor Meat", + }, + }, + ["Kongou Inaho"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Firesand", + "Copper Nugget", + }, + }, + ["Konigskuchen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Pie Dough", + "Maple Sugar", + "Selbina Milk", + "San d'Or. Grape", + "Yagudo Cherry", + "Distilled Water", + "Bird Egg", + }, + }, + ["Konron Hassen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bomb Ash", + "Firesand", + "Bast Parchment", + "Copper Nugget", + }, + }, + ["Kororito"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Shinobi-Gatana", + }, + }, + ["Kotatsu Table"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Rosewood Lbr.", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Bomb Arm", + }, + }, + ["Kote"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Iron Chain", + "Iron Chain", + "Silk Thread", + "Silk Cloth", + }, + }, + ["Kotetsu +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Rosewood Lbr.", + "Silver Thread", + "Kotetsu", + }, + }, + ["Kris"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Steel Ingot", + "Black Pearl", + }, + }, + ["Krousis Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Imperial Topaz", + "Platinum Ring", + }, + }, + ["Kujaku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Hirenjaku", + }, + }, + ["Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Chestnut Lumber", + "Lizard Skin", + }, + }, + ["Kunai"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Lizard Skin", + }, + }, + ["Kunitsuna"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dark Matter", + "Vulcanite Ore", + "Vulcanite Ore", + "Butachi", + }, + }, + ["Kunwu Iron"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ore", + "Kunwu Ore", + "Kunwu Ore", + "Kunwu Ore", + }, + }, + ["Kunwu Iron Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kunwu Iron", + }, + }, + ["Kusamochi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sticky Rice", + "Fresh Mugwort", + "Cornstarch", + "Distilled Water", + }, + }, + ["Kwahu Kachina Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sealord Leather", + "Ra'Kaznar Ingot", + "Macuil Plating", + }, + }, + ["Kyahan"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + "Sheep Leather", + }, + }, + ["Kyofu"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Iron Sheet", + "Shinobi-Gatana", + }, + }, + ["Kyoshu Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + "Lineadach", + }, + }, + ["Kyudogi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Kejusu Satin", + "Sailcloth", + "Sheep Chammy", + "Cerber. Leather", + "Scintillant Ingot", + "Yoichi's Sash", + "Imp. Silk Cloth", + }, + }, + ["Lacquer Tree Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lacquer Tree Log", + "Lacquer Tree Log", + "Lacquer Tree Log", + "Bundling Twine", + }, + }, + ["Lacquer Tree Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lacquer Tree Log", + }, + }, + ["Lacquer Tree Sap"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Lacquer Tree Log", + }, + }, + ["Lacryma Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Ram Leather", + "Urunday Lumber", + "Rockfin Tooth", + }, + }, + ["Lady Bell"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Lauan Lumber", + }, + }, + ["Ladybug Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ladybug Wing", + "Electrum Chain", + }, + }, + ["Ladybug Earring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 75", + }, + }, + ["Ladybug Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ladybug Wing", + "Ladybug Wing", + }, + }, + ["Ladybug Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 65", + }, + }, + ["Lamia Garland"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Brass Chain", + "Garnet", + "Manta Leather", + "Uragnite Shell", + "A.U. Brass Ingot", + }, + }, + ["Lamia Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manta Skin", + "Lamia Skin", + "Mohbwa Thread", + }, + }, + ["Lamian Kaman"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Scorpion Claw", + "Marid Tusk", + "Lamian Kaman -1", + }, + }, + ["Laminated Buffalo Leather"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Animal Glue", + "Buffalo Leather", + "Distilled Water", + }, + }, + ["Laminated Ram Leather"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Animal Glue", + "Distilled Water", + }, + }, + ["Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Ash Lumber", + "Ash Lumber", + }, + }, + ["Lancewood Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lancewood Log", + "Lancewood Log", + "Lancewood Log", + "Bundling Twine", + }, + }, + ["Lancewood Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lancewood Log", + }, + }, + ["Lanner Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Lancewood Lbr.", + "Karakul Cloth", + "Wyrdstrand", + }, + }, + ["Lapis Lazuli"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Blue Rock", + }, + }, + ["Lapis Lazuli 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Aqua Geode", + }, + }, + ["Lapis Lazuli Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Silver Earring", + }, + }, + ["Lapis Lazuli Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Silver Earring +1", + }, + }, + ["Lapis Lazuli Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Silver Ring", + }, + }, + ["Lapis Lazuli Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Silver Ring +1", + }, + }, + ["Lauan Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lauan Log", + }, + }, + ["Lauan Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lauan Log", + "Lauan Log", + "Lauan Log", + "Bundling Twine", + }, + }, + ["Lauan Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Lauan Lumber", + "Lauan Lumber", + }, + }, + ["Lavalier"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Manticore Hair", + }, + }, + ["Leather Bandana"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + }, + }, + ["Leather Bandana 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Leath. Kit 5", + }, + }, + ["Leather Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Chain", + "Sheep Leather", + }, + }, + ["Leather Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Leather Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Ram Leather", + }, + }, + ["Leather Highboots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Sheep Leather", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Leather Pot"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hickory Lumber", + "Karakul Leather", + "Rheiyoh Leather", + "Karakul Thread", + "Kaolin", + "Kaolin", + }, + }, + ["Leather Pouch"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Sheep Leather", + "Sheep Leather", + "Rabbit Hide", + }, + }, + ["Leather Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dhalmel Leather", + }, + }, + ["Leather Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Raptor Skin", + "Lauan Shield", + }, + }, + ["Leather Shield 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 50", + }, + }, + ["Leather Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Cloth", + "Grass Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Leather Vest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + "Sheep Leather", + "Lizard Skin", + }, + }, + ["Leather Vest 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 10", + }, + }, + ["Leathercraft Set 25"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + }, + }, + ["Leathercraft Set 45"] = { + ["crystal"] = "Fluid Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Beeswax", + "Leather Trousers", + }, + }, + ["Leathercraft Set 66"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Iron Scales", + "Ram Leather", + "Ram Leather", + "Tiger Leather", + }, + }, + ["Leathercraft Set 70"] = { + ["crystal"] = "Frost Crystal", + ["ingredients"] = { + "Wool Thread", + "Behemoth Hide", + }, + }, + ["Leathercraft Set 75"] = { + ["crystal"] = "Frost Crystal", + ["ingredients"] = { + "Wool Thread", + "Tiger Hide", + }, + }, + ["Leathercraft Set 79"] = { + ["crystal"] = "Frost Crystal", + ["ingredients"] = { + "Marid Hide", + "Karakul Thread", + }, + }, + ["Leathercraft Set 85"] = { + ["crystal"] = "Frost Crystal", + ["ingredients"] = { + "Wool Thread", + "Coeurl Hide", + }, + }, + ["Leathercraft Set 90"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Gold Chain", + "Manticore Lth.", + "Manticore Lth.", + }, + }, + ["Leathercraft Set 95"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Grass Thread", + "Peiste Skin", + "Peiste Skin", + }, + }, + ["Lebkuchen House"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Baking Soda", + "Honey", + "Cinna-cookie", + "Bbl. Chocolate", + "Distilled Water", + "Bird Egg", + }, + }, + ["Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Leo Crossbow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Lion Crossbow", + }, + }, + ["Lethe Consomme"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Eastern Ginger", + "San d'Or. Carrot", + "Tiger Cod", + "Distilled Water", + }, + }, + ["Lethe Potage"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Popoto", + "Eastern Ginger", + "Bastore Bream", + "Distilled Water", + }, + }, + ["Leucous Voulge"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Holly Lumber", + "Platinum Ingot", + "Cermet Chunk", + "Super Cermet", + "Super Cermet", + }, + }, + ["Light Anima"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Radiant Memory", + "Radiant Memory", + "Radiant Memory", + "Radiant Memory", + }, + }, + ["Light Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Light Ore", + }, + }, + ["Light Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Light Cluster", + }, + }, + ["Light Crossbow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Ash Lumber", + "Grass Thread", + }, + }, + ["Light Fewell"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Slime Oil", + "White Rock", + "Catalytic Oil", + }, + }, + ["Light Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Light Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Light Opal"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "White Rock", + }, + }, + ["Light Opal 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Light Geode", + }, + }, + ["Light Ram Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ram Skin", + "Ice Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Light Ram Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ram Skin", + "Ice Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Light Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Bead", + "Orichalcum Ring", + }, + }, + ["Light Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Light Bead", + }, + }, + ["Light Steel Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Ice Anima", + "Lightning Anima", + "Light Anima", + }, + }, + ["Lightning Anima"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Strtlng. Memory", + "Strtlng. Memory", + "Strtlng. Memory", + "Strtlng. Memory", + }, + }, + ["Lightning Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Ltng. Arrowhd.", + "Insect Fltchg.", + }, + }, + ["Lightning Arrow 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Steel Ingot", + "Maple Lumber", + "Insect Wing", + "Insect Wing", + }, + }, + ["Lightning Arrowheads"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Copper Ingot", + "Steel Ingot", + }, + }, + ["Lightning Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lightning Ore", + }, + }, + ["Lightning Bow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ose Whisker", + "Kaman", + }, + }, + ["Lightning Fewell"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Slime Oil", + "Purple Rock", + "Catalytic Oil", + }, + }, + ["Lightning Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lightning Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Lightweight Steel Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Light Steel", + }, + }, + ["Lightweight Steel Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Light Steel", + "Light Steel", + "Light Steel", + "Light Steel", + "Light Steel", + "Light Steel", + "Workshop Anvil", + }, + }, + ["Lik Kabob"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lik", + "Bomb Arm", + }, + }, + ["Lilac Corsage"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silk Cloth", + "Spider Web", + "Lilac", + "Twinthread", + }, + }, + ["Linen Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Thread", + "Linen Thread", + }, + }, + ["Linen Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sardonyx", + "Sardonyx", + "Linen Thread", + "Cotton Cloth", + "Linen Cloth", + }, + }, + ["Linen Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Linen Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Cotton Cloth", + "Linen Cloth", + "Saruta Cotton", + }, + }, + ["Linen Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Linen Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Linen Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Linen Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Flax Flower", + "Flax Flower", + }, + }, + ["Linen Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Flax Flower", + "Flax Flower", + "Flax Flower", + "Flax Flower", + "Flax Flower", + "Flax Flower", + "Spindle", + }, + }, + ["Liquefaction Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Bomb Coal", + }, + }, + ["Liquefaction Sphere 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Ancient Salt", + }, + }, + ["Liquefaction Sphere 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Fetich Arms", + }, + }, + ["Liquefaction Sphere 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Rusty Medal", + }, + }, + ["Liquefaction Sphere 5"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Fire Card", + }, + }, + ["Lithic Wyvern Scale"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wyvern Scales", + "Earth Anima", + "Earth Anima", + "Light Anima", + }, + }, + ["Little Comet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Rain Lily", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Little Worm Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Animal Glue", + "Leather Pouch", + "Worm Mulch", + }, + }, + ["Living Key"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Slime Oil", + "Beeswax", + "Malboro Vine", + }, + }, + ["Living Key 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Slime Oil", + "Beeswax", + "Rafflesia Vine", + }, + }, + ["Lizard Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Chain", + "Lizard Skin", + }, + }, + ["Lizard Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Cesti", + }, + }, + ["Lizard Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Leather Gloves", + }, + }, + ["Lizard Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Lizard Helm 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 15", + }, + }, + ["Lizard Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Lizard Skin", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Lizard Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Lizard Skin", + "Leather Highboots", + }, + }, + ["Lizard Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Lizard Skin", + "Lizard Molt", + }, + }, + ["Lizard Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Lizard Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Lizard Skin", + "Leather Trousers", + }, + }, + ["Loach Slop"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Soy Stock", + "2Leaf Mandra Bud", + "Distilled Water", + "Bird Egg", + "Cibol", + "Brass Loach", + "Brass Loach", + "Burdock", + }, + }, + ["Longbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Yew Lumber", + "Linen Cloth", + "Ram Horn", + "Coeurl Whisker", + }, + }, + ["Longsword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Dhalmel Leather", + }, + }, + ["Lord's Armet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Mercury", + "Darksteel Armet", + }, + }, + ["Lord's Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Platinum Ingot", + "Gold Sheet", + "Gold Sheet", + "Gold Sheet", + "Mercury", + "Darksteel Cuirass", + }, + }, + ["Lord's Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Mercury", + "Darksteel Cuisses", + }, + }, + ["Lord's Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Mercury", + "Dst. Gauntlets", + }, + }, + ["Lord's Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Dst. Sabatons", + }, + }, + ["Loudspeaker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Treant Bulb", + "Glass Sheet", + "Baking Soda", + "Colibri Beak", + "Water Tank", + }, + }, + ["Loudspeaker II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Glass Sheet", + "Baking Soda", + "Baking Soda", + "Colibri Beak", + "Water Tank", + }, + }, + ["Loudspeaker III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Baking Soda", + "Baking Soda", + "Colibri Beak", + "F. Glass Sheet", + "Water Tank", + }, + }, + ["Loudspeaker IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Baking Soda", + "Colibri Beak", + "F. Glass Sheet", + "F. Glass Sheet", + "Water Tank", + }, + }, + ["Loudspeaker V"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Baking Soda", + "Colibri Beak", + "F. Glass Sheet", + "F. Glass Sheet", + "F. Glass Sheet", + "Water Tank", + }, + }, + ["Love Chocolate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Parchment", + "Heart Chocolate", + "Scarlet Ribbon", + }, + }, + ["Lu Shang's Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Lu Rod", + }, + }, + ["Lucent Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lucent Steel", + "Heavy Axe", + }, + }, + ["Lucent Iron Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Earth Anima", + "Lightning Anima", + "Light Anima", + }, + }, + ["Lucent Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lucent Steel", + "Lance", + }, + }, + ["Lucent Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lucent Iron", + "Scythe", + }, + }, + ["Lucent Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lucent Iron", + "Two-Hand. Sword", + }, + }, + ["Lucky Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Bladefish", + "Buffalo Meat", + }, + }, + ["Lucky Carrot Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Carrot", + "San d'Or. Carrot", + "San d'Or. Carrot", + "Snoll Arm", + }, + }, + ["Lugworm Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Animal Glue", + "Leather Pouch", + "Lugworm Sand", + }, + }, + ["Luminous Core"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Adaman Ore", + "Adaman Ore", + "Adaman Ore", + "Lightning Anima", + "Lightning Anima", + "Dark Anima", + }, + }, + ["Luminous Shell"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Crab Shell", + "Lightning Anima", + "Lightning Anima", + "Dark Anima", + }, + }, + ["Luxurious Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Gold Ingot", + }, + }, + ["Lynx Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Lynx Hide", + "Distilled Water", + }, + }, + ["Lynx Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Lynx Hide", + "Distilled Water", + }, + }, + ["Lynx Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Lynx Hide", + "Lynx Hide", + "Lynx Hide", + "Distilled Water", + }, + }, + ["Lynx Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Lynx Hide", + }, + }, + ["Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + }, + }, + ["Mache Earring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Shadow Gem", + "Hepatizon Ingot", + "Tartarian Soul", + }, + }, + ["Macuahuitl"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bugard Tusk", + "Rhinochimera", + "Macuahuitl -1", + }, + }, + ["Magic Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ram Leather", + "Mercury", + "Toad Oil", + }, + }, + ["Magical Cotton Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Thread", + "Cotton Thread", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Magical Linen Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Thread", + "Linen Thread", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Magical Silk Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Magma Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Bay Leaves", + "Black Pepper", + "Millioncorn", + "Olive Oil", + "Rock Salt", + "San d'Or. Carrot", + "Gabbrath Meat", + }, + }, + ["Magniplug"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Fire Bead", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Magniplug II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Fire Bead", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Maharaja's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "G. Bgd. Leather", + "Koenigs Belt", + }, + }, + ["Mahogany Bed"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + }, + }, + ["Mahogany Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Log", + }, + }, + ["Mahogany Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Log", + "Mahogany Log", + "Mahogany Log", + "Bundling Twine", + }, + }, + ["Mahogany Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + }, + }, + ["Mahogany Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Mahogany Lbr.", + "Mahogany Lbr.", + }, + }, + ["Mahogany Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Turquoise", + "Demon Horn", + }, + }, + ["Mailbreaker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Bilbo", + }, + }, + ["Makibishi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Scales", + }, + }, + ["Malayo Crossbow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Durium Ingot", + "Giant Femur", + "Carbon Fiber", + "Urunday Lumber", + "Urunday Lumber", + }, + }, + ["Malfeasance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Damascus Ingot", + "Ormolu Ingot", + "Squamous Hide", + "Yggdreant Bole", + "Hades' Claw", + "Tartarian Soul", + }, + }, + ["Malison Medallion"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Heliodor", + "Neutralizing Slv.", + "Holy Water", + "Holy Water", + "Hallowed Water", + }, + }, + ["Maliya Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Urunday Lumber", + "Raaz Leather", + "Maliya. Coral", + "Ra'Kaznar Ingot", + }, + }, + ["Maliyakaleya Orb"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Maliya. Coral", + }, + }, + ["Mammoth Tusk"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Frozen Head", + }, + }, + ["Mamool Ja Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Karakul Leather", + "Blk. Tiger Fang", + "Blk. Tiger Fang", + "Coeurl Whisker", + "Mamool Ja Helmet", + "Marid Hair", + }, + }, + ["Mamushito"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Shinobi-Gatana", + }, + }, + ["Mana Booster"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mercury", + "Fiend Blood", + "Brass Tank", + "Imperial Cermet", + "Mana Wand", + }, + }, + ["Mana Chestnut Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Log", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Mana Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mag. Lin. Cloth", + "Cloak", + }, + }, + ["Mana Conserver"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Dark Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Mana Converter"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mercury", + "Coeurl Whisker", + "Brass Tank", + "Brass Tank", + "Imperial Cermet", + }, + }, + ["Mana Jammer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Artificial Lens", + "Sieglinde Putty", + "Water Tank", + "Hydro Pump", + }, + }, + ["Mana Jammer II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Artificial Lens", + "Sieglinde Putty", + "Water Tank", + "Kilo Fan", + }, + }, + ["Mana Jammer III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Artificial Lens", + "Sieglinde Putty", + "Water Tank", + "Mega Pump", + }, + }, + ["Mana Jammer IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Artificial Lens", + "Sieglinde Putty", + "Water Tank", + "Kilo Pump", + "Mega Pump", + }, + }, + ["Mana Tank"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sieglinde Putty", + "Glass Sheet", + "Hi-Ether Tank", + "Hydro Pump", + }, + }, + ["Mana Tank II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sieglinde Putty", + "Glass Sheet", + "Hi-Ether Tank", + "Kilo Pump", + }, + }, + ["Mana Tank III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sieglinde Putty", + "Glass Sheet", + "Hi-Ether Tank", + "Mega Pump", + }, + }, + ["Mana Tank IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sieglinde Putty", + "Glass Sheet", + "Hi-Ether Tank", + "Kilo Pump", + "Mega Pump", + }, + }, + ["Mana Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mag. Ctn. Cloth", + "Tunic", + }, + }, + ["Mana Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "M. Will. Lumber", + "Willow Wand", + }, + }, + ["Mana Willow Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Log", + "Earth Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Manashell Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wailing Shell", + "Shell Ring", + }, + }, + ["Mandarin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Saruta Orange", + "Saruta Orange", + "Saruta Orange", + "Saruta Orange", + }, + }, + ["Mandibular Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ebony Lumber", + "Ram Leather", + "Antlion Jaw", + }, + }, + ["Manji Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Darksteel Sheet", + }, + }, + ["Manoples"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Darksteel Sheet", + "Carbon Fiber", + "Scintillant Ingot", + }, + }, + ["Manta Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Manta Skin", + "Distilled Water", + }, + }, + ["Manta Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Manta Skin", + "Distilled Water", + }, + }, + ["Manta Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Manta Skin", + "Manta Skin", + "Manta Skin", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Manticore Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Manticore Hide", + "Distilled Water", + }, + }, + ["Manticore Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Manticore Hide", + "Distilled Water", + }, + }, + ["Manticore Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Manticore Hide", + "Manticore Hide", + "Manticore Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Mantid Arrowhd."] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Mantid Foreleg", + }, + }, + ["Mantid Arrowhd. 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Mantid Foreleg", + "Mantid Foreleg", + "Mantid Foreleg", + }, + }, + ["Maple Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Maple Sugar", + "Olive Oil", + "Selbina Milk", + "Bird Egg", + "Apkallu Egg", + }, + }, + ["Maple Harp"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Maple Lumber", + "Maple Lumber", + "Coeurl Whisker", + }, + }, + ["Maple Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Log", + }, + }, + ["Maple Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Log", + "Maple Log", + "Maple Log", + "Bundling Twine", + }, + }, + ["Maple Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Maple Lumber", + "Maple Lumber", + }, + }, + ["Maple Sugar"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Maple Log", + }, + }, + ["Maple Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Maple Lumber", + "Maple Lumber", + "Maple Lumber", + "Maple Lumber", + }, + }, + ["Maple Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Maple Lumber", + "Chocobo Fthr.", + }, + }, + ["Marath Baghnakhs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mlbd. Sheet", + "Marid Leather", + "Gargouille Horn", + "Gargouille Horn", + "Gargouille Horn", + }, + }, + ["Marble Bed"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Aquamarine", + "Rainbow Thread", + "Silk Cloth", + "Sarcenet Cloth", + "Marble", + "Marble", + }, + }, + ["Marble Plaque"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Amethyst", + "Black Ink", + "Bast Parchment", + "Marble", + }, + }, + ["Marbled Drawers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Dhalmel Hide", + "Buffalo Leather", + }, + }, + ["Marbled Drawers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Gold Ingot", + "Tufa", + "Tufa", + "Tufa", + }, + }, + ["Margherita Pizza"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Holy Basil", + "Pizza Dough", + "Mithran Tomato", + "Pomodoro Sauce", + "Chalaimbille", + }, + }, + ["Margherita Slice"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Holy Basil", + "Pizza Dough", + "Mithran Tomato", + "Pomodoro Sauce", + "Chalaimbille", + "Pizza Cutter", + }, + }, + ["Marid Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Apkal. Fletching", + "M. Tusk Arwhds.", + }, + }, + ["Marid Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Marid Leather", + "Marid Leather", + }, + }, + ["Marid Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Marid Hide", + "Im. Tea Leaves", + "Distilled Water", + }, + }, + ["Marid Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Tanning Vat", + "Marid Hide", + "Marid Hide", + "Marid Hide", + "Im. Tea Leaves", + "Im. Tea Leaves", + "Im. Tea Leaves", + "Distilled Water", + }, + }, + ["Marid Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Karakul Leather", + "Merrow Scale", + "Marid Tusk", + }, + }, + ["Marid Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Marid Hide", + "Karakul Thread", + }, + }, + ["Marid Mantle 2"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Leath. Kit 79", + }, + }, + ["Marid Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Merrow Scale", + "Marid Tusk", + }, + }, + ["Marid Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Marid Tusk", + "Marid Tusk", + }, + }, + ["Marid Tusk Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Marid Tusk", + }, + }, + ["Marid Tusk Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Marid Tusk", + "Marid Tusk", + "Marid Tusk", + }, + }, + ["Marinara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Spaghetti", + "Jacknife", + "Pomodoro Sauce", + "Kalamar", + "Bastore Sweeper", + }, + }, + ["Marinara Pizza"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Dried Marjoram", + "Holy Basil", + "Pizza Dough", + "Chalaimbille", + "Marinara Sauce", + }, + }, + ["Marinara Sauce"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Mithran Tomato", + "Anchovy", + }, + }, + ["Marinara Slice"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Dried Marjoram", + "Holy Basil", + "Pizza Dough", + "Chalaimbille", + "Marinara Sauce", + "Pizza Cutter", + }, + }, + ["Marine Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fish Stock", + "Distilled Water", + "Mola Mola", + "Cotton Tofu", + "Cibol", + "Shungiku", + "Shirataki", + "Agaricus", + }, + }, + ["Maringna"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Olive Oil", + "Imperial Flour", + "Bird Egg", + "Vongola Clam", + "Bastore Sweeper", + "Gigant Octopus", + "Uleguerand Milk", + }, + }, + ["Marksman's Oil"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Goblin Grease", + "Slime Juice", + }, + }, + ["Marron Glace"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Maple Sugar", + "Chestnut", + "Chestnut", + "Baking Soda", + "Grape Juice", + }, + }, + ["Marron Glace 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Maple Sugar", + "Chestnut", + "Chestnut", + "Grape Juice", + }, + }, + ["Mars's Hexagun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Silver Ingot", + }, + }, + ["Master's Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Jujitsu Gi", + }, + }, + ["Master's Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Linen Cloth", + "Jujitsu Sitabaki", + }, + }, + ["Matamata Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Craklaw Pincer", + "Matamata Shell", + }, + }, + ["Matchlock Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Darksteel Ingot", + "Walnut Lumber", + }, + }, + ["Matka"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Karugo Clay", + "Karugo Clay", + "Karugo Clay", + "Karugo Clay", + "Karugo Clay", + }, + }, + ["Maul"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Oak Lumber", + }, + }, + ["Max-Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Sage", + "Sage", + "Dragon Blood", + "Reishi Mushroom", + "Distilled Water", + }, + }, + ["Max-Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 95", + }, + }, + ["Mdr. Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Midrium Ingot", + }, + }, + ["Meat Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "Dhalmel Meat", + "G. Sheep Meat", + }, + }, + ["Meat Jerky"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Rock Salt", + "G. Sheep Meat", + }, + }, + ["Meat Mithkabob"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Wild Onion", + "Ziz Meat", + }, + }, + ["Meat Mithkabob 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Wild Onion", + "Cockatrice Meat", + }, + }, + ["Meatball"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Hare Meat", + "Distilled Water", + }, + }, + ["Meatball 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cook. Kit 35", + }, + }, + ["Meatloaf"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Blue Peas", + "Black Pepper", + "Rock Salt", + "Wild Onion", + "Bird Egg", + "Buffalo Meat", + "Lynx Meat", + }, + }, + ["Mega Battery"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Iron Ingot", + "Silver Ingot", + "Cermet Chunk", + "Rock Salt", + "Lightning Cluster", + "Distilled Water", + }, + }, + ["Mega Fan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Giant Femur", + "Scorpion Shell", + "Beeswax", + "Bat Wing", + "Wind Cluster", + }, + }, + ["Mega Pump"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Chestnut Lumber", + "Coeurl Whisker", + "Coeurl Whisker", + "Coeurl Whisker", + "Carbon Fiber", + "Animal Glue", + "Water Cluster", + }, + }, + ["Meifu Goma"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Cotton Thread", + "Firesand", + "Koma", + }, + }, + ["Melanzane"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Spaghetti", + "Wild Onion", + "Eggplant", + "Pomodoro Sauce", + }, + }, + ["Melee Fists"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hades' Claw", + "Dark Matter", + "S. Faulpie Leather", + "Moldy Weapon", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Sapphire Crystal", + }, + }, + ["Mellow Bird Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Dhalmel Meat", + "Lizard Egg", + "Cockatrice Meat", + "Bird Egg", + }, + }, + ["Melody Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Southern Pearl", + "Mythril Earring", + }, + }, + ["Melon Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Thundermelon", + "Watermelon", + }, + }, + ["Melon Juice 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cook. Kit 40", + }, + }, + ["Melon Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Lizard Egg", + "Thundermelon", + }, + }, + ["Melon Pie 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Thundermelon", + "Bird Egg", + }, + }, + ["Melt Baselard"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Baselard", + }, + }, + ["Melt Baselard 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 65", + }, + }, + ["Melt Claws"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Claws", + }, + }, + ["Melt Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Dagger", + }, + }, + ["Melt Katana"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Yoto", + }, + }, + ["Melt Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Knife", + }, + }, + ["Melt Kukri"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Invitriol", + "Acid Kukri", + }, + }, + ["Menemen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Selbina Butter", + "Black Pepper", + "Rock Salt", + "Wild Onion", + "Mithran Tomato", + "Puk Egg", + "Puk Egg", + }, + }, + ["Menemen 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 45", + }, + }, + ["Menetrier's Alb"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Sheep Leather", + "Ram Leather", + "Beeswax", + "Red Grs. Thread", + "Red Grass Cloth", + "Red Grass Cloth", + "Yel. Brass Chain", + }, + }, + ["Mensa Lunata"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Rosewood Lbr.", + "Gold Ingot", + }, + }, + ["Mensch Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cerberus Hide", + "Yggdreant Root", + }, + }, + ["Mercury"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Cobalt Jellyfish", + "Cobalt Jellyfish", + "Cobalt Jellyfish", + "Cobalt Jellyfish", + }, + }, + ["Mercury 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Denizanasi", + "Denizanasi", + "Denizanasi", + "Denizanasi", + }, + }, + ["Messhikimaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bewitched Gold", + "Kazaridachi", + }, + }, + ["Metal Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Sheet", + "Holly Lumber", + }, + }, + ["Mettle Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + "Bone Chip", + "Samwell's Shank", + }, + }, + ["Mezraq"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Im. Wootz Ingot", + "Im. Wootz Ingot", + "Bldwd. Lumber", + "Platinum Ingot", + "Wamoura Silk", + }, + }, + ["Midare"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Mythril Ingot", + "Ash Lumber", + "Cotton Thread", + "Lizard Skin", + "T. Tama-hagane", + }, + }, + ["Midrium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Midrium Ore", + "Midrium Ore", + "Midrium Ore", + "Midrium Ore", + }, + }, + ["Midrium Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Midrium Ingot", + }, + }, + ["Midrium Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + }, + }, + ["Mighty Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mighty Sardonyx", + "Sardonyx Ring", + }, + }, + ["Mighty Sardonyx"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sardonyx", + "Fire Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Mikazuki"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Lizard Skin", + }, + }, + ["Mille Feuille"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pie Dough", + "Pie Dough", + "Pie Dough", + "Maple Sugar", + "Rolanberry", + "Selbina Milk", + "Distilled Water", + "Bird Egg", + }, + }, + ["Millionaire Desk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Ancient Lumber", + "Gold Sheet", + "Gold Sheet", + "Platinum Sheet", + "Granite", + "Granite", + "Granite", + }, + }, + ["Mind Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bat Wing", + "Casablanca", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Minnow"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Glass Fiber", + }, + }, + ["Minnow 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Glass Fiber", + }, + }, + ["Mirage Stole"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Bole", + "Dark Matter", + "Cypress Log", + "Lapis Lazuli Crystal", + "Moldy Stole", + }, + }, + ["Mirage Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Dark Matter", + "Khoma Thread", + "Moldy Sword", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Lapis Lazuli Crystal", + }, + }, + ["Misericorde"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Adaman Ingot", + }, + }, + ["Miso Ramen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Millioncorn", + "Rarab Tail", + "Porxie Pork", + "Ramen Noodles", + "Miso Ramen Soup", + "Bamboo Shoots", + }, + }, + ["Miso Ramen Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Ginger", + "Chicken Bone", + "Chicken Bone", + "Distilled Water", + "Miso", + "Soy Sauce", + }, + }, + ["Miso Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adoulinian Kelp", + "Cotton Tofu", + "Cibol", + "Miso", + "Dried Bonito", + }, + }, + ["Mist Crown"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smth. Btl. Jaw", + "Garish Crown", + }, + }, + ["Mist Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smooth Velvet", + "Garish Mitts", + }, + }, + ["Mist Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smth. Shp. Lth.", + "Garish Pumps", + }, + }, + ["Mist Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smooth Velvet", + "Garish Slacks", + }, + }, + ["Mist Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smooth Velvet", + "Garish Tunic", + }, + }, + ["Mithran Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Mithran Rod", + }, + }, + ["Mithran Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rattan Lumber", + "Rainbow Thread", + }, + }, + ["Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Cotton Cloth", + "Saruta Cotton", + }, + }, + ["Mixed Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chapuli Wing", + "Porxie Wing", + }, + }, + ["Mixed Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Chapuli Wing", + "Chapuli Wing", + "Chapuli Wing", + "Porxie Wing", + "Porxie Wing", + "Porxie Wing", + }, + }, + ["Mizu-Deppo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Chestnut Lumber", + "Distilled Water", + }, + }, + ["Moblin Putty"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Flaxseed Oil", + "Flaxseed Oil", + "Shell Powder", + "Shell Powder", + "Zincite", + "Zincite", + "Zincite", + }, + }, + ["Moblin Sheep Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Moblin Sheepskin", + "Distilled Water", + }, + }, + ["Moblin Sheep Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Moblin Sheepskin", + "Distilled Water", + }, + }, + ["Moblin Sheep Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Moblin Sheepskin", + "Moblin Sheepskin", + "Moblin Sheepskin", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Moblin Sheep Wool"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Moblin Sheepskin", + "Moblin Sheepskin", + }, + }, + ["Moblinweave"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moblin Thread", + "Moblin Thread", + "Moblin Thread", + }, + }, + ["Moblumin Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Moblumin Ingot", + }, + }, + ["Moccasins"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Dhalmel Leather", + "Raptor Skin", + }, + }, + ["Mohbwa Sash"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Red Grs. Thread", + "Red Grass Cloth", + "Mohbwa Cloth", + }, + }, + ["Mohbwa Scarf"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Mohbwa Cloth", + "Mohbwa Thread", + }, + }, + ["Mohbwa Scarf 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 55", + }, + }, + ["Mohbwa Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Mohbwa Grass", + "Mohbwa Grass", + }, + }, + ["Mohbwa Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spindle", + "Mohbwa Grass", + "Mohbwa Grass", + "Mohbwa Grass", + "Mohbwa Grass", + "Mohbwa Grass", + "Mohbwa Grass", + }, + }, + ["Mokujin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Silk Cloth", + "Feyweald Log", + }, + }, + ["Mokuto"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Silencing Potion", + "Shinobi-Gatana", + }, + }, + ["Mole Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Snapping Mole", + "Snapping Mole", + "Helmet Mole", + "Loam", + "Lugworm", + "Shell Bug", + }, + }, + ["Mole Broth 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Snapping Mole", + "Snapping Mole", + "Helmet Mole", + "Loam", + "Little Worm", + "Shell Bug", + }, + }, + ["Molybdenum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Iron Ore", + "Iron Ore", + "Molybdenum Ore", + }, + }, + ["Molybdenum Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Molybden. Ingot", + }, + }, + ["Monk's Nodowa"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Root", + "Dark Matter", + "Azure Leaf", + "Sapphire Crystal", + "Moldy Nodowa", + }, + }, + ["Monster Axe"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Plating", + "Dark Matter", + "S. Faulpie Leather", + "Moldy Axe", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Topaz Crystal", + }, + }, + ["Mont Blanc"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Chestnut", + "Chestnut", + "Selbina Milk", + "Distilled Water", + "Bird Egg", + }, + }, + ["Montagna"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Maple Sugar", + "Olive Oil", + "Habaneros", + "Imperial Flour", + "Bird Egg", + "Buffalo Meat", + "Burdock", + }, + }, + ["Moon Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonstone", + "Gold Earring", + }, + }, + ["Moon Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 65", + }, + }, + ["Moon Earring 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonstone", + "Gold Earring +1", + }, + }, + ["Moon Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonstone", + "Gold Ring", + }, + }, + ["Moon Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonstone", + "Gold Ring +1", + }, + }, + ["Moonbeam Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Cloth", + "Moonlight Coral", + "S. Faulpie Leather", + }, + }, + ["Moonbeam Necklace"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Stone", + "Moonlight Coral", + "Khoma Thread", + }, + }, + ["Moonbeam Nodowa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonlight Coral", + "Khoma Thread", + }, + }, + ["Moonbeam Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Moonbow Stone", + "Moonlight Coral", + "Cyan Coral", + }, + }, + ["Moonbow Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Steel", + "Moonbow Cloth", + "Moonbow Leather", + "Comaa Belt", + }, + }, + ["Moonbow Whistle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Urushi", + "Moonbow Stone", + "Brioso Whistle", + }, + }, + ["Moonring Blade"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Tama-Hagane", + "Gold Ingot", + "Mercury", + }, + }, + ["Moonstone"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Carbite", + }, + }, + ["Morion Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Earring", + "Morion Tathlum", + }, + }, + ["Mousai Crackows"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Thrd.", + "Yggdreant Bole", + "Plovid Effluvium", + "Hades' Claw", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Mousai Gages"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Thrd.", + "Cashmere Thrd.", + "Yggdreant Bole", + "Plovid Effluvium", + "Hades' Claw", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Mousai Manteel"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Cloth", + "Yggdreant Bole", + "Plovid Effluvium", + "Plovid Effluvium", + "Hades' Claw", + "Cyan Orb", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Mousai Seraweels"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Cloth", + "Yggdreant Bole", + "Plovid Effluvium", + "Hades' Claw", + "Cyan Orb", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Mousai Turban"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cashmere Cloth", + "Yggdreant Bole", + "Plovid Effluvium", + "Hades' Claw", + "Cyan Orb", + "Cyan Orb", + }, + }, + ["Movalpolos Water"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Carbon Dioxide", + "Distilled Water", + "Distilled Water", + "Distilled Water", + "Distilled Water", + }, + }, + ["Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Chain Mittens", + }, + }, + ["Muketsu"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Sakurafubuki", + }, + }, + ["Mulsum"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Honey", + "Grape Juice", + "Distilled Water", + }, + }, + ["Muscle Belt"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Gold Ingot", + "Manticore Lth.", + }, + }, + ["Mushroom Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Black Pepper", + "Danceshroom", + "Puffball", + "Bird Egg", + "Beaugreens", + "Uleguerand Milk", + }, + }, + ["Mushroom Paella"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Tarutaru Rice", + "Saffron", + "Woozyshroom", + "Danceshroom", + "Wild Onion", + "Coral Fungus", + "Distilled Water", + }, + }, + ["Mushroom Risotto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Selbina Butter", + "Tarutaru Rice", + "Black Pepper", + "Olive Oil", + "Danceshroom", + "Puffball", + "Distilled Water", + }, + }, + ["Mushroom Salad"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Olive Oil", + "Batagreens", + "Woozyshroom", + "King Truffle", + "Nopales", + "Burdock", + "Walnut", + "Agaricus", + }, + }, + ["Mushroom Saute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Selbina Butter", + "Danceshroom", + "Reishi Mushroom", + "Misx. Parsley", + "Walnut", + "Agaricus", + }, + }, + ["Mushroom Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Danceshroom", + "Scream Fungus", + "Coral Fungus", + "Distilled Water", + }, + }, + ["Mushroom Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Popoto", + "Black Pepper", + "Rock Salt", + "Danceshroom", + "Puffball", + "Coral Fungus", + "Distilled Water", + }, + }, + ["Musketeer Gun +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Musketeer Gun", + }, + }, + ["Musketeer's Pole +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Musketeer's Pole", + }, + }, + ["Musketeer's Sword +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Musketeer's Sword", + }, + }, + ["Musketoon"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Brass Ingot", + "Willow Lumber", + }, + }, + ["Muting Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Wind Anima", + "Water Anima", + "Dark Anima", + "2Leaf Mandra Bud", + "Scream Fungus", + }, + }, + ["Muting Potion 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Wind Anima", + "Water Anima", + "Dark Anima", + "Distilled Water", + "Sobbing Fungus", + "Sobbing Fungus", + }, + }, + ["Mutton Tortilla"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "La Theine Cbg.", + "G. Sheep Meat", + "Mithran Tomato", + "Tortilla", + "Tomato Juice", + "Stone Cheese", + }, + }, + ["Myrtle Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Desk", + "Green Text. Dye", + }, + }, + ["Mythic Harp"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Coeurl Whisker", + }, + }, + ["Mythic Harp 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 90", + }, + }, + ["Mythic Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + }, + }, + ["Mythic Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Phoenix Feather", + }, + }, + ["Mythril Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Chestnut Lumber", + }, + }, + ["Mythril Baselard"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Bell"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Tin Ingot", + "Manticore Hair", + "Molybden. Ingot", + "Scintillant Ingot", + }, + }, + ["Mythril Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Mtl. Bolt Heads", + }, + }, + ["Mythril Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Mtl. Bolt Heads", + "Mtl. Bolt Heads", + "Mtl. Bolt Heads", + "Bundling Twine", + }, + }, + ["Mythril Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + }, + }, + ["Mythril Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Ram Leather", + "Ram Leather", + }, + }, + ["Mythril Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mandrel", + }, + }, + ["Mythril Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Beetle Jaw", + }, + }, + ["Mythril Claymore"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Holly Lumber", + "Dhalmel Leather", + }, + }, + ["Mythril Coil"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mandrel", + }, + }, + ["Mythril Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 60", + }, + }, + ["Mythril Cuisses 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Ram Leather", + }, + }, + ["Mythril Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Degen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Darksteel Sheet", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Mythril Gear Machine"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Sheet", + }, + }, + ["Mythril Gorget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Chain", + "Gold Ingot", + "Jadeite", + "Mercury", + }, + }, + ["Mythril Grip"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mandrel", + }, + }, + ["Mythril Heart"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Lockheart", + }, + }, + ["Mythril Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mtl. Beastcoin", + "Mtl. Beastcoin", + "Mtl. Beastcoin", + "Mtl. Beastcoin", + }, + }, + ["Mythril Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ore", + "Mythril Ore", + "Mythril Ore", + "Mythril Ore", + }, + }, + ["Mythril Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 40", + }, + }, + ["Mythril Ingot 4"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ore", + "Mythril Nugget", + "Mythril Nugget", + "Mythril Nugget", + "Mythril Nugget", + "Mythril Nugget", + "Mythril Nugget", + }, + }, + ["Mythril Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Chestnut Lumber", + }, + }, + ["Mythril Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mythril Sheet", + "Chestnut Lumber", + }, + }, + ["Mythril Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Oak Lumber", + "Raptor Skin", + }, + }, + ["Mythril Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Ash Lumber", + "Ash Lumber", + }, + }, + ["Mythril Leggings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Darksteel Sheet", + "Ram Leather", + "Ram Leather", + }, + }, + ["Mythril Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Mesh Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Wind Anima", + "Wind Anima", + "Light Anima", + }, + }, + ["Mythril Nugget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Leaf", + "Panacea", + }, + }, + ["Mythril Pick"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Elm Lumber", + }, + }, + ["Mythril Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Mythril Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 45", + }, + }, + ["Mythril Sallet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Mythril Sheet", + "Darksteel Sheet", + "Sheep Leather", + }, + }, + ["Mythril Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Yew Lumber", + "Grass Cloth", + }, + }, + ["Mythril Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + }, + }, + ["Mythril Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Workshop Anvil", + }, + }, + ["Mythril Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Dhalmel Leather", + }, + }, + ["Mythril Zaghnal"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Walnut Lumber", + "Grass Cloth", + }, + }, + ["Nadziak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Oak Lumber", + "Gold Ingot", + "Mercury", + }, + }, + ["Nagan"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Mahogany Lbr.", + "Gold Ingot", + "Wyvern Skin", + }, + }, + ["Naigama"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bldwd. Lumber", + "Karakul Leather", + "Relic Steel", + "Marid Tusk", + "Scintillant Ingot", + }, + }, + ["Nakajimarai"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Rhodium Ingot", + "Umbril Ooze", + "Acuex Poison", + "Shinobi-Gatana", + }, + }, + ["Nanti Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Waktza Rostrum", + "Gua. Lumber", + }, + }, + ["Narasimha Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Narasimha Hide", + "Distilled Water", + }, + }, + ["Narasimha Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Narasimha Hide", + "Distilled Water", + }, + }, + ["Narasimha's Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Narasimha Lth.", + "Cesti", + }, + }, + ["Narasimha's Vest"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Beeswax", + "Manticore Lth.", + "Narasimha Lth.", + "Cardinal Vest", + }, + }, + ["Nathushne"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Kidney Stone", + "Belladonna Sap", + "Healing Staff", + }, + }, + ["Navarin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "G. Sheep Meat", + "Wild Onion", + "Mithran Tomato", + "Distilled Water", + }, + }, + ["Navy Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Mythril Axe", + }, + }, + ["Nebimonite Bake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Selbina Butter", + "Nebimonite", + }, + }, + ["Negoroshiki"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Steel Ingot", + "Darksteel Ingot", + "Oak Lumber", + }, + }, + ["Nepenthe Grip"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mandrel", + "Rhodium Ingot", + "Gabbrath Horn", + }, + }, + ["Nero di Seppia"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Black Pepper", + "Olive Oil", + "Holy Basil", + "Spaghetti", + "Wild Onion", + "Mithran Tomato", + "Kalamar", + }, + }, + ["Nero di Seppia 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Black Pepper", + "Olive Oil", + "Holy Basil", + "Spaghetti", + "Wild Onion", + "Mithran Tomato", + "Cone Calamary", + }, + }, + ["Neutralizing Silver Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ore", + "Silver Ore", + "Silver Ore", + "Silver Ore", + "Fire Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Ngoma"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Divine Log", + "Buffalo Leather", + }, + }, + ["Night Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Painite", + "Gold Earring", + }, + }, + ["Night Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Painite", + "Gold Earring +1", + }, + }, + ["Ninja Nodowa"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Root", + "Dark Matter", + "Azure Leaf", + "Tanzanite Crystal", + "Moldy Nodowa", + }, + }, + ["Niobium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Niobium Ore", + "Niobium Ore", + "Niobium Ore", + "Niobium Ore", + }, + }, + ["Noble Himantes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Himantes", + }, + }, + ["Noble's Bed"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Gold Ingot", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Noble's Crown"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Gold Ingot", + "Topaz", + }, + }, + ["Noble's Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Cotton Cloth", + "Rainbow Cloth", + "Saruta Cotton", + }, + }, + ["Noble's Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Rainbow Cloth", + "Tiger Leather", + }, + }, + ["Noble's Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Rainbow Cloth", + "Rainbow Cloth", + }, + }, + ["Noble's Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Rainbow Cloth", + "Shining Cloth", + }, + }, + ["Noct Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Chocobo Fthr.", + "Chocobo Fthr.", + "Sheep Leather", + }, + }, + ["Noct Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Sheep Leather", + }, + }, + ["Noct Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + "Sheep Leather", + }, + }, + ["Noct Gaiters"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Noct Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Sheep Leather", + }, + }, + ["Nodachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Lizard Skin", + }, + }, + ["Nodowa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Silk Thread", + }, + }, + ["Nohkux Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Midrium Ingot", + "Midrium Ingot", + "Rhodium Ingot", + "Urunday Lumber", + }, + }, + ["Nomad's Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rabbit Hide", + "Traveler's Mantle", + }, + }, + ["Nopales Salad"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Olive Oil", + "Rock Salt", + "Coriander", + "Kitron", + "Wild Onion", + "Mithran Tomato", + "Nopales", + }, + }, + ["Northern Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Undead Skin", + "Tiger Jerkin", + }, + }, + ["Numinous Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 84", + }, + }, + ["Numinous Shield 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Round Shield", + }, + }, + ["Nuna Gorget"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Craklaw Pincer", + }, + }, + ["Nurigomeyumi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "P. Brass Sheet", + "Wisteria Lumber", + "Wisteria Lumber", + "Urushi", + "Gendawa", + }, + }, + ["Nymph Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Oak Lumber", + "Faerie Shield", + }, + }, + ["Oak Bed"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Oak Cudgel"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + }, + }, + ["Oak Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Log", + }, + }, + ["Oak Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Log", + "Oak Log", + "Oak Log", + "Bundling Twine", + }, + }, + ["Oak Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Oak Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Oak Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Blk. Tiger Fang", + }, + }, + ["Oak Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Ob. Gold Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Oberon's Gold", + }, + }, + ["Obelisk Lance"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Willow Lumber", + "Lance", + "Obelisk", + }, + }, + ["Oberon's Gold"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ore", + "Gold Ore", + "Gold Ore", + "Fool's Gold Ore", + }, + }, + ["Oberon's Knuckles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Gargouille Eye", + "Gargouille Eye", + "Oberon's Gold", + }, + }, + ["Oberon's Rapier"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Fluorite", + "Mercury", + "Oberon's Gold", + }, + }, + ["Oberon's Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Ebony Lumber", + "Platinum Ingot", + "Mercury", + "Oberon's Gold", + }, + }, + ["Obsidian Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Teak Lumber", + "Obsid. Arrowhd.", + "Gnat Fletchings", + }, + }, + ["Obsidian Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Obsidian", + "Obsidian", + }, + }, + ["Octopus Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Gigant Octopus", + }, + }, + ["Octopus Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Contortopus", + "Contortopus", + }, + }, + ["Octopus Sushi 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Contortacle", + "Contortacle", + "Contortacle", + }, + }, + ["Odorous Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "White Steel", + "Magnolia Lumber", + }, + }, + ["Ogapepo Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Sheep Chammy", + "Sheep Chammy", + "Bztavian Wing", + "Bztavian Wing", + }, + }, + ["Ogre Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Coeurl Gloves", + }, + }, + ["Ogre Jambiya"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Silver Ingot", + "Sunstone", + "Mercury", + "Buggane Horn", + }, + }, + ["Ogre Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Undead Skin", + "Manticore Lth.", + "Coeurl Jerkin", + }, + }, + ["Ogre Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Coeurl Ledelsens", + }, + }, + ["Ogre Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Coeurl Mask", + }, + }, + ["Ogre Sickle"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ebony Lumber", + "Ruszor Leather", + "Buggane Horn", + }, + }, + ["Ogre Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Manticore Lth.", + "Coeurl Trousers", + }, + }, + ["Oil-Soaked Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Gold Thread", + "Flaxseed Oil", + "Flaxseed Oil", + "Flaxseed Oil", + "Wamoura Silk", + }, + }, + ["Onyx"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Black Rock", + }, + }, + ["Onyx 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Shadow Geode", + }, + }, + ["Onyx Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Onyx", + "Silver Earring", + }, + }, + ["Onyx Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Onyx", + "Silver Earring +1", + }, + }, + ["Onyx Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Onyx", + "Silver Ring", + }, + }, + ["Onyx Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Onyx", + "Silver Ring +1", + }, + }, + ["Opal Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Opal", + "Silver Earring", + }, + }, + ["Opal Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Opal", + "Silver Earring +1", + }, + }, + ["Opal Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Opal", + "Silver Ring", + }, + }, + ["Opal Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Light Opal", + "Silver Ring +1", + }, + }, + ["Opaline Boots"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Silk Cloth", + "Rainbow Cloth", + "Sheep Leather", + "Sheep Leather", + "Manticore Lth.", + "Twinthread", + }, + }, + ["Opaline Dress"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + "Rainbow Cloth", + "Twinthread", + "Twinthread", + }, + }, + ["Opaline Hose"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Velvet Cloth", + "Velvet Cloth", + "Twinthread", + "Twinthread", + }, + }, + ["Opprimo"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Walnut Lumber", + "Thokcha Ingot", + "Thokcha Ingot", + "P. Brass Ingot", + }, + }, + ["Orange Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Maple Sugar", + "Olive Oil", + "Selbina Milk", + "Saruta Orange", + "Bird Egg", + "Apkallu Egg", + }, + }, + ["Orange Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Saruta Orange", + "Saruta Orange", + "Saruta Orange", + "Saruta Orange", + }, + }, + ["Orange Juice 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cook. Kit 10", + }, + }, + ["Orange Kuchen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Saruta Orange", + "Bird Egg", + }, + }, + ["Orange Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Orange au Lait", + "Orange au Lait", + "Orange au Lait", + "Orange au Lait", + }, + }, + ["Orange au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Honey", + "Selbina Milk", + "Saruta Orange", + "Saruta Orange", + }, + }, + ["Orchestrion"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Melodious Plans", + "Timbre Case Kit", + "Musichinery Kit", + }, + }, + ["Orichalcum Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Ocl. Ingot", + }, + }, + ["Orichalcum Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + }, + }, + ["Orichalcum Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ocl. Ingot", + "Ruby", + }, + }, + ["Orichalcum Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Ocl. Ingot", + }, + }, + ["Orichalcum Gearbox"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Ocl. Ingot", + }, + }, + ["Orichalcum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + }, + }, + ["Orichalcum Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ash Lumber", + "Ash Lumber", + "Ocl. Ingot", + "Ruby", + }, + }, + ["Orichalcum Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Ocl. Ingot", + }, + }, + ["Orichalcum Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Yew Lumber", + "Ocl. Ingot", + "Ruby", + "Grass Cloth", + }, + }, + ["Orichalcum Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + }, + }, + ["Orichalcum Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Workshop Anvil", + }, + }, + ["Orochi Nodowa"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hydra Scale", + "Wamoura Silk", + }, + }, + ["Ortolana"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Spaghetti", + "Frost Turnip", + "Eggplant", + "Nopales", + }, + }, + ["Oshosi Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Sheet", + "Waktza Crest", + "Plovid Flesh", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Oshosi Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Waktza Crest", + "Plovid Flesh", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Oshosi Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Waktza Crest", + "Plovid Flesh", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Oshosi Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Waktza Crest", + "Plovid Flesh", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Oshosi Vest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Waktza Crest", + "Plovid Flesh", + "Plovid Flesh", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + }, + }, + ["Osseous Serum"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Mercury", + }, + }, + ["Ouka Ranman"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Amaryllis", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Ovinnik Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ovinnik Hide", + "Distilled Water", + }, + }, + ["Ovinnik Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ovinnik Hide", + "Distilled Water", + }, + }, + ["Ox Tongue"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ash Lumber", + "Gold Ingot", + "Aquamarine", + "Gold Thread", + }, + }, + ["Oxblood Orb"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Oxblood", + }, + }, + ["Oxidant Baselard"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Rhodium Ingot", + "Umbril Ooze", + "Acuex Poison", + "Mythril Baselard", + }, + }, + ["Oxidant Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Acid Bolt Heads", + "Urunday Lumber", + }, + }, + ["Oxidant Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Acid Bolt Heads", + "Acid Bolt Heads", + "Acid Bolt Heads", + "Bundling Twine", + "Urunday Lumber", + "Urunday Lumber", + "Urunday Lumber", + }, + }, + ["Oxossi Facon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Manta Leather", + "Cassia Lumber", + "Simian Horn", + }, + }, + ["Padded Armor"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Iron Sheet", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Padded Box"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bast Parchment", + "Bast Parchment", + "Inferior Cocoon", + }, + }, + ["Padded Box 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 5", + }, + }, + ["Padded Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Lizard Skin", + "Lizard Skin", + }, + }, + ["Painite"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fenrite", + }, + }, + ["Painite Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Painite", + "Gold Ring", + }, + }, + ["Painite Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Painite", + "Gold Ring +1", + }, + }, + ["Pak Corselet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Behem. Leather", + "Behem. Leather", + "Mercury", + "Siren's Macrame", + "Fiendish Skin", + "Scintillant Ingot", + "Tanzanite Jewel", + }, + }, + ["Paktong Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Firesand", + "Paktong Ingot", + }, + }, + ["Paktong Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ore", + "Copper Ore", + "Zinc Ore", + "Kopparnickel Ore", + }, + }, + ["Palladian Brass Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "P. Brass Ingot", + "P. Brass Ingot", + }, + }, + ["Palladian Brass Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Ore", + "P. Brass Ore", + "P. Brass Ore", + "P. Brass Ore", + }, + }, + ["Palladian Brass Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Ingot", + }, + }, + ["Palmer's Bangles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rogue's Silver", + "Silver Bangles", + }, + }, + ["Pamama Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Pamama au Lait", + "Pamama au Lait", + "Pamama au Lait", + "Pamama au Lait", + }, + }, + ["Pamama Tart"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Pamamas", + "Bbl. Chocolate", + "Bbl. Chocolate", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pamama au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Honey", + "Selbina Milk", + "Pamamas", + "Pamamas", + }, + }, + ["Pamun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Ormolu Ingot", + "Habu Skin", + "Bztavian Wing", + }, + }, + ["Panacea"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Phil. Stone", + "Sulfur", + }, + }, + ["Panther Mask"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Ram Leather", + "Wyvern Skin", + "H.Q. Coeurl Hide", + "H.Q. Coeurl Hide", + }, + }, + ["Pantin Fists"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hades' Claw", + "Dark Matter", + "S. Faulpie Leather", + "Moldy Weapon", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Sardonyx Crystal", + }, + }, + ["Papillion"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Insect Wing", + "Insect Wing", + "Artificial Lens", + "Prism Powder", + }, + }, + ["Paralysis Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Apkal. Fletching", + "Par. Arrowheads", + }, + }, + ["Paralysis Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Animal Glue", + "Imperial Cermet", + "Paralyze Potion", + }, + }, + ["Paralysis Dust"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Puffball", + "Puffball", + }, + }, + ["Paralysis Dust 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Three-eyed Fish", + }, + }, + ["Paralyze Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Paralysis Dust", + }, + }, + ["Paralyze Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Mercury", + "Mercury", + "Paralysis Dust", + "Paralysis Dust", + "Paralysis Dust", + "Triturator", + }, + }, + ["Parchment"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Karakul Leather", + "Rolanberry", + }, + }, + ["Parchment 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheep Leather", + "Rolanberry", + }, + }, + ["Parclose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Teak Lumber", + "Teak Lumber", + }, + }, + ["Partisan"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Ash Lumber", + "Silver Thread", + }, + }, + ["Partition"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + }, + }, + ["Passaddhi Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Blk. Tiger Fang", + "Hefty Oak Lbr.", + }, + }, + ["Pastoral Staff"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Quarterstaff", + }, + }, + ["Patas"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Iron Sheet", + "Carbon Fiber", + }, + }, + ["Patlican Salata"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Eggplant", + "Eggplant", + "Yogurt", + }, + }, + ["Pattern Reader"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Wind Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Pea Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blue Peas", + "Dried Marjoram", + "Wild Onion", + "Distilled Water", + }, + }, + ["Peace Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Silk Cloth", + "Amity Cape", + }, + }, + ["Pealing Anelace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Luminous Core", + "Anelace", + }, + }, + ["Pealing Nagan"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Luminous Core", + "Nagan", + }, + }, + ["Peapuk Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Peapuk Wing", + "Peapuk Wing", + }, + }, + ["Peapuk Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Peapuk Wing", + "Peapuk Wing", + "Peapuk Wing", + "Peapuk Wing", + "Peapuk Wing", + "Peapuk Wing", + }, + }, + ["Pear Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Cinnamon", + "Derfland Pear", + "Faerie Apple", + "Honey", + "Bird Egg", + "Uleguerand Milk", + }, + }, + ["Pear Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Pear au Lait", + "Pear au Lait", + "Pear au Lait", + "Pear au Lait", + }, + }, + ["Pear au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Derfland Pear", + "Honey", + "Selbina Milk", + }, + }, + ["Pearl"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Shall Shell", + }, + }, + ["Pearl 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Istiridye", + }, + }, + ["Pearl Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pearl", + "Mythril Earring", + }, + }, + ["Pearl Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pearl", + "Mythril Earring +1", + }, + }, + ["Pearl Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pearl", + "Mythril Ring", + }, + }, + ["Pearl Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pearl", + "Mythril Ring +1", + }, + }, + ["Pebble Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Flint Stone", + "Flint Stone", + "Flint Stone", + "Distilled Water", + }, + }, + ["Pebble Soup 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 5", + }, + }, + ["Peeled Crayfish"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Crayfish", + }, + }, + ["Peeled Lobster"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Lobster", + }, + }, + ["Peeled Lobster 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Istakoz", + }, + }, + ["Peiste Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Chain", + "Peiste Leather", + }, + }, + ["Peiste Dart"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Animal Glue", + "Gnat Wing", + "Gnat Wing", + "Peiste Stinger", + }, + }, + ["Peiste Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Peiste Skin", + "Distilled Water", + }, + }, + ["Peiste Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Peiste Skin", + "Distilled Water", + }, + }, + ["Peiste Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Peiste Skin", + "Peiste Skin", + "Peiste Skin", + "Distilled Water", + }, + }, + ["Peiste Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Peiste Skin", + "Peiste Skin", + }, + }, + ["Peiste Mantle 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 95", + }, + }, + ["Pellet Belt"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sheep Leather", + "Igneous Rock", + "Igneous Rock", + "Leather Pouch", + }, + }, + ["Pendragon's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "S. Bgd. Leather", + "Koenigs Belt", + }, + }, + ["Pennon Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gnat Wing", + "Gnat Wing", + "Electrum Ingot", + }, + }, + ["Peperoncino"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Spaghetti", + "Misx. Parsley", + }, + }, + ["Pepper Biscuit"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Black Pepper", + "Crawler Egg", + "Honey", + "Acorn", + "Acorn", + "Acorn", + }, + }, + ["Pepperoni"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Black Pepper", + "Sage", + "Oak Log", + "Rock Salt", + "G. Sheep Meat", + "Buffalo Meat", + }, + }, + ["Pepperoni Pizza"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pizza Dough", + "Pomodoro Sauce", + "Misx. Parsley", + "Pepperoni", + "Agaricus", + "Chalaimbille", + }, + }, + ["Pepperoni Slice"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pizza Dough", + "Pomodoro Sauce", + "Misx. Parsley", + "Pepperoni", + "Agaricus", + "Chalaimbille", + "Pizza Cutter", + }, + }, + ["Peridot Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Peridot", + "Mythril Earring", + }, + }, + ["Peridot Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 45", + }, + }, + ["Peridot Earring 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Peridot", + "Mythril Earring +1", + }, + }, + ["Peridot Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Peridot", + "Mythril Ring", + }, + }, + ["Peridot Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Peridot", + "Mythril Ring +1", + }, + }, + ["Persikos Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Persikos au Lait", + "Persikos au Lait", + "Persikos au Lait", + "Persikos au Lait", + }, + }, + ["Persikos au Lait"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Persikos", + "Persikos", + "Honey", + "Selbina Milk", + }, + }, + ["Personal Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Rosewood Lbr.", + "Gold Ingot", + }, + }, + ["Pescatora"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Spaghetti", + "Sandfish", + "Grimmonite", + "Gold Lobster", + "Shall Shell", + "Pomodoro Sauce", + }, + }, + ["Pescatora 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Spaghetti", + "Sandfish", + "Pomodoro Sauce", + "Istakoz", + "Ahtapot", + "Istiridye", + }, + }, + ["Pet Food Alpha"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Hare Meat", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pet Food Beta"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "G. Sheep Meat", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pet Food Beta 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Distilled Water", + "Apkallu Egg", + "Karakul Meat", + }, + }, + ["Pet Food Delta"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rye Flour", + "Land Crab Meat", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pet Food Epsilon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rye Flour", + "Lizard Egg", + "Distilled Water", + "Ziz Meat", + }, + }, + ["Pet Food Epsilon 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rye Flour", + "Lizard Egg", + "Cockatrice Meat", + "Distilled Water", + }, + }, + ["Pet Food Eta"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Distilled Water", + "Buffalo Meat", + "Apkallu Egg", + }, + }, + ["Pet Food Gamma"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Dhalmel Meat", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pet Food Theta"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Distilled Water", + "Puk Egg", + "Ruszor Meat", + }, + }, + ["Pet Food Zeta"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Lizard Egg", + "Coeurl Meat", + "Distilled Water", + }, + }, + ["Pet Poultice"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Linen Cloth", + "Flaxseed Oil", + "Lycopodium Flower", + "Holy Water", + }, + }, + ["Pet Roborant"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Boyahda Moss", + "Hecteyes Eye", + "Beast Blood", + "Scream Fungus", + "Ca Cuong", + }, + }, + ["Ph. Gold Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ph. Gold Ingot", + }, + }, + ["Ph. Gold Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ph. Gold Ingot", + "Ph. Gold Ingot", + "Ph. Gold Ingot", + "Ph. Gold Ingot", + "Ph. Gold Ingot", + "Ph. Gold Ingot", + "Workshop Anvil", + }, + }, + ["Phantom Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Earring", + "Phtm. Tathlum", + }, + }, + ["Phos Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Scarlet Kadife", + "Sealord Leather", + "Sonic Belt", + }, + }, + ["Photoanima"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fire Anima", + "Ice Anima", + "Wind Anima", + "Earth Anima", + "Lightning Anima", + "Water Anima", + "Light Anima", + "Dark Anima", + }, + }, + ["Phrygian Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ph. Gold Ingot", + "Ph. Gold Ingot", + }, + }, + ["Phrygian Gold Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Phrygian Ore", + "Phrygian Ore", + "Phrygian Ore", + "Phrygian Ore", + }, + }, + ["Phrygian Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ph. Gold Ingot", + "Ph. Gold Ingot", + }, + }, + ["Phrygian Ring 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 94", + }, + }, + ["Piccolo"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Lumber", + "Parchment", + }, + }, + ["Piccolo 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 20", + }, + }, + ["Pickaxe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Maple Lumber", + }, + }, + ["Pickled Herring"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Rock Salt", + "Nosteau Herring", + }, + }, + ["Pickled Rarab Tail"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Water Barrel", + "Smooth Stone", + "Kazham Peppers", + "Mhaura Garlic", + "Rock Salt", + "Sea Foliage", + "Kitron", + }, + }, + ["Pie Dough"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + }, + }, + ["Piercing Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Wind Cell", + "Brass Dagger", + }, + }, + ["Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Silk Cloth", + "Raptor Skin", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Pigeon Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Pigeon's Blood", + "Platinum Earring", + }, + }, + ["Pineapple Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Pineapl.", + "Kazham Pineapl.", + }, + }, + ["Pineapple Juice 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cook. Kit 30", + }, + }, + ["Pinga Crown"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Rockfin Tooth", + "Defiant Scarf", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Pinga Mittens"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Rockfin Tooth", + "Defiant Scarf", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Pinga Pants"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Rockfin Tooth", + "Defiant Scarf", + "Azure Cermet", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Pinga Pumps"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Rockfin Tooth", + "Defiant Scarf", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Pinga Tunic"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Rockfin Tooth", + "Defiant Scarf", + "Defiant Scarf", + "Azure Cermet", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Pinwheel"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Animal Glue", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Pirate's Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Darksteel Ingot", + "Turtle Shell", + }, + }, + ["Pizza Cutter"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Holly Lumber", + }, + }, + ["Pizza Dough"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Selbina Butter", + "Olive Oil", + "Rock Salt", + "Semolina", + }, + }, + ["Plain Cap"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Cap", + }, + }, + ["Plain Pick"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Pick", + }, + }, + ["Plain Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Rusty Greatsword", + }, + }, + ["Planus Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Silk Cloth", + "Silk Cloth", + "Siren's Macrame", + "Lancewood Lbr.", + "Lancewood Lbr.", + "Lancewood Lbr.", + "Bloodthread", + }, + }, + ["Plasma Oil"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Flan Meat", + "Flan Meat", + }, + }, + ["Plastron"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Ocl. Ingot", + "Tiger Leather", + "Tiger Leather", + "Kunwu Iron", + "Kunwu Sheet", + }, + }, + ["Plate Leggings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Iron Sheet", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Platinum Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Yagudo Fltchg.", + "Plt. Arrowheads", + }, + }, + ["Platinum Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Bangles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Bangles 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 85", + }, + }, + ["Platinum Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Firesand", + }, + }, + ["Platinum Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Mandrel", + }, + }, + ["Platinum Cutlass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Platinum Ingot", + "Mercury", + "Cutlass", + }, + }, + ["Platinum Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Gear"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Sheet", + }, + }, + ["Platinum Grip"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Mandrel", + }, + }, + ["Platinum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Plt. Beastcoin", + "Plt. Beastcoin", + "Plt. Beastcoin", + "Plt. Beastcoin", + }, + }, + ["Platinum Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Platinum Ore", + "Platinum Ore", + "Platinum Ore", + }, + }, + ["Platinum Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Platinum Nugget", + "Platinum Nugget", + "Platinum Nugget", + "Platinum Nugget", + "Platinum Nugget", + "Platinum Nugget", + }, + }, + ["Platinum Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Platinum Ingot", + "Mercury", + "Darksteel Mace", + }, + }, + ["Platinum Nugget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Leaf", + "Panacea", + }, + }, + ["Platinum Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Gold Ingot", + "Platinum Ingot", + }, + }, + ["Platinum Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + }, + }, + ["Platinum Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Workshop Anvil", + }, + }, + ["Platinum Silk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Silk Thread", + }, + }, + ["Platinum Silk 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Platinum Ingot", + "Platinum Ingot", + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Spindle", + }, + }, + ["Poet's Circlet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Mythril Ingot", + }, + }, + ["Pogaca"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Rock Salt", + "Stone Cheese", + "Distilled Water", + "Apkallu Egg", + "Yogurt", + }, + }, + ["Poison Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Chocobo Fltchg.", + "Poison Arrowhd.", + }, + }, + ["Poison Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Animal Glue", + "Poison Potion", + }, + }, + ["Poison Baghnakhs"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Baghnakhs", + }, + }, + ["Poison Baghnakhs 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 35", + }, + }, + ["Poison Baselard"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Baselard", + }, + }, + ["Poison Baselard 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 25", + }, + }, + ["Poison Cesti"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Lizard Cesti", + }, + }, + ["Poison Claws"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Claws", + }, + }, + ["Poison Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Dagger", + }, + }, + ["Poison Dust"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Deathball", + "Deathball", + }, + }, + ["Poison Dust 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Giant Stinger", + "Giant Stinger", + }, + }, + ["Poison Dust 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Yellow Globe", + "Yellow Globe", + }, + }, + ["Poison Katars"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Katars", + }, + }, + ["Poison Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Knife", + }, + }, + ["Poison Kukri"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Poison Potion", + "Kukri", + }, + }, + ["Poison Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Poison Dust", + }, + }, + ["Poison Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Mercury", + "Mercury", + "Poison Dust", + "Poison Dust", + "Poison Dust", + "Triturator", + }, + }, + ["Poisona Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Neutralizing Slv.", + "Silver Ring", + }, + }, + ["Poisonous Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Venom Dust", + "Umbril Ooze", + "Distilled Water", + "Gnatbane", + }, + }, + ["Polyflan"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Flan Meat", + "Chimera Blood", + }, + }, + ["Polyflan Paper"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Polyflan", + }, + }, + ["Pomodoro Sauce"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bay Leaves", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Holy Basil", + "Wild Onion", + "Mithran Tomato", + "Misx. Parsley", + }, + }, + ["Ponderous Gully"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Spirit Core", + "Gully", + }, + }, + ["Ponderous Lance"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sprt. Orichalcum", + "Orichalcum Lance", + }, + }, + ["Popstar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Firesand", + "Bast Parchment", + "Twinkle Powder", + "Twinkle Powder", + "Prism Powder", + }, + }, + ["Porcelain Flowerpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cermet Chunk", + }, + }, + ["Pork Cutlet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Olive Oil", + "Rock Salt", + "White Bread", + "Bird Egg", + "Porxie Pork", + }, + }, + ["Pork Cutlet Rice Bowl"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Soy Stock", + "2Leaf Mandra Bud", + "Wild Onion", + "Bird Egg", + "Pork Cutlet", + }, + }, + ["Porxie Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Porxie Wing", + "Porxie Wing", + }, + }, + ["Porxie Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Porxie Wing", + "Porxie Wing", + "Porxie Wing", + "Porxie Wing", + "Porxie Wing", + "Porxie Wing", + }, + }, + ["Poseidon's Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Aqueous Ocl.", + "Orichalcum Ring", + }, + }, + ["Pot-au-feu"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Giant Femur", + "Hare Meat", + "Frost Turnip", + "San d'Or. Carrot", + }, + }, + ["Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Lizard Tail", + "Distilled Water", + }, + }, + ["Potion Drop"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sage", + "Lizard Tail", + "Distilled Water", + }, + }, + ["Potion Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Brass Tank", + "Potion", + "Potion", + "Potion", + "Potion", + }, + }, + ["Poultry Pitaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Olive Oil", + "Rock Salt", + "Imperial Flour", + "Cockatrice Meat", + "Distilled Water", + "Apkallu Egg", + "Graubg. Lettuce", + }, + }, + ["Powder Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tgh. Dhal. Lth.", + "Cuir Highboots", + }, + }, + ["Power Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elm Lumber", + "Elm Lumber", + "Wool Cloth", + "Scorpion Claw", + "Coeurl Whisker", + }, + }, + ["Premium Bag"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Platinum Ingot", + "Rainbow Thread", + "Gold Thread", + "Rainbow Cloth", + "Bugard Leather", + "M. Sheep Lth.", + "M. Sheep Wool", + }, + }, + ["Primate Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Gold Ingot", + "Yellow Rock", + "Yellow Rock", + "Mercury", + "Cassia Lumber", + "Cassia Lumber", + "Vrml. Lacquer", + }, + }, + ["Prism Powder"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Ahriman Lens", + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Prism Powder 2"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Glass Fiber", + "Glass Fiber", + "Artificial Lens", + }, + }, + ["Prism Powder 3"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + "Artificial Lens", + "Artificial Lens", + "Triturator", + }, + }, + ["Pristine Sap"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Divine Sap", + "Holy Water", + "Holy Water", + }, + }, + ["Pro-Ether"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dried Marjoram", + "Ahriman Wing", + "Ahriman Wing", + "Treant Bulb", + "Wyvern Wing", + "Distilled Water", + }, + }, + ["Prominence Axe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Inferno Axe", + }, + }, + ["Prominence Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Inferno Sword", + }, + }, + ["Protect Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "S. Smil. Leather", + "Smilodon Ring", + }, + }, + ["Puce Chest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Thief's Tools", + "Green Text. Dye", + "Yellow Txt. Dye", + "Gua. Lumber", + }, + }, + ["Puk Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Puk Wing", + "Puk Wing", + }, + }, + ["Puk Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Puk Wing", + "Puk Wing", + "Puk Wing", + "Puk Wing", + "Puk Wing", + "Puk Wing", + }, + }, + ["Pukatrice Egg"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "White Bread", + "Cockatrice Meat", + "Bird Egg", + "Puk Egg", + }, + }, + ["Puls"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rye Flour", + "Horo Flour", + "Honey", + "Distilled Water", + }, + }, + ["Pumpkin Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Maple Sugar", + "Ogre Pumpkin", + "Olive Oil", + "Selbina Milk", + "Bird Egg", + "Apkallu Egg", + }, + }, + ["Pumpkin Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Pie Dough", + "Maple Sugar", + "Cinnamon", + "Ogre Pumpkin", + "Selbina Milk", + "Distilled Water", + "Bird Egg", + }, + }, + ["Pumpkin Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ogre Pumpkin", + "Sage", + "Rock Salt", + "Selbina Milk", + "Distilled Water", + }, + }, + ["Pup. Collar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Sardonyx Crystal", + "Moldy Collar", + }, + }, + ["Purple Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fluorite", + "Gold Earring", + }, + }, + ["Purple Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fluorite", + "Gold Earring +1", + }, + }, + ["Pya'ekue Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Bztavian Wing", + "Phos Belt", + }, + }, + ["Qi Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Demon Horn", + "Blue Jasper", + }, + }, + ["Qiqirn Hood"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Lapis Lazuli", + "Lapis Lazuli", + "Wool Cloth", + "Wool Cloth", + "Coeurl Whisker", + "Qiqirn Cape", + }, + }, + ["Qiqirn Sash"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Scarlet Linen", + "Red Grs. Thread", + "Karakul Cloth", + }, + }, + ["Quadav Barbut"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Black Pearl", + "Wool Thread", + "Tiger Leather", + "Lizard Molt", + "Bugard Tusk", + "Red Grass Cloth", + "Quadav Parts", + }, + }, + ["Quake Grenade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Yuhtunga Sulfur", + "Firesand", + "Firesand", + }, + }, + ["Quake Grenade 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Firesand", + "Firesand", + "Sulfur", + }, + }, + ["Quake Grenade 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Firesand", + "Firesand", + "Sulfur", + "Cluster Ash", + }, + }, + ["Quarterstaff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Walnut Lumber", + "Walnut Lumber", + }, + }, + ["Ra'Kaznar Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Ra'Kaznar Ingot", + }, + }, + ["Ra'Kaznar Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ra'Kaznar Ingot", + }, + }, + ["Ra'Kaznar Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ore", + "Darksteel Ore", + "Darksteel Ore", + "Ra'Kaznar Ore", + }, + }, + ["Raaz Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Raaz Tusk", + }, + }, + ["Raaz Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Raaz Tusk", + "Raaz Tusk", + "Raaz Tusk", + }, + }, + ["Raaz Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Distilled Water", + "Raaz Hide", + }, + }, + ["Raaz Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Distilled Water", + "Raaz Hide", + }, + }, + ["Raaz Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Distilled Water", + "Raaz Hide", + "Raaz Hide", + "Raaz Hide", + }, + }, + ["Rabbit Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Rabbit Hide", + "Rabbit Hide", + "Rabbit Hide", + "Rabbit Hide", + "Rabbit Hide", + }, + }, + ["Rabbit Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Pie Dough", + "Black Pepper", + "Rock Salt", + "Wild Onion", + "San d'Or. Carrot", + "Lynx Meat", + "Agaricus", + }, + }, + ["Radical Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leafkin Frond", + "Chapuli Wing", + "Chapuli Wing", + "Raptor Mantle", + }, + }, + ["Raetic Algol"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thought Crystal", + "Azure Cermet", + "Rune Algol", + }, + }, + ["Raetic Arrow"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thought Crystal", + "Cypress Lumber", + "Niobium Ore", + "Rune Arrow", + "Rune Arrow", + "Rune Arrow", + }, + }, + ["Raetic Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hope Crystal", + "Niobium Ingot", + "Rune Axe", + }, + }, + ["Raetic Baghnakhs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fulfillment Crystal", + "Ruthenium Ingot", + "Rune Baghnakhs", + }, + }, + ["Raetic Bangles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fulfillment Crystal", + "Cyan Coral", + "Cyan Coral", + "Cyan Orb", + "Cyan Orb", + "Cyan Orb", + "Rune Bangles", + }, + }, + ["Raetic Blade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thought Crystal", + "Ruthenium Ingot", + "Rune Blade", + }, + }, + ["Raetic Bow"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thought Crystal", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + "Rune Bow", + }, + }, + ["Raetic Chopper"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fulfillment Crystal", + "Niobium Ingot", + "Rune Chopper", + }, + }, + ["Raetic Halberd"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hope Crystal", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + "Rune Halberd", + }, + }, + ["Raetic Kris"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hope Crystal", + "Niobium Ingot", + "Rune Kris", + }, + }, + ["Raetic Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thought Crystal", + "Niobium Ingot", + "Rune Rod", + }, + }, + ["Raetic Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hope Crystal", + "Cyan Coral", + "Cyan Coral", + "Cyan Orb", + "Cyan Orb", + "Cyan Orb", + "Rune Scythe", + }, + }, + ["Raetic Staff"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fulfillment Crystal", + "Cypress Lumber", + "Cypress Lumber", + "Cypress Lumber", + "Rune Staff", + }, + }, + ["Rainbow Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Rainbow Cloth", + "Rainbow Cloth", + }, + }, + ["Rainbow Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + }, + }, + ["Rainbow Headband"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Cloth", + "Carbon Fiber", + }, + }, + ["Rainbow Headband 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 85", + }, + }, + ["Rainbow Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Gold Thread", + }, + }, + ["Rainbow Obi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 90", + }, + }, + ["Rainbow Powder"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Glass Fiber", + "Glass Fiber", + "Wamoura Scale", + }, + }, + ["Rainbow Powder 2"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + "Glass Fiber", + "Triturator", + "Wamoura Scale", + "Wamoura Scale", + }, + }, + ["Rainbow Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spider Web", + "Spider Web", + }, + }, + ["Rainbow Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spider Web", + "Spider Web", + "Spider Web", + "Spider Web", + "Spider Web", + "Spider Web", + "Spindle", + }, + }, + ["Rainbow Velvet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Rainbow Thread", + "Rainbow Thread", + }, + }, + ["Raisin Bread"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Rock Salt", + "San d'Or. Grape", + "Distilled Water", + }, + }, + ["Ram Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ram Skin", + "Distilled Water", + }, + }, + ["Ram Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Ram Skin", + "Ram Skin", + "Ram Skin", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Ram Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Leath. Kit 35", + }, + }, + ["Ram Leather 4"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ram Skin", + "Distilled Water", + }, + }, + ["Ram Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Ram Skin", + }, + }, + ["Ram-Dao"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Mahogany Lbr.", + "Raptor Skin", + }, + }, + ["Ramen Noodles"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Rock Salt", + "Baking Soda", + "Distilled Water", + }, + }, + ["Rancid Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Frigorifish", + "Frigorifish", + "Frigorifish", + "Frigorifish", + }, + }, + ["Ranging Knife"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Stately Crab Sh.", + }, + }, + ["Ranka"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Night Queen", + "Night Queen", + }, + }, + ["Rapid Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Ebony Lumber", + "Silver Thread", + "Silk Cloth", + }, + }, + ["Rapier"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Gold Ingot", + "Fluorite", + "Mercury", + }, + }, + ["Raptor Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 55", + }, + }, + ["Raptor Gloves 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Raptor Skin", + "Cuir Gloves", + }, + }, + ["Raptor Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Sheep Leather", + "Raptor Skin", + "Raptor Skin", + }, + }, + ["Raptor Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Raptor Skin", + "Raptor Skin", + }, + }, + ["Raptor Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Raptor Skin", + "Cuir Highboots", + }, + }, + ["Raptor Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Raptor Skin", + "Raptor Skin", + }, + }, + ["Raptor Strap"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Raptor Skin", + "Raptor Skin", + }, + }, + ["Raptor Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Raptor Skin", + "Raptor Skin", + "Cuir Trousers", + }, + }, + ["Rarab Meatball"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Mhaura Garlic", + "Black Pepper", + "Rock Salt", + "Hare Meat", + "Stone Cheese", + "Distilled Water", + "Bird Egg", + }, + }, + ["Rasetsu Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Tiger Leather", + "Raxa", + }, + }, + ["Rasetsu Jinpachi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Tiger Leather", + "Raxa", + }, + }, + ["Rasetsu Samue"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Steel Sheet", + "Rainbow Thread", + "Velvet Cloth", + "Tiger Leather", + "Tiger Leather", + "Raxa", + "Raxa", + }, + }, + ["Rasetsu Sune-Ate"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + "Tiger Leather", + "Tiger Leather", + "Raxa", + }, + }, + ["Rasetsu Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Iron Chain", + "Linen Thread", + "Tiger Leather", + "Raxa", + }, + }, + ["Ratatouille"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Bay Leaves", + "Olive Oil", + "Wild Onion", + "Eggplant", + "Mithran Tomato", + "Zucchini", + "Paprika", + }, + }, + ["Ratatouille 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 65", + }, + }, + ["Ratri Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Cehuetzi Claw", + "Macuil Plating", + "Macuil Plating", + "Azure Cermet", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Ratri Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Cehuetzi Claw", + "Macuil Plating", + "Azure Cermet", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Ratri Gadlings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Sheet", + "Cehuetzi Claw", + "Macuil Plating", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Ratri Sallet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Chain", + "Cehuetzi Claw", + "Macuil Plating", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Ratri Sollerets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Cehuetzi Claw", + "Macuil Plating", + "Azure Cermet", + "Azure Cermet", + }, + }, + ["Ravenous Breastplate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Gold Thread", + "Sealord Leather", + "Rhodium Ingot", + "Macuil Plating", + "Macuil Plating", + }, + }, + ["Razor Brain Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Hare Meat", + "G. Sheep Meat", + "Ziz Meat", + }, + }, + ["Razorfury"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Damascus Ingot", + "Damascus Ingot", + "Ormolu Ingot", + "Urunday Lumber", + "Rockfin Tooth", + }, + }, + ["Reaver Grip"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Malebolge Mand.", + }, + }, + ["Recital Bench"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Hickory Lumber", + "Wolf Felt", + }, + }, + ["Red Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Chocobo Fthr.", + }, + }, + ["Red Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Red Curry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Curry Powder", + "Coriander", + "Dragon Meat", + "San d'Or. Carrot", + "Mithran Tomato", + "Distilled Water", + }, + }, + ["Red Curry Bun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Olive Oil", + "Red Curry", + "Bird Egg", + }, + }, + ["Red Grass Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Red Grs. Thread", + "Red Grs. Thread", + "Red Grs. Thread", + }, + }, + ["Red Grass Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Red Moko Grass", + "Red Moko Grass", + }, + }, + ["Red Grass Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Red Moko Grass", + "Red Moko Grass", + "Red Moko Grass", + "Red Moko Grass", + "Red Moko Grass", + "Red Moko Grass", + "Spindle", + }, + }, + ["Red Grass Thread 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Cloth. Kit 15", + }, + }, + ["Red Hobby Bo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Hickory Lumber", + "Dogwd. Lumber", + "Onyx", + "Onyx", + "Red Chocobo Dye", + }, + }, + ["Red Mahogany Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mahogany Bed", + "Wool Thread", + "Red Textile Dye", + }, + }, + ["Red Round Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Platinum Silk", + "Teak Lumber", + "Red Textile Dye", + }, + }, + ["Red Storm Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Brass Ingot", + "Silver Ingot", + "Silk Cloth", + "Glass Sheet", + "Sailcloth", + "Red Textile Dye", + }, + }, + ["Red Textile Dye"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Nopales", + }, + }, + ["Red Viola"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Red Rock", + "Granite", + "Red Gravel", + "Wildgrass Seeds", + "Humus", + }, + }, + ["Reef Aquarium"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Oak Lumber", + "Coral Fragment", + "Sieglinde Putty", + "Glass Sheet", + "Saltwater Set", + "Coral Butterfly", + "Coral Butterfly", + "Coral Butterfly", + }, + }, + ["Regen Collar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Water Cell", + "Lam. Earth Cell", + "Feather Collar", + }, + }, + ["Regen Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Vivio Platinum", + "Lord's Cuirass", + }, + }, + ["Regiment Kheten"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Kheten", + }, + }, + ["Regulator"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Tank", + "Spect. Goldenrod", + "Chimera Blood", + "Imperial Cermet", + "Umbril Ooze", + }, + }, + ["Relic Steel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Relic Iron", + "Relic Iron", + }, + }, + ["Reliquary"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Dogwd. Lumber", + "Imp. Silk Cloth", + "A.U. Brass Ingot", + "A.U Brass Sheet", + "A.U Brass Sheet", + }, + }, + ["Remedy"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Chamomile", + "Sage", + "Mistletoe", + "Boyahda Moss", + "Wijnruit", + "Honey", + "Distilled Water", + }, + }, + ["Remedy Ointment"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Qutrub Bandage", + "Distilled Water", + "White Honey", + }, + }, + ["Repeater"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Glass Fiber", + "Sieglinde Putty", + "Glass Sheet", + "Homncl. Nerves", + "High Ebonite", + }, + }, + ["Repeating Crossbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Rosewood Lbr.", + "Scorpion Claw", + "Coeurl Whisker", + "Carbon Fiber", + }, + }, + ["Replicator"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Sieglinde Putty", + "Polyflan Paper", + "Polyflan Paper", + "Wind Fan", + }, + }, + ["Reppu"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tin Ingot", + "Steel Sheet", + "Kodachi", + }, + }, + ["Republican Legionnaire's Bedding"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Steel Ingot", + "Iron Sheet", + "Iron Sheet", + "Chestnut Lumber", + "Linen Cloth", + "Linen Cloth", + "Saruta Cotton", + }, + }, + ["Reraise Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Vivified Mythril", + "Mythril Earring", + }, + }, + ["Reraise Gorget"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Vivified Coral", + "Coral Gorget", + }, + }, + ["Reraise Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Vivified Coral", + "Coral Hairpin", + }, + }, + ["Resister"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Goblin Grease", + "Ebonite", + "Water Tank", + "Hydro Pump", + }, + }, + ["Resister II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blsd. Mtl. Sheet", + "Goblin Grease", + "Ebonite", + "Water Tank", + "Kilo Pump", + }, + }, + ["Resolution Ring"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Fiend Blood", + "Dragon Blood", + "Avatar Blood", + "Lizard Blood", + "Bird Blood", + "Chimera Blood", + "Demon Blood", + "Platinum Ring", + }, + }, + ["Revealer's Crown"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Lizard Molt", + "Baldurno's Horn", + "Abyssdiver Feather", + }, + }, + ["Revealer's Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Ancestral Cloth", + "Ancestral Cloth", + "Pitriv's Thread", + "Warblade's Hide", + }, + }, + ["Revealer's Pants"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Ancestral Cloth", + "Ancestral Cloth", + "Pitriv's Thread", + "Warblade's Hide", + }, + }, + ["Revealer's Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ancestral Cloth", + "Ancestral Cloth", + "Pitriv's Thread", + "Pitriv's Thread", + "Warblade's Hide", + }, + }, + ["Revealer's Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Linen Cloth", + "Ancestral Cloth", + "Ancestral Cloth", + "Ancestral Cloth", + "Pitriv's Thread", + "Warblade's Hide", + }, + }, + ["Revenging Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Arioch Fang", + }, + }, + ["Reverberation Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Delkfutt Key", + }, + }, + ["Reverberation Sphere 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Fetich Torso", + }, + }, + ["Reverberation Sphere 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Desert Venom", + }, + }, + ["Reverberation Sphere 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Water Card", + }, + }, + ["Reverberation Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Indigo Memosphere", + }, + }, + ["Rhodium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rhodium Ore", + "Rhodium Ore", + "Rhodium Ore", + "Rhodium Ore", + }, + }, + ["Rhodium Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rhodium Ingot", + "Rhodium Ingot", + }, + }, + ["Rhodium Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rhodium Ingot", + }, + }, + ["Rhodium Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + }, + }, + ["Rhodonite"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Scarlet Stone", + }, + }, + ["Rice Ball"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rock Salt", + "Distilled Water", + }, + }, + ["Rice Dumpling"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Bamboo Stick", + "Rock Salt", + "Sticky Rice", + "Dhalmel Meat", + "Coral Fungus", + "Distilled Water", + }, + }, + ["Righteous Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Animal Glue", + "Avatar Blood", + "Hallowed Water", + "Hallowed Water", + "Ra'Kaznar Ingot", + }, + }, + ["Rindomaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Darksteel Ingot", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Raptor Skin", + "Dweomer Steel", + }, + }, + ["Riot Grenade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Yuhtunga Sulfur", + "Paralysis Dust", + "Firesand", + "Firesand", + }, + }, + ["Riot Grenade 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Paralysis Dust", + "Firesand", + "Firesand", + "Sulfur", + }, + }, + ["Riot Grenade 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Sheet", + "Paralysis Dust", + "Firesand", + "Firesand", + "Sulfur", + "Cluster Ash", + }, + }, + ["Rising Sun"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Gold Ingot", + "Mercury", + }, + }, + ["Ritter Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Adaman Sheet", + "Adaman Sheet", + "Ash Lumber", + "Sheep Leather", + }, + }, + ["River Aquarium"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Petrified Log", + "Oak Lumber", + "Sieglinde Putty", + "Glass Sheet", + "Freshwater Set", + "Kayabaligi", + "Kayabaligi", + "Kayabaligi", + }, + }, + ["Riverfin Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ginger", + "Chicken Bone", + "Rock Salt", + "Rockfin Fin", + "Distilled Water", + "Cibol", + "Isleracea", + }, + }, + ["Roast Carp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Forest Carp", + }, + }, + ["Roast Carp 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Moat Carp", + }, + }, + ["Roast Coffee Beans"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Coffee Beans", + }, + }, + ["Roast Mushroom"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Danceshroom", + }, + }, + ["Roast Mushroom 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Woozyshroom", + }, + }, + ["Roast Mushroom 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Sleepshroom", + }, + }, + ["Roast Mutton"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Dried Marjoram", + "G. Sheep Meat", + }, + }, + ["Roast Pipira"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Pipira", + }, + }, + ["Roast Trout"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Shining Trout", + }, + }, + ["Roast Trout 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Alabaligi", + }, + }, + ["Roasted Almonds"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Almond", + }, + }, + ["Roasted Corn"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Millioncorn", + }, + }, + ["Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Rococo Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + }, + }, + ["Rod"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Iron Ingot", + "Iron Ingot", + }, + }, + ["Rogue's Silver Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ore", + "Silver Ore", + "Silver Ore", + "Silver Ore", + "Ice Anima", + "Lightning Anima", + "Dark Anima", + }, + }, + ["Rolan. Daifuku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Sticky Rice", + "Cornstarch", + "Rolanberry", + "Rolanberry", + "Distilled Water", + "Azuki Bean", + }, + }, + ["Rolanberry Pie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Pie Dough", + "Maple Sugar", + "Gelatin", + "Rolanberry", + "Selbina Milk", + "Bird Egg", + }, + }, + ["Rook Banner"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Gold Ingot", + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Rainbow Cloth", + "Pigeon's Blood", + "Siren's Macrame", + }, + }, + ["Roppo Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Mercury", + "Titanium Sheet", + }, + }, + ["Rose Harp"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Rosewood Lbr.", + "Coeurl Whisker", + }, + }, + ["Rose Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Black C. Feather", + }, + }, + ["Rosenbogen"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Dryad Root", + "Fiend Blood", + "Red Rose", + "Leshonki Bulb", + "Rapid Bow", + }, + }, + ["Rosewood Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Log", + }, + }, + ["Rosewood Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Log", + "Rosewood Log", + "Rosewood Log", + "Bundling Twine", + }, + }, + ["Rosewood Lumber 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 45", + }, + }, + ["Roshi Jinpachi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Arhat's Jinpachi", + "Arhat's Jinpachi", + }, + }, + ["Rosschinder"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Ash Lumber", + "Thokcha Ingot", + "Larimar", + "Wyrdstrand", + "Paralyze Potion", + }, + }, + ["Round Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Oak Lumber", + "Mahogany Lbr.", + }, + }, + ["Royal Bed"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Ebony Lumber", + "Ancient Lumber", + "Gold Ingot", + "Ruby", + "Gold Thread", + "Silk Cloth", + "Damascene Cloth", + }, + }, + ["Royal Bookshelf"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Ebony Lumber", + "Ebony Lumber", + "Lqr. Tree Lbr.", + "Lqr. Tree Lbr.", + }, + }, + ["Royal Knight Army Lance +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "R.K. Army Lance", + }, + }, + ["Royal Knight Army Shield +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "R.K. Army Shield", + }, + }, + ["Royal Knight's Belt +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Ryl.Kgt. Belt", + }, + }, + ["Royal Knight's Cloak +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Silk Cloth", + "Ryl.Kgt. Cloak", + }, + }, + ["Royal Knight's Mufflers +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Ryl.Kgt. Mufflers", + }, + }, + ["Royal Knight's Sollerets +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Chain", + "Ryl.Kgt. Sollerets", + }, + }, + ["Royal Omelette"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "King Truffle", + "Wild Onion", + "Cockatrice Meat", + "Bird Egg", + }, + }, + ["Royal Squire's Breeches +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Ryl.Sqr. Breeches", + }, + }, + ["Royal Squire's Bunk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Chestnut Lumber", + "Walnut Lumber", + "Ash Lumber", + "Ash Lumber", + "Wool Cloth", + "Wool Cloth", + "Sheep Wool", + }, + }, + ["Royal Squire's Chainmail +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Ryl.Sqr. Chainmail", + }, + }, + ["Royal Squire's Robe +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Wool Cloth", + "Ryl.Sqr. Robe", + }, + }, + ["Royal Squire's Shield +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Ryl.Sqr. Shield", + }, + }, + ["Royal Swordsman's Blade +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Ryl.Swd. Blade", + }, + }, + ["Ruby Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ruby", + "Platinum Earring", + }, + }, + ["Ruby Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ruby", + "Ptm. Earring +1", + }, + }, + ["Ruby Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ruby", + "Platinum Ring", + }, + }, + ["Ruby Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ruby", + "Platinum Ring +1", + }, + }, + ["Rugged Bugard Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Bugard Skin", + "Fire Anima", + "Fire Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Rugged Bugard Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Bugard Skin", + "Fire Anima", + "Fire Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Runner's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "S. Bgd. Leather", + "Barbarian's Belt", + }, + }, + ["Ruszor Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Ruszor Fang", + }, + }, + ["Ruszor Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Shagreen File", + "Ruszor Fang", + "Ruszor Fang", + "Ruszor Fang", + }, + }, + ["Ruszor Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ruszor Hide", + "Distilled Water", + }, + }, + ["Ruszor Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ruszor Hide", + "Distilled Water", + }, + }, + ["Ruszor Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Ruszor Hide", + "Ruszor Hide", + "Ruszor Hide", + "Distilled Water", + }, + }, + ["Ruthenium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ruthenium Ore", + "Ruthenium Ore", + "Ruthenium Ore", + "Ruthenium Ore", + }, + }, + ["S. Salis. Steak"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Sage", + "Rock Salt", + "White Bread", + "Wild Onion", + "Bird Egg", + "Buffalo Meat", + "Cerberus Meat", + }, + }, + ["Saber"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Cermet Chunk", + "Cermet Chunk", + }, + }, + ["Saber 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Alch. Kit 70", + }, + }, + ["Sabiki Rig"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Copper Ingot", + "Cotton Thread", + "Cotton Thread", + "Cotton Thread", + }, + }, + ["Sacred Degen"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Degen", + }, + }, + ["Sacred Lance"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Lance", + }, + }, + ["Sacred Mace"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Mace", + }, + }, + ["Sacred Maul"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Maul", + }, + }, + ["Sacred Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Sword", + }, + }, + ["Sacred Wand"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Hallowed Water", + "Holy Wand", + }, + }, + ["Saffron"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Saffron Blossom", + "Saffron Blossom", + "Saffron Blossom", + }, + }, + ["Sagacious Brocade Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dull Gold Thread", + "Brocade Obi", + }, + }, + ["Sagacious Gold Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dull Gold Thread", + "Gold Obi", + }, + }, + ["Sai"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Silver Thread", + "Habu Skin", + }, + }, + ["Saida Ring"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Holy Water", + "Holy Water", + "Hallowed Water", + "Orichalcum Ring", + }, + }, + ["Sailcloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Grass Thread", + "Rainbow Thread", + }, + }, + ["Sainti"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Rosewood Lbr.", + "Gold Ingot", + "Gold Ingot", + "Mercury", + }, + }, + ["Saintly Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Fish Scales", + "Ascetic's Ring", + }, + }, + ["Sairui-Ran"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Bomb Ash", + "Bast Parchment", + "Bird Egg", + }, + }, + ["Sairui-Ran 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Bast Parchment", + "Djinn Ash", + "Bird Egg", + }, + }, + ["Sakurafubuki"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Tama-Hagane", + "Silk Thread", + "Raptor Skin", + "Cermet Chunk", + }, + }, + ["Saline Broth"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Mercury", + "Beastman Blood", + "Phil. Stone", + "Buried Vestige", + }, + }, + ["Sallet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Sheet", + "Iron Sheet", + "Sheep Leather", + }, + }, + ["Sallet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 55", + }, + }, + ["Salmon Croute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Pie Dough", + "Sage", + "Rock Salt", + "Crawler Egg", + "Cheval Salmon", + "Grape Juice", + }, + }, + ["Salmon Eggs"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Cheval Salmon", + }, + }, + ["Salmon Meuniere"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Black Pepper", + "Olive Oil", + "Rock Salt", + "Cheval Salmon", + }, + }, + ["Salmon Rice Ball"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rock Salt", + "Smoked Salmon", + "Distilled Water", + }, + }, + ["Salmon Roe"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Rock Salt", + "Salmon Eggs", + }, + }, + ["Salmon Sub"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Crying Mustard", + "Apple Vinegar", + "Black Bread", + "La Theine Cbg.", + "Smoked Salmon", + "Mithran Tomato", + }, + }, + ["Salmon Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Cheval Salmon", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Salsa"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Rock Salt", + "Wild Onion", + "Mithran Tomato", + "Gysahl Greens", + }, + }, + ["Salt Ramen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Bastore Sardine", + "Batagreens", + "Black Prawn", + "Ramen Noodles", + "Salt Ramen Soup", + "Bamboo Shoots", + }, + }, + ["Salt Ramen Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Lufet Salt", + "Cheval Salmon", + "Bluetail", + "Bastore Bream", + "Distilled Water", + "Vongola Clam", + }, + }, + ["Salted Dragonfly Trout"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Dragonfly Trout", + }, + }, + ["Salted Hare"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Hare Meat", + }, + }, + ["Saltena"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Maple Sugar", + "Olive Oil", + "Imperial Flour", + "Wild Onion", + "Cockatrice Meat", + "Bird Egg", + "Paprika", + }, + }, + ["Saltwater Aquarium"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Oak Lumber", + "Coral Fragment", + "Sieglinde Putty", + "Glass Sheet", + "Saltwater Set", + "Moorish Idol", + "Moorish Idol", + "Moorish Idol", + }, + }, + ["Saltwater Set"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "White Sand", + "Sea Foliage", + "Salinator", + "Holy Water", + }, + }, + ["Salubrious Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Akaso", + "Buffalo Meat", + "Apkallufa", + }, + }, + ["Salutary Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Wool Cloth", + "Wool Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Mandra Scale", + }, + }, + ["Samsonian Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "R. Bgd. Leather", + "Barbarian's Belt", + }, + }, + ["Samurai's Nodowa"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Root", + "Dark Matter", + "Azure Leaf", + "Amber Crystal", + "Moldy Nodowa", + }, + }, + ["San d'Orian Bandana"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Ftm. Bandana", + }, + }, + ["San d'Orian Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Ftm. Boots", + }, + }, + ["San d'Orian Bow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Yew Lumber", + "Ryl.Arc. Longbow", + }, + }, + ["San d'Orian Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Arc. Cesti", + }, + }, + ["San d'Orian Clogs"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Holly Lumber", + "Ryl.Ftm. Clogs", + }, + }, + ["San d'Orian Dagger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Ryl.Sqr. Dagger", + }, + }, + ["San d'Orian Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Ftm. Gloves", + }, + }, + ["San d'Orian Halberd"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ryl.Sqr. Halberd", + }, + }, + ["San d'Orian Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Ryl. Squire's Helm", + }, + }, + ["San d'Orian Horn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Femur", + "Ryl.Spr. Horn", + }, + }, + ["San d'Orian Mace"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Ryl.Sqr. Mace", + }, + }, + ["San d'Orian Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Ryl.Sqr. Mufflers", + }, + }, + ["San d'Orian Sill"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["San d'Orian Sollerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Ryl.Sqr. Sollerets", + }, + }, + ["San d'Orian Spear"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ryl.Spr. Spear", + }, + }, + ["San d'Orian Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Ryl.Arc. Sword", + }, + }, + ["San d'Orian Tea"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Win. Tea Leaves", + "Sage", + "Selbina Milk", + "Distilled Water", + }, + }, + ["San d'Orian Tea 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 70", + }, + }, + ["San d'Orian Tea Set"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Angelstone", + "Kaolin", + "Kaolin", + }, + }, + ["San d'Orian Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Ftm. Trousers", + }, + }, + ["San d'Orian Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Ryl.Ftm. Tunic", + }, + }, + ["San d'Orian Vest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ryl.Ftm. Vest", + }, + }, + ["Sanctified Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Petrified Log", + "Ice Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Sancus Sachet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Siren's Macrame", + "Sif's Macrame", + "Sif's Macrame", + "Vulcanite Ore", + "Arasy Sachet", + }, + }, + ["Sandals"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Sheep Leather", + }, + }, + ["Sanjaku-Tenugui"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Saotome-no-tachi"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Plating", + "Dark Matter", + "Ruthenium Ore", + "Moldy G. Katana", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Amber Crystal", + }, + }, + ["Sapara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Ingot", + "Silver Ingot", + }, + }, + ["Sapphire Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sapphire", + "Platinum Earring", + }, + }, + ["Sapphire Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sapphire", + "Ptm. Earring +1", + }, + }, + ["Sapphire Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sapphire", + "Platinum Ring", + }, + }, + ["Sapphire Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sapphire", + "Platinum Ring +1", + }, + }, + ["Sarcenet Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Sarcenet Cloth", + "Sarcenet Cloth", + }, + }, + ["Sardine Ball"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Bastore Sardine", + "Distilled Water", + }, + }, + ["Sardine Ball 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Distilled Water", + "Hamsi", + }, + }, + ["Sardine Ball 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Horo Flour", + "Distilled Water", + "Senroh Sardine", + }, + }, + ["Sardonyx"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Red Rock", + }, + }, + ["Sardonyx 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Flame Geode", + }, + }, + ["Sardonyx Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sardonyx", + "Silver Earring", + }, + }, + ["Sardonyx Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sardonyx", + "Silver Earring +1", + }, + }, + ["Sardonyx Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sardonyx", + "Silver Ring", + }, + }, + ["Sardonyx Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sardonyx", + "Silver Ring +1", + }, + }, + ["Sasah Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Phoenix Feather", + "Urunday Lumber", + }, + }, + ["Sasah Wand 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 94", + }, + }, + ["Sasanuki"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Ancient Lumber", + "Thokcha Ingot", + "Thokcha Ingot", + "Gold Ingot", + "Rainbow Thread", + "Scintillant Ingot", + "Smildn. Leather", + }, + }, + ["Sasuke Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Mercury", + "Bismuth Sheet", + }, + }, + ["Sausage"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Black Pepper", + "Sage", + "Maple Log", + "Rock Salt", + "G. Sheep Meat", + }, + }, + ["Sausage Roll"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + "Honey", + "Distilled Water", + "Sausage", + }, + }, + ["Savage Mole Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Red Gravel", + "Snapping Mole", + "Helmet Mole", + "Loam", + "Little Worm", + "Little Worm", + }, + }, + ["Scale Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Bronze Scales", + "Cotton Thread", + "Leather Trousers", + }, + }, + ["Scale Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Bronze Scales", + "Cotton Thread", + "Leather Gloves", + }, + }, + ["Scale Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Bronze Scales", + "Cotton Thread", + "Leather Highboots", + }, + }, + ["Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Bronze Scales", + "Bronze Scales", + "Bronze Scales", + "Cotton Thread", + "Sheep Leather", + "Leather Vest", + }, + }, + ["Scanner"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Ice Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Scapegoat"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Phoenix Feather", + "Marid Tusk", + }, + }, + ["Scarlet Linen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Thread", + "Bloodthread", + }, + }, + ["Scarlet Ribbon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Velvet Cloth", + }, + }, + ["Scepter"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Mercury", + }, + }, + ["Schlaeger"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Darksteel Ingot", + "Silver Ingot", + }, + }, + ["Schwert"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Mythril Ingot", + "Moonstone", + "Cermet Chunk", + }, + }, + ["Scimitar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Steel Ingot", + }, + }, + ["Scimitar Cactus"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Iron Sheet", + "Red Gravel", + "Cactus Arm", + "Humus", + }, + }, + ["Scintillant Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Orichalcum Ore", + "Luminium Ore", + "Luminium Ore", + "Luminium Ore", + }, + }, + ["Scission Sphere"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Scorpion Stinger", + }, + }, + ["Scission Sphere 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Magicked Steel", + }, + }, + ["Scission Sphere 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Fetich Head", + }, + }, + ["Scission Sphere 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elshimo Marble", + }, + }, + ["Scission Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Earth Card", + }, + }, + ["Scope"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Glass Fiber", + "Artificial Lens", + "Artificial Lens", + "Myth.Gear Mach.", + }, + }, + ["Scope II"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Glass Fiber", + "Artificial Lens", + "Artificial Lens", + "Golden Gear", + "A.U Brass Sheet", + }, + }, + ["Scope III"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Glass Fiber", + "Artificial Lens", + "Artificial Lens", + "A.U Brass Sheet", + "Platinum Gear", + }, + }, + ["Scope IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Glass Fiber", + "Artificial Lens", + "Artificial Lens", + "A.U Brass Sheet", + "Ocl. Gearbox", + }, + }, + ["Scorpion Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Insect Fltchg.", + "Scp. Arrowhd.", + }, + }, + ["Scorpion Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Scorpion Claw", + }, + }, + ["Scorpion Arrowheads 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Bone Chip", + "Bone Chip", + "Scorpion Claw", + "Scorpion Claw", + "Scorpion Claw", + "Shagreen File", + }, + }, + ["Scorpion Breastplate"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Ram Leather", + "Ram Leather", + "H.Q. Scp. Shell", + "H.Q. Scp. Shell", + "H.Q. Scp. Shell", + "H.Q. Scp. Shell", + }, + }, + ["Scorpion Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "H.Q. Scp. Shell", + "H.Q. Scp. Shell", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Scorpion Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Scorpion Shell", + "Scorpion Shell", + "Venomous Claw", + }, + }, + ["Scorpion Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Sheep Leather", + "Ram Leather", + "H.Q. Scp. Shell", + "H.Q. Scp. Shell", + }, + }, + ["Scorpion Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Ram Leather", + "Pugil Scales", + "Scorpion Shell", + }, + }, + ["Scorpion Mask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Scorpion Shell", + }, + }, + ["Scorpion Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Pugil Scales", + "Scorpion Shell", + }, + }, + ["Scorpion Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Scorpion Shell", + }, + }, + ["Scorpion Ring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 60", + }, + }, + ["Scorpion Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Cloth", + "Ram Leather", + "Scorpion Shell", + }, + }, + ["Scout's Bolt"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Dark Matter", + "Moldy Bolt", + "Relic Adaman", + "Malachite Crystal", + }, + }, + ["Scout's Crossbow"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Macuil Plating", + "Dark Matter", + "Cyan Coral", + "Moldy Crossbow", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Malachite Crystal", + }, + }, + ["Scout's Gorget"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Pelt", + "Dark Matter", + "S. Faulpie Leather", + "Malachite Crystal", + "Moldy Gorget", + }, + }, + ["Scutum"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Oak Lumber", + }, + }, + ["Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Holly Lumber", + "Grass Cloth", + }, + }, + ["Sea Bass Croute"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Pie Dough", + "Sage", + "Rock Salt", + "Lizard Egg", + "Zafmlug Bass", + "King Truffle", + "Grape Juice", + }, + }, + ["Sea Dragon Liver"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Soryu's Liver", + "Sekiryu's Liver", + "Hakuryu's Liver", + "Kokuryu's Liver", + }, + }, + ["Seadog Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Coral Fragment", + "Coral Fragment", + }, + }, + ["Seafood Paella"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Tarutaru Rice", + "Saffron", + "Wild Onion", + "Gigant Squid", + "Distilled Water", + "Black Prawn", + "Mussel", + }, + }, + ["Seafood Pitaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rock Salt", + "Imperial Flour", + "Wild Onion", + "Distilled Water", + "Graubg. Lettuce", + "Butterpear", + "Peeled Lobster", + }, + }, + ["Seafood Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Nebimonite", + "Gold Lobster", + "Shall Shell", + "Distilled Water", + "Gysahl Greens", + }, + }, + ["Seafood Stew 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Distilled Water", + "Gysahl Greens", + "Istakoz", + "Ahtapot", + "Istiridye", + }, + }, + ["Seafood Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Fish Stock", + "Danceshroom", + "Gold Lobster", + "Bastore Bream", + "Distilled Water", + "Cotton Tofu", + "Cibol", + "Napa", + }, + }, + ["Sealord Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Sealord Skin", + "Distilled Water", + }, + }, + ["Sealord Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Sealord Skin", + "Distilled Water", + }, + }, + ["Secretaire"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + "Mahogany Lbr.", + }, + }, + ["Seer's Crown"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Lizard Molt", + "Bone Chip", + "Coeurl Whisker", + }, + }, + ["Seer's Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + "Sheep Leather", + }, + }, + ["Seer's Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Thread", + "Cotton Cloth", + "Cotton Cloth", + "Sheep Leather", + }, + }, + ["Seer's Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + "Sheep Leather", + }, + }, + ["Seer's Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + "Linen Cloth", + "Linen Cloth", + "Sheep Leather", + }, + }, + ["Seito"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Muting Potion", + "Mokuto", + }, + }, + ["Sekishitsu"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Vrml. Lacquer", + "Honey", + }, + }, + ["Selbina Butter"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Rock Salt", + "Selbina Milk", + }, + }, + ["Self Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Lumber", + "Grass Thread", + "Cotton Cloth", + "Giant Femur", + }, + }, + ["Semainier"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Ebony Lumber", + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Senbaak Nagan"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Damascus Ingot", + "Damascus Ingot", + "Damascus Ingot", + "Scarletite Ingot", + "Wyvern Skin", + "Urunday Lumber", + "Gabbrath Horn", + }, + }, + ["Senroh Skewer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bluetail", + "Shall Shell", + "Shall Shell", + "Contortopus", + "Senroh Sardine", + }, + }, + ["Senroh Skewer 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bluetail", + "Shall Shell", + "Shall Shell", + "Contortacle", + "Contortacle", + "Contortacle", + "Senroh Sardine", + }, + }, + ["Serpette"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Grass Cloth", + "Giant Femur", + "Giant Femur", + "Bukktooth", + }, + }, + ["Seshaw Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Sif's Macrame", + "Ancestral Cloth", + "Cehuetzi Pelt", + }, + }, + ["Severus Claws"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Likho Talon", + }, + }, + ["Sha'ir Crackows"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Gold Thread", + "Velvet Cloth", + "Tiger Leather", + "Tiger Leather", + "Buffalo Leather", + }, + }, + ["Sha'ir Gages"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Garnet", + "Gold Thread", + "Velvet Cloth", + "Tiger Leather", + "H.Q. Bugard Skin", + }, + }, + ["Sha'ir Manteel"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Ruby", + "Sapphire", + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Cashmere Cloth", + }, + }, + ["Sha'ir Seraweels"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Tiger Leather", + "Taffeta Cloth", + }, + }, + ["Sha'ir Turban"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Pigeon's Blood", + "Hippogryph Fthr.", + }, + }, + ["Shabti Armet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Gold Ingot", + "Sheep Leather", + "Mercury", + "Bismuth Sheet", + "Bismuth Sheet", + }, + }, + ["Shabti Cuirass"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + "Bismuth Sheet", + "Bismuth Sheet", + "Bismuth Sheet", + "Bismuth Sheet", + }, + }, + ["Shabti Cuisses"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Bismuth Sheet", + "Bismuth Sheet", + }, + }, + ["Shabti Gauntlets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "Bismuth Sheet", + "Bismuth Sheet", + "Leather Gloves", + "Leather Gloves", + }, + }, + ["Shabti Sabatons"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Ram Leather", + "Ram Leather", + "Mercury", + "Bismuth Sheet", + "Bismuth Sheet", + "Bismuth Sheet", + }, + }, + ["Shade Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminicloth", + "Velvet Cloth", + "Tiger Leather", + "Uragnite Shell", + "Uragnite Shell", + "Eft Skin", + "Eft Skin", + "Photoanima", + }, + }, + ["Shade Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminicloth", + "Lizard Skin", + "Tiger Leather", + "Tiger Leather", + "Uragnite Shell", + "Eft Skin", + "Photoanima", + }, + }, + ["Shade Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminicloth", + "Sheep Leather", + "Tiger Leather", + "Uragnite Shell", + "Eft Skin", + "Photoanima", + }, + }, + ["Shade Tiara"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Uragnite Shell", + "Eft Skin", + "Photoanima", + }, + }, + ["Shade Tights"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminicloth", + "Velvet Cloth", + "Tiger Leather", + "Uragnite Shell", + "Eft Skin", + "Photoanima", + }, + }, + ["Shadow Apple"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Coffee Powder", + "Faerie Apple", + }, + }, + ["Shadow Bow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Assassin's Bow", + }, + }, + ["Shadow Roll"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Thread", + "Wool Cloth", + "Sheep Leather", + }, + }, + ["Shadow Roll 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 40", + }, + }, + ["Shadow Throne"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Demon Skull", + "Giant Femur", + "Giant Femur", + "Demon Horn", + "Demon Horn", + "Fiend Blood", + "Necropsyche", + "Demon Blood", + }, + }, + ["Shadowy Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dragon Meat", + "Nopales", + "Agaricus", + }, + }, + ["Shallops Tropicale"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Bay Leaves", + "Rock Salt", + "Kazham Pineapl.", + "Shall Shell", + "Distilled Water", + }, + }, + ["Shallops Tropicale 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 80", + }, + }, + ["Shark Fin Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ginger", + "Popoto", + "Chicken Bone", + "Rock Salt", + "Sleepshroom", + "Silver Shark", + "Distilled Water", + "Bird Egg", + }, + }, + ["Sharur"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ormolu Ingot", + "Mercury", + "Urunday Lumber", + "Bztavian Stinger", + }, + }, + ["Sheep Chammy"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Win. Tea Leaves", + "Clot Plasma", + "Distilled Water", + }, + }, + ["Sheep Chammy 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Willow Log", + "Clot Plasma", + "Distilled Water", + }, + }, + ["Sheep Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Willow Log", + "Distilled Water", + }, + }, + ["Sheep Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Win. Tea Leaves", + "Distilled Water", + }, + }, + ["Sheep Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Sheepskin", + "Sheepskin", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Sheep Wool"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheepskin", + "Sheepskin", + }, + }, + ["Shell Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Seashell", + "Seashell", + }, + }, + ["Shell Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Turtle Shell", + }, + }, + ["Shell Lamp"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Uragnite Shell", + "F. Glass Sheet", + "Kaolin", + "Kaolin", + "Bomb Arm", + }, + }, + ["Shell Powder"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Seashell", + "Seashell", + "Seashell", + }, + }, + ["Shell Powder 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Vongola Clam", + "Vongola Clam", + }, + }, + ["Shell Powder 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 5", + }, + }, + ["Shell Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Fish Scales", + "Seashell", + }, + }, + ["Shield Plaque"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Mythril Sheet", + "Rosewood Lbr.", + "Gold Ingot", + "Gold Sheet", + "Turquoise", + "Rhodonite", + "Scarlet Linen", + }, + }, + ["Shigeto Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Gold Sheet", + "Wisteria Lumber", + "Wisteria Lumber", + "Urushi", + "Shortbow", + }, + }, + ["Shihei"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Black Ink", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Shimmering Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Ruddy Seema", + "Ruddy Seema", + "Ruddy Seema", + "Ruddy Seema", + }, + }, + ["Shining Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Ingot", + "Scintillant Ingot", + }, + }, + ["Shinobi Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Iron Chain", + "Linen Thread", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Shinobi Hachigane"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Iron Chain", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Shinobi Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Silk Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Shinobi Kyahan"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Linen Thread", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Shinobi Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Iron Chain", + "Linen Thread", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Shinobi-Gatana"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Tama-Hagane", + "Elm Lumber", + "Cotton Thread", + "Lizard Skin", + }, + }, + ["Shinobi-Tabi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Cotton Cloth", + "Saruta Cotton", + }, + }, + ["Shiny Gold Thread"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Silk Thread", + "Light Anima", + "Light Anima", + "Light Anima", + }, + }, + ["Shirogatana"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Ancient Lumber", + "Silver Ingot", + "Scintillant Ingot", + "Wamoura Silk", + }, + }, + ["Shiromochi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sticky Rice", + "Cornstarch", + "Distilled Water", + }, + }, + ["Shock Absorber"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Darksteel Sheet", + "Bomb Ash", + "Carbon Fiber", + "Imperial Cermet", + }, + }, + ["Shock Absorber II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Sheet", + "Carbon Fiber", + "Cluster Ash", + "Dark Adaman", + "Imperial Cermet", + }, + }, + ["Shock Absorber III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Carbon Fiber", + "Drk. Adm. Sheet", + "Imperial Cermet", + "Djinn Ash", + "Titanium Sheet", + }, + }, + ["Shock Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminous Shell", + "Carapace Subligar", + }, + }, + ["Shoes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Dhalmel Leather", + "Dhalmel Leather", + }, + }, + ["Shofar"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Behemoth Horn", + "Beetle Jaw", + }, + }, + ["Shortbow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Lumber", + "Grass Thread", + "Grass Cloth", + }, + }, + ["Shotel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Silver Ingot", + "Bugard Tusk", + "Tiger Eye", + }, + }, + ["Shower Stand"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Gold Ingot", + "Sieglinde Putty", + }, + }, + ["Shrimp Cracker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rock Salt", + "Gold Lobster", + "Distilled Water", + }, + }, + ["Shrimp Cracker 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rock Salt", + "Distilled Water", + "Istakoz", + }, + }, + ["Shrimp Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tin Ingot", + "Beeswax", + "Moblumin Sheet", + "Moblin Putty", + }, + }, + ["Shrimp Lure"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Glass Fiber", + "Crayfish", + }, + }, + ["Shrimp Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Bastore Sweeper", + }, + }, + ["Shuriken"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Cotton Thread", + }, + }, + ["Sickle"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Ash Lumber", + "Grass Cloth", + }, + }, + ["Sieglinde Putty"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Flaxseed Oil", + "Flaxseed Oil", + "Shell Powder", + "Shell Powder", + "Zinc Oxide", + }, + }, + ["Sieglinde Putty 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Alch. Kit 30", + }, + }, + ["Sif's Macrame"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Gold Thread", + "Gold Thread", + "Siren's Hair", + "Sif's Lock", + }, + }, + ["Silence Baghnakhs"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Silencing Potion", + "Brass Baghnakhs", + }, + }, + ["Silence Dagger"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Silencing Potion", + "Brass Dagger", + }, + }, + ["Silencing Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Peppers", + "2Leaf Mandra Bud", + "Scream Fungus", + }, + }, + ["Silencing Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Lycopodium Flower", + "Scream Fungus", + }, + }, + ["Silencing Potion 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Distilled Water", + "Sobbing Fungus", + "Sobbing Fungus", + }, + }, + ["Silencing Potion 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Mercury", + "Cobalt Jellyfish", + }, + }, + ["Silencing Potion 5"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Mercury", + "Denizanasi", + }, + }, + ["Silent Oil"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Slime Oil", + "Beeswax", + "Beeswax", + }, + }, + ["Silent Oil 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Olive Oil", + "Beeswax", + "Beeswax", + }, + }, + ["Silk Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Silk Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Silk Thread", + }, + }, + ["Silk Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Silk Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Aquamarine", + "Aquamarine", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Silk Cuffs 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Lapis Lazuli", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Silk Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Silver Thread", + "Velvet Cloth", + }, + }, + ["Silk Headband"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Cloth", + "Carbon Fiber", + }, + }, + ["Silk Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Silk Cloth", + "Saruta Cotton", + }, + }, + ["Silk Pumps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Silk Cloth", + "Sheep Leather", + }, + }, + ["Silk Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Silk Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Silk Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["Silk Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Crawler Cocoon", + "Crawler Cocoon", + }, + }, + ["Silk Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Crawler Cocoon", + "Crawler Cocoon", + "Crawler Cocoon", + "Crawler Cocoon", + "Crawler Cocoon", + "Crawler Cocoon", + "Spindle", + }, + }, + ["Silken Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + }, + }, + ["Silken Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lapis Lazuli", + "Lapis Lazuli", + "Gold Thread", + "Velvet Cloth", + "Imp. Silk Cloth", + }, + }, + ["Silken Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Silver Thread", + "Velvet Cloth", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + }, + }, + ["Silken Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Raptor Skin", + "Tiger Leather", + "Tiger Leather", + "Imp. Silk Cloth", + }, + }, + ["Silken Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Sheet", + "Gold Thread", + "Velvet Cloth", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + }, + }, + ["Silky Suede"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Garnet", + "Buffalo Leather", + }, + }, + ["Silver Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Slv. Arrowheads", + "Yagudo Fltchg.", + }, + }, + ["Silver Arrow 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Silver Ingot", + "Yagudo Feather", + "Yagudo Feather", + }, + }, + ["Silver Arrow 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 30", + }, + }, + ["Silver Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Silver Ingot", + }, + }, + ["Silver Bangles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + }, + }, + ["Silver Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Lizard Belt", + }, + }, + ["Silver Belt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 25", + }, + }, + ["Silver Brocade"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Silk Thread", + "Rainbow Thread", + "Rainbow Thread", + "Silver Thread", + "Silver Thread", + }, + }, + ["Silver Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Firesand", + }, + }, + ["Silver Cassandra"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Ocl. Ingot", + "Ocl. Ingot", + "Mercury", + "Smildn. Leather", + "Darksteel Hexagun", + }, + }, + ["Silver Chain"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + }, + }, + ["Silver Chain 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Mandrel", + }, + }, + ["Silver Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + }, + }, + ["Silver Greaves"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Mercury", + "Greaves", + }, + }, + ["Silver Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Ingot", + }, + }, + ["Silver Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Chain Hose", + }, + }, + ["Silver Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Beastcoin", + "Silver Beastcoin", + "Silver Beastcoin", + "Silver Beastcoin", + }, + }, + ["Silver Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ore", + "Silver Ore", + "Silver Ore", + "Silver Ore", + }, + }, + ["Silver Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold. Kit 20", + }, + }, + ["Silver Ingot 4"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ore", + "Silver Nugget", + "Silver Nugget", + "Silver Nugget", + "Silver Nugget", + "Silver Nugget", + "Silver Nugget", + }, + }, + ["Silver Mail"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Chain", + "Silver Chain", + "Chainmail", + }, + }, + ["Silver Mask"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Mercury", + "Iron Mask", + }, + }, + ["Silver Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Chain", + "Chain Mittens", + }, + }, + ["Silver Nugget"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Leaf", + "Panacea", + }, + }, + ["Silver Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Silver Thread", + "Silver Thread", + }, + }, + ["Silver Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + }, + }, + ["Silver Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + }, + }, + ["Silver Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Workshop Anvil", + }, + }, + ["Silver Thread"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silk Thread", + }, + }, + ["Silver Thread 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Silver Ingot", + "Silver Ingot", + "Silk Thread", + "Silk Thread", + "Silk Thread", + "Spindle", + }, + }, + ["Simit"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rock Salt", + "Simsim", + "Imperial Flour", + "Selbina Milk", + "Buburimu Grape", + "Distilled Water", + "White Honey", + }, + }, + ["Simple Bed"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Lauan Lumber", + "Holly Lumber", + "Holly Lumber", + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Single Hook Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. S.H. Rod", + }, + }, + ["Single Hook Fishing Rod 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Carbon Fiber", + "Glass Fiber", + "Glass Fiber", + }, + }, + ["Sipahi Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Mythril Sheet", + "Karakul Leather", + "Marid Leather", + "Marid Leather", + "Wamoura Cloth", + }, + }, + ["Sipahi Dastanas"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Karakul Leather", + "Marid Leather", + "Marid Hair", + "Karakul Cloth", + }, + }, + ["Sipahi Jawshan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Steel Sheet", + "Gold Chain", + "Silk Cloth", + "Karakul Leather", + "Marid Leather", + "Marid Leather", + "Wamoura Cloth", + }, + }, + ["Sipahi Turban"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Chain", + "Marid Hair", + "Wamoura Cloth", + "Wamoura Cloth", + }, + }, + ["Sipahi Zerehs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Marid Leather", + "Marid Hair", + "Karakul Cloth", + "Karakul Cloth", + "Wamoura Cloth", + }, + }, + ["Siren's Macrame"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Rainbow Thread", + "Gold Thread", + "Gold Thread", + "Siren's Hair", + "Siren's Hair", + }, + }, + ["Sis Kebabi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Black Pepper", + "Rock Salt", + "Wild Onion", + "Mithran Tomato", + "Karakul Meat", + }, + }, + ["Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + "Cotton Cloth", + }, + }, + ["Skeleton Key"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Tooth", + "Bat Fang", + "Bat Fang", + "Chicken Bone", + "Carbon Fiber", + "Glass Fiber", + "Animal Glue", + "Hecteyes Eye", + }, + }, + ["Skeleton Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + "Osseous Serum", + }, + }, + ["Skrymir Cord"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Gold Thread", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + "Khoma Thread", + "Wyrm Ash", + }, + }, + ["Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Sleep Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Bird Fletchings", + "Sleep Arrowhd.", + }, + }, + ["Sleep Arrow 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 50", + }, + }, + ["Sleep Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Ram Horn", + "Animal Glue", + "Sleeping Potion", + }, + }, + ["Sleep Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Slp. Bolt Heads", + }, + }, + ["Sleep Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Slp. Bolt Heads", + "Slp. Bolt Heads", + "Slp. Bolt Heads", + "Bundling Twine", + }, + }, + ["Sleep Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Animal Glue", + "Sleeping Potion", + }, + }, + ["Sleeping Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Chamomile", + "Poison Flour", + "Sleepshroom", + }, + }, + ["Sleeping Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Chamomile", + "Chamomile", + "Poison Flour", + "Poison Flour", + "Triturator", + "Sleepshroom", + "Sleepshroom", + }, + }, + ["Slice of Bluetail"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bluetail", + }, + }, + ["Slice of Bluetail 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cook. Kit 15", + }, + }, + ["Slice of Carp"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Moat Carp", + }, + }, + ["Slice of Carp 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Forest Carp", + }, + }, + ["Sliced Cod"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tiger Cod", + }, + }, + ["Sliced Sardine"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bastore Sardine", + }, + }, + ["Sliced Sardine 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Hamsi", + }, + }, + ["Sliced Sardine 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Senroh Sardine", + }, + }, + ["Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Smash Cesti"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Earth Cell", + "Lam. Wind Cell", + "Lizard Cesti", + }, + }, + ["Smilodon Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Smilodon Hide", + "Distilled Water", + }, + }, + ["Smilodon Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Smilodon Hide", + "Distilled Water", + }, + }, + ["Smilodon Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tanning Vat", + "Smilodon Hide", + "Smilodon Hide", + "Smilodon Hide", + "Distilled Water", + }, + }, + ["Smilodon Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Smilodon Hide", + }, + }, + ["Smilodon Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Smildn. Leather", + }, + }, + ["Smithing Set 25"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Lizard Skin", + }, + }, + ["Smithing Set 45"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mythril Ingot", + "Mythril Ingot", + }, + }, + ["Smithing Set 65"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Cockatrice Skin", + }, + }, + ["Smithing Set 71"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Lizard Skin", + }, + }, + ["Smithing Set 76"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Tin Ingot", + "Muketsu", + }, + }, + ["Smithing Set 80"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Raptor Skin", + "Broadsword", + }, + }, + ["Smithing Set 84"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Steel Sheet", + "Darksteel Mufflers", + }, + }, + ["Smithing Set 91"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Mahogany Lbr.", + }, + }, + ["Smithing Set 94"] = { + ["crystal"] = "Pyre Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Cockatrice Skin", + "Mercury", + }, + }, + ["Smiting Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tenebrium", + "Mythril Scythe", + }, + }, + ["Smoked Salmon"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Walnut Log", + "Rock Salt", + "Cheval Salmon", + }, + }, + ["Smooth Beetle Jaw"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Beetle Jaw", + "Wind Anima", + "Wind Anima", + "Light Anima", + }, + }, + ["Smooth Sheep Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Willow Log", + "Wind Anima", + "Wind Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Smooth Sheep Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Win. Tea Leaves", + "Wind Anima", + "Wind Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Smooth Velvet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Wool Thread", + "Wool Thread", + "Wind Anima", + "Wind Anima", + "Light Anima", + }, + }, + ["Smooth Velvet 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Cotton Thread", + "Cotton Thread", + "Wind Anima", + "Wind Anima", + "Light Anima", + }, + }, + ["Snakeeye"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Hydra Fang", + "Hydra Fang", + "Wamoura Silk", + }, + }, + ["Sniper's Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Archer's Ring", + }, + }, + ["Snoll Gelato"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Olive Oil", + "Selbina Milk", + "Bird Egg", + "Snoll Arm", + }, + }, + ["Snoll Gelato 2"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Olive Oil", + "Vanilla", + "Selbina Milk", + "Bird Egg", + "Snoll Arm", + }, + }, + ["Snow Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ice Bead", + "Orichalcum Ring", + }, + }, + ["Snowsteel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Snowsteel Ore", + "Snowsteel Ore", + "Snowsteel Ore", + "Snowsteel Ore", + }, + }, + ["Snowsteel Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Snowsteel", + }, + }, + ["Snowsteel Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "Snowsteel", + "Snowsteel", + "Snowsteel", + "Snowsteel", + "Snowsteel", + "Snowsteel", + }, + }, + ["Soba Noodles"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Buckwheat Flour", + }, + }, + ["Socks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + "Dhalmel Leather", + }, + }, + ["Soft Bugard Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Bugard Skin", + "Lightning Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Soft Bugard Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Bugard Skin", + "Lightning Anima", + "Lightning Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Soil Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Soil Hachimaki"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Soil Kyahan"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Cotton Thread", + "Linen Cloth", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Soil Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Earth Bead", + "Orichalcum Ring", + }, + }, + ["Soil Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Linen Cloth", + "Linen Cloth", + "Wool Cloth", + }, + }, + ["Soil Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Grass Thread", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Sole Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Dil", + }, + }, + ["Sole Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Black Sole", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Solea"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Sollerets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Mythril Sheet", + "Greaves", + }, + }, + ["Sombra Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Cloth", + "Damascene Cloth", + "Raaz Leather", + "Intuila's Hide", + "Intuila's Hide", + "Arthro's Shell", + "Arthro's Shell", + }, + }, + ["Sombra Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Damascene Cloth", + "Behemoth Hide", + "Raaz Leather", + "Raaz Leather", + "Intuila's Hide", + "Arthro's Shell", + }, + }, + ["Sombra Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Damascene Cloth", + "Buffalo Leather", + "Raaz Leather", + "Intuila's Hide", + "Arthro's Shell", + }, + }, + ["Sombra Tiara"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Intuila's Hide", + "Arthro's Shell", + }, + }, + ["Sombra Tights"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Cloth", + "Damascene Cloth", + "Raaz Leather", + "Intuila's Hide", + "Arthro's Shell", + }, + }, + ["Sonic Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ram Leather", + "Speed Belt", + }, + }, + ["Sopa Pez Blanco"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Selbina Milk", + "Wild Onion", + "San d'Or. Carrot", + "Distilled Water", + "Dil", + }, + }, + ["Sopa Pez Blanco 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Selbina Milk", + "Black Sole", + "Wild Onion", + "San d'Or. Carrot", + "Distilled Water", + }, + }, + ["Sorcerer's Staff"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Effluvium", + "Dark Matter", + "Khoma Thread", + "Moldy Staff", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Iolite Crystal", + }, + }, + ["Sorcerer's Stole"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Yggdreant Bole", + "Dark Matter", + "Cypress Log", + "Iolite Crystal", + "Moldy Stole", + }, + }, + ["Soshi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Black Ink", + "Bast Parchment", + "Bast Parchment", + }, + }, + ["Soy Milk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blue Peas", + "Blue Peas", + "Distilled Water", + }, + }, + ["Soy Ramen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pamtam Kelp", + "Tiger Cod", + "Bird Egg", + "Cibol", + "Porxie Pork", + "Ramen Noodles", + "Soy Ramen Soup", + "Bamboo Shoots", + }, + }, + ["Soy Ramen Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Chicken Bone", + "Chicken Bone", + "Wild Onion", + "Cockatrice Meat", + "Distilled Water", + "Cibol", + "Soy Sauce", + }, + }, + ["Spaghetti"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Rock Salt", + "Semolina", + }, + }, + ["Spark Baselard"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Mythril Baselard", + }, + }, + ["Spark Bilbo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Bilbo", + }, + }, + ["Spark Dagger"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Dagger", + }, + }, + ["Spark Degen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Mythril Degen", + }, + }, + ["Spark Fork"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Battle Fork", + }, + }, + ["Spark Kris"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Darksteel Kris", + }, + }, + ["Spark Lance"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Lance", + }, + }, + ["Spark Rapier"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Rapier", + }, + }, + ["Spark Spear"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Copper Ingot", + "Brass Spear", + }, + }, + ["Sparkling Hand"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Silver Ingot", + "Parchment", + "Twinkle Powder", + "Prism Powder", + }, + }, + ["Sparkstrand Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "W. Spider's Web", + "W. Spider's Web", + }, + }, + ["Sparkstrand Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spindle", + "W. Spider's Web", + "W. Spider's Web", + "W. Spider's Web", + "W. Spider's Web", + "W. Spider's Web", + "W. Spider's Web", + }, + }, + ["Spartan Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Bronze Ingot", + "Firesand", + "Twinkle Powder", + }, + }, + ["Spartan Hoplon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bld. Ram Lthr.", + "Hoplon", + }, + }, + ["Spatha"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Bronze Ingot", + "Lizard Skin", + }, + }, + ["Spatha 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 25", + }, + }, + ["Spear"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Ingot", + "Ash Lumber", + "Linen Thread", + }, + }, + ["Spectral Serum"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Luminicloth", + "Luminicloth", + "Mercury", + }, + }, + ["Speed Apple"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Honey", + }, + }, + ["Speedloader"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Mythril Sheet", + "Glass Fiber", + "Mythril Coil", + "Myth.Gear Mach.", + }, + }, + ["Speedloader II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Gold Sheet", + "Glass Fiber", + "Mythril Coil", + "Myth.Gear Mach.", + }, + }, + ["Spence"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Hickory Lumber", + "Hickory Lumber", + "Teak Lumber", + "Teak Lumber", + }, + }, + ["Sphene Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sphene", + "Mythril Earring", + }, + }, + ["Sphene Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sphene", + "Mythril Earring +1", + }, + }, + ["Sphene Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sphene", + "Mythril Ring", + }, + }, + ["Sphene Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sphene", + "Mythril Ring +1", + }, + }, + ["Spicy Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mand. Sprout", + "Mand. Sprout", + "G. Sheep Meat", + "Isleracea", + }, + }, + ["Spicy Cracker"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Tarutaru Rice", + "Rock Salt", + "Distilled Water", + }, + }, + ["Spiked Club"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Walnut Lumber", + "Walnut Lumber", + }, + }, + ["Spinel Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Spinel", + "Platinum Earring", + }, + }, + ["Spinel Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Spinel", + "Ptm. Earring +1", + }, + }, + ["Spinel Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Spinel", + "Platinum Ring", + }, + }, + ["Spinel Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Spinel", + "Platinum Ring +1", + }, + }, + ["Spirit Core"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Adaman Ore", + "Adaman Ore", + "Adaman Ore", + "Earth Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Spirit Maul"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Holy Maul", + }, + }, + ["Spirit Orichalcum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Orichalcum Ore", + "Earth Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Spirit Shell"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Turtle Shell", + "Earth Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Spirit Smilodon Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Earth Anima", + "Earth Anima", + "Dark Anima", + "Smilodon Hide", + "Distilled Water", + }, + }, + ["Spirit Smilodon Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Earth Anima", + "Earth Anima", + "Dark Anima", + "Smilodon Hide", + "Distilled Water", + }, + }, + ["Spirit Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Holy Sword", + }, + }, + ["Spolia Chapeau"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beeswax", + "Mag. Ctn. Cloth", + "Sheep Chammy", + "Wyrdweave", + "Wyrdweave", + "Wyrdweave", + }, + }, + ["Spolia Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mag. Ctn. Cloth", + "Sheep Chammy", + "Wyrdweave", + "Wyrdweave", + "Wyrdweave", + }, + }, + ["Spolia Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Chammy", + "Catobl. Leather", + "Wyrdstrand", + "Wyrdweave", + "Wyrdweave", + }, + }, + ["Spolia Saio"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Chain", + "Beeswax", + "Buffalo Leather", + "Mag. Ctn. Cloth", + "Sheep Chammy", + "Wyrdstrand", + "Wyrdweave", + "Wyrdweave", + }, + }, + ["Spolia Trews"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mag. Ctn. Cloth", + "Mag. Ctn. Cloth", + "Sheep Chammy", + "Wyrdstrand", + "Wyrdweave", + }, + }, + ["Spore Bomb"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Bast Parchment", + "Danceshroom", + }, + }, + ["Sprightly Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Danceshroom", + "Wild Onion", + "Reishi Mushroom", + "Distilled Water", + "Agaricus", + }, + }, + ["Square Jalousie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Squid Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Gigant Squid", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Stabilizer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Sieglinde Putty", + "Ebonite", + "Black Ghost", + "Water Tank", + }, + }, + ["Stabilizer II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Sieglinde Putty", + "High Ebonite", + "Black Ghost", + "Water Tank", + }, + }, + ["Stabilizer III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Sieglinde Putty", + "High Ebonite", + "Black Ghost", + "Water Tank", + }, + }, + ["Stabilizer IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Sieglinde Putty", + "High Ebonite", + "Black Ghost", + "Water Tank", + }, + }, + ["Stabilizer V"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Sieglinde Putty", + "High Ebonite", + "Black Ghost", + "Water Tank", + }, + }, + ["Stamina Apple"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Yogurt", + }, + }, + ["Star Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Star Sapphire", + "Ocl. Earring", + }, + }, + ["Star Globe"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Garnet", + "Gold Thread", + "Animal Glue", + "Bast Parchment", + "Bast Parchment", + "Wisteria Lumber", + "Lqr. Tree Lbr.", + }, + }, + ["Star Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Star Sapphire", + "Orichalcum Ring", + }, + }, + ["Staunch Tathlum"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Macuil Horn", + "Defiant Scarf", + "Hades' Claw", + }, + }, + ["Staurobow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mahogany Lbr.", + "Ebony Lumber", + "Rattan Lumber", + "Carbon Fiber", + "Flauros Whisker", + "Unicorn Horn", + }, + }, + ["Stealth Screen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Water Anima", + "Glass Sheet", + "Homncl. Nerves", + "Plasma Oil", + }, + }, + ["Stealth Screen II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hecteyes Eye", + "Water Anima", + "Homncl. Nerves", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Steamed Catfish"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Dried Marjoram", + "Maple Sugar", + "Grape Juice", + "Giant Catfish", + "Distilled Water", + "Gysahl Greens", + }, + }, + ["Steel Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Firesand", + }, + }, + ["Steel Cuisses"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Scales", + "Steel Scales", + "Cotton Thread", + "Leather Trousers", + }, + }, + ["Steel Finger Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Scales", + "Steel Scales", + "Cotton Thread", + "Leather Gloves", + }, + }, + ["Steel Greaves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Scales", + "Steel Scales", + "Cotton Thread", + "Leather Highboots", + }, + }, + ["Steel Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Bomb Ash", + "Iron Sand", + "Iron Sand", + "Iron Sand", + "Iron Sand", + }, + }, + ["Steel Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bomb Ash", + "Iron Sand", + "Iron Sand", + "Iron Sand", + "Iron Sand", + "Cluster Ash", + }, + }, + ["Steel Ingot 3"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Steel Nugget", + "Steel Nugget", + "Steel Nugget", + "Steel Nugget", + "Steel Nugget", + "Steel Nugget", + }, + }, + ["Steel Ingot 4"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sand", + "Iron Sand", + "Iron Sand", + "Iron Sand", + "Djinn Ash", + }, + }, + ["Steel Kilij"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Aluminum Ingot", + "Aluminum Ingot", + "Rosewood Lbr.", + "Amethyst", + "Ametrine", + "Karakul Leather", + }, + }, + ["Steel Scale Mail"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Scales", + "Steel Scales", + "Steel Scales", + "Steel Scales", + "Cotton Thread", + "Sheep Leather", + "Leather Vest", + }, + }, + ["Steel Scales"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Sheet", + }, + }, + ["Steel Scales 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Smith. Kit 40", + }, + }, + ["Steel Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + }, + }, + ["Steel Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Workshop Anvil", + }, + }, + ["Steel Visor"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Sheet", + "Iron Scales", + "Sheep Leather", + }, + }, + ["Steel Walnut Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Log", + "Earth Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Steel-splitter"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Stl. Wal. Lumber", + "Iron-splitter", + }, + }, + ["Stepping Stool"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Ebony Lumber", + "Ebony Lumber", + }, + }, + ["Sticky Webbing"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gnat Wing", + "Twitherym Wing", + "Mantid Carapace", + "Chapuli Wing", + }, + }, + ["Stikini Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Koh-I-Noor", + "Dark Matter", + "Dark Matter", + "Tartarian Chain", + "Tartarian Chain", + }, + }, + ["Stirge Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ram Leather", + "Mercury", + "Toad Oil", + "Volant Serum", + }, + }, + ["Stone Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Stone Arrowhd.", + "Chocobo Fltchg.", + }, + }, + ["Stone Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Flint Stone", + "Flint Stone", + }, + }, + ["Stone Bangles"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Spirit Shell", + "Turtle Bangles", + }, + }, + ["Stone Cheese"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Rock Salt", + "Selbina Milk", + }, + }, + ["Stoneskin Torque"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Indurated Gold", + "Torque", + }, + }, + ["Strength Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Boyahda Moss", + "Red Rose", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Strobe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Silver Sheet", + "Glass Sheet", + "Orobon Lure", + "Polyflan", + "Plasma Oil", + }, + }, + ["Strobe II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Orobon Lure", + "Polyflan", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Studded Bandana"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Leather Bandana", + }, + }, + ["Studded Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + "Leather Highboots", + }, + }, + ["Studded Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + "Leather Gloves", + }, + }, + ["Studded Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + "Leather Trousers", + }, + }, + ["Studded Trousers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Leath. Kit 30", + }, + }, + ["Studded Vest"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + "Ram Leather", + "Leather Vest", + }, + }, + ["Stuffed Pitaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rock Salt", + "Imperial Flour", + "G. Sheep Meat", + "Mithran Tomato", + "Distilled Water", + "Graubg. Lettuce", + "Paprika", + }, + }, + ["Stun Claws"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Cermet Claws", + }, + }, + ["Stun Jamadhars"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Jamadhars", + }, + }, + ["Stun Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Cermet Knife", + }, + }, + ["Stun Knife 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 80", + }, + }, + ["Stun Kukri"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Paralyze Potion", + "Cermet Kukri", + }, + }, + ["Sturdy Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Water Cell", + "Slacks", + }, + }, + ["Sturdy Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Earth Cell", + "Leather Trousers", + }, + }, + ["Styrne Byrnie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Dhalmel Leather", + "Tiger Leather", + "Behem. Leather", + "Herensugue Skin", + "Silver Mail", + }, + }, + ["Sublime Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Bibiki Urchin", + "Cheval Salmon", + "Black Sole", + "Gugru Tuna", + "Bird Egg", + "Ground Wasabi", + }, + }, + ["Sublime Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Bibiki Urchin", + "Cheval Salmon", + "Black Sole", + "Gugru Tuna", + "Bird Egg", + "Wasabi", + }, + }, + ["Sugar Rusk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Iron Bread", + "Bird Egg", + }, + }, + ["Sugary Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Rolanberry", + "Honey", + "Walnut", + "Ulbuconut", + }, + }, + ["Sukezane"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Cotton Thread", + "Manta Leather", + "Kunwu Iron", + "Yggdreant Bole", + }, + }, + ["Sultan's Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "R. Bgd. Leather", + "Koenigs Belt", + }, + }, + ["Summoner's Collar"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Cehuetzi Claw", + "Dark Matter", + "Cyan Coral", + "Rutile Crystal", + "Moldy Collar", + }, + }, + ["Summoner's Staff"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Effluvium", + "Dark Matter", + "Khoma Thread", + "Moldy Staff", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Rutile Crystal", + }, + }, + ["Sun Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sunstone", + "Gold Earring", + }, + }, + ["Sun Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sunstone", + "Gold Earring +1", + }, + }, + ["Sun Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sunstone", + "Gold Ring", + }, + }, + ["Sun Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sunstone", + "Gold Ring +1", + }, + }, + ["Sun Water"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Beastman Blood", + "Phil. Stone", + "Cactuar Root", + }, + }, + ["Sune-Ate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Silk Thread", + "Silk Cloth", + "Sheep Leather", + }, + }, + ["Sunstone"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ifritite", + }, + }, + ["Super Ether"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Dried Marjoram", + "Dried Marjoram", + "Ahriman Wing", + "Treant Bulb", + "Distilled Water", + }, + }, + ["Sutlac"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Rock Salt", + "Imperial Rice", + "Cornstarch", + "Selbina Milk", + "Apkallu Egg", + }, + }, + ["Sweet Lizard"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lizard Tail", + "Honey", + }, + }, + ["Sweet Rice Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Cinnamon", + "Sticky Rice", + "Gardenia Seed", + "Fresh Mugwort", + "Distilled Water", + }, + }, + ["Sweordfaetels"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "P. Brass Chain", + "Sealord Leather", + "Cehuetzi Pelt", + }, + }, + ["Swirling Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "San d'Or. Carrot", + "San d'Or. Carrot", + "San d'Or. Carrot", + "Verboshroom", + }, + }, + ["Swith Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sparkstrand", + "Wyrdweave", + "Wyrdweave", + }, + }, + ["Swordbelt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Chain", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Tabar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Mythril Ingot", + "Chestnut Lumber", + }, + }, + ["Tabarzin"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Mahogany Lbr.", + "Gold Ingot", + "Mercury", + }, + }, + ["Tabin Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Imp. Silk Cloth", + "Green Beret", + }, + }, + ["Tabin Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Marid Leather", + "Battle Boots", + }, + }, + ["Tabin Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Imp. Silk Cloth", + "Battle Bracers", + }, + }, + ["Tabin Bracers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 75", + }, + }, + ["Tabin Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Marid Leather", + "Battle Hose", + }, + }, + ["Tabin Jupon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Imp. Silk Cloth", + "Battle Jupon", + }, + }, + ["Tachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Lizard Skin", + }, + }, + ["Tactician Magician's Coat +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Tct.Mgc. Coat", + }, + }, + ["Tactician Magician's Cuffs +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Tct.Mgc. Cuffs", + }, + }, + ["Tactician Magician's Espadon +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Tct.Mag. Espadon", + }, + }, + ["Tactician Magician's Hat +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Tct.Mgc. Hat", + }, + }, + ["Tactician Magician's Hooks +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Demon Horn", + "Tct.Mag. Hooks", + }, + }, + ["Tactician Magician's Pigaches +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Tct.Mgc. Pigaches", + }, + }, + ["Tactician Magician's Slops +1"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Tct.Mgc. Slops", + }, + }, + ["Tactician Magician's Wand +1"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Tct.Mag. Wand", + }, + }, + ["Taffeta Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Taffeta Cloth", + "Taffeta Cloth", + }, + }, + ["Taikyoku Kenpogi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lam. Earth Cell", + "Lam. Wind Cell", + "Kenpogi", + }, + }, + ["Talisman Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Al. Cotton Cloth", + "Cotton Cape", + }, + }, + ["Talisman Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Al. Cotton Cloth", + "Heko Obi", + }, + }, + ["Tanegashima"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Steel Ingot", + "Steel Ingot", + "Oak Lumber", + }, + }, + ["Tarasque Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Thread", + "Saruta Cotton", + "Tarasque Skin", + "Tarasque Skin", + }, + }, + ["Targe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Sheet", + "Iron Sheet", + "Holly Lumber", + }, + }, + ["Targe 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 35", + }, + }, + ["Tariqah"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Marid Leather", + "Marid Hair", + "Tariqah -1", + }, + }, + ["Tarutaru Desk"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Lauan Lumber", + "Lauan Lumber", + "Lauan Lumber", + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + }, + }, + ["Tarutaru Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Taru. Rod", + }, + }, + ["Tarutaru Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Silk Thread", + }, + }, + ["Tarutaru Fishing Rod 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wood. Kit 65", + }, + }, + ["Tarutaru Folding Screen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Parchment", + "Parchment", + "Parchment", + }, + }, + ["Tarutaru Sash"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Rainbow Thread", + "Silver Thread", + "Gold Thread", + "Manticore Hair", + "Manticore Hair", + }, + }, + ["Tarutaru Sash 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 95", + }, + }, + ["Tarutaru Stool"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Elm Lumber", + }, + }, + ["Tarutaru Stool 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wood. Kit 25", + }, + }, + ["Tathlum"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + }, + }, + ["Tathlum 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Smith. Kit 30", + }, + }, + ["Tati Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Silver Chain", + "Gabbrath Horn", + "Gabbrath Horn", + }, + }, + ["Tavern Bench"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Mahogany Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + }, + }, + ["Tavnazian Salad"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Apple Vinegar", + "Flint Caviar", + "Grimmonite", + "Frost Turnip", + "San d'Or. Carrot", + "Bastore Bream", + "Noble Lady", + "Beaugreens", + }, + }, + ["Tavnazian Taco"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tavnazian Salad", + "Tortilla", + "Tortilla", + "Salsa", + }, + }, + ["Teak Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Teak Log", + "Teak Log", + "Teak Log", + }, + }, + ["Teak Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Teak Log", + }, + }, + ["Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Grass Thread", + "Grass Cloth", + "Grass Cloth", + }, + }, + ["Temple Knight Army Shield +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Mercury", + "T.K. Army Shield", + }, + }, + ["Temple Knight Army Sword +1"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "T.K. Army Sword", + }, + }, + ["Tempus Fugit"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Defiant Scarf", + "Pya'ekue Belt", + }, + }, + ["Tension Spring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Darksteel Ingot", + "Mythril Coil", + "Mythril Coil", + "Myth.Gear Mach.", + }, + }, + ["Tension Spring II"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Darksteel Ingot", + "Mythril Coil", + "Mythril Coil", + "Golden Gear", + }, + }, + ["Tension Spring III"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Darksteel Ingot", + "Mythril Coil", + "Mythril Coil", + "Platinum Gear", + }, + }, + ["Tension Spring IV"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Darksteel Ingot", + "Mythril Coil", + "Mythril Coil", + "Ocl. Gearbox", + }, + }, + ["Tension Spring V"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Darksteel Ingot", + "Golden Coil", + "Golden Coil", + "Ocl. Gearbox", + }, + }, + ["Tentacle Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ginger", + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Kalamar", + }, + }, + ["Tentacle Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ginger", + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Cone Calamary", + }, + }, + ["Terebrokath"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Damascus Ingot", + "Damascus Ingot", + "Gold Ingot", + "Mercury", + "Yggdreant Bole", + }, + }, + ["Testudo Mantle"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Manticore Hair", + "Herensugue Skin", + }, + }, + ["Tewhatewha"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Steel Ingot", + "Steel Ingot", + "Holly Lumber", + "Aramid Fiber", + }, + }, + ["Thalassocrat"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Scintillant Ingot", + "Wamoura Silk", + "Jacaranda Lbr.", + }, + }, + ["The Big One"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Ebony Lumber", + "Gold Sheet", + "Saruta Cotton", + "Beeswax", + "Animal Glue", + "Beetle Blood", + "Titanic Sawfish", + }, + }, + ["Thick Breeches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Chain", + "Dst. Breeches", + }, + }, + ["Thick Mufflers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Darksteel Mufflers", + }, + }, + ["Thick Mufflers 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Smith. Kit 84", + }, + }, + ["Thick Sollerets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Steel Sheet", + "Dst. Sollerets", + }, + }, + ["Thief's Tools"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Yew Lumber", + }, + }, + ["Thokcha Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Thokcha Ore", + "Thokcha Ore", + "Thokcha Ore", + "Thokcha Ore", + }, + }, + ["Throwing Tomahawk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Chestnut Lumber", + "Chestnut Lumber", + }, + }, + ["Thug's Jambiya"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Mythril Ingot", + "Turquoise", + "Wivre Horn", + }, + }, + ["Thunder Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Lightning Cluster", + }, + }, + ["Thunder Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Luminous Shell", + "Carapace Mittens", + }, + }, + ["Thunder Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lightning Bead", + "Orichalcum Ring", + }, + }, + ["Thunder Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Lightning Bead", + }, + }, + ["Thurible"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scintillant Ingot", + "Tr. Brz. Ingot", + "A.U Brass Sheet", + "A.U Brass Sheet", + "A.U Brass Sheet", + }, + }, + ["Thurisaz Blade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lizard Skin", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + "Rhodium Ingot", + "Urunday Lumber", + "Twitherym Scale", + }, + }, + ["Tiger Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cuir Gloves", + }, + }, + ["Tiger Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Sheep Leather", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Tiger Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["Tiger Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Tiger Hide", + "Distilled Water", + }, + }, + ["Tiger Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Tiger Hide", + "Distilled Water", + }, + }, + ["Tiger Leather 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Win. Tea Leaves", + "Win. Tea Leaves", + "Tiger Hide", + "Tiger Hide", + "Tiger Hide", + "Tanning Vat", + "Distilled Water", + }, + }, + ["Tiger Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Tiger Leather", + "Cuir Highboots", + }, + }, + ["Tiger Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Tiger Hide", + }, + }, + ["Tiger Mantle 2"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Leath. Kit 75", + }, + }, + ["Tiger Mask"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Ram Leather", + "Tiger Hide", + "Tiger Hide", + "Wyvern Skin", + }, + }, + ["Tiger Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Leather", + "Tiger Leather", + "Cuir Trousers", + }, + }, + ["Tigereye Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tiger Eye", + "Silver Ring", + }, + }, + ["Tigereye Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold. Kit 35", + }, + }, + ["Tigerfangs"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Blk. Tiger Fang", + "Blk. Tiger Fang", + "Scorpion Shell", + "Carbon Fiber", + }, + }, + ["Tin Bullet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tin Ingot", + "Firesand", + }, + }, + ["Tin Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tin Ore", + "Tin Ore", + "Tin Ore", + "Tin Ore", + }, + }, + ["Tin Ingot 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Smith. Kit 15", + }, + }, + ["Titanictus Shell"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Titanictus", + }, + }, + ["Titanictus Shell 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Armored Pisces", + }, + }, + ["Titanium Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Titanium Ingot", + }, + }, + ["Titanium Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Titanium Ore", + "Titanium Ore", + "Titanium Ore", + "Titanium Ore", + }, + }, + ["Titanium Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Titanium Ingot", + }, + }, + ["Titanium Sheet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Workshop Anvil", + "Titanium Ingot", + "Titanium Ingot", + "Titanium Ingot", + "Titanium Ingot", + "Titanium Ingot", + "Titanium Ingot", + }, + }, + ["Tojaku"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sprt. Orichalcum", + "Hirenjaku", + }, + }, + ["Tomahawk"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Ash Lumber", + }, + }, + ["Tomato Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Rock Salt", + "Mithran Tomato", + "Mithran Tomato", + "Mithran Tomato", + }, + }, + ["Tomato Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "2Leaf Mandra Bud", + "Wild Onion", + "Tomato Juice", + "Distilled Water", + }, + }, + ["Tomato Soup 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Dried Marjoram", + "Bay Leaves", + "Wild Onion", + "Tomato Juice", + "Distilled Water", + }, + }, + ["Tomonari"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Ancient Lumber", + "Manta Leather", + "Scintillant Ingot", + "Wamoura Silk", + "Midrium Ingot", + "Midrium Ingot", + "Mantid Carapace", + }, + }, + ["Tonno Rosso"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Holy Basil", + "Spaghetti", + "Wild Onion", + "Pomodoro Sauce", + "Lakerda", + }, + }, + ["Tonno Rosso 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mhaura Garlic", + "Olive Oil", + "Rock Salt", + "Holy Basil", + "Spaghetti", + "Wild Onion", + "Gugru Tuna", + "Pomodoro Sauce", + }, + }, + ["Tonosama Rice Ball"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rock Salt", + "Flint Caviar", + "Distilled Water", + }, + }, + ["Topaz Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Topaz", + "Platinum Earring", + }, + }, + ["Topaz Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Topaz", + "Ptm. Earring +1", + }, + }, + ["Topaz Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Topaz", + "Platinum Ring", + }, + }, + ["Topaz Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Topaz", + "Platinum Ring +1", + }, + }, + ["Toporok"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Adaman Ingot", + "Ebony Lumber", + "Gold Ingot", + "Painite", + "Painite", + "Mercury", + }, + }, + ["Torque"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + "Gold Ingot", + }, + }, + ["Tortilla"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Millioncorn", + "Olive Oil", + "Rock Salt", + }, + }, + ["Tortoise Earring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Gold Chain", + "Turtle Shell", + }, + }, + ["Tortoise Earring 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone. Kit 55", + }, + }, + ["Totem Pole"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Rosewood Lbr.", + "Rosewood Lbr.", + "Rosewood Lbr.", + "White Rock", + "Manticore Hair", + "Manticore Hair", + "Wivre Horn", + }, + }, + ["Tough Belt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "T. Bgd. Leather", + "Barbarian's Belt", + }, + }, + ["Tough Bugard Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Bugard Skin", + "Earth Anima", + "Earth Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Tough Bugard Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Bugard Skin", + "Earth Anima", + "Earth Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Tough Dhalmel Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Dhalmel Hide", + "Wind Anima", + "Wind Anima", + "Dark Anima", + "Distilled Water", + }, + }, + ["Tough Dhalmel Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Dhalmel Hide", + "Wind Anima", + "Wind Anima", + "Dark Anima", + "Distilled Water", + }, + }, + ["Tourmaline"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Green Rock", + }, + }, + ["Tourmaline 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Breeze Geode", + }, + }, + ["Tourmaline Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Silver Earring", + }, + }, + ["Tourmaline Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Silver Earring +1", + }, + }, + ["Tourmaline Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Silver Ring", + }, + }, + ["Tourmaline Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Silver Ring +1", + }, + }, + ["Tower Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Ingot", + "Brass Sheet", + "Oak Lumber", + "Oak Lumber", + "Mahogany Lbr.", + "Ebony Lumber", + }, + }, + ["Tracker's Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mahogany Htwd.", + "Crossbow", + }, + }, + ["Trader's Chapeau"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Sheep Leather", + "Beeswax", + "Red Grs. Thread", + "Red Grass Cloth", + "Red Grass Cloth", + }, + }, + ["Trader's Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Sheep Leather", + "Sheep Leather", + "Red Grs. Thread", + "Red Grass Cloth", + }, + }, + ["Trader's Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dhalmel Leather", + "Sheep Leather", + "Red Grs. Thread", + "Red Grass Cloth", + "Red Grass Cloth", + }, + }, + ["Trader's Saio"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Chain", + "Cotton Cloth", + "Sheep Leather", + "Ram Leather", + "Beeswax", + "Red Grs. Thread", + "Red Grass Cloth", + "Red Grass Cloth", + }, + }, + ["Trader's Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Cotton Cloth", + "Sheep Leather", + "Red Grs. Thread", + "Red Grass Cloth", + }, + }, + ["Tranquilizer II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Sieglinde Putty", + "Homncl. Nerves", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Tranquilizer III"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Sieglinde Putty", + "Homncl. Nerves", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Tranquilizer IV"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Sieglinde Putty", + "Homncl. Nerves", + "Plasma Oil", + "F. Glass Sheet", + }, + }, + ["Transfixion Sphere"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lucky Egg", + }, + }, + ["Transfixion Sphere 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Carbuncle's Ruby", + }, + }, + ["Transfixion Sphere 3"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "White Memosphere", + }, + }, + ["Transfixion Sphere 4"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bruised Starfruit", + }, + }, + ["Transfixion Sphere 5"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Light Card", + }, + }, + ["Translucent Rock"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Marble Nugget", + }, + }, + ["Transom"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Mahogany Lbr.", + "Sieglinde Putty", + "Glass Sheet", + "Silica", + "Pebble", + }, + }, + ["Traversiere"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Parchment", + }, + }, + ["Tree Sap"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Maple Sugar", + "Chestnut Log", + }, + }, + ["Triangular Jalousie"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Troll Bronze Sheet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tr. Brz. Ingot", + }, + }, + ["Troll Coif"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Karakul Leather", + "Manticore Hair", + "Marid Leather", + "Marid Hair", + "Mohbwa Cloth", + }, + }, + ["Tropical Crepe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Maple Sugar", + "Kitron", + "Persikos", + "Bird Egg", + "Uleguerand Milk", + "Felicifruit", + }, + }, + ["Tropical Punches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Fruit Punches", + }, + }, + ["Trout Ball"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rye Flour", + "Shining Trout", + "Distilled Water", + }, + }, + ["Trout Ball 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rye Flour", + "Distilled Water", + "Alabaligi", + }, + }, + ["Truesights"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Sheet", + "Wind Bead", + "Homncl. Nerves", + "Plasma Oil", + "High Ebonite", + }, + }, + ["Trumpet Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Trumpet Shell", + "Trumpet Shell", + }, + }, + ["Tsahyan Mask"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Ebony Lumber", + "Ebony Lumber", + "G. Bird Plume", + "G. Bird Plume", + "G. Bird Plume", + "Manticore Hair", + "Avatar Blood", + }, + }, + ["Tsukumo"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Garglle. Shank", + "Ruszor Leather", + "Dweomer Steel", + }, + }, + ["Tsukumo 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Garglle. Shank", + "Ruszor Leather", + "Dweomer Steel", + }, + }, + ["Tsurara"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Rock Salt", + "Distilled Water", + "Distilled Water", + }, + }, + ["Tsurugitachi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lam. Fire Cell", + "Lam. Wind Cell", + "Hosodachi", + }, + }, + ["Tuck"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Silver Ingot", + }, + }, + ["Tulfaire Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Tulfaire Feather", + "Tulfaire Feather", + }, + }, + ["Tulfaire Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Zephyr Thread", + "Tulfaire Feather", + "Tulfaire Feather", + "Tulfaire Feather", + "Tulfaire Feather", + "Tulfaire Feather", + "Tulfaire Feather", + }, + }, + ["Tulwar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Mythril Ingot", + "Silver Ingot", + }, + }, + ["Tuna Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Gugru Tuna", + "Distilled Water", + "Ground Wasabi", + }, + }, + ["Tuna Sushi 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Rice Vinegar", + "Distilled Water", + "Ground Wasabi", + "Lakerda", + }, + }, + ["Tunic"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Turbo Charger II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Glass Fiber", + "Glass Sheet", + "Haste", + "Mega Fan", + "Vanir Battery", + }, + }, + ["Turms Cap"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Bztavian Wing", + "Macuil Horn", + "Ruthenium Ore", + "Ruthenium Ore", + "Ruthenium Ore", + }, + }, + ["Turms Harness"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Bztavian Wing", + "Macuil Horn", + "Macuil Horn", + "Ruthenium Ore", + "Ruthenium Ingot", + }, + }, + ["Turms Leggings"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Bztavian Wing", + "Macuil Horn", + "Ruthenium Ore", + "Ruthenium Ore", + "Ruthenium Ore", + }, + }, + ["Turms Mittens"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Tiger Leather", + "Bztavian Wing", + "Macuil Horn", + "Ruthenium Ore", + "Ruthenium Ore", + "Ruthenium Ore", + }, + }, + ["Turms Subligar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tiger Leather", + "Cermet Chunk", + "Bztavian Wing", + "Macuil Horn", + "Ruthenium Ore", + "Ruthenium Ingot", + }, + }, + ["Turpid Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Gelatin", + "Yawning Catfish", + "Warthog Meat", + }, + }, + ["Turquoise Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turquoise", + "Mythril Earring", + }, + }, + ["Turquoise Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turquoise", + "Mythril Earring +1", + }, + }, + ["Turquoise Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turquoise", + "Mythril Ring", + }, + }, + ["Turquoise Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turquoise", + "Mythril Ring +1", + }, + }, + ["Turtle Bangles"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Turtle Shell", + "Turtle Shell", + "Giant Femur", + }, + }, + ["Turtle Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Turtle Shell", + "Beetle Shell", + }, + }, + ["Turtle Shield 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone. Kit 35", + }, + }, + ["Turtle Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ginger", + "Chicken Bone", + "Rock Salt", + "Danceshroom", + "San d'Or. Carrot", + "Red Terrapin", + "Acorn", + "Distilled Water", + }, + }, + ["Twicer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Light Steel", + "Voulge", + }, + }, + ["Twill Damask"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Sparkstrand", + "Sparkstrand", + }, + }, + ["Twinkle Shower"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Firesand", + "Bast Parchment", + "Twinkle Powder", + }, + }, + ["Twinthread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Twincoon", + "Twincoon", + }, + }, + ["Twinthread Obi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Twinthread", + "Twinthread", + "Twinthread", + }, + }, + ["Twitherym Scale"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Twitherym Wing", + "Twitherym Wing", + }, + }, + ["Two-Handed Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Steel Ingot", + "Ash Lumber", + "Lizard Skin", + }, + }, + ["Tyro Katars"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Steel Ingot", + "Ram Leather", + "Grimy Brz. Sheet", + }, + }, + ["Tzustes Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Midrium Ingot", + "Urunday Lumber", + }, + }, + ["Uchigatana"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Iron Ingot", + "Tama-Hagane", + "Elm Lumber", + "Silk Thread", + "Lizard Skin", + }, + }, + ["Uchitake"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bamboo Stick", + "Grass Cloth", + "Toad Oil", + }, + }, + ["Ugol Brayettes"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Durium Chain", + "P. Brass Chain", + "Linen Cloth", + "Light Ram Lth.", + "Light Ram Lth.", + }, + }, + ["Ugol Haubert"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Sheet", + "Durium Ingot", + "Durium Sheet", + "P. Brass Chain", + "P. Brass Chain", + "P. Brass Chain", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["Ugol Moufles"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Sheet", + "Durium Sheet", + "Chain Mittens", + }, + }, + ["Ugol Salade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Sheet", + "Durium Ingot", + "P. Brass Chain", + "Vi. Sh. Leather", + "Scintillant Ingot", + "Scintillant Ingot", + }, + }, + ["Ugol Sollerets"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "P. Brass Sheet", + "Durium Sheet", + "Greaves", + }, + }, + ["Ulbuconut Milk"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Elshimo Coconut", + }, + }, + ["Ulbuconut Milk 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ulbuconut", + }, + }, + ["Unicorn Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Gold Thread", + "Tiger Leather", + "Behem. Leather", + "Unicorn Horn", + }, + }, + ["Unicorn Harness"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Tiger Leather", + "Behem. Leather", + "Unicorn Horn", + "Unicorn Horn", + }, + }, + ["Unicorn Leggings"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Behem. Leather", + "Behem. Leather", + "Wyvern Scales", + "Unicorn Horn", + }, + }, + ["Unicorn Mittens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Behem. Leather", + "Wyvern Scales", + "Unicorn Horn", + }, + }, + ["Unicorn Subligar"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Behem. Leather", + "Taffeta Cloth", + "Unicorn Horn", + }, + }, + ["Urchin Sushi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Pamtam Kelp", + "Rice Vinegar", + "Bibiki Urchin", + "Distilled Water", + }, + }, + ["Urja Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Squamous Hide", + "Squamous Hide", + }, + }, + ["Urja Helm"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dark Bronze", + "Marid Leather", + "Squamous Hide", + "Squamous Hide", + }, + }, + ["Urja Jerkin"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Dark Bronze", + "Squamous Hide", + "Squamous Hide", + "Ogre Jerkin", + }, + }, + ["Urja Ledelsens"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Drk. Brz. Sheet", + "Squamous Hide", + "Squamous Hide", + }, + }, + ["Urja Trousers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Buffalo Leather", + "Buffalo Leather", + "Squamous Hide", + "Squamous Hide", + }, + }, + ["Urunday Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bundling Twine", + "Urunday Log", + "Urunday Log", + "Urunday Log", + }, + }, + ["Urunday Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Urunday Log", + }, + }, + ["Urushi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ore", + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Lqr. Tree Sap", + "Honey", + }, + }, + ["Uruz Blade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Lizard Skin", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Midrium Ingot", + "Urunday Lumber", + "Twitherym Scale", + }, + }, + ["Vagabond's Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bronze Scales", + "Grass Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Vagabond's Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Cloth", + "Sheep Leather", + "Sheep Leather", + }, + }, + ["Vagabond's Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Cotton Cloth", + "Cotton Cloth", + }, + }, + ["Vagabond's Tunica"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Grass Cloth", + "Grass Cloth", + "Grass Cloth", + "Cotton Cloth", + "Sheep Leather", + }, + }, + ["Valance"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Valor Sword"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Dark Matter", + "Khoma Thread", + "Moldy Sword", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Diamond Crystal", + }, + }, + ["Vampire Juice"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Rolanberry", + "Mithran Tomato", + "Red Terrapin", + }, + }, + ["Vampire Juice 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Faerie Apple", + "Rolanberry", + "Mithran Tomato", + "Kaplumbaga", + }, + }, + ["Vampire Juice 3"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cook. Kit 90", + }, + }, + ["Varar Ring"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Scarletite Ingot", + "Beryllium Ingot", + "Tartarian Chain", + }, + }, + ["Vegetable Gruel"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Chamomile", + "Frost Turnip", + "Rarab Tail", + "Distilled Water", + "Beaugreens", + }, + }, + ["Vegetable Gruel 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tarutaru Rice", + "Chamomile", + "Batagreens", + "Frost Turnip", + "Rarab Tail", + "Distilled Water", + }, + }, + ["Vegetable Paste"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "La Theine Millet", + "Lizard Egg", + "Distilled Water", + "Gysahl Greens", + "Gysahl Greens", + }, + }, + ["Vegetable Soup"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bay Leaves", + "Rock Salt", + "La Theine Cbg.", + "Frost Turnip", + "Wild Onion", + "Eggplant", + "San d'Or. Carrot", + "Distilled Water", + }, + }, + ["Vegetable Soup 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 25", + }, + }, + ["Vejovis Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Kapor Lumber", + "Black C. Feather", + }, + }, + ["Vela Justaucorps"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Beetle Shell", + "Bastet Fang", + "Bastet Fang", + "Wool Robe", + }, + }, + ["Veldt Axe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Penumbral Brass", + "Brass Axe", + }, + }, + ["Vellum"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheep Leather", + "Gold Dust", + "Rolanberry", + }, + }, + ["Velocity Bow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Flauros Whisker", + "Heavy Crossbow", + }, + }, + ["Velvet Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Wool Thread", + "Wool Thread", + }, + }, + ["Velvet Cloth 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silk Thread", + "Cotton Thread", + "Cotton Thread", + }, + }, + ["Velvet Cloth 3"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 50", + }, + }, + ["Velvet Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Peridot", + "Peridot", + "Silver Thread", + "Wool Cloth", + "Velvet Cloth", + }, + }, + ["Velvet Cuffs 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Tourmaline", + "Silver Thread", + "Wool Cloth", + "Velvet Cloth", + }, + }, + ["Velvet Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Silk Thread", + "Wool Cloth", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Velvet Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Silver Thread", + "Wool Cloth", + "Wool Cloth", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Velvet Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Silver Thread", + "Wool Cloth", + "Velvet Cloth", + "Velvet Cloth", + }, + }, + ["Vendor's Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Scarlet Linen", + "Bloodthread", + "Imp. Silk Cloth", + "Imp. Silk Cloth", + }, + }, + ["Venom Baselard"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Dst. Baselard", + }, + }, + ["Venom Bolt"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Vnm. Bolt Heads", + }, + }, + ["Venom Bolt 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Ash Lumber", + "Ash Lumber", + "Vnm. Bolt Heads", + "Vnm. Bolt Heads", + "Vnm. Bolt Heads", + "Bundling Twine", + }, + }, + ["Venom Bolt Heads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Animal Glue", + "Venom Potion", + }, + }, + ["Venom Claws"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Claws", + }, + }, + ["Venom Dust"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Scorpion Claw", + "Scorpion Claw", + }, + }, + ["Venom Dust 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Istavrit", + }, + }, + ["Venom Dust 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Ogre Eel", + "Ogre Eel", + }, + }, + ["Venom Dust 4"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Monke-Onke", + }, + }, + ["Venom Katars"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Katars", + }, + }, + ["Venom Knife"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Knife", + }, + }, + ["Venom Kris"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Kris", + }, + }, + ["Venom Kukri"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Venom Potion", + "Darksteel Kukri", + }, + }, + ["Venom Kukri 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 75", + }, + }, + ["Venom Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Venom Dust", + }, + }, + ["Venom Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Mercury", + "Mercury", + "Venom Dust", + "Venom Dust", + "Venom Dust", + "Triturator", + }, + }, + ["Verdun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Adaman Ingot", + "Adaman Ingot", + "Gold Ingot", + "Platinum Ingot", + "Spinel", + "Mercury", + }, + }, + ["Vermihumus"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Humus", + "Humus", + }, + }, + ["Vermihumus 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Derfland Humus", + }, + }, + ["Vermihumus 3"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Rich Humus", + "Rich Humus", + }, + }, + ["Vermilion Lacquer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mercury", + "Sulfur", + }, + }, + ["Vermillion Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Damascene Cloth", + "Damascene Cloth", + "Damascene Cloth", + }, + }, + ["Vermin Slayer"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Lizard Blood", + "Ameretat Vine", + "Darksteel Kilij", + }, + }, + ["Vexed Bliaut"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Bliaut -1", + }, + }, + ["Vexed Bliaut 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bztavian Wing", + "Intuila's Hide", + "Eschite Ore", + "Hexed Bliaut", + }, + }, + ["Vexed Bonnet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Bonnet -1", + }, + }, + ["Vexed Bonnet 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Waktza Crest", + "Hepatizon Ingot", + "Eschite Ore", + "Hexed Bonnet", + }, + }, + ["Vexed Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Boots -1", + }, + }, + ["Vexed Boots 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Cehuetzi Pelt", + "Eschite Ore", + "Hexed Boots", + }, + }, + ["Vexed Coif"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Coif -1", + }, + }, + ["Vexed Coif 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Defiant Sweat", + "Eschite Ore", + "Hexed Coif", + }, + }, + ["Vexed Coronet"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Coronet -1", + }, + }, + ["Vexed Coronet 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hepatizon Ingot", + "Immani. Hide", + "Eschite Ore", + "Hexed Coronet", + }, + }, + ["Vexed Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Cuffs -1", + }, + }, + ["Vexed Cuffs 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bztavian Wing", + "Hepatizon Ingot", + "Eschite Ore", + "Hexed Cuffs", + }, + }, + ["Vexed Domaru"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Domaru -1", + }, + }, + ["Vexed Domaru 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beryllium Ingot", + "Muut's Vestment", + "Eschite Ore", + "Hexed Domaru", + }, + }, + ["Vexed Doublet"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Doublet -1", + }, + }, + ["Vexed Doublet 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Defiant Sweat", + "Eschite Ore", + "Hexed Doublet", + }, + }, + ["Vexed Gages"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Gages -1", + }, + }, + ["Vexed Gages 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Defiant Sweat", + "Eschite Ore", + "Hexed Gages", + }, + }, + ["Vexed Gamashes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Gamash. -1", + }, + }, + ["Vexed Gamashes 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Macuil Horn", + "Samantha's Vine", + "Eschite Ore", + "Hexed Gamashes", + }, + }, + ["Vexed Gambieras"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Gamb. -1", + }, + }, + ["Vexed Gambieras 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hepatizon Ingot", + "Immani. Hide", + "Eschite Ore", + "Hexed Gambieras", + }, + }, + ["Vexed Gauntlets"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Gantl. -1", + }, + }, + ["Vexed Gauntlets 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hepatizon Ingot", + "Immani. Hide", + "Eschite Ore", + "Hexed Gauntlets", + }, + }, + ["Vexed Hakama"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Hakama -1", + }, + }, + ["Vexed Hakama 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cehuetzi Pelt", + "Muut's Vestment", + "Eschite Ore", + "Hexed Hakama", + }, + }, + ["Vexed Haubert"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Haubert -1", + }, + }, + ["Vexed Haubert 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hepatizon Ingot", + "Immani. Hide", + "Eschite Ore", + "Hexed Haubert", + }, + }, + ["Vexed Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Hose -1", + }, + }, + ["Vexed Hose 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Hepatizon Ingot", + "Plovid Flesh", + "Eschite Ore", + "Hexed Hose", + }, + }, + ["Vexed Jacket"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Jacket -1", + }, + }, + ["Vexed Jacket 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Warblade's Hide", + "Macuil Horn", + "Eschite Ore", + "Hexed Jacket", + }, + }, + ["Vexed Kecks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Kecks -1", + }, + }, + ["Vexed Kecks 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Waktza Crest", + "Warblade's Hide", + "Eschite Ore", + "Hexed Kecks", + }, + }, + ["Vexed Kote"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Tekko -1", + }, + }, + ["Vexed Kote 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cehuetzi Pelt", + "Muut's Vestment", + "Eschite Ore", + "Hexed Tekko", + }, + }, + ["Vexed Mitra"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Mitra -1", + }, + }, + ["Vexed Mitra 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bztavian Wing", + "Intuila's Hide", + "Eschite Ore", + "Hexed Mitra", + }, + }, + ["Vexed Nails"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Nails -1", + }, + }, + ["Vexed Nails 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Defiant Sweat", + "Eschite Ore", + "Hexed Nails", + }, + }, + ["Vexed Slops"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Slops -1", + }, + }, + ["Vexed Slops 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sif's Macrame", + "Defiant Sweat", + "Eschite Ore", + "Hexed Slops", + }, + }, + ["Vexed Somen"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Somen -1", + }, + }, + ["Vexed Somen 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beryllium Ingot", + "Muut's Vestment", + "Eschite Ore", + "Hexed Somen", + }, + }, + ["Vexed Sune-Ate"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Sune-Ate -1", + }, + }, + ["Vexed Sune-Ate 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Beryllium Ingot", + "Muut's Vestment", + "Eschite Ore", + "Hexed Sune-Ate", + }, + }, + ["Vexed Tights"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Tights -1", + }, + }, + ["Vexed Tights 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bztavian Wing", + "Jill's Spittle", + "Eschite Ore", + "Hexed Tights", + }, + }, + ["Vexed Wristbands"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Eschite Ore", + "Hexed Wrist. -1", + }, + }, + ["Vexed Wristbands 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Warblade's Hide", + "Macuil Horn", + "Eschite Ore", + "Hexed Wristbands", + }, + }, + ["Vexer Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Coral Fragment", + "Coral Fragment", + "Twitherym Scale", + "Matamata Shell", + }, + }, + ["Viper Dust"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Khimaira Tail", + }, + }, + ["Viper Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Viper Dust", + }, + }, + ["Viper Potion 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Mercury", + "Mercury", + "Triturator", + "Viper Dust", + "Viper Dust", + "Viper Dust", + }, + }, + ["Vision Amethyst Stone"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Amethyst", + "Earth Anima", + "Lightning Anima", + "Dark Anima", + }, + }, + ["Vision Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Vision Amethyst", + "Amethyst Ring", + }, + }, + ["Vitality Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Chamomile", + "Lizard Tail", + "Dried Mugwort", + "Honey", + "Distilled Water", + }, + }, + ["Vitriol"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Dhalmel Saliva", + }, + }, + ["Vitriol 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + }, + }, + ["Vitriol 3"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Treant Bulb", + "Treant Bulb", + "Treant Bulb", + "Treant Bulb", + "Treant Bulb", + "Treant Bulb", + "Triturator", + }, + }, + ["Vivacity Coat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + "Rgd. Gld. Thread", + }, + }, + ["Vivi-Valve II"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Sheet", + "Glass Fiber", + "Light Bead", + "Vanir Battery", + }, + }, + ["Vivified Coral"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Coral Fragment", + "Lightning Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivified Mythril"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ore", + "Mythril Ore", + "Mythril Ore", + "Mythril Ore", + "Lightning Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivio Crab Shell"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Crab Shell", + "Wind Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivio Femur"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Giant Femur", + "Wind Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivio Platinum Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Platinum Ore", + "Platinum Ore", + "Platinum Ore", + "Platinum Ore", + "Wind Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivio Scorpion Claw"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Scorpion Claw", + "Wind Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Vivio Sheep Leather"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Willow Log", + "Wind Anima", + "Water Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Vivio Sheep Leather 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheepskin", + "Win. Tea Leaves", + "Wind Anima", + "Water Anima", + "Light Anima", + "Distilled Water", + }, + }, + ["Vivio Wyvern Scale"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wyvern Scales", + "Wind Anima", + "Water Anima", + "Light Anima", + }, + }, + ["Voay Staff"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gua. Lumber", + "Snowsteel Ore", + "Voay Staff -1", + }, + }, + ["Voay Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gua. Lumber", + "Titanium Ore", + "Voay Sword -1", + }, + }, + ["Volant Serum"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Bat Fang", + "Bat Fang", + "Mercury", + }, + }, + ["Volt Gun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Gold Sheet", + "Hiraishin", + "Ebonite", + "Myth.Gear Mach.", + "Battery", + }, + }, + ["Vongole Rosso"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Kazham Peppers", + "Mhaura Garlic", + "Black Pepper", + "Olive Oil", + "Spaghetti", + "Wild Onion", + "Vongola Clam", + "Pomodoro Sauce", + }, + }, + ["Voulge"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Steel Ingot", + "Mythril Ingot", + "Holly Lumber", + }, + }, + ["Vulcan Blade"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Flame Blade", + }, + }, + ["Vulcan Claymore"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Flame Claymore", + }, + }, + ["Vulcan Degen"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Flame Degen", + }, + }, + ["Vulcan Sword"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Slime Oil", + "Brimsand", + "Fire Sword", + }, + }, + ["Wailing Bone Chip"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bone Chip", + "Ice Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Wailing Ram Horn"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ram Horn", + "Ice Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Wailing Shell"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Seashell", + "Ice Anima", + "Earth Anima", + "Dark Anima", + }, + }, + ["Waistbelt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Grass Thread", + "Ram Leather", + "Ram Leather", + }, + }, + ["Wakizashi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Tama-Hagane", + "Elm Lumber", + "Silk Thread", + "Lizard Skin", + }, + }, + ["Walahra Burner"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Ocl. Sheet", + "Ancient Lumber", + "Gold Ingot", + "Gold Sheet", + "Gold Sheet", + }, + }, + ["Walnut Cookie"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Maple Sugar", + "Imperial Flour", + "Distilled Water", + "Apkallu Egg", + "Walnut", + }, + }, + ["Walnut Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Log", + }, + }, + ["Walnut Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Walnut Log", + "Walnut Log", + "Walnut Log", + "Bundling Twine", + }, + }, + ["Wamoura Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wamoura Silk", + "Wamoura Silk", + "Wamoura Silk", + }, + }, + ["Wamoura Silk"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Wam. Cocoon", + "Wam. Cocoon", + }, + }, + ["Wamoura Silk 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Spindle", + "Wam. Cocoon", + "Wam. Cocoon", + "Wam. Cocoon", + "Wam. Cocoon", + "Wam. Cocoon", + "Wam. Cocoon", + }, + }, + ["Wamoura Silk 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Wamoura Hair", + "Wamoura Hair", + }, + }, + ["War Aketon"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Ingot", + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["War Beret"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "G. Bird Plume", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["War Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Coeurl Leather", + "Gold Chain", + "Tiger Leather", + "Tiger Leather", + }, + }, + ["War Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Oak Lumber", + "Oak Lumber", + "Silk Cloth", + "Carbon Fiber", + "Glass Fiber", + }, + }, + ["War Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Linen Cloth", + "Linen Cloth", + "Tiger Leather", + }, + }, + ["War Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Rainbow Thread", + "Velvet Cloth", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["War Pick"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Ash Lumber", + }, + }, + ["War Shinobi Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Darksteel Chain", + "Rainbow Thread", + "Raxa", + "Raxa", + "Raxa", + }, + }, + ["Wardrobe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Walnut Lumber", + "Glass Sheet", + "A.U. Brass Ingot", + "A.U Brass Sheet", + "A.U Brass Sheet", + }, + }, + ["Warhammer"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Elm Lumber", + }, + }, + ["Warp Cudgel"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ether. Oak Lbr.", + "Oak Cudgel", + }, + }, + ["Warrior's Beads"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Waktza Crest", + "Dark Matter", + "Khoma Thread", + "Ruby Crystal", + "Moldy Necklace", + }, + }, + ["Warrior's Belt"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Iron Chain", + "Dhalmel Leather", + }, + }, + ["Warrior's Belt 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Leath. Kit 25", + }, + }, + ["Warrior's Chopper"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Plovid Flesh", + "Dark Matter", + "Khoma Thread", + "Moldy Great Axe", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Ruby Crystal", + }, + }, + ["Warthog Stewpot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Soy Stock", + "Danceshroom", + "Distilled Water", + "Bird Egg", + "Cotton Tofu", + "Napa", + "Shungiku", + "Warthog Meat", + }, + }, + ["Was"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Moonbow Cloth", + "Moonbow Urushi", + "Khoma Cloth", + "Astral Signa", + }, + }, + ["Water Anima"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Somber Memory", + "Somber Memory", + "Somber Memory", + "Somber Memory", + }, + }, + ["Water Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Puk Fletching", + "Water Arrowhds.", + }, + }, + ["Water Arrowheads"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Copper Ingot", + "Merrow Scale", + }, + }, + ["Water Barrel"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Sheet", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Water Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Water Ore", + }, + }, + ["Water Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Water Cluster", + }, + }, + ["Water Fewell"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Slime Oil", + "Blue Rock", + "Catalytic Oil", + }, + }, + ["Water Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Water Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Water Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Humid. Velvet", + "Black Mitts", + }, + }, + ["Water Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Water Bead", + }, + }, + ["Water Tank"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Sheep Leather", + "Brass Tank", + "Water Cluster", + }, + }, + ["Water Tank 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Karakul Leather", + "Brass Tank", + "Water Cluster", + }, + }, + ["Wax Sword"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Beeswax", + "Bronze Sword", + }, + }, + ["Wetlands Broth"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Petrified Log", + "Loam", + "Snap. Secretion", + "Distilled Water", + }, + }, + ["Whale Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Dolphin Staff", + }, + }, + ["White 3-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "3-Drawer Almirah", + "Gold Thread", + "White Text. Dye", + }, + }, + ["White 6-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "6-Drawer Almirah", + "Gold Thread", + "White Text. Dye", + }, + }, + ["White 9-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "9-Drawer Almirah", + "Gold Thread", + "White Text. Dye", + }, + }, + ["White Bread"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Selbina Butter", + "Rock Salt", + "Honey", + "Distilled Water", + }, + }, + ["White Cape"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["White Cape 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 64", + }, + }, + ["White Cloak"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + "Silk Cloth", + }, + }, + ["White Honey"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Peph. Hive Chip", + "Peph. Hive Chip", + "Peph. Hive Chip", + "Peph. Hive Chip", + }, + }, + ["White Mitts"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Silk Cloth", + "Saruta Cotton", + }, + }, + ["White Mouton"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ram Skin", + "Shell Powder", + "Shell Powder", + "Distilled Water", + }, + }, + ["White Mouton 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ram Skin", + "Shell Powder", + "Shell Powder", + "Distilled Water", + }, + }, + ["White Noble's Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Noble's Bed", + "Gold Thread", + "White Text. Dye", + }, + }, + ["White Puppet Turban"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Cotton Cloth", + "Silk Cloth", + }, + }, + ["White Round Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Platinum Silk", + "Teak Lumber", + "White Text. Dye", + }, + }, + ["White Slacks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Velvet Cloth", + "Silk Cloth", + }, + }, + ["White Storm Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Brass Ingot", + "Silver Ingot", + "Silk Cloth", + "Glass Sheet", + "Sailcloth", + "White Text. Dye", + }, + }, + ["White Tarutaru Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tarutaru Desk", + "White Text. Dye", + }, + }, + ["White Tarutaru Standing Screen"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Taru F. Screen", + "White Text. Dye", + }, + }, + ["White Textile Dye"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Baking Soda", + }, + }, + ["White Viola"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "White Rock", + "Granite", + "Red Gravel", + "Wildgrass Seeds", + "Humus", + }, + }, + ["White Viola 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Wood. Kit 60", + }, + }, + ["Whitefish Stew"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Selbina Milk", + "Wild Onion", + "San d'Or. Carrot", + "Bastore Bream", + "Distilled Water", + }, + }, + ["Whitefish Stew 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Black Pepper", + "Rock Salt", + "Selbina Milk", + "Wild Onion", + "San d'Or. Carrot", + "Distilled Water", + "Mercanbaligi", + }, + }, + ["Wicker Box"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Willow Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Rattan Lumber", + "Ram Leather", + }, + }, + ["Willow Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Willow Rod", + }, + }, + ["Willow Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Lumber", + "Grass Thread", + }, + }, + ["Willow Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Log", + }, + }, + ["Willow Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Log", + "Willow Log", + "Willow Log", + "Bundling Twine", + }, + }, + ["Willow Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Willow Lumber", + "Insect Wing", + }, + }, + ["Wind Anima"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mercury", + "Rock Salt", + "Sulfur", + "Fleeting Memory", + "Fleeting Memory", + "Fleeting Memory", + "Fleeting Memory", + }, + }, + ["Wind Arrow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Puk Fletching", + "Wind Arrowhds.", + }, + }, + ["Wind Arrowheads"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Copper Ingot", + "Colibri Beak", + }, + }, + ["Wind Bead"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wind Ore", + }, + }, + ["Wind Card"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Mercury", + "Polyflan Paper", + "Wind Cluster", + }, + }, + ["Wind Fan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Beetle Shell", + "Giant Femur", + "Beeswax", + "Bat Wing", + "Wind Cluster", + }, + }, + ["Wind Fewell"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Slime Oil", + "Green Rock", + "Catalytic Oil", + }, + }, + ["Wind Lamp"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Wind Ore", + "Scintillant Ingot", + "A.U. Brass Ingot", + }, + }, + ["Wind Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Wind Bead", + }, + }, + ["Windurst Salad"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Apple Vinegar", + "Rolanberry", + "La Theine Cbg.", + "San d'Or. Carrot", + "Mithran Tomato", + "Kazham Pineapl.", + "Yagudo Cherry", + "Pamamas", + }, + }, + ["Windurst Taco"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Hare Meat", + "Wild Onion", + "Tortilla", + "Tortilla", + "Stone Cheese", + "Windurst Salad", + "Salsa", + }, + }, + ["Windurstian Baghnakhs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Bone Chip", + "Fsd. Baghnakhs", + }, + }, + ["Windurstian Bow"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Willow Lumber", + "Freesword's Bow", + }, + }, + ["Windurstian Brais"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Mrc.Cpt. Hose", + }, + }, + ["Windurstian Club"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ash Lumber", + "Freesword's Club", + }, + }, + ["Windurstian Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Mrc.Cpt. Doublet", + }, + }, + ["Windurstian Gaiters"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Mrc.Cpt. Gaiters", + }, + }, + ["Windurstian Gi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Mercenary's Gi", + }, + }, + ["Windurstian Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Mrc.Cpt. Gloves", + }, + }, + ["Windurstian Hachimaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Mrc. Hachimaki", + }, + }, + ["Windurstian Headgear"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Cotton Cloth", + "Mrc.Cpt. Headgear", + }, + }, + ["Windurstian Knife"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Mercenary's Knife", + }, + }, + ["Windurstian Kukri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Mrc.Cpt. Kukri", + }, + }, + ["Windurstian Kyahan"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Mrc. Kyahan", + }, + }, + ["Windurstian Pole"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ash Lumber", + "Mercenary's Pole", + }, + }, + ["Windurstian Scythe"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Darksteel Ingot", + "Mrc.Cpt. Scythe", + }, + }, + ["Windurstian Sill"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Dogwd. Lumber", + "Sieglinde Putty", + "Glass Sheet", + }, + }, + ["Windurstian Sitabaki"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Mrc. Sitabaki", + }, + }, + ["Windurstian Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Linen Thread", + "Linen Cloth", + "Freesword's Slops", + }, + }, + ["Windurstian Staff"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Holly Lumber", + "Freesword's Staff", + }, + }, + ["Windurstian Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Mrc. Greatsword", + }, + }, + ["Windurstian Tea Set"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Bast Parchment", + "Jacaranda Lbr.", + }, + }, + ["Windurstian Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Grass Cloth", + "Mrc. Tekko", + }, + }, + ["Wing Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Ingot", + "Insect Wing", + "Insect Wing", + }, + }, + ["Wing Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Ardent Jadeite", + "Mythril Gorget", + }, + }, + ["Wing Sword"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Gold Ingot", + "Platinum Ingot", + "Ruby", + "Jagdplaute", + }, + }, + ["Winged Altar"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Platinum Ingot", + "Ocl. Ingot", + "Ruby", + "Gold Brocade", + "Teak Lumber", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + }, + }, + ["Winged Balance"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ocl. Chain", + "Pearl", + "Nidhogg Scales", + "Southern Pearl", + "Angel Skin", + "Marid Tusk", + "Wivre Horn", + "Trumpet Shell", + }, + }, + ["Winged Boots"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lizard Skin", + "Leaping Boots", + }, + }, + ["Winged Plaque"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Sheet", + "Steel Sheet", + "Blsd. Mtl. Sheet", + "Blsd. Mtl. Sheet", + "Ocl. Sheet", + "Platinum Sheet", + "Sapphire", + "Scintillant Ingot", + }, + }, + ["Wingedge"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Silver Ingot", + "Cotton Thread", + }, + }, + ["Wise Braconi"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Silver Thread", + "Velvet Cloth", + "Velvet Cloth", + "Eft Skin", + "Eltoro Leather", + }, + }, + ["Wise Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Silk Cloth", + "Manticore Lth.", + "Eltoro Leather", + }, + }, + ["Wise Gloves"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Silver Thread", + "Velvet Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Eltoro Leather", + "Leather Gloves", + }, + }, + ["Wise Pigaches"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Gold Thread", + "Velvet Cloth", + "Sheep Leather", + "Tiger Leather", + "Eltoro Leather", + }, + }, + ["Wispy Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Loc. Elutriator", + "Shell Bug", + }, + }, + ["Witch Nougat"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Maple Sugar", + "Lizard Egg", + "Selbina Milk", + "Yagudo Cherry", + "White Honey", + "Roasted Almonds", + }, + }, + ["Wivre Gorget"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Karakul Leather", + "Wivre Horn", + }, + }, + ["Wivre Hairpin"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wivre Maul", + }, + }, + ["Wivre Mask"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Karakul Leather", + "Wivre Hide", + }, + }, + ["Wivre Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "A.U. Brass Ingot", + "Wivre Horn", + "Wivre Horn", + }, + }, + ["Wivre Shield"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Sheet", + "Ash Lumber", + "Wivre Hide", + "Wivre Hide", + }, + }, + ["Wolf Felt"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Sheep Wool", + "Sheep Wool", + "Sheep Wool", + "Manticore Hair", + "Wolf Fur", + "Wolf Fur", + "Wolf Fur", + "Distilled Water", + }, + }, + ["Wolf Fur"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Wolf Hide", + "Wolf Hide", + "Wolf Hide", + }, + }, + ["Wolf Gorget"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cotton Thread", + "Wolf Hide", + }, + }, + ["Wolf Mantle"] = { + ["crystal"] = "Ice Crystal", + ["ingredients"] = { + "Wool Thread", + "Wolf Hide", + }, + }, + ["Wooden Arrow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Arrowwood Lbr.", + "Flint Stone", + "Chocobo Fthr.", + "Chocobo Fthr.", + }, + }, + ["Woodworking Set 25"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Elm Lumber", + }, + }, + ["Woodworking Set 45"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Rosewood Log", + }, + }, + ["Woodworking Set 65"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Walnut Lumber", + "Silk Thread", + }, + }, + ["Woodworking Set 71"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Ebony Lumber", + "Ebony Lumber", + "Coeurl Whisker", + }, + }, + ["Woodworking Set 74"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Rosewood Lbr.", + "Silver Thread", + }, + }, + ["Woodworking Set 81"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Holly Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + "Oak Lumber", + }, + }, + ["Woodworking Set 84"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Round Shield", + }, + }, + ["Woodworking Set 90"] = { + ["crystal"] = "Geo Crystal", + ["ingredients"] = { + "Ancient Lumber", + "Ancient Lumber", + "Coeurl Whisker", + }, + }, + ["Woodworking Set 94"] = { + ["crystal"] = "Vortex Crystal", + ["ingredients"] = { + "Phoenix Feather", + "Urunday Lumber", + }, + }, + ["Wool Bracers"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Wool Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Chocobo Fthr.", + }, + }, + ["Wool Cap 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Cloth. Kit 45", + }, + }, + ["Wool Cloth"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Thread", + "Wool Thread", + }, + }, + ["Wool Cuffs"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Tourmaline", + "Tourmaline", + "Wool Thread", + "Linen Cloth", + "Wool Cloth", + }, + }, + ["Wool Doublet"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + "Saruta Cotton", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Wool Gambison"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + "Saruta Cotton", + "Saruta Cotton", + }, + }, + ["Wool Grease"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Lesser Chigoe", + "Lesser Chigoe", + "Shell Bug", + }, + }, + ["Wool Hat"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Linen Thread", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + }, + }, + ["Wool Hose"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Ram Leather", + }, + }, + ["Wool Robe"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Scales", + "Linen Thread", + "Linen Cloth", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + }, + }, + ["Wool Slops"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Brass Sheet", + "Wool Thread", + "Linen Cloth", + "Wool Cloth", + "Wool Cloth", + }, + }, + ["Wool Socks"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wool Thread", + "Wool Cloth", + "Wool Cloth", + "Wool Cloth", + "Ram Leather", + }, + }, + ["Wool Thread"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Sheep Wool", + "Sheep Wool", + }, + }, + ["Wool Thread 2"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Sheep Wool", + "Sheep Wool", + "Sheep Wool", + "Sheep Wool", + "Sheep Wool", + "Sheep Wool", + "Spindle", + }, + }, + ["Wool Thread 3"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Cloth. Kit 35", + }, + }, + ["Wootz Amood"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Steel Ingot", + "Steel Ingot", + "Im. Wootz Ingot", + "Im. Wootz Ingot", + "Scintillant Ingot", + "Scintillant Ingot", + "Jacaranda Lbr.", + }, + }, + ["Wootz Ingot"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Steel Ingot", + "Rosewood Lbr.", + "Wootz Ore", + }, + }, + ["Workbench"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Lauan Lumber", + "Lauan Lumber", + "Lauan Lumber", + }, + }, + ["Worm Lure"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Brass Ingot", + "Glass Fiber", + "Little Worm", + }, + }, + ["Worm Lure 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Glass Fiber", + "Animal Glue", + }, + }, + ["Worm Paste"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Cupid Worm", + "Cupid Worm", + "La Theine Millet", + "Lizard Egg", + "Distilled Water", + }, + }, + ["Wormy Broth"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Little Worm", + "Shell Bug", + }, + }, + ["Wrapped Bow"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Wool Thread", + "Sheep Leather", + }, + }, + ["Wretched Coat"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Malboro Fiber", + "Eltoro Leather", + "Penelope's Cloth", + "Belladonna Sap", + "Plovid Flesh", + "Plovid Flesh", + }, + }, + ["Wulong Shoes"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Grass Thread", + "Cotton Cloth", + "Kung Fu Shoes", + }, + }, + ["Wyrdweave"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Wyrdstrand", + "Wyrdstrand", + "Wyrdstrand", + }, + }, + ["Wyrm Lance"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Defiant Scarf", + "Dark Matter", + "Cypress Log", + "Moldy Polearm", + "Ratnaraj", + "Relic Adaman", + "Relic Adaman", + "Andalusite Crystal", + }, + }, + ["Wyvern Helm"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheep Leather", + "Tiger Leather", + "Guivre's Skull", + "Beeswax", + }, + }, + ["Wyvern Helm 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Sheep Leather", + "Tiger Leather", + "Wyvern Skull", + "Beeswax", + }, + }, + ["Wyvern Spear"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Iron Ingot", + "Tama-Hagane", + "Mahogany Lbr.", + "Gold Thread", + "Wyvern Skin", + }, + }, + ["X-Potion"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Sage", + "Sage", + "Hecteyes Eye", + "Reishi Mushroom", + "Distilled Water", + }, + }, + ["Xiphos"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Bronze Ingot", + "Bronze Ingot", + "Giant Femur", + }, + }, + ["Yacuruna Ring"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yggdreant Root", + "Maliya. Coral", + "Maliya. Coral", + }, + }, + ["Yagudo Drink"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Yagudo Cherry", + "Buburimu Grape", + "Buburimu Grape", + "Buburimu Grape", + }, + }, + ["Yagudo Fletchings"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yagudo Feather", + "Yagudo Feather", + }, + }, + ["Yagudo Fletchings 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yagudo Feather", + "Yagudo Feather", + "Yagudo Feather", + "Yagudo Feather", + "Yagudo Feather", + "Yagudo Feather", + "Zephyr Thread", + }, + }, + ["Yagudo Headgear"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Black Pearl", + "Black Pearl", + "Wool Thread", + "Yagudo Feather", + "Yagudo Feather", + "Cockatrice Skin", + "Bugard Tusk", + "Yagudo Cutting", + }, + }, + ["Yasha Hakama"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + "Velvet Cloth", + "Velvet Cloth", + "Sheep Leather", + "Tiger Leather", + "Kejusu Satin", + "Kejusu Satin", + }, + }, + ["Yasha Jinpachi"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Chain", + "Kejusu Satin", + "Kejusu Satin", + }, + }, + ["Yasha Samue"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Steel Sheet", + "Rainbow Thread", + "Velvet Cloth", + "Tiger Leather", + "Tiger Leather", + "Kejusu Satin", + "Kejusu Satin", + }, + }, + ["Yasha Sune-Ate"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Silk Thread", + "Tiger Leather", + "Tiger Leather", + "Kejusu Satin", + }, + }, + ["Yasha Tekko"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Darksteel Sheet", + "Darksteel Sheet", + "Darksteel Chain", + "Linen Thread", + "Tiger Leather", + "Kejusu Satin", + }, + }, + ["Yayla Corbasi"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Selbina Butter", + "Rock Salt", + "Apple Mint", + "Imperial Rice", + "Imperial Flour", + "Distilled Water", + "Apkallu Egg", + "Yogurt", + }, + }, + ["Yellow 3-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "3-Drawer Almirah", + "Gold Thread", + "Yellow Txt. Dye", + }, + }, + ["Yellow 6-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "6-Drawer Almirah", + "Gold Thread", + "Yellow Txt. Dye", + }, + }, + ["Yellow 9-Drawer Almirah"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "9-Drawer Almirah", + "Gold Thread", + "Yellow Txt. Dye", + }, + }, + ["Yellow Curry"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Popoto", + "Curry Powder", + "Turmeric", + "Coeurl Meat", + "Selbina Milk", + "Wild Onion", + "Distilled Water", + }, + }, + ["Yellow Curry 2"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Cook. Kit 85", + }, + }, + ["Yellow Curry Bun"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Olive Oil", + "Yellow Curry", + "Bird Egg", + }, + }, + ["Yellow Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chrysoberyl", + "Gold Earring", + }, + }, + ["Yellow Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chrysoberyl", + "Gold Earring +1", + }, + }, + ["Yellow Hobby Bo"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Chestnut Lumber", + "Hickory Lumber", + "Dogwd. Lumber", + "Onyx", + "Onyx", + "Ylw. Chocobo Dye", + }, + }, + ["Yellow Mahogany Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Mahogany Bed", + "Wool Thread", + "Yellow Txt. Dye", + }, + }, + ["Yellow Mouton"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Win. Tea Leaves", + "Ram Skin", + "Orpiment", + "Orpiment", + "Distilled Water", + }, + }, + ["Yellow Mouton 2"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Willow Log", + "Ram Skin", + "Orpiment", + "Orpiment", + "Distilled Water", + }, + }, + ["Yellow Noble's Bed"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Noble's Bed", + "Gold Thread", + "Yellow Txt. Dye", + }, + }, + ["Yellow Round Table"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Lauan Lumber", + "Linen Cloth", + "Linen Cloth", + "Velvet Cloth", + "Velvet Cloth", + "Platinum Silk", + "Teak Lumber", + "Yellow Txt. Dye", + }, + }, + ["Yellow Storm Lantern"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Olive Oil", + "Brass Ingot", + "Silver Ingot", + "Silk Cloth", + "Glass Sheet", + "Sailcloth", + "Yellow Txt. Dye", + }, + }, + ["Yellow Tarutaru Desk"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Tarutaru Desk", + "Yellow Txt. Dye", + }, + }, + ["Yellow Tarutaru Standing Screen"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Taru F. Screen", + "Yellow Txt. Dye", + }, + }, + ["Yellow Textile Dye"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Turmeric", + }, + }, + ["Yellow Viola"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Yellow Rock", + "Granite", + "Red Gravel", + "Wildgrass Seeds", + "Humus", + }, + }, + ["Yetshila"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Animal Glue", + "Waktza Rostrum", + "Waktza Crest", + }, + }, + ["Yew Fishing Rod"] = { + ["crystal"] = "Light Crystal", + ["ingredients"] = { + "Bkn. Yew Rod", + }, + }, + ["Yew Fishing Rod 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Linen Thread", + }, + }, + ["Yew Lumber"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Log", + }, + }, + ["Yew Lumber 2"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Log", + "Yew Log", + "Yew Log", + "Bundling Twine", + }, + }, + ["Yew Wand"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Yew Lumber", + "Yagudo Feather", + }, + }, + ["Yhatdhara"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Divine Lumber", + "Thokcha Ingot", + "Thokcha Ingot", + "Thokcha Ingot", + "Linen Cloth", + }, + }, + ["Yogurt"] = { + ["crystal"] = "Dark Crystal", + ["ingredients"] = { + "Dogwood Log", + "Selbina Milk", + }, + }, + ["Yogurt Cake"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "San d'Or. Flour", + "Maple Sugar", + "Olive Oil", + "Kitron", + "Selbina Milk", + "Bird Egg", + "Apkallu Egg", + "Yogurt", + }, + }, + ["Yoshikiri"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Tama-Hagane", + "Ebony Lumber", + "Rainbow Thread", + "Wyvern Skin", + "Bismuth Ingot", + }, + }, + ["Yoto"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Animal Glue", + "Vitriol", + "Shinobi-Gatana", + }, + }, + ["Yoto 2"] = { + ["crystal"] = "Water Crystal", + ["ingredients"] = { + "Alch. Kit 55", + }, + }, + ["Zaghnal"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Iron Ingot", + "Iron Ingot", + "Holly Lumber", + "Grass Cloth", + }, + }, + ["Zamburak"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Steel Ingot", + "Oak Lumber", + "Coeurl Whisker", + }, + }, + ["Zamzummim Staff"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Ruby", + "Jacaranda Lbr.", + "Jacaranda Lbr.", + "Daimonic Mandible", + }, + }, + ["Zanbato"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Tama-Hagane", + "Ash Lumber", + "Cotton Thread", + "Raptor Skin", + }, + }, + ["Zaru Soba"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Pamtam Kelp", + "Fish Stock", + "Soba Noodles", + "Distilled Water", + "Ground Wasabi", + "Cibol", + "Puk Egg", + }, + }, + ["Zeal Cap"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Thunder Coral", + "Darksteel Cap", + }, + }, + ["Zestful Sap"] = { + ["crystal"] = "Lightng. Crystal", + ["ingredients"] = { + "Maple Sugar", + "Maple Sugar", + "Maple Sugar", + "Urunday Log", + }, + }, + ["Zinc Oxide"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Zinc Ore", + "Zinc Ore", + "Zinc Ore", + }, + }, + ["Zircon"] = { + ["crystal"] = "Wind Crystal", + ["ingredients"] = { + "Shivite", + }, + }, + ["Zircon Earring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Zircon", + "Gold Earring", + }, + }, + ["Zircon Earring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Zircon", + "Gold Earring +1", + }, + }, + ["Zircon Ring"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Zircon", + "Gold Ring", + }, + }, + ["Zircon Ring 2"] = { + ["crystal"] = "Earth Crystal", + ["ingredients"] = { + "Zircon", + "Gold Ring +1", + }, + }, + ["Zoni"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Rock Salt", + "Sticky Rice", + "Toko. Wildgrass", + "San d'Or. Carrot", + "Tiger Cod", + "Distilled Water", + "Lakerda", + "Ziz Meat", + }, + }, + ["Zunari Kabuto"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Copper Ingot", + "Iron Sheet", + "Iron Sheet", + "Silk Thread", + "Silk Cloth", + }, + }, + ["Zweihander"] = { + ["crystal"] = "Fire Crystal", + ["ingredients"] = { + "Mythril Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Darksteel Ingot", + "Chestnut Lumber", + "Ram Leather", + }, + }, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/craft/slpp.py b/Data/DefaultContent/Libraries/addons/addons/craft/slpp.py new file mode 100644 index 0000000..3da97b8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/craft/slpp.py @@ -0,0 +1,271 @@ +"""Copyright (c) 2010, 2011, 2012 SirAnthony <anthony at adsorbtion.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.""" + +import re +import sys + +ERRORS = { + 'unexp_end_string': u'Unexpected end of string while parsing Lua string.', + 'unexp_end_table': u'Unexpected end of table while parsing Lua string.', + 'mfnumber_minus': u'Malformed number (no digits after initial minus).', + 'mfnumber_dec_point': u'Malformed number (no digits after decimal point).', + 'mfnumber_sci': u'Malformed number (bad scientific format).', +} + + +class ParseError(Exception): + pass + + +class SLPP(object): + + def __init__(self): + self.text = '' + self.ch = '' + self.at = 0 + self.len = 0 + self.depth = 0 + self.space = re.compile('\s', re.M) + self.alnum = re.compile('\w', re.M) + self.newline = '\n' + self.tab = '\t' + + def decode(self, text): + if not text or not isinstance(text, basestring): + return + #FIXME: only short comments removed + reg = re.compile('--.*$', re.M) + text = reg.sub('', text, 0) + self.text = text + self.at, self.ch, self.depth = 0, '', 0 + self.len = len(text) + self.next_chr() + result = self.value() + return result + + def encode(self, obj): + self.depth = 0 + return self.__encode(obj) + + def __encode(self, obj): + s = '' + tab = self.tab + newline = self.newline + tp = type(obj) + if isinstance(obj, str): + s += '"%s"' % obj.replace(r'"', r'\"') + if isinstance(obj, unicode): + s += '"%s"' % obj.encode('utf-8').replace(r'"', r'\"') + elif tp in [int, float, long, complex]: + s += str(obj) + elif tp is bool: + s += str(obj).lower() + elif obj is None: + s += 'nil' + elif tp in [list, tuple, dict]: + self.depth += 1 + if len(obj) == 0 or ( tp is not dict and len(filter( + lambda x: type(x) in (int, float, long) \ + or (isinstance(x, basestring) and len(x) < 10), obj + )) == len(obj) ): + newline = tab = '' + dp = tab * self.depth + s += "%s{%s" % (tab * (self.depth - 2), newline) + if tp is dict: + contents = [] + for k, v in obj.iteritems(): + if type(k) is int: + contents.append(self.__encode(v)) + else: + contents.append(dp + '%s = %s' % (k, self.__encode(v))) + s += (',%s' % newline).join(contents) + else: + s += (',%s' % newline).join( + [dp + self.__encode(el) for el in obj]) + self.depth -= 1 + s += "%s%s}" % (newline, tab * self.depth) + return s + + def white(self): + while self.ch: + if self.space.match(self.ch): + self.next_chr() + else: + break + + def next_chr(self): + if self.at >= self.len: + self.ch = None + return None + self.ch = self.text[self.at] + self.at += 1 + return True + + def value(self): + self.white() + if not self.ch: + return + if self.ch == '{': + return self.object() + if self.ch == "[": + self.next_chr() + if self.ch in ['"', "'", '[']: + return self.string(self.ch) + if self.ch.isdigit() or self.ch == '-': + return self.number() + return self.word() + + def string(self, end=None): + s = '' + start = self.ch + if end == '[': + end = ']' + if start in ['"', "'", '[']: + while self.next_chr(): + if self.ch == end: + self.next_chr() + if start != "[" or self.ch == ']': + return s + if self.ch == '\\' and start == end: + self.next_chr() + if self.ch != end: + s += '\\' + s += self.ch + print ERRORS['unexp_end_string'] + + def object(self): + o = {} + k = None + idx = 0 + numeric_keys = False + self.depth += 1 + self.next_chr() + self.white() + if self.ch and self.ch == '}': + self.depth -= 1 + self.next_chr() + return o #Exit here + else: + while self.ch: + self.white() + if self.ch == '{': + o[idx] = self.object() + idx += 1 + continue + elif self.ch == '}': + self.depth -= 1 + self.next_chr() + if k is not None: + o[idx] = k + if not numeric_keys and len([ key for key in o if isinstance(key, (str, unicode, float, bool, tuple))]) == 0: + ar = [] + for key in o: + ar.insert(key, o[key]) + o = ar + return o #or here + else: + if self.ch == ',': + self.next_chr() + continue + else: + k = self.value() + if self.ch == ']': + numeric_keys = True + self.next_chr() + self.white() + ch = self.ch + if ch in ('=', ','): + self.next_chr() + self.white() + if ch == '=': + o[k] = self.value() + else: + o[idx] = k + idx += 1 + k = None + print ERRORS['unexp_end_table'] #Bad exit here + + words = {'true': True, 'false': False, 'nil': None} + def word(self): + s = '' + if self.ch != '\n': + s = self.ch + self.next_chr() + while self.ch is not None and self.alnum.match(self.ch) and s not in self.words: + s += self.ch + self.next_chr() + return self.words.get(s, s) + + def number(self): + def next_digit(err): + n = self.ch + self.next_chr() + if not self.ch or not self.ch.isdigit(): + raise ParseError(err) + return n + n = '' + try: + if self.ch == '-': + n += next_digit(ERRORS['mfnumber_minus']) + n += self.digit() + if n == '0' and self.ch in ['x', 'X']: + n += self.ch + self.next_chr() + n += self.hex() + else: + if self.ch and self.ch == '.': + n += next_digit(ERRORS['mfnumber_dec_point']) + n += self.digit() + if self.ch and self.ch in ['e', 'E']: + n += self.ch + self.next_chr() + if not self.ch or self.ch not in ('+', '-'): + raise ParseError(ERRORS['mfnumber_sci']) + n += next_digit(ERRORS['mfnumber_sci']) + n += self.digit() + except ParseError: + t, e = sys.exc_info()[:2] + print(e) + return 0 + try: + return int(n, 0) + except: + pass + return float(n) + + def digit(self): + n = '' + while self.ch and self.ch.isdigit(): + n += self.ch + self.next_chr() + return n + + def hex(self): + n = '' + while self.ch and \ + (self.ch in 'ABCDEFabcdef' or self.ch.isdigit()): + n += self.ch + self.next_chr() + return n + + +slpp = SLPP() + +__all__ = ['slpp'] diff --git a/Data/DefaultContent/Libraries/addons/addons/digger/README b/Data/DefaultContent/Libraries/addons/addons/digger/README new file mode 100644 index 0000000..d79b4ed --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/digger/README @@ -0,0 +1,23 @@ +This script is a Chocobo digging addon to be used with Windower for +Final Fantasy XI. + +You can report issues, get the latest version, and view the source at: + https://github.com/svanheulen/digger-windower-addon + +*********************************************************************** +I stopped playing FFXI (in February 2015) so I may not be able to help +with bugs but I'll try... So long as you can give me more information +then "it doesn't work" +*********************************************************************** + +This addon requires the Timers plugin to display your "recast" timer for +digging. It will also display statistics about your dig accuracy, +fatigue and remaining greens after each dig. The fatigue tracking will +also work properly with the Blue Racing Silks bonus. + +The available commands are: + digger rank <crafting rank> + Sets your digging rank. Accepts rank name (like "Artisan") or + area delay (like "A25"). + digger stats [clear] + Displays or clears digging accuracy statistics. diff --git a/Data/DefaultContent/Libraries/addons/addons/digger/digger.lua b/Data/DefaultContent/Libraries/addons/addons/digger/digger.lua new file mode 100644 index 0000000..90d4acd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/digger/digger.lua @@ -0,0 +1,226 @@ +--[[ +Copyright (c) 2014, Seth VanHeulen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR 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. +--]] + +-- addon information + +_addon.name = 'digger' +_addon.version = '2.2.0' +_addon.command = 'digger' +_addon.author = 'Seth VanHeulen (Acacia@Odin)' + +-- modules + +config = require('config') +require('pack') + +-- default settings + +defaults = {} +defaults.delay = {} +defaults.delay.area = 60 +defaults.delay.lag = 3 +defaults.delay.dig = 15 +defaults.fatigue = {} +defaults.fatigue.date = os.date('!%Y-%m-%d', os.time() + 32400) +defaults.fatigue.items = 0 +defaults.fatigue.diff = 0 +defaults.fatigue.free = 0 +defaults.accuracy = {} +defaults.accuracy.failed = 0 +defaults.accuracy.total = 0 + +settings = config.load(defaults) + +-- load message constants + +require('messages') + +-- buff helper function + +function get_chocobo_buff() + for _,buff_id in pairs(windower.ffxi.get_player().buffs) do + if buff_id == 252 then + return true + end + end + return false +end + +-- inventory helper function + +function get_gysahl_count() + local count = 0 + for _,item in pairs(windower.ffxi.get_items(0)) do + if type(item) == 'table' and item.id == 4545 and item.status == 0 then + count = count + item.count + end + end + return count +end + +-- stats helper functions + +function update_day() + local today = os.date('!%Y-%m-%d', os.time() + 32400) + if settings.fatigue.date ~= today then + settings.fatigue.date = today + settings.fatigue.items = 0 + settings.fatigue.free = 0 + settings.fatigue.diff = settings.accuracy.failed - settings.accuracy.total + end +end + +function display_stats() + local accuracy = 0 + local successful = settings.accuracy.total - settings.accuracy.failed + if settings.accuracy.total > 0 then + accuracy = (successful / settings.accuracy.total) * 100 + end + windower.add_to_chat(207, 'dig accuracy: %d%% (%d/%d), fatigue today: %d, items today: %d gysahl greens remaining: %d':format(accuracy, successful, settings.accuracy.total, successful - settings.fatigue.free + settings.fatigue.diff, settings.fatigue.items, get_gysahl_count())) +end + +function update_stats(mode) + update_day() + if mode == 3 then + settings.fatigue.free = settings.fatigue.free + 1 + elseif mode == 2 then + settings.fatigue.items = settings.fatigue.items + 1 + display_stats() + elseif mode == 1 then + settings.accuracy.total = settings.accuracy.total + 1 + else + settings.accuracy.failed = settings.accuracy.failed + 1 + display_stats() + end + settings:save() +end + +-- event callback functions + +function check_zone_change(new_zone_id, old_zone_id) + if messages[new_zone_id] then + windower.send_command('timers c "Chocobo Area Delay" %d down':format(settings.delay.area + settings.delay.lag)) + else + windower.send_command('timers d "Chocobo Area Delay"') + end + windower.send_command('timers d "Chocobo Dig Delay"') +end + +function check_incoming_chunk(id, original, modified, injected, blocked) + local zone_id = windower.ffxi.get_info().zone + if messages[zone_id] then + if id == 0x2A then + local message_id = original:unpack('H', 27) % 0x8000 + if (messages[zone_id].full == message_id or messages[zone_id].success == message_id or messages[zone_id].points == message_id or messages[zone_id].standing == message_id or messages[zone_id].notes == message_id or messages[zone_id].bayld == message_id) and get_chocobo_buff() then + update_stats(2) + elseif messages[zone_id].ease == message_id then + update_stats(3) + end + elseif id == 0x2F and windower.ffxi.get_player().id == original:unpack('I', 5) then + if settings.delay.dig > 0 then + windower.send_command('timers c "Chocobo Dig Delay" %d down':format(settings.delay.dig)) + end + update_stats(1) + elseif id == 0x36 then + local message_id = original:unpack('H', 11) % 0x8000 + if messages[zone_id].fail == message_id then + update_stats(0) + end + end + end +end + +function digger_command(...) + local arg = {...} + if #arg == 1 and arg[1]:lower() == 'stats' then + update_day() + display_stats() + elseif #arg == 2 and arg[1]:lower() == 'stats' and arg[2]:lower() == 'clear' then + update_day() + settings.fatigue.diff = settings.accuracy.total - settings.accuracy.failed + settings.fatigue.diff + settings.accuracy.failed = 0 + settings.accuracy.total = 0 + settings:save() + windower.add_to_chat(204, 'reset dig accuracy statistics') + elseif #arg == 2 and arg[1]:lower() == 'rank' then + local rank = arg[2]:lower() + if rank == 'amateur' or rank == 'a60' then + settings.delay.area = 60 + settings.delay.dig = 15 + elseif rank == 'recruit' or rank == 'a55' then + settings.delay.area = 55 + settings.delay.dig = 10 + elseif rank == 'initiate' or rank == 'a50' then + settings.delay.area = 50 + settings.delay.dig = 5 + elseif rank == 'novice' or rank == 'a45' then + settings.delay.area = 45 + settings.delay.dig = 0 + elseif rank == 'apprentice' or rank == 'a40' then + settings.delay.area = 40 + settings.delay.dig = 0 + elseif rank == 'journeyman' or rank == 'a35' then + settings.delay.area = 35 + settings.delay.dig = 0 + elseif rank == 'craftsman' or rank == 'a30' then + settings.delay.area = 30 + settings.delay.dig = 0 + elseif rank == 'artisan' or rank == 'a25' then + settings.delay.area = 25 + settings.delay.dig = 0 + elseif rank == 'adept' or rank == 'a20' then + settings.delay.area = 20 + settings.delay.dig = 0 + elseif rank == 'veteran' or rank == 'a15' then + settings.delay.area = 15 + settings.delay.dig = 0 + elseif rank == 'expert' or rank == 'a10' then + settings.delay.area = 10 + settings.delay.dig = 0 + else + windower.add_to_chat(167, 'invalid digging rank') + return + end + windower.add_to_chat(204, 'digging rank: %s, area delay: %d seconds, dig delay: %d seconds':format(rank, settings.delay.area, settings.delay.dig)) + settings:save() + else + windower.add_to_chat(167, 'usage:') + windower.add_to_chat(167, ' digger rank <crafting rank>') + windower.add_to_chat(167, ' digger stats [clear]') + end +end + +-- register event callbacks + +windower.register_event('zone change', check_zone_change) +windower.register_event('incoming chunk', check_incoming_chunk) +windower.register_event('addon command', digger_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/digger/messages.lua b/Data/DefaultContent/Libraries/addons/addons/digger/messages.lua new file mode 100644 index 0000000..8da59e0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/digger/messages.lua @@ -0,0 +1,61 @@ +messages = {} +messages[2] = {bayld=7368, ease=7360, fail=7285, full=7283, notes=7367, points=7365, standing=7366, success=6388} +messages[4] = {bayld=7362, ease=7354, fail=7279, full=7277, notes=7361, points=7359, standing=7360, success=6388} +messages[5] = {bayld=7319, ease=7311, fail=7236, full=7234, notes=7318, points=7316, standing=7317, success=6401} +messages[7] = {bayld=7313, ease=7305, fail=7230, full=7228, notes=7312, points=7310, standing=7311, success=6388} +messages[24] = {bayld=7649, ease=7641, fail=7566, full=7564, notes=7648, points=7646, standing=7647, success=6388} +messages[25] = {bayld=7169, ease=7161, fail=7086, full=7084, notes=7168, points=7166, standing=7167, success=6388} +messages[51] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[52] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[61] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[79] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[81] = {bayld=7828, ease=7820, fail=7745, full=7743, notes=7827, points=7825, standing=7826, success=6388} +messages[82] = {bayld=7460, ease=7452, fail=7377, full=7375, notes=7459, points=7457, standing=7458, success=6388} +messages[83] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[84] = {bayld=7167, ease=7159, fail=7084, full=7082, notes=7166, points=7164, standing=7165, success=6388} +messages[88] = {bayld=7453, ease=7445, fail=7370, full=7368, notes=7452, points=7450, standing=7451, success=6388} +messages[89] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[90] = {bayld=7244, ease=7236, fail=7161, full=7159, notes=7243, points=7241, standing=7242, success=6388} +messages[91] = {bayld=7167, ease=7159, fail=7084, full=7082, notes=7166, points=7164, standing=7165, success=6388} +messages[95] = {bayld=7174, ease=7166, fail=7091, full=7089, notes=7173, points=7171, standing=7172, success=6388} +messages[96] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[97] = {bayld=7705, ease=7697, fail=7622, full=7620, notes=7704, points=7702, standing=7703, success=6388} +messages[98] = {bayld=7705, ease=7697, fail=7622, full=7620, notes=7704, points=7702, standing=7703, success=6388} +messages[100] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[101] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[102] = {bayld=7310, ease=7302, fail=7227, full=7225, notes=7309, points=7307, standing=7308, success=6388} +messages[103] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[104] = {bayld=7802, ease=7794, fail=7719, full=7717, notes=7801, points=7799, standing=7800, success=6410} +messages[105] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[106] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6569} +messages[107] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[108] = {bayld=7310, ease=7302, fail=7227, full=7225, notes=7309, points=7307, standing=7308, success=6388} +messages[109] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[110] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[111] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6569} +messages[112] = {bayld=7345, ease=7337, fail=7262, full=7260, notes=7344, points=7342, standing=7343, success=6401} +messages[113] = {bayld=7648, ease=7640, fail=7565, full=7563, notes=7647, points=7645, standing=7646, success=6388} +messages[114] = {bayld=7648, ease=7640, fail=7565, full=7563, notes=7647, points=7645, standing=7646, success=6388} +messages[115] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[116] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[117] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6569} +messages[118] = {bayld=7347, ease=7339, fail=7264, full=7262, notes=7346, points=7344, standing=7345, success=6423} +messages[119] = {bayld=7328, ease=7320, fail=7245, full=7243, notes=7327, points=7325, standing=7326, success=6410} +messages[120] = {bayld=7336, ease=7328, fail=7253, full=7251, notes=7335, points=7333, standing=7334, success=6410} +messages[121] = {bayld=7648, ease=7640, fail=7565, full=7563, notes=7647, points=7645, standing=7646, success=6388} +messages[122] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[123] = {bayld=7648, ease=7640, fail=7565, full=7563, notes=7647, points=7645, standing=7646, success=6388} +messages[124] = {bayld=7648, ease=7640, fail=7565, full=7563, notes=7647, points=7645, standing=7646, success=6388} +messages[125] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[126] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[127] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[128] = {bayld=7306, ease=7298, fail=7223, full=7221, notes=7305, points=7303, standing=7304, success=6388} +messages[136] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[137] = {bayld=7669, ease=7661, fail=7586, full=7584, notes=7668, points=7666, standing=7667, success=6388} +messages[260] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[261] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[262] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[263] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[265] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[266] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} +messages[267] = {bayld=7147, ease=7139, fail=7064, full=7062, notes=7146, points=7144, standing=7145, success=6388} diff --git a/Data/DefaultContent/Libraries/addons/addons/distance/distance.lua b/Data/DefaultContent/Libraries/addons/addons/distance/distance.lua new file mode 100644 index 0000000..9c22862 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/distance/distance.lua @@ -0,0 +1,47 @@ +_addon.name = 'Distance' +_addon.author = 'Windower' +_addon.version = '1.0.0.1' +_addon.command = 'distance' + +config = require('config') +texts = require('texts') + +defaults = {} +defaults.pos = {} +defaults.pos.x = -178 +defaults.pos.y = 21 +defaults.text = {} +defaults.text.font = 'Arial' +defaults.text.size = 14 +defaults.flags = {} +defaults.flags.right = true + +settings = config.load(defaults) +distance = texts.new('${value||%.1f}', settings) + +debug.setmetatable(nil, {__index = {}, __call = functions.empty}) + +windower.register_event('prerender', function() + local t = windower.ffxi.get_mob_by_index(windower.ffxi.get_player().target_index or 0) + distance.value = t.distance:sqrt() + distance:visible(t ~= nil) +end) + +windower.register_event('addon command', function(command) + if command == 'save' then + config.save(settings, 'all') + end +end) + +--[[ +Copyright © 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/distance/readme.md b/Data/DefaultContent/Libraries/addons/addons/distance/readme.md new file mode 100644 index 0000000..2d857ef --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/distance/readme.md @@ -0,0 +1,3 @@ +# Distance + +Emulates the functionality of the Distance plugin. Shows the distance towards the current target in yalms. Useful for range estimations.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/dynamishelper/README.md b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/README.md new file mode 100644 index 0000000..25055b2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/README.md @@ -0,0 +1,16 @@ +Authors: Krizz + +Version: 1 + +Date: 20130419 + +Dynamis Helper + +Abbreviation: //dh + +Commands: +* help - Shows a menu of commands in game +* timer [on/off] - Displays a timer each time a mob is staggered. +* tracker [on/off/reset/pos x y] - Tracks the amount of currency obtained. +* proc [on/off/pos x y] - Displays the proc for the targeted mob. +* ll create - Creates and loads a light luggage profile that will automatically lot currency. diff --git a/Data/DefaultContent/Libraries/addons/addons/dynamishelper/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/data/settings.xml new file mode 100644 index 0000000..dbd5ac8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/data/settings.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<settings> + <global> + <timer>on</timer> + <tracker>off</tracker> + <trposx>1000</trposx> + <trposy>250</trposy> + <proc>off</proc> + <pposx>900</pposx> + <pposy>250</pposy> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/dynamishelper/dynamishelper.lua b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/dynamishelper.lua new file mode 100644 index 0000000..3ba0c8c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/dynamishelper/dynamishelper.lua @@ -0,0 +1,431 @@ +--Copyright (c) 2013, Krizz +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of Dynamis Helper nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL KRIZZ 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. +--Features +-- Stagger timer +-- Currency tracker +-- Proc identifier +-- Lot currency + +_addon.name = 'DynamisHelper' +_addon.author = 'Krizz, maintainer: Skyrant' +_addon.commands = {'DynamisHelper','dh'} +_addon.version = '1.0.2.0' + +require('strings') +require('sets') +config = require('config') +res = require('resources') + +-- Variables +staggers = T{} +staggers['morning'] = T{} +staggers['morning']['ja'] = { "Kindred Thief", "Kindred Beastmaster", "Kindred Monk", "Kindred Ninja", "Kindred Ranger", + "Duke Gomory", "Marquis Andras", "Marquis Gamygyn", "Count Raum", "Marquis Cimeries", "Marquis Caim", "Baron Avnas", + "Hydra Thief", "Hydra Beastmaster", "Hydra Monk", "Hydra Ninja", "Hydra Ranger", + "Vanguard Backstabber", "Vanguard Grappler", "Vanguard Hawker", "Vanguard Pillager", "Vanguard Predator", "Voidstreaker Butchnotch", "Steelshank Kratzvatz", + "Vanguard Beasttender", "Vanguard Kusa", "Vanguard Mason", "Vanguard Militant", "Vanguard Purloiner", "Ko'Dho Cannonball", + "Vanguard Assassin", "Vanguard Liberator", "Vanguard Ogresoother", "Vanguard Salvager", "Vanguard Sentinel", "Wuu Qoho the Razorclaw", "Tee Zaksa the Ceaseless", + "Vanguard Ambusher", "Vanguard Hitman", "Vanguard Pathfinder", "Vanguard Pit", "Vanguard Welldigger", + "Bandrix Rockjaw", "Lurklox Dhalmelneck", "Trailblix Goatmug", "Kikklix Longlegs", "Snypestix Eaglebeak", "Jabkix Pigeonpecs", "Blazox Boneybod", "Bootrix Jaggedelbow", "Mobpix Mucousmouth", "Prowlox Barrelbelly", "Slystix Megapeepers", "Feralox Honeylips", + "Bordox Kittyback", "Droprix Granitepalms", "Routsix Rubbertendon", "Slinkix Trufflesniff", "Swypestix Tigershins", + "Nightmare Crawler", "Nightmare Raven", "Nightmare Uragnite", + "Nightmare Fly", "Nightmare Flytrap", "Nightmare Funguar", + "Nightmare Gaylas", "Nightmare Kraken", "Nightmare Roc", + "Nightmare Hornet", "Nightmare Bugard", + "Woodnix Shrillwhistle", "Hamfist Gukhbuk", "Lyncean Juwgneg", "Va'Rhu Bodysnatcher", "Doo Peku the Fleetfoot", + "Nant'ina", "Antaeus"} + +staggers['morning']['magic'] = {"Kindred White Mage", "Kindred Bard", "Kindred Summoner", "Kindred Black Mage", "Kindred Red Mage", + "Duke Berith", "Marquis Decarabia", "Prince Seere", "Marquis Orias", "Marquis Nebiros", "Duke Haures", + "Hydra White Mage", "Hydra Bard", "Hydra Summoner", "Hydra Black Mage", "Hydra Red Mage", + "Vanguard Amputator", "Vanguard Bugler", "Vanguard Dollmaster", "Vanguard Mesmerizer", "Vanguard Vexer", "Soulsender Fugbrag", "Reapertongue Gadgquok", "Battlechoir Gitchfotch", + "Vanguard Constable", "Vanguard Minstrel", "Vanguard Protector", "Vanguard Thaumaturge", "Vanguard Undertaker", "Gi'Pha Manameister", "Gu'Nhi Noondozer", "Ra'Gho Darkfount", "Va'Zhe Pummelsong", + "Vanguard Chanter", "Vanguard Oracle", "Vanguard Prelate", "Vanguard Priest", "Vanguard Visionary", "Loo Hepe the Eyepiercer", "Xoo Kaza the Solemn", "Haa Pevi the Stentorian", "Xuu Bhoqa the Enigma", "Fuu Tzapo the Blessed", "Naa Yixo the Stillrage", + "Vanguard Alchemist", "Vanguard Enchanter", "Vanguard Maestro", "Vanguard Necromancer", "Vanguard Shaman", + "Elixmix Hooknose", "Gabblox Magpietongue", "Hermitrix Toothrot", "Humnox Drumbelly", "Morgmox Moldnoggin", "Mortilox Wartpaws", "Distilix Stickytoes", "Jabbrox Grannyguise", "Quicktrix Hexhands", "Wilywox Tenderpalm", + "Ascetox Ratgums", "Brewnix Bittypupils", "Gibberox Pimplebeak", "Morblox Stubthumbs", "Whistrix Toadthroat", + "Nightmare Bunny", "Nightmare Eft", "Nightmare Mandragora", + "Nightmare Hippogryph", "Nightmare Sabotender", "Nightmare Sheep", + "Nightmare Snoll", "Nightmare Stirge", "Nightmare Weapon", + "Nightmare Makara", "Nightmare Cluster", + "Gosspix Blabblerlips", "Flamecaller Zoeqdoq", "Gi'Bhe Fleshfeaster", "Ree Nata the Melomanic", "Baa Dava the Bibliophage", + "Aitvaras" } + +staggers['morning']['ws'] = { "Kindred Paladin", "Kindred Warrior", "Kindred Samurai", "Kindred Dragoon", "Kindred Dark Knight", + "Count Zaebos", "Duke Scox", "Marquis Sabnak", "King Zagan", "Count Haagenti", + "Hydra Paladin", "Hydra Warrior", "Hydra Samurai", "Hydra Dragoon", "Hydra Dark Knight", + "Vanguard Footsoldier", "Vanguard Gutslasher", "Vanguard Impaler", "Vanguard Neckchopper", "Vanguard Trooper", "Wyrmgnasher Bjakdek", "Bladerunner Rokgevok", "Bloodfist Voshgrosh", "Spellspear Djokvukk", + "Vanguard Defender", "Vanguard Drakekeeper", "Vanguard Hatamoto", "Vanguard Vigilante", "Vanguard Vindicator", "Ze'Vho Fallsplitter", "Zo'Pha Forgesoul", "Bu'Bho Truesteel", + "Vanguard Exemplar", "Vanguard Inciter", "Vanguard Partisan", "Vanguard Persecutor", "Vanguard Skirmisher", "Maa Febi the Steadfast", "Muu Febi the Steadfast", + "Vanguard Armorer", "Vanguard Dragontamer", "Vanguard Ronin", "Vanguard Smithy", + "Buffrix Eargone", "Cloktix Longnail", "Sparkspox Sweatbrow", "Ticktox Beadyeyes", "Tufflix Loglimbs", "Wyrmwix Snakespecs", "Karashix Swollenskull", "Smeltix Thickhide", "Wasabix Callusdigit", "Anvilix Sootwrists", "Scruffix Shaggychest", "Tymexox Ninefingers", "Scourquix Scaleskin", + "Draklix Scalecrust", "Moltenox Stubthumbs", "Ruffbix Jumbolobes", "Shisox Widebrow", "Tocktix Thinlids", + "Nightmare Crab", "Nightmare Dhalmel", "Nightmare Scorpion", + "Nightmare Goobbue", "Nightmare Manticore", "Nightmare Treant", + "Nightmare Diremite", "Nightmare Tiger", "Nightmare Raptor", + "Nightmare Leech", "Nightmare Worm", + "Shamblix Rottenheart", "Elvaansticker Bxafraff", "Qu'Pho Bloodspiller", "Te'Zha Ironclad", "Koo Rahi the Levinblade", + "Barong", "Alklha", "Stihi", "Fairy Ring", "Stcemqestcint", "Stringes", "Suttung" } + +staggers['morning']['random'] = {"Nightmare Taurus"} +staggers['morning']['none'] = {"Animated Claymore", "Animated Dagger", "Animated Great Axe", "Animated Gun", "Animated Hammer", "Animated Horn", "Animated Kunai", "Animated Knuckles", "Animated Longbow", "Animated Longsword", "Animated Scythe", "Animated Shield", "Animated Spear", "Animated Staff", "Animated Tabar", "Animated Tachi", "Fire Pukis", "Petro Pukis", "Poison Pukis", "Wind Pukis", "Kindred's Vouivre", "Kindred's Wyvern", "Kindred's Avatar", "Vanguard Eye", "Prototype Eye", "Nebiros's Avatar", "Haagenti's Avatar", "Caim's Vouivre", "Andras's Vouivre", "Adamantking Effigy", "Avatar Icon", "Goblin Replica", "Serjeant Tombstone", "Zagan's Wyvern", "Hydra's Hound", "Hydra's Wyvern", "Hydra's Avatar", "Rearguard Eye", "Adamantking Effigy", "Adamantking Image", "Avatar Icon", "Avatar Idol", "Effigy Prototype", "Goblin Replica", "Goblin Statue", "Icon Prototype", "Manifest Icon", "Manifest Icon", "Prototype Eye", "Serjeant Tombstone", "Statue Prototype", "Tombstone Prototype", "Vanguard Eye", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Crow", "Vanguard's Hecteyes", "Vanguard's Scorpion", "Vanguard's Slime", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Warchief Tombstone"} + +staggers['day'] = T{} +staggers['day']['ja'] = { "Kindred Thief", "Kindred Beastmaster", "Kindred Monk", "Kindred Ninja", "Kindred Ranger", + "Duke Gomory", "Marquis Andras", "Marquis Gamygyn", "Count Raum", "Marquis Cimeries", "Marquis Caim", "Baron Avnas", + "Hydra Thief", "Hydra Beastmaster", "Hydra Monk", "Hydra Ninja", "Hydra Ranger", + "Vanguard Backstabber", "Vanguard Grappler", "Vanguard Hawker", "Vanguard Pillager", "Vanguard Predator", "Voidstreaker Butchnotch", "Steelshank Kratzvatz", + "Vanguard Beasttender", "Vanguard Kusa", "Vanguard Mason", "Vanguard Militant", "Vanguard Purloiner", "Ko'Dho Cannonball", + "Vanguard Assassin", "Vanguard Liberator", "Vanguard Ogresoother", "Vanguard Salvager", "Vanguard Sentinel", "Wuu Qoho the Razorclaw", "Tee Zaksa the Ceaseless", + "Vanguard Ambusher", "Vanguard Hitman", "Vanguard Pathfinder", "Vanguard Pit", "Vanguard Welldigger", + "Bandrix Rockjaw", "Lurklox Dhalmelneck", "Trailblix Goatmug", "Kikklix Longlegs", "Snypestix Eaglebeak", "Jabkix Pigeonpecs", "Blazox Boneybod", "Bootrix Jaggedelbow", "Mobpix Mucousmouth", "Prowlox Barrelbelly", "Slystix Megapeepers", "Feralox Honeylips", + "Bordox Kittyback", "Droprix Granitepalms", "Routsix Rubbertendon", "Slinkix Trufflesniff", "Swypestix Tigershins", + "Nightmare Bunny", "Nightmare Eft", "Nightmare Mandragora", + "Nightmare Hippogryph", "Nightmare Sabotender", "Nightmare Sheep", + "Nightmare Snoll", "Nightmare Stirge", "Nightmare Weapon", + "Nightmare Makara", "Nightmare Cluster", + "Woodnix Shrillwhistle", "Hamfist Gukhbuk", "Lyncean Juwgneg", "Va'Rhu Bodysnatcher", "Doo Peku the Fleetfoot", + "Nant'ina", "Antaeus"} + +staggers['day']['magic'] = { "Kindred White Mage", "Kindred Bard", "Kindred Summoner", "Kindred Black Mage", "Kindred Red Mage", + "Duke Berith", "Marquis Decarabia", "Prince Seere", "Marquis Orias", "Marquis Nebiros", "Duke Haures", + "Hydra White Mage", "Hydra Bard", "Hydra Summoner", "Hydra Black Mage", "Hydra Red Mage", + "Vanguard Amputator", "Vanguard Bugler", "Vanguard Dollmaster", "Vanguard Mesmerizer", "Vanguard Vexer", "Soulsender Fugbrag", "Reapertongue Gadgquok", "Battlechoir Gitchfotch", + "Vanguard Constable", "Vanguard Minstrel", "Vanguard Protector", "Vanguard Thaumaturge", "Vanguard Undertaker", "Gi'Pha Manameister", "Gu'Nhi Noondozer", "Ra'Gho Darkfount", "Va'Zhe Pummelsong", + "Vanguard Chanter", "Vanguard Oracle", "Vanguard Prelate", "Vanguard Priest", "Vanguard Visionary", "Loo Hepe the Eyepiercer", "Xoo Kaza the Solemn", "Haa Pevi the Stentorian", "Xuu Bhoqa the Enigma", "Fuu Tzapo the Blessed", "Naa Yixo the Stillrage", + "Vanguard Alchemist", "Vanguard Enchanter", "Vanguard Maestro", "Vanguard Necromancer", "Vanguard Shaman", + "Elixmix Hooknose", "Gabblox Magpietongue", "Hermitrix Toothrot", "Humnox Drumbelly", "Morgmox Moldnoggin", "Mortilox Wartpaws", "Distilix Stickytoes", "Jabbrox Grannyguise", "Quicktrix Hexhands", "Wilywox Tenderpalm", + "Ascetox Ratgums", "Brewnix Bittypupils", "Gibberox Pimplebeak", "Morblox Stubthumbs", "Whistrix Toadthroat", + "Nightmare Crab", "Nightmare Dhalmel", "Nightmare Scorpion", + "Nightmare Goobbue", "Nightmare Manticore", "Nightmare Treant", + "Nightmare Diremite", "Nightmare Tiger", "Nightmare Raptor", + "Nightmare Leech", "Nightmare Worm" , + "Gosspix Blabblerlips", "Flamecaller Zoeqdoq", "Gi'Bhe Fleshfeaster", "Ree Nata the Melomanic", "Baa Dava the Bibliophage", + "Aitvaras" } + +staggers['day']['ws'] = { "Kindred Paladin", "Kindred Warrior", "Kindred Samurai", "Kindred Dragoon", "Kindred Dark Knight", + "Count Zaebos", "Duke Scox", "Marquis Sabnak", "King Zagan", "Count Haagenti", + "Hydra Paladin", "Hydra Warrior", "Hydra Samurai", "Hydra Dragoon", "Hydra Dark Knight", + "Vanguard Footsoldier", "Vanguard Gutslasher", "Vanguard Impaler", "Vanguard Neckchopper", "Vanguard Trooper", "Wyrmgnasher Bjakdek", "Bladerunner Rokgevok", "Bloodfist Voshgrosh", "Spellspear Djokvukk", + "Vanguard Defender", "Vanguard Drakekeeper", "Vanguard Hatamoto", "Vanguard Vigilante", "Vanguard Vindicator", "Ze'Vho Fallsplitter", "Zo'Pha Forgesoul", "Bu'Bho Truesteel", + "Vanguard Exemplar", "Vanguard Inciter", "Vanguard Partisan", "Vanguard Persecutor", "Vanguard Skirmisher", "Maa Febi the Steadfast", "Muu Febi the Steadfast", + "Vanguard Armorer", "Vanguard Dragontamer", "Vanguard Ronin", "Vanguard Smithy", + "Buffrix Eargone", "Cloktix Longnail", "Sparkspox Sweatbrow", "Ticktox Beadyeyes", "Tufflix Loglimbs", "Wyrmwix Snakespecs", "Karashix Swollenskull", "Smeltix Thickhide", "Wasabix Callusdigit", "Anvilix Sootwrists", "Scruffix Shaggychest", "Tymexox Ninefingers", "Scourquix Scaleskin", + "Draklix Scalecrust", "Moltenox Stubthumbs", "Ruffbix Jumbolobes", "Shisox Widebrow", "Tocktix Thinlids", + "Nightmare Crawler", "Nightmare Raven", "Nightmare Uragnite", + "Nightmare Fly", "Nightmare Flytrap", "Nightmare Funguar", + "Nightmare Gaylas", "Nightmare Kraken", "Nightmare Roc", + "Nightmare Hornet", "Nightmare Bugard", + "Shamblix Rottenheart", "Elvaansticker Bxafraff", "Qu'Pho Bloodspiller", "Te'Zha Ironclad", "Koo Rahi the Levinblade", + "Barong", "Alklha", "Stihi", "Fairy Ring", "Stcemqestcint", "Stringes", "Suttung" } + +staggers['day']['random'] = {"Nightmare Taurus"} +staggers['day']['none'] = {"Animated Claymore", "Animated Dagger", "Animated Great Axe", "Animated Gun", "Animated Hammer", "Animated Horn", "Animated Kunai", "Animated Knuckles", "Animated Longbow", "Animated Longsword", "Animated Scythe", "Animated Shield", "Animated Spear", "Animated Staff", "Animated Tabar", "Animated Tachi", "Fire Pukis", "Petro Pukis", "Poison Pukis", "Wind Pukis", "Kindred's Vouivre", "Kindred's Wyvern", "Kindred's Avatar", "Vanguard Eye", "Prototype Eye", "Nebiros's Avatar", "Haagenti's Avatar", "Caim's Vouivre", "Andras's Vouivre", "Adamantking Effigy", "Avatar Icon", "Goblin Replica", "Serjeant Tombstone", "Zagan's Wyvern", "Hydra's Hound", "Hydra's Wyvern", "Hydra's Avatar", "Rearguard Eye", "Adamantking Effigy", "Adamantking Image", "Avatar Icon", "Avatar Idol", "Effigy Prototype", "Goblin Replica", "Goblin Statue", "Icon Prototype", "Manifest Icon", "Manifest Icon", "Prototype Eye", "Serjeant Tombstone", "Statue Prototype", "Tombstone Prototype", "Vanguard Eye", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Crow", "Vanguard's Hecteyes", "Vanguard's Scorpion", "Vanguard's Slime", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Warchief Tombstone"} + +staggers['night'] = T{} +staggers['night']['ja'] = { "Kindred Thief", "Kindred Beastmaster", "Kindred Monk", "Kindred Ninja", "Kindred Ranger", + "Duke Gomory", "Marquis Andras", "Marquis Gamygyn", "Count Raum", "Marquis Cimeries", "Marquis Caim", "Baron Avnas", + "Hydra Thief", "Hydra Beastmaster", "Hydra Monk", "Hydra Ninja", "Hydra Ranger", + "Vanguard Backstabber", "Vanguard Grappler", "Vanguard Hawker", "Vanguard Pillager", "Vanguard Predator", "Voidstreaker Butchnotch", "Steelshank Kratzvatz", + "Vanguard Beasttender", "Vanguard Kusa", "Vanguard Mason", "Vanguard Militant", "Vanguard Purloiner", "Ko'Dho Cannonball", + "Vanguard Assassin", "Vanguard Liberator", "Vanguard Ogresoother", "Vanguard Salvager", "Vanguard Sentinel", "Wuu Qoho the Razorclaw", "Tee Zaksa the Ceaseless", + "Vanguard Ambusher", "Vanguard Hitman", "Vanguard Pathfinder", "Vanguard Pit", "Vanguard Welldigger", + "Bandrix Rockjaw", "Lurklox Dhalmelneck", "Trailblix Goatmug", "Kikklix Longlegs", "Snypestix Eaglebeak", "Jabkix Pigeonpecs", "Blazox Boneybod", "Bootrix Jaggedelbow", "Mobpix Mucousmouth", "Prowlox Barrelbelly", "Slystix Megapeepers", "Feralox Honeylips", + "Bordox Kittyback", "Droprix Granitepalms", "Routsix Rubbertendon", "Slinkix Trufflesniff", "Swypestix Tigershins", + "Nightmare Crab", "Nightmare Dhalmel", "Nightmare Scorpion", + "Nightmare Goobbue", "Nightmare Manticore", "Nightmare Treant", + "Nightmare Diremite", "Nightmare Tiger", "Nightmare Raptor", + "Nightmare Leech", "Nightmare Worm", + "Woodnix Shrillwhistle", "Hamfist Gukhbuk", "Lyncean Juwgneg", "Va'Rhu Bodysnatcher", "Doo Peku the Fleetfoot", + "Nant'ina", "Antaeus"} + +staggers['night']['magic'] = { "Kindred White Mage", "Kindred Bard", "Kindred Summoner", "Kindred Black Mage", "Kindred Red Mage", + "Duke Berith", "Marquis Decarabia", "Prince Seere", "Marquis Orias", "Marquis Nebiros", "Duke Haures", + "Hydra White Mage", "Hydra Bard", "Hydra Summoner", "Hydra Black Mage", "Hydra Red Mage", + "Vanguard Amputator", "Vanguard Bugler", "Vanguard Dollmaster", "Vanguard Mesmerizer", "Vanguard Vexer", "Soulsender Fugbrag", "Reapertongue Gadgquok", "Battlechoir Gitchfotch", + "Vanguard Constable", "Vanguard Minstrel", "Vanguard Protector", "Vanguard Thaumaturge", "Vanguard Undertaker", "Gi'Pha Manameister", "Gu'Nhi Noondozer", "Ra'Gho Darkfount", "Va'Zhe Pummelsong", + "Vanguard Chanter", "Vanguard Oracle", "Vanguard Prelate", "Vanguard Priest", "Vanguard Visionary", "Loo Hepe the Eyepiercer", "Xoo Kaza the Solemn", "Haa Pevi the Stentorian", "Xuu Bhoqa the Enigma", "Fuu Tzapo the Blessed", "Naa Yixo the Stillrage", + "Vanguard Alchemist", "Vanguard Enchanter", "Vanguard Maestro", "Vanguard Necromancer", "Vanguard Shaman", + "Elixmix Hooknose", "Gabblox Magpietongue", "Hermitrix Toothrot", "Humnox Drumbelly", "Morgmox Moldnoggin", "Mortilox Wartpaws", "Distilix Stickytoes", "Jabbrox Grannyguise", "Quicktrix Hexhands", "Wilywox Tenderpalm", + "Ascetox Ratgums", "Brewnix Bittypupils", "Gibberox Pimplebeak", "Morblox Stubthumbs", "Whistrix Toadthroat", + "Nightmare Crawler", "Nightmare Raven", "Nightmare Uragnite", + "Nightmare Fly", "Nightmare Flytrap", "Nightmare Funguar", + "Nightmare Gaylas", "Nightmare Kraken", "Nightmare Roc", + "Nightmare Hornet", "Nightmare Bugard", + "Gosspix Blabblerlips", "Flamecaller Zoeqdoq", "Gi'Bhe Fleshfeaster", "Ree Nata the Melomanic", "Baa Dava the Bibliophage", + "Aitvaras" } + +staggers['night']['ws'] = { "Kindred Paladin", "Kindred Warrior", "Kindred Samurai", "Kindred Dragoon", "Kindred Dark Knight", + "Count Zaebos", "Duke Scox", "Marquis Sabnak", "King Zagan", "Count Haagenti", + "Hydra Paladin", "Hydra Warrior", "Hydra Samurai", "Hydra Dragoon", "Hydra Dark Knight", + "Vanguard Footsoldier", "Vanguard Gutslasher", "Vanguard Impaler", "Vanguard Neckchopper", "Vanguard Trooper", "Wyrmgnasher Bjakdek", "Bladerunner Rokgevok", "Bloodfist Voshgrosh", "Spellspear Djokvukk", + "Vanguard Defender", "Vanguard Drakekeeper", "Vanguard Hatamoto", "Vanguard Vigilante", "Vanguard Vindicator", "Ze'Vho Fallsplitter", "Zo'Pha Forgesoul", "Bu'Bho Truesteel", + "Vanguard Exemplar", "Vanguard Inciter", "Vanguard Partisan", "Vanguard Persecutor", "Vanguard Skirmisher", "Maa Febi the Steadfast", "Muu Febi the Steadfast", + "Vanguard Armorer", "Vanguard Dragontamer", "Vanguard Ronin", "Vanguard Smithy", + "Buffrix Eargone", "Cloktix Longnail", "Sparkspox Sweatbrow", "Ticktox Beadyeyes", "Tufflix Loglimbs", "Wyrmwix Snakespecs", "Karashix Swollenskull", "Smeltix Thickhide", "Wasabix Callusdigit", "Anvilix Sootwrists", "Scruffix Shaggychest", "Tymexox Ninefingers", "Scourquix Scaleskin", + "Draklix Scalecrust", "Moltenox Stubthumbs", "Ruffbix Jumbolobes", "Shisox Widebrow", "Tocktix Thinlids", + "Nightmare Bunny", "Nightmare Eft", "Nightmare Mandragora", + "Nightmare Hippogryph", "Nightmare Sabotender", "Nightmare Sheep", + "Nightmare Snoll", "Nightmare Stirge", "Nightmare Weapon", + "Nightmare Makara", "Nightmare Cluster", + "Shamblix Rottenheart", "Elvaansticker Bxafraff", "Qu'Pho Bloodspiller", "Te'Zha Ironclad", "Koo Rahi the Levinblade", + "Barong", "Alklha", "Stihi", "Fairy Ring", "Stcemqestcint", "Stringes", "Suttung" } +staggers['night']['random'] = {"Nightmare Taurus"} +staggers['night']['none'] = {"Animated Claymore", "Animated Dagger", "Animated Great Axe", "Animated Gun", "Animated Hammer", "Animated Horn", "Animated Kunai", "Animated Knuckles", "Animated Longbow", "Animated Longsword", "Animated Scythe", "Animated Shield", "Animated Spear", "Animated Staff", "Animated Tabar", "Animated Tachi", "Fire Pukis", "Petro Pukis", "Poison Pukis", "Wind Pukis", "Kindred's Vouivre", "Kindred's Wyvern", "Kindred's Avatar", "Vanguard Eye", "Prototype Eye", "Nebiros's Avatar", "Haagenti's Avatar", "Caim's Vouivre", "Andras's Vouivre", "Adamantking Effigy", "Avatar Icon", "Goblin Replica", "Serjeant Tombstone", "Zagan's Wyvern", "Hydra's Hound", "Hydra's Wyvern", "Hydra's Avatar", "Rearguard Eye", "Adamantking Effigy", "Adamantking Image", "Avatar Icon", "Avatar Idol", "Effigy Prototype", "Goblin Replica", "Goblin Statue", "Icon Prototype", "Manifest Icon", "Manifest Icon", "Prototype Eye", "Serjeant Tombstone", "Statue Prototype", "Tombstone Prototype", "Vanguard Eye", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Avatar", "Vanguard's Crow", "Vanguard's Hecteyes", "Vanguard's Scorpion", "Vanguard's Slime", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Vanguard's Wyvern", "Warchief Tombstone"} + +Currency = {"Ordelle Bronzepiece", "Montiont Silverpiece", "One Byne Bill","One Hundred Byne Bill","Tukuku Whiteshell", "Lungo-Nango Jadeshell", "Forgotten Thought", "Forgotten Hope", "Forgotten Touch", "Forgotten Journey", "Forgotten Step"} +ProcZones = res.zones:english(string.startswith-{'Dynamis'}):keyset() +proctype = {"ja","magic","ws","random","none"} +StaggerCount = 0 +current_proc = "lolidk" +currentime = 0 +goodzone = false +timer = "off" +tracker = "off" +proc = "off" +trposx = 1000 +trposy = 250 +pposx = 800 +pposy = 250 + +settings = config.load() +timer = settings['timer'] +tracker = settings['tracker'] +trposx = settings['trposx'] +trposy = settings['trposy'] +proc = settings['proc'] +pposx = settings['pposx'] +pposy = settings['pposy'] + +for i=1, #Currency do + Currency[Currency[i]] = 0 +end + +windower.register_event('load', 'login', function() + if windower.ffxi.get_info().logged_in then + player = windower.ffxi.get_player().name + obtained = nil + initializebox() + end +end) + +windower.register_event('addon command',function (...) +-- print('event_addon_command function') + local params = {...}; + if #params < 1 then + return end + if params[1] then + if params[1]:lower() == "help" then + print('dh help : Shows help message') + print('dh timer [on/off] : Displays a timer each time a mob is staggered.') + print('dh tracker [on/off/reset/pos x y] : Tracks the amount of currency obtained.') + print('dh proc [on/off/pos x y] : Displays the current proc for the targeted mob.') + print('dh ll create : Creates and loads a light luggage profile that will automatically lot all currency.') + --print(goodzone) + --print(ProcZones) + elseif params[1]:lower() == "timer" then + if params[2]:lower() == "on" or params[2]:lower() == "off" then + timer = params[2] + print('Timer feature is '..timer) + else print("Invalid timer option.") + end + elseif params[1]:lower() == "tracker" then + if params[2]:lower() == "on" then + tracker = "on" + initializebox() + windower.text.set_visibility('dynamis_box',true) + print('Tracker enabled') + elseif params[2]:lower() == "off" then + tracker = "off" + windower.text.set_visibility('dynamis_box',false) + print('Tracker disabled') + elseif params[2]:lower() == "reset" then + for i=1, #Currency do + Currency[Currency[i]] = 0 + end + obtainedf() + initializebox() + print('Tracker reset') + elseif params[2]:lower() == "pos" then + if params[3] then + trposx, trposy = tonumber(params[3]), tonumber(params[4]) + obtainedf() + initializebox() + else print("Invalid tracker option.") + end + end + elseif params[1]:lower() == "ll" then + if params[2]:lower() == "create" then + player = windower.ffxi.get_player()['name'] + io.open(windower.addon_path..'../../plugins/ll/dynamis-'..player..'.txt',"w"):write('if item is 1452, 1453, 1455, 1456, 1449, 1450 then lot'):close() + windower.send_command('ll profile dynamis-'..player..'.txt') + else print("Invalid light luggage option.") + end + elseif params[1]:lower() == "proc" then + if params[2]:lower() == "on" then + proc = params[2] + print('Proc feature enabled.') + elseif params[2]:lower() == "off" then + proc = params[2] + windower.text.set_visibility('proc_box',false) + print('Proc feature disabled.') + elseif params[2]:lower() == "pos" then + pposx, pposy = tonumber(params[3]), tonumber(params[4]) + initializeproc() + end + end + end +end) + + +windower.register_event('incoming text',function (original, new, color) +-- print('event_incoming_text function') + if timer == 'on' then + a,b,fiend = string.find(original,"%w+'s attack staggers the (%w+)%!") + if fiend == 'fiend' then + StaggerCount = StaggerCount + 1 + windower.send_command('timers c '..StaggerCount..' 30 down') + return new, color + end + end + if tracker == 'on' then + a,b,item = string.find(original,"%w+ obtains an? ..(%w+ %w+ %w+ %w+)..\46") + if item == nil then + a,b,item = string.find(original,"%w+ obtains an? ..(%w+ %w+ %w+)..\46") + if item == nil then + a,b,item = string.find(original,"%w+ obtains an? ..(%w+%-%w+ %w+)..\46") + if item == nil then + a,b,item = string.find(original,"%w+ obtains an? ..(%w+ %w+)..\46") + end + end + end +-- a,b,item = string.find(original,"%w+ obtains an? ..(.*)..\46") + if item ~= nil then + item = item:lower() + for i=1, #Currency do + if item == Currency[i]:lower() then + Currency[Currency[i]] = Currency[Currency[i]] + 1 + end + end + obtainedf() + end + initializebox() + end + return new, color +end) + +function obtainedf() + obtained = nil + for i=1,#Currency do + if Currency[Currency[i]] ~= 0 then + if obtained == nil then + obtained = " " + end + obtained = (obtained..Currency[i]..': '..Currency[Currency[i]]..' \n ') + end + end +end + +windower.register_event('zone change', function(id) + goodzone = ProcZones:contains(id) + if not goodzone then + windower.text.set_visibility('proc_box', false) + end +end) + +function initializebox() + if obtained ~= nil and tracker == "on" then + windower.text.create('dynamis_box') + windower.text.set_bg_color('dynamis_box',200,30,30,30) + windower.text.set_color('dynamis_box',255,200,200,200) + windower.text.set_location('dynamis_box',trposx,trposy) + windower.text.set_visibility('dynamis_box',true) + windower.text.set_bg_visibility('dynamis_box',true) + windower.text.set_font('dynamis_box','Arial',12) + windower.text.set_text('dynamis_box',obtained); + end +end + +windower.register_event('target change', function(targ_id) + --goodzone = ProcZones:contains(windower.ffxi.get_info().zone) + if goodzone and proc == 'on' and targ_id ~= 0 then + mob = windower.ffxi.get_mob_by_index(targ_id)['name'] + setproc() + end + + --print(ProcZones:contains(windower.ffxi.get_info().zone)) + +end) + +function setproc() + current_proc = 'lolidk' + local currenttime = windower.ffxi.get_info().time + if currenttime >= 0*60 and currenttime < 8*60 then + window = 'morning' + elseif currenttime >= 8*60 and currenttime < 16*60 then + window = 'day' + elseif currenttime >= 16*60 and currenttime <= 24*60 then + window = 'night' + end + --figure out the stupid mob's proc + for i=1, #proctype do + for j=1, #staggers[window][proctype[i]] do + if mob == staggers[window][proctype[i]][j] then + current_proc = proctype[i] + end + end + end + if current_proc == 'ja' then + current_proc = 'Job Ability' + elseif current_proc == 'magic' then + current_proc = 'Magic' + elseif current_proc == 'ws' then + current_proc = 'Weapon Skill' + end + initializeproc() +end + +function initializeproc() +-- print('initializeproc function') + windower.text.create('proc_box') + windower.text.set_bg_color('proc_box',200,30,30,30) + windower.text.set_color('proc_box',255,200,200,200) + windower.text.set_location('proc_box',pposx,pposy) + if proc == 'on' then + windower.text.set_visibility('proc_box', true) + end + windower.text.set_bg_visibility('proc_box',1) + windower.text.set_font('proc_box','Arial',12) + windower.text.set_text('proc_box',' Current proc for \n '..mob..'\n is '..current_proc); + if proc == "off" then + windower.text.set_visibility('proc_box', false) + end +end + +windower.register_event('unload',function () + windower.text.delete('dynamis_box') + windower.text.delete('proc_box') +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/README.md b/Data/DefaultContent/Libraries/addons/addons/enemybar/README.md new file mode 100644 index 0000000..bf25b28 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/README.md @@ -0,0 +1,3 @@ +# enemybar + +This is an addon for Windower4 for FFXI. It creates a big health bar for the target to make it easy to see. diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_body.png b/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_body.png Binary files differnew file mode 100644 index 0000000..5bc94f3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_body.png diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_cap.png b/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_cap.png Binary files differnew file mode 100644 index 0000000..7d546de --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/bg_cap.png diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/enemybar.lua b/Data/DefaultContent/Libraries/addons/addons/enemybar/enemybar.lua new file mode 100644 index 0000000..0a5edeb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/enemybar.lua @@ -0,0 +1,54 @@ +--[[ +Copyright © 2015, Mike McKee +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of enemybar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mike McKee BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--]] + +_addon.name = 'enemybar' +_addon.author = 'mmckee' +_addon.version = '1.0.2' +_addon.language = 'English' + +config = require('config') +images = require('images') +texts = require('texts') +require('gui_settings') +require('targetBar') +require('subtargetBar') + +player_id = 0 +debug_string = '' + +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in then + player_id = windower.ffxi.get_player().id + end +end) + +windower.register_event('prerender', render_target_bar) +windower.register_event('prerender', render_subtarget_bar) +windower.register_event('target change', target_change) + +windower.register_event('logout', function(...) + -- This is a super cheap fix, but it works. + windower.send_command("input //lua r enemybar"); +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/fg_body.png b/Data/DefaultContent/Libraries/addons/addons/enemybar/fg_body.png Binary files differnew file mode 100644 index 0000000..70804bd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/fg_body.png diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/gui_settings.lua b/Data/DefaultContent/Libraries/addons/addons/enemybar/gui_settings.lua new file mode 100644 index 0000000..3cf47d2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/gui_settings.lua @@ -0,0 +1,298 @@ +--[[ +Copyright © 2015, Mike McKee +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of enemybar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mike McKee 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. +--]] + +targetBarHeight = 12 +targetBarWidth = 598 +subtargetBarHeight = 12 +subtargetBarWidth = 198 +visible = false + +bg_cap_path = windower.addon_path.. 'bg_cap.png' +bg_body_path = windower.addon_path.. 'bg_body.png' +fg_body_path = windower.addon_path.. 'fg_body.png' + +center_screen = windower.get_windower_settings().x_res / 2 - targetBarWidth / 2 + +text_settings = {} +text_settings.pos = {} +text_settings.pos.x = center_screen +text_settings.pos.y = 50 +text_settings.text = {} +text_settings.text.size = 14 +text_settings.text.font = 'Arial' +text_settings.text.fonts = {'Arial'} +text_settings.text.stroke = {} +text_settings.text.stroke.width = 2 +text_settings.text.stroke.alpha = 127 +text_settings.text.stroke.red = 50 +text_settings.text.stroke.green = 50 +text_settings.text.stroke.blue = 50 +text_settings.flags = {} +text_settings.flags.bold = true +text_settings.flags.draggable = false +text_settings.bg = {} +text_settings.bg.visible = false + +tbg_cap_settings = {} +tbg_cap_settings.pos = {} +tbg_cap_settings.pos.x = center_screen +tbg_cap_settings.pos.y = 50 +tbg_cap_settings.visible = true +tbg_cap_settings.color = {} +tbg_cap_settings.color.alpha = 255 +tbg_cap_settings.color.red = 150 +tbg_cap_settings.color.green = 0 +tbg_cap_settings.color.blue = 0 +tbg_cap_settings.size = {} +tbg_cap_settings.size.width = 1 +tbg_cap_settings.size.height = 598 +tbg_cap_settings.texture = {} +tbg_cap_settings.texture.path = bg_cap_path +tbg_cap_settings.texture.fit = true +tbg_cap_settings.repeatable = {} +tbg_cap_settings.repeatable.x = 1 +tbg_cap_settings.repeatable.y = 1 +tbg_cap_settings.draggable = false + +stbg_cap_settings = {} +stbg_cap_settings.pos = {} +stbg_cap_settings.pos.x = center_screen +stbg_cap_settings.pos.y = 50 +stbg_cap_settings.visible = true +stbg_cap_settings.color = {} +stbg_cap_settings.color.alpha = 255 +stbg_cap_settings.color.red = 0 +stbg_cap_settings.color.green = 51 +stbg_cap_settings.color.blue = 255 +stbg_cap_settings.size = {} +stbg_cap_settings.size.width = 1 +stbg_cap_settings.size.height = subtargetBarHeight +stbg_cap_settings.texture = {} +stbg_cap_settings.texture.path = bg_cap_path +stbg_cap_settings.texture.fit = true +stbg_cap_settings.repeatable = {} +stbg_cap_settings.repeatable.x = 1 +stbg_cap_settings.repeatable.y = 1 +stbg_cap_settings.draggable = false + +tbg_body_settings = {} +tbg_body_settings.pos = {} +tbg_body_settings.pos.x = center_screen +tbg_body_settings.pos.y = 50 +tbg_body_settings.visible = true +tbg_body_settings.color = {} +tbg_body_settings.color.alpha = 255 +tbg_body_settings.color.red = 150 +tbg_body_settings.color.green = 0 +tbg_body_settings.color.blue = 0 +tbg_body_settings.size = {} +tbg_body_settings.size.width = targetBarWidth +tbg_body_settings.size.height = targetBarHeight +tbg_body_settings.texture = {} +tbg_body_settings.texture.path = bg_body_path +tbg_body_settings.texture.fit = true +tbg_body_settings.repeatable = {} +tbg_body_settings.repeatable.x = 1 +tbg_body_settings.repeatable.y = 1 +tbg_body_settings.draggable = false + +stbg_body_settings = {} +stbg_body_settings.pos = {} +stbg_body_settings.pos.x = center_screen + 400 +stbg_body_settings.pos.y = 65 +stbg_body_settings.visible = true +stbg_body_settings.color = {} +stbg_body_settings.color.alpha = 255 +stbg_body_settings.color.red = 0 +stbg_body_settings.color.green = 51 +stbg_body_settings.color.blue = 255 +stbg_body_settings.size = {} +stbg_body_settings.size.width = subtargetBarWidth +stbg_body_settings.size.height = subtargetBarHeight +stbg_body_settings.texture = {} +stbg_body_settings.texture.path = bg_body_path +stbg_body_settings.texture.fit = true +stbg_body_settings.repeatable = {} +stbg_body_settings.repeatable.x = 1 +stbg_body_settings.repeatable.y = 1 +stbg_body_settings.draggable = false + +tfgg_body_settings = {} +tfgg_body_settings.pos = {} +tfgg_body_settings.pos.x = center_screen +tfgg_body_settings.pos.y = 50 +tfgg_body_settings.visible = true +tfgg_body_settings.color = {} +tfgg_body_settings.color.alpha = 200 +tfgg_body_settings.color.red = 255 +tfgg_body_settings.color.green = 0 +tfgg_body_settings.color.blue = 0 +tfgg_body_settings.size = {} +tfgg_body_settings.size.width = targetBarWidth +tfgg_body_settings.size.height = targetBarHeight +tfgg_body_settings.texture = {} +tfgg_body_settings.texture.path = fg_body_path +tfgg_body_settings.texture.fit = true +tfgg_body_settings.repeatable = {} +tfgg_body_settings.repeatable.x = 1 +tfgg_body_settings.repeatable.y = 1 +tfgg_body_settings.draggable = false + +tfg_body_settings = {} +tfg_body_settings.pos = {} +tfg_body_settings.pos.x = center_screen +tfg_body_settings.pos.y = 50 +tfg_body_settings.visible = true +tfg_body_settings.color = {} +tfg_body_settings.color.alpha = 255 +tfg_body_settings.color.red = 255 +tfg_body_settings.color.green = 51 +tfg_body_settings.color.blue = 0 +tfg_body_settings.size = {} +tfg_body_settings.size.width = targetBarWidth +tfg_body_settings.size.height = targetBarHeight +tfg_body_settings.texture = {} +tfg_body_settings.texture.path = fg_body_path +tfg_body_settings.texture.fit = true +tfg_body_settings.repeatable = {} +tfg_body_settings.repeatable.x = 1 +tfg_body_settings.repeatable.y = 1 +tfg_body_settings.draggable = false + +stfg_body_settings = {} +stfg_body_settings.pos = {} +stfg_body_settings.pos.x = center_screen + 400 +stfg_body_settings.pos.y = 65 +stfg_body_settings.visible = true +stfg_body_settings.color = {} +stfg_body_settings.color.alpha = 255 +stfg_body_settings.color.red = 0 +stfg_body_settings.color.green = 102 +stfg_body_settings.color.blue = 255 +stfg_body_settings.size = {} +stfg_body_settings.size.width = subtargetBarWidth +stfg_body_settings.size.height = subtargetBarHeight +stfg_body_settings.texture = {} +stfg_body_settings.texture.path = fg_body_path +stfg_body_settings.texture.fit = true +stfg_body_settings.repeatable = {} +stfg_body_settings.repeatable.x = 1 +stfg_body_settings.repeatable.y = 1 +stfg_body_settings.draggable = false + +defaults = {} +defaults.font = 'Arial' +defaults.font_size = 14 +defaults.pos = {} +defaults.pos.x = 400 +defaults.pos.y = 50 + +settings = config.load(defaults) +config.save(settings) + +config.register(settings, function(settings_table) + --Validating settings.xml values + local nx = 0 + if settings_table.pos.x == nil or settings_table.pos.x < 0 then + nx = center_screen + else + nx = settings_table.pos.x + end + + text_settings.pos.x = nx + text_settings.pos.y = settings_table.pos.y + text_settings.text.font = settings_table.font + text_settings.text.size = settings_table.font_size + + tbg_cap_settings.pos.x = nx + tbg_cap_settings.pos.y = settings_table.pos.y + + stbg_cap_settings.pos.x = nx + stbg_cap_settings.pos.y = settings_table.pos.y + + tbg_body_settings.pos.x = nx + tbg_body_settings.pos.y = settings_table.pos.y + + stbg_body_settings.pos.x = nx + stbg_body_settings.pos.y = settings_table.pos.y + + tfgg_body_settings.pos.x = nx + tfgg_body_settings.pos.y = settings_table.pos.y + + tfg_body_settings.pos.x = nx + tfg_body_settings.pos.y = settings_table.pos.y + + stfg_body_settings.pos.x = nx + stfg_body_settings.pos.y = settings_table.pos.y + + tbg_cap_l = images.new(tbg_cap_settings) + tbg_cap_r = images.new(tbg_cap_settings) + tbg_body = images.new(tbg_body_settings) + tfgg_body = images.new(tfgg_body_settings) + tfg_body = images.new(tfg_body_settings) + t_text = texts.new(' ${name|(Name)} - HP: ${hpp|(100)}% ${debug|}', text_settings) + + stbg_cap_l = images.new(stbg_cap_settings) + stbg_cap_r = images.new(stbg_cap_settings) + stbg_body = images.new(stbg_body_settings) + stfg_body = images.new(stfg_body_settings) + st_text = texts.new(' ${name|(Name)}', text_settings) + + tbg_cap_l:pos_x(tbg_cap_l:pos_x() - 1) + tbg_cap_r:pos_x(tbg_cap_r:pos_x() + targetBarWidth + 1) + + stbg_cap_l:pos(stbg_cap_l:pos_x() + 399, 65) + stbg_cap_r:pos(stbg_cap_r:pos_x() + subtargetBarWidth + 1, 65) + stfg_body:pos(stfg_body:pos_x() + 400, 65) + stbg_body:pos(stbg_body:pos_x() + 400, 65) + st_text:pos(st_text:pos_x() + 400, 65) +end) + + + +check_claim = function(claim_id) + if player_id == claim_id then + return true + else + for i = 1, 5, 1 do + member = windower.ffxi.get_mob_by_target('p'..i) + if member == nil then + -- do nothing + elseif member.id == claim_id then + return true + end + end + end + return false +end + +target_change = function(index) + if index == 0 then + visible = false + else + visible = true + end +end + diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/subtargetBar.lua b/Data/DefaultContent/Libraries/addons/addons/enemybar/subtargetBar.lua new file mode 100644 index 0000000..83e7c24 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/subtargetBar.lua @@ -0,0 +1,64 @@ +--[[ +Copyright © 2015, Mike McKee +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of enemybar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mike McKee 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. +--]] + +render_subtarget_bar = function(...) + if visible == true then + local subtarget = windower.ffxi.get_mob_by_target('st') + + if subtarget ~= nil and target ~= nil and subtarget.id ~= target.id then + stbg_cap_l:show() + stbg_cap_r:show() + stbg_body:show() + stfg_body:show() + st_text:show() + + local i = subtarget.hpp / 100 + local new_width = math.floor(subtargetBarWidth * i) + stfg_body:width(new_width) + stbg_body:width(subtargetBarWidth) + + st_text.name = subtarget.name + if subtarget.hpp == 0 then + st_text:color(155, 155, 155) + elseif check_claim(subtarget.claim_id) then + st_text:color(255, 204, 204) + elseif subtarget.in_party == true and subtarget.id ~= player_id then + st_text:color(102, 255, 255) + elseif subtarget.is_npc == false then + st_text:color(255, 255, 255) + elseif subtarget.claim_id == 0 then + st_text:color(230, 230, 138) + elseif subtarget.claim_id ~= 0 then + st_text:color(153, 102, 255) + end + else + stbg_cap_l:hide() + stbg_cap_r:hide() + stbg_body:hide() + stfg_body:hide() + st_text:hide() + end + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/enemybar/targetBar.lua b/Data/DefaultContent/Libraries/addons/addons/enemybar/targetBar.lua new file mode 100644 index 0000000..57db968 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enemybar/targetBar.lua @@ -0,0 +1,87 @@ +--[[ +Copyright © 2015, Mike McKee +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of enemybar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mike McKee 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. +--]] + +render_target_bar = function (...) + if visible == true then + tbg_cap_l:show() + tbg_cap_r:show() + tbg_body:show() + tfg_body:show() + tfgg_body:show() + t_text:show() + + target = windower.ffxi.get_mob_by_target('t') + + if target ~= nil then + local player = windower.ffxi.get_player() + local i = target.hpp / 100 + local new_width = math.floor(targetBarWidth * i) + local old_width = tfgg_body:width() + + tfgg_body:width(0) + + local now = os.clock() + if new_width ~= nil and new_width > 0 then + if new_width < old_width and player.in_combat then + local x = old_width + math.floor(((new_width - old_width) * 0.1)) + tfgg_body:width(x) + elseif new_width >= old_width or not player.in_combat then + tfgg_body:width(new_width) + end + end + + tfg_body:width(new_width) + tbg_body:width(targetBarWidth) -- I still have no idea why removing this breaks the bg. + + t_text.name = target.name + t_text.hpp = target.hpp + t_text.debug = debug_string + + --Check claim_id with player and party_id + if target.hpp == 0 then + t_text:color(155, 155, 155) + elseif check_claim(target.claim_id) then + t_text:color(255, 204, 204) + elseif target.in_party == true and target.id ~= player_id then + t_text:color(102, 255, 255) + elseif target.is_npc == false then + t_text:color(255, 255, 255) + elseif target.claim_id == 0 then + t_text:color(230, 230, 138) + elseif target.claim_id ~= 0 then + t_text:color(153, 102, 255) + end + end + + else + tbg_cap_l:hide() + tbg_cap_r:hide() + tbg_body:hide() + tfg_body:hide() + tfgg_body:hide() + tfgg_body:size(0, 12) + t_text:hide() + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/enternity/README.md b/Data/DefaultContent/Libraries/addons/addons/enternity/README.md new file mode 100644 index 0000000..c3f9ef8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enternity/README.md @@ -0,0 +1,19 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20131102 + +# Enternity # + +Enters "Enter" automatically when prompted during a cutscene or when talking to NPCs. It will not skip choice dialog boxes. + +---- + +##Changelog## +### v1.20130620 ### +* **fix:** Sentences that contain items will not be skipped. +* **fix:** Added NPCs exceptions. + +### v1.20130607 ### +* **change:** Changed from artificial button press to ignoring the stop. Allows to type text freely while it goes through cutscenes. + +### v1.20130606 ### +* First release. diff --git a/Data/DefaultContent/Libraries/addons/addons/enternity/enternity.lua b/Data/DefaultContent/Libraries/addons/addons/enternity/enternity.lua new file mode 100644 index 0000000..c86c471 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/enternity/enternity.lua @@ -0,0 +1,51 @@ +--[[ +enternity v1.20131102 + +Copyright (c) 2013, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of enternity nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require('sets') + +_addon.name = 'enternity' +_addon.author = 'Zohno' +_addon.version = '1.1.0.0' + +blist = S{ + 'Paintbrush of Souls', -- Requires correct timing, should not be skipped + 'Geomantic Reservoir', -- Causes dialogue freeze for some reason +} + +windower.register_event('incoming text', function(original, modified, mode) + if (mode == 150 or mode == 151) and not original:match(string.char(0x1e, 0x02)) then + local target = windower.ffxi.get_mob_by_target('t') + if not (target and blist:contains(target.name)) then + modified = modified:gsub(string.char(0x7F, 0x31), '') + end + end + + return modified +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/equipviewer/README.md b/Data/DefaultContent/Libraries/addons/addons/equipviewer/README.md new file mode 100644 index 0000000..c528c70 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/equipviewer/README.md @@ -0,0 +1,44 @@ +**Author:** Tako, Rubenator<br> +**Version:** 3.3.0<br> +**Date:** April 13, 2021<br> + +* Displays current equipment grid on screen. Also can show current Ammo count and current Encumbrance. + +## Settings + +* Most settings can be modified via commands, but you can edit the settings.xml directly for a few uncommon settings. + +**Abbreviation:** `//ev` + +## Commands +1. position <xpos> <ypos>: move display to position (from top left) +2. size <pixels>: set pixel size of each item slot (defaults to 32 -- same as the size of the item icons) +3. scale <factor>: scale multiplier for size of each item slot (1 is 32px) -- modifies same setting as size +4. alpha <opacity>: set opacity of icons (out of 255) +5. transparency <transparency>: inverse of alpha (out of 255) -- modifies same setting as alpha +6. background <red> <green> <blue> <alpha>: sets color and opacity of background (out of 255) +7. ammocount: toggles showing current ammo count (defaults to on/true) +8. encumbrance: toggles showing encumbrance Xs (defaultis on/true) +9. hideonzone: toggles hiding while crossing zone lines (default is on/true) +10. hideoncutscene: toggles hiding when in cutscene/npc menu/etc (default is on/true) +11. justify: toggles between ammo text being right or left justifed (default is right justified) +12. help: displays explanations of each command + +Legacy Command: +game_path <path>: sets path to FFXI folder where you want dats extracted from. Backslashes `\` must be escaped (like so: `\\`) or use forewardslash `/` instead. (legacy command as of `3.3.1` in which the game path is now pulled from the registry, but this command is still here in case you want to pull from dats that exist elsewhere) + +### Example Commands +``` +//ev pos 700 400 +//ev size 64 +//ev scale 1.5 +//ev alpha 255 +//ev transparency 200 +//ev background 0 0 0 72 +//ev ammocount +//ev encumbrance +//ev hideonzone +//ev hideoncutscene +//ev justify +//ev help +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/equipviewer/encumbrance.png b/Data/DefaultContent/Libraries/addons/addons/equipviewer/encumbrance.png Binary files differnew file mode 100644 index 0000000..0b0bff3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/equipviewer/encumbrance.png diff --git a/Data/DefaultContent/Libraries/addons/addons/equipviewer/equipviewer.lua b/Data/DefaultContent/Libraries/addons/addons/equipviewer/equipviewer.lua new file mode 100644 index 0000000..ff5e1e1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/equipviewer/equipviewer.lua @@ -0,0 +1,743 @@ +--[[ + Copyright © 2021, Rubenator + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of EquipViewer nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Rubenator BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] +_addon.name = 'Equipviewer' +_addon.version = '3.3.1' +_addon.author = 'Tako, Rubenator' +_addon.commands = { 'equipviewer', 'ev' } + +require('luau') +local bit = require('bit') +local config = require('config') +local images = require('images') +local texts = require('texts') +local functions = require('functions') +local packets = require('packets') +local icon_extractor = require('icon_extractor') + +local equipment_data = { + [0] = {slot_name = 'main', slot_id = 0, display_pos = 0, item_id = 0, image = nil}, + [1] = {slot_name = 'sub', slot_id = 1, display_pos = 1, item_id = 0, image = nil}, + [2] = {slot_name = 'range', slot_id = 2, display_pos = 2, item_id = 0, image = nil}, + [3] = {slot_name = 'ammo', slot_id = 3, display_pos = 3, item_id = 0, image = nil}, + [4] = {slot_name = 'head', slot_id = 4, display_pos = 4, item_id = 0, image = nil}, + [5] = {slot_name = 'body', slot_id = 5, display_pos = 8, item_id = 0, image = nil}, + [6] = {slot_name = 'hands', slot_id = 6, display_pos = 9, item_id = 0, image = nil}, + [7] = {slot_name = 'legs', slot_id = 7, display_pos = 14, item_id = 0, image = nil}, + [8] = {slot_name = 'feet', slot_id = 8, display_pos = 15, item_id = 0, image = nil}, + [9] = {slot_name = 'neck', slot_id = 9, display_pos = 5, item_id = 0, image = nil}, + [10] = {slot_name = 'waist', slot_id = 10, display_pos = 13, item_id = 0, image = nil}, + [11] = {slot_name = 'left_ear', slot_id = 11, display_pos = 6, item_id = 0, image = nil}, + [12] = {slot_name = 'right_ear', slot_id = 12, display_pos = 7, item_id = 0, image = nil}, + [13] = {slot_name = 'left_ring', slot_id = 13, display_pos = 10, item_id = 0, image = nil}, + [14] = {slot_name = 'right_ring', slot_id = 14, display_pos = 11, item_id = 0, image = nil}, + [15] = {slot_name = 'back', slot_id = 15, display_pos = 12, item_id = 0, image = nil}, +} +local encumbrance_data = {} +for i=0,15 do + encumbrance_data[i] = { slot_name = 'encumbrance', slot_id = i, display_pos = equipment_data[i].display_pos, image = nil } +end +local ammo_count_text = nil +local bg_image = nil + +local defaults = { + pos = { + x = 500, + y = 500 + }, + size = 32, + ammo_text = { + alpha = 230, + red = 255, + green = 255, + blue = 255, + stroke = { + width = 1, + alpha = 127, + red = 0, + green = 0, + blue = 0, + }, + flags = { + bold = true, + italic = true, + } + }, + icon = { + alpha = 230, + red = 255, + green = 255, + blue = 255, + }, + bg = { + alpha = 72, + red = 0, + green = 0, + blue = 0, + }, + show_encumbrance = true, + show_ammo_count = true, + hide_on_zone = true, + hide_on_cutscene = true, + left_justify = false, +} +settings = config.load(defaults) +config.save(settings) +if settings.game_path then + icon_extractor.ffxi_path(settings.game_path) +end +local last_encumbrance_bitfield = 0 + +-- gets the currently equipped item data for the slot information provided +local function get_equipped_item(slotName, slotId, bag, index) + if not bag or not index then -- from memory + local equipment = windower.ffxi.get_items('equipment') + bag = equipment[string.format('%s_bag', slotName)] + index = equipment[slotName] + if equipment_data[slotId] then + equipment_data[slotId].bag_id = bag + equipment_data[slotId].index = index + end + end + if index == 0 then -- empty equipment slot + return 0, 0 + end + local item_data = windower.ffxi.get_items(bag, index) + return item_data.id, item_data.count +end + +-- desc: Updates the ui object(s) for the given slot +local function update_equipment_slot(source, slot, bag, index, item, count) + local slot_data = equipment_data[slot] + slot_data.bag_id = bag or slot_data.bag_id + slot_data.index = index or slot_data.index + if not item then + item, count = get_equipped_item(slot_data.slot_name, slot_data.slot_id, bag, index) + end + if evdebug then + bag = slot_data.bag_id + index = slot_data.index + log('%s %s %d %d %d':format(source, slot_data.slot_name, item, bag or -1, index or -1)) + print('%s %s %d %d %d':format(source, slot_data.slot_name, item, bag or -1, index or -1)) + end + if slot_data.slot_name == 'ammo' then + slot_data.count = count or slot_data.count or 0 + end + if slot_data.image and item ~= nil then + if item == 0 or item == 65535 then -- empty slot + slot_data.image:hide() + slot_data.image:clear() + slot_data.item_id = 0 + slot_data.count = nil + slot_data.image:update() + elseif slot_data.item_id ~= item then + slot_data.item_id = item + local icon_path = string.format('%sicons/%s.bmp', windower.addon_path, slot_data.item_id) + + if not windower.file_exists(icon_path) then + icon_extractor.item_by_id(slot_data.item_id, icon_path) + end + if windower.file_exists(icon_path) then + slot_data.image:path(icon_path) + slot_data.image:alpha(settings.icon.alpha) + slot_data.image:show() + end + end + if slot_data.slot_name == 'ammo' then + display_ammo_count(slot_data.count) + end + slot_data.image:update() + end +end + +-- Updates the texture for all slots if it's a different piece of equipment +local function update_equipment_slots(source) + for slot in pairs(equipment_data) do + update_equipment_slot(source, slot) + end +end + +-- Sets up the image and text ui objects for our equipment +local function setup_ui() + refresh_ui_settings() + destroy() + + bg_image = images.new(bg_image_settings) + bg_image:show() + + for key, slot in pairs(equipment_data) do + slot.item_id = 0 + slot.image = images.new(equipment_image_settings) + position(slot) + end + update_equipment_slots('setup_ui') + + for key, slot in pairs(encumbrance_data) do + slot.image = images.new(encumbrance_image_settings) + slot.image:path(windower.addon_path..'encumbrance.png') + slot.image:hide() + position(slot) + end + display_encumbrance() + + ammo_count_text = texts.new(settings.left_justify and ammo_count_text_settings_left_justify or ammo_count_text_settings) + display_ammo_count() +end + +-- Called when the addon is first loaded. +windower.register_event('load', function() + --Make sure icons directory exists + if not windower.dir_exists(string.format('%sicons', windower.addon_path)) then + windower.create_dir(string.format('%sicons', windower.addon_path)) + end + + if windower.ffxi.get_info().logged_in then + setup_ui() + end +end) + +-- Called whenever character logs out. +windower.register_event('logout', function() + clear_all_equipment_slots() + destroy() +end) + +-- Called whenever character logs in. +windower.register_event('login', function() + setup_ui() + update_equipment_slots('login') +end) + +-- Called when our addon receives an incoming chunk. +windower.register_event('incoming chunk', function(id, original, modified, injected, blocked) + if id == 0x050 then --Equip/Unequip + local packet = packets.parse('incoming', original) + local index = packet['Inventory Index'] + local slot = packet['Equipment Slot'] + local bag = packet['Inventory Bag'] + equipment_data[slot].bag_id = bag + equipment_data[slot].index = index + update_equipment_slot:schedule(0, '0x050', slot, bag, index) + elseif id == 0x020 or id == 0x01F or id == 0x01E then --Item Update / Item Assign (ammo consumption) / 0x01E item count/last ammo shot + local packet = packets.parse('incoming', original) + local bag = packet['Bag'] + local index = packet['Index'] + + local slot = nil + for _,slot_data in pairs(equipment_data) do + if slot_data.bag_id == bag and slot_data.index == index then + slot = slot_data.slot_id + break + end + end + + if slot then + if packet['Status'] ~= 5 and packet['Count'] == 0 then --item not equipped + update_equipment_slot:schedule(0, '0x%x':format(id), slot, 0, 0, 0) + return + end + if slot == 3 then --ammo + local count = packet['Count'] or 0 + display_ammo_count(count) + end + local item = packet['Item'] + update_equipment_slot:schedule(0,'0x%x':format(id), slot, bag, index, item, count) + end + elseif id == 0x01B then -- Job Info (Encumbrance Flags) + local packet = packets.parse('incoming', original) + display_encumbrance(packet['Encumbrance Flags']) + elseif id == 0x0A then -- Finish Zone + show() + elseif id == 0x0B then -- Zone + if settings.hide_on_zone then + hide() + end + end +end) + +-- Called when our addon receives an outgoing chunk. +windower.register_event('outgoing chunk', function(id, original, modified, injected, blocked) + if id == 0x100 then -- Job Change Request + clear_all_equipment_slots() + end +end) + + +-- Destroys all created ui objects +function destroy() + if bg_image then + bg_image:destroy() + bg_image = nil + end + for key, slot_data in pairs(equipment_data) do + if slot_data.image ~= nil then + slot_data.image:destroy() + slot_data.image = nil + end + end + for key, slot_data in pairs(encumbrance_data) do + if slot_data.image ~= nil then + slot_data.image:destroy() + slot_data.image = nil + end + end + if ammo_count_text then + ammo_count_text:destroy() + ammo_count_text = nil + end +end + +-- Shows appropriate ui objects +function show() + if bg_image then + bg_image:show() + end + for key, slot_data in pairs(equipment_data) do + if slot_data.item_id ~= 0 and slot_data.image then + slot_data.image:show() + end + end + display_encumbrance() + display_ammo_count() +end + +-- Hides all ui objects +function hide() + if bg_image then + bg_image:hide() + end + for key, slot_data in pairs(equipment_data) do + if slot_data.image then + slot_data.image:hide() + end + end + for key, slot_data in pairs(encumbrance_data) do + if slot_data.image then + slot_data.image:hide() + end + end + if ammo_count_text then + ammo_count_text:hide() + end +end + +-- Moves ui object to correct spot based on 'display_pos' field +function position(slot) + local pos_x = settings.pos.x + ((slot.display_pos % 4) * settings.size) + local pos_y = settings.pos.y + (math.floor(slot.display_pos / 4) * settings.size) + slot.image:pos(pos_x, pos_y) +end + +-- Clears all equipment slot data and hides ui object +function clear_slot(slot) + local slot_data = equipment_data[slot] + slot_data.image:hide() + slot_data.image:clear() + slot_data.item_id = 0 + slot_data.bag_id = nil + slot_data.index = nil + slot_data.count = nil + slot_data.image:update() + + display_ammo_count() +end + +-- Clears all equipment slot data and hides equipment slot ui objects +function clear_all_equipment_slots() + for slot in pairs(equipment_data) do + clear_slot(slot) + end +end + +-- Shows and hides appropriate encumbrance ui objects and possibly updates encumbrance +-- flags based on provided bitfield number +function display_encumbrance(bitfield) + bitfield = bitfield or last_encumbrance_bitfield + last_encumbrance_bitfield = bitfield + for key, slot in pairs(encumbrance_data) do + if slot.image then + if not settings.show_encumbrance or bit.band(bitfield, bit.lshift(1,key)) == 0 then + slot.image:hide() + else + slot.image:show() + end + end + end +end + +-- Displays appropriatly and possibly updates ammo count and ui object +function display_ammo_count(count) + if not ammo_count_text then return end + count = count or equipment_data[3] and equipment_data[3].count -- 3 == Ammo + equipment_data[3].count = count + if not settings.show_ammo_count or not count or count <= 1 then + ammo_count_text:hide() + else + ammo_count_text:text(count and tostring(count) or '') + ammo_count_text:show() + end +end + +-- Called when player status changes. +windower.register_event('status change', function(new_status_id) + if new_status_id == 4 and settings.hide_on_cutscene then --Cutscene/Menu + hide() + else + show() + end +end) + +-- Called when our addon is unloaded. +windower.register_event('unload', function() + destroy() +end) + +-- Called when the addon receives a command. +windower.register_event('addon command', function (...) + config.reload(settings) + coroutine.sleep(0.5) + local cmd = (...) and (...):lower() or '' + local cmd_args = {select(2, ...)} + if cmd == "gamepath" or cmd == "game_path" then + if #cmd_args == 0 then + error("Must provide path.") + log('Current Path: %s':format( + "\""..settings.game_path.."\"" or "(Default): \""..windower.ffxi_path + )) + return + end + local path = table.concat(cmd_args, " ") + if path:lower() == "default" then + settings.game_path = nil + else + settings.game_path = table.concat(cmd_args, " ") + end + config.save(settings) + icon_extractor.ffxi_path(settings.game_path) + + setup_ui() + + log('game_path set to "%s"':format(path)) + elseif cmd == 'position' or cmd == 'pos' then + if #cmd_args < 2 then + error('Not enough arguments.') + log('Current position: '..settings.pos.x..' '..settings.pos.y) + return + end + + settings.pos.x = tonumber(cmd_args[1]) + settings.pos.y = tonumber(cmd_args[2]) + config.save(settings) + + setup_ui() + + log('Position changed to '..settings.pos.x..', '..settings.pos.y) + elseif cmd == 'size' then + if #cmd_args < 1 then + error('Not enough arguments.') + log('Current size: '..settings.size) + return + end + + settings.size = tonumber(cmd_args[1]) + config.save(settings) + + setup_ui() + + log('Size changed to '..settings.size) + elseif cmd == 'scale' then + if #cmd_args < 1 then + error('Not enough arguments.') + log('Current scale: '..settings.size/32) + return + end + local size = tonumber(cmd_args[1])*32 + if size > 100 then + error('Size too large') + end + settings.size = size + config.save(settings) + + setup_ui() + + log('Size changed to '..settings.size) + elseif cmd == 'alpha' or cmd == 'opacity' then + if #cmd_args < 1 then + error('Not enough arguments.') + log('Current alpha/opacity: %d/255 = %d%%':format( + settings.icon.alpha, math.floor(settings.icon.alpha/255*100) + )) + return + end + local alpha = tonumber(cmd_args[1]) + if alpha <= 1 and alpha > 0 then + settings.icon.alpha = math.floor(255 * (alpha)) + else + settings.icon.alpha = math.floor(alpha) + end + config.save(settings) + + setup_ui() + + log('Alpha/Opacity changed to '..settings.icon.alpha..'/255') + elseif cmd:contains('transpar') then + if #cmd_args < 1 then + error('Not enough arguments.') + log('Current transparency: %d/255 = %d%%':format( + (255-settings.icon.alpha), math.floor((255-settings.icon.alpha)/255)*100 + )) + return + end + local transparency = tonumber(cmd_args[1]) + if transparency <= 1 and transparency > 0 then + settings.icon.alpha = math.floor(255 * (1-transparency)) + else + settings.icon.alpha = math.floor(255-transparency) + end + config.save(settings) + + setup_ui() + + log('Transparency changed to '..255-settings.icon.alpha..'/255') + elseif cmd == 'background' or cmd == 'bg' then + if #cmd_args < 1 then + error('Not enough arguments.') + log('Current BG color: RED:%d/255 GREEN:%d/255 BLUE:%d/255 ALPHA:%d/255 = %d%%':format( + settings.bg.red, settings.bg.green, settings.bg.blue, settings.bg.alpha, math.floor(settings.bg.alpha/255*100) + )) + return + elseif #cmd_args == 1 then + local alpha = tonumber(cmd_args[1]) + if alpha <= 1 and alpha > 0 then + settings.bg.alpha = math.floor(255 * (alpha)) + else + settings.bg.alpha = math.floor(alpha) + end + elseif #cmd_args >= 3 then + settings.bg.red = tonumber(cmd_args[1]) + settings.bg.green = tonumber(cmd_args[2]) + settings.bg.blue = tonumber(cmd_args[3]) + if #cmd_args == 4 then + local alpha = tonumber(cmd_args[4]) + if alpha <= 1 and alpha > 0 then + settings.bg.alpha = math.floor(255 * (alpha)) + else + settings.bg.alpha = math.floor(alpha) + end + end + end + config.save(settings) + + setup_ui() + + log('BG color changed to: RED:%d/255 GREEN:%d/255 BLUE:%d/255 ALPHA:%d/255 = %d%%':format( + settings.bg.red, settings.bg.green, settings.bg.blue, settings.bg.alpha, math.floor(settings.bg.alpha/255*100) + )) + elseif cmd:contains('encumb') then + settings.show_encumbrance = not settings.show_encumbrance + config.save(settings) + + display_encumbrance() + + log('show_encumbrance changed to '..tostring(settings.show_encumbrance)) + elseif cmd:contains('ammo') or cmd:contains('count') then + settings.show_ammo_count = not settings.show_ammo_count + config.save(settings) + + display_ammo_count() + + log('show_ammo_count changed to '..tostring(settings.show_ammo_count)) + elseif cmd == 'hideonzone' or cmd == 'zone' then + settings.hide_on_zone = not settings.hide_on_zone + config.save(settings) + + log('hide_on_zone changed to '..tostring(settings.hide_on_zone)) + elseif cmd == 'hideoncutscene' or cmd == 'cutscene' then + settings.hide_on_cutscene = not settings.hide_on_cutscene + config.save(settings) + + log('hide_on_cutscene changed to '..tostring(settings.hide_on_cutscene)) + elseif cmd == 'justify' then + settings.left_justify = not settings.left_justify + config.save(settings) + + setup_ui() + + log('Ammo text justification changed to '..tostring(settings.left_justify and 'Left' or 'Right')) + elseif cmd == 'testenc' then + display_encumbrance(0xffff) + elseif cmd == 'debug' then + if #cmd_args < 1 then + local e = windower.ffxi.get_items('equipment') + for i=0,15 do + local v = equipment_data[i] + local b = e[string.format('%s_bag', v.slot_name)] + local eb = v.bag_id + local ind = v.index + local eind = e[v.slot_name] + local it = v.item_id + local eit = windower.ffxi.get_items(eb, eind).id + log('%s[%d] it=%d eit=%d b=%d eb=%d i=%d ei=%d':format(v.slot_name,i, it, eit, b, eb, ind, eind)) + end + elseif S{'1', 'on', 'true'}:contains(cmd_args[1]) then + evdebug = true + elseif S{'0', 'off', 'false'}:contains(cmd_args[1]) then + evdebug = false + end + else + log('HELP:') + log('ev position <xpos> <ypos>: move to position (from top left)') + log('ev size <pixels>: set pixel size of each item slot') + log('ev scale <factor>: scale multiplier for each item slot (from 32px)') + log('ev alpha <opacity>: set opacity of icons (out of 255)') + log('ev transparency <transparency>: inverse of alpha (out of 255)') + log('ev background <red> <green> <blue> <alpha>: sets color and opacity of background (out of 255)') + log('ev ammocount: toggles showing current ammo count') + log('ev encumbrance: toggles showing encumbrance Xs') + log('ev hideonzone: toggles hiding while crossing zone line') + log('ev hideoncutscene: toggles hiding when in cutscene/npc menu/etc') + log('ev justify: toggles between ammo text left and right justify') + end +end) + +function refresh_ui_settings() + --Image and text settings + bg_image_settings = { + alpha = settings.bg.alpha, + color = { + alpha = settings.bg.alpha, + red = settings.bg.red, + green = settings.bg.green, + blue = settings.bg.blue, + }, + pos = { + x = settings.pos.x, + y = settings.pos.y, + }, + size = { + width = settings.size * 4, + height = settings.size * 4, + }, + draggable = false, + } + equipment_image_settings = { + color = { + alpha = settings.icon.alpha, + red = settings.icon.red, + green = settings.icon.green, + blue = settings.icon.blue, + }, + texture = { + fit = false, + }, + size = { + width = settings.size, + height = settings.size, + }, + draggable = false, + } + encumbrance_image_settings = { + color = { + alpha = settings.icon.alpha*0.8, + red = settings.icon.red, + green = settings.icon.green, + blue = settings.icon.blue, + }, + texture = { + fit = false, + }, + size = { + width = settings.size, + height = settings.size, + }, + draggable = false, + } + ammo_count_text_settings = { + text = { + size = settings.size*0.27, + alpha = settings.ammo_text.alpha, + red = settings.ammo_text.red, + green = settings.ammo_text.green, + blue = settings.ammo_text.blue, + stroke = { + width = settings.ammo_text.stroke.width, + alpha = settings.ammo_text.stroke.alpha, + red = settings.ammo_text.stroke.red, + green = settings.ammo_text.stroke.green, + blue = settings.ammo_text.stroke.blue, + }, + }, + bg = { + alpha = 0, + red = 255, + blue = 255, + green = 255 + }, + pos = { + x = (windower.get_windower_settings().ui_x_res - (settings.pos.x + settings.size*4))*-1, + y = settings.pos.y + settings.size*0.58, + }, + flags = { + draggable = false, + right = true, + bold = settings.ammo_text.flags.bold, + italic = settings.ammo_text.flags.italic, + }, + } + ammo_count_text_settings_left_justify = { + text = { + size = settings.size*0.27, + alpha = settings.ammo_text.alpha, + red = settings.ammo_text.red, + green = settings.ammo_text.green, + blue = settings.ammo_text.blue, + stroke = { + width = settings.ammo_text.stroke.width, + alpha = settings.ammo_text.stroke.alpha, + red = settings.ammo_text.stroke.red, + green = settings.ammo_text.stroke.green, + blue = settings.ammo_text.stroke.blue, + }, + }, + bg = { + alpha = 0, + red = 255, + blue = 255, + green = 255 + }, + pos = { + x = settings.pos.x + settings.size*3, + y = settings.pos.y + settings.size*0.58 + }, + flags = { + draggable = false, + right = false, + bold = settings.ammo_text.flags.bold, + italic = settings.ammo_text.flags.italic, + }, + } +end diff --git a/Data/DefaultContent/Libraries/addons/addons/equipviewer/icon_extractor.lua b/Data/DefaultContent/Libraries/addons/addons/equipviewer/icon_extractor.lua new file mode 100644 index 0000000..0424e7b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/equipviewer/icon_extractor.lua @@ -0,0 +1,266 @@ +--[[ + Copyright © 2021, Rubenator + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of EquipViewer nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Rubenator 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. +]] +-- icon_extractor v1.1.2 +-- Written by Rubenator of Leviathan +-- Base Extraction Code graciously provided by Trv of Windower discord +local icon_extractor = {} + +local game_path_default = windower.ffxi_path +local game_path = game_path_default + +local string = require('string') +local io = require('io') +local math = require('math') + +local concat = table.concat +local floor = math.floor +local byte = string.byte +local char = string.char +local sub = string.sub + +local file_size = '\122\16\00\00' +local reserved1 = '\00\00' +local reserved2 = '\00\00' +local starting_address = '\122\00\00\00' + +local default = '\00\00\00\00' + +local dib_header_size = '\108\00\00\00' +local bitmap_width = '\32\00\00\00' +local bitmap_height = '\32\00\00\00' +local n_color_planes = '\01\00' +local bits_per_pixel = '\32\00' +local compression_type = '\03\00\00\00' +local image_size = '\00\16\00\00' +local h_resolution_target = default +local v_resolution_target = default +local default_n_colors = default +local important_colors = default +local alpha_mask = '\00\00\00\255' +local red_mask = '\00\00\255\00' +local green_mask = '\00\255\00\00' +local blue_mask = '\255\00\00\00' +local colorspace = 'sRGB' +local endpoints = string.rep('\00', 36) +local red_gamma = default +local green_gamma = default +local blue_gamma = default + +local header = 'BM' .. file_size .. reserved1 .. reserved2 .. starting_address + .. dib_header_size .. bitmap_width .. bitmap_height .. n_color_planes + .. bits_per_pixel .. compression_type .. image_size + .. h_resolution_target .. v_resolution_target + .. default_n_colors .. important_colors + .. red_mask .. green_mask .. blue_mask .. alpha_mask + .. colorspace .. endpoints .. red_gamma .. green_gamma .. blue_gamma + +--local icon_file = io.open('C:/Program Files (x86)/PlayOnline/SquareEnix/FINAL FANTASY XI/ROM/118/106.DAT', 'rb') +local color_lookup = {} +local bmp_segments = {} + +for i = 0x000, 0x0FF do + color_lookup[string.char(i)] = '' +end + +--[[ +3072 bytes per icon +640 bytes for stats, string table, etc. +2432 bytes for pixel data +--]] + +local item_dat_map = { + [1]={min=0x0001, max=0x0FFF, dat_path='118/106', offset=-1}, -- General Items + [2]={min=0x1000, max=0x1FFF, dat_path='118/107', offset=0}, -- Usable Items + [3]={min=0x2000, max=0x21FF, dat_path='118/110', offset=0}, -- Automaton Items + [4]={min=0x2200, max=0x27FF, dat_path='301/115', offset=0}, -- General Items 2 + [5]={min=0x2800, max=0x3FFF, dat_path='118/109', offset=0}, -- Armor Items + [6]={min=0x4000, max=0x59FF, dat_path='118/108', offset=0}, -- Weapon Items + [7]={min=0x5A00, max=0x6FFF, dat_path='286/73', offset=0}, -- Armor Items 2 + [8]={min=0x7000, max=0x73FF, dat_path='217/21', offset=0}, -- Maze Items, Basic Items + [9]={min=0x7400, max=0x77FF, dat_path='288/80', offset=0}, -- Instinct Items + [10]={min=0xF000, max=0xF1FF, dat_path='288/67', offset=0}, -- Monipulator Items + [11]={min=0xFFFF, max=0xFFFF, dat_path='174/48', offset=0}, -- Gil +} + +local item_by_id = function (id, output_path) + local dat_stats = find_item_dat_map(id) + local icon_file = open_dat(dat_stats) + + local id_offset = dat_stats.min + dat_stats.offset + icon_file:seek('set', (id - id_offset) * 0xC00 + 0x2BD) + local data = icon_file:read(0x800) + + bmp = convert_item_icon_to_bmp(data) + + local f = io.open(output_path, 'wb') + f:write(bmp) + coroutine.yield() + f:close() +end +icon_extractor.item_by_id = item_by_id + +function find_item_dat_map(id) + for _,stats in pairs(item_dat_map) do + if id >= stats.min and id <= stats.max then + return stats + end + end + return nil +end + +function open_dat(dat_stats) + local icon_file = nil + if dat_stats.file then + icon_file = dat_stats.file + else + if not game_path then + error('ffxi_path must be set before using icon_extractor library') + end + filename = game_path .. '/ROM/' .. tostring(dat_stats.dat_path) .. '.DAT' + icon_file, err = io.open(filename, 'rb') + if not icon_file then + error(err) + return + end + dat_stats.file = icon_file + end + return icon_file +end + +-- 32 bit color palette-indexed bitmaps. Bits are rotated and must be decoded. +local encoded_to_decoded_char = {} +local encoded_byte_to_rgba = {} +local alpha_encoded_to_decoded_adjusted_char = {} +local decoded_byte_to_encoded_char = {} +for i = 0x000, 0x0FF do + encoded_byte_to_rgba[i] = '' + local n = (i % 0x20) * 0x8 + floor(i / 0x20) + encoded_to_decoded_char[char(i)] = char(n) + decoded_byte_to_encoded_char[n] = char(i) + n = n * 0x2 + n = n < 0x100 and n or 0x0FF + alpha_encoded_to_decoded_adjusted_char[char(i)] = char(n) +end +local decoder = function(a, b, c, d) + return encoded_to_decoded_char[a].. + encoded_to_decoded_char[b].. + encoded_to_decoded_char[c].. + alpha_encoded_to_decoded_adjusted_char[d] +end +function convert_item_icon_to_bmp(data) + local color_palette = string.gsub(sub(data, 0x001, 0x400), '(.)(.)(.)(.)', decoder) + -- rather than decoding all 2048 bytes, decode only the palette and index it by encoded byte + for i = 0x000, 0x0FF do + local offset = i * 0x4 + 0x1 + encoded_byte_to_rgba[decoded_byte_to_encoded_char[i]] = sub(color_palette, offset, offset + 0x3) + end + + return header .. string.gsub(sub(data, 0x401, 0x800), '(.)', function(a) return encoded_byte_to_rgba[a] end) +end + + +local buff_dat_map = { + [1]={min=0x000, max=0x400, dat_path='119/57', offset=0}, +} +function find_buff_dat_map(id) + for _,stats in pairs(buff_dat_map) do + if id >= stats.min and id <= stats.max then + return stats + end + end + return nil +end +local buff_by_id = function (id, output_path) + local dat_stats = find_buff_dat_map(id) + local icon_file = open_dat(dat_stats) + + local id_offset = dat_stats.min + dat_stats.offset + icon_file:seek('set', (id - id_offset) * 0x1800) + local data = icon_file:read(0x1800) + + bmp = convert_buff_icon_to_bmp(data) + + local f = io.open(output_path, 'wb') + f:write(bmp) + coroutine.yield() + f:close() +end +icon_extractor.buff_by_id = buff_by_id + + +local ffxi_path = function(location) + game_path = location or game_path_default + close_dats() +end +icon_extractor.ffxi_path = ffxi_path + + +-- A mix of 32 bit color uncompressed and *color palette-indexed bitmaps +-- Offsets defined specifically for status icons +-- * some maps use this format as well, but at 512 x 512 +function convert_buff_icon_to_bmp(data) + local length = byte(data, 0x282) -- The length is technically sub(0x281, 0x284), but only 0x282 is unique + + if length == 16 then -- uncompressed + data = sub(data, 0x2BE, 0x12BD) + data = string.gsub(data, '(...)\x80', '%1\xFF') -- All of the alpha bytes are currently 0 or 0x80. + elseif length == 08 then -- color table + local color_palette = sub(data, 0x2BE, 0x6BD) + color_palette = string.gsub(color_palette, '(...)\x80', '%1\xFF') + + local n = 0x0 + for i = 1, 0x400, 0x4 do + color_lookup[char(n)] = sub(color_palette, i, i + 3) + n = n + 1 + end + + data = string.gsub(sub(data, 0x6BE, 0xABD), '(.)', function(i) return color_lookup[i] end) + elseif length == 04 then -- XIVIEW + data = sub(data, 0x2BE, 0x12BD) + end + + return header .. data +end + +function close_dats() + for _,dat in pairs(item_dat_map) do + if dat and dat.file then + dat.file:close() + dat.file = nil + end + end + for _,dat in pairs(buff_dat_map) do + if dat and dat.file then + dat.file:close() + dat.file = nil + end + end +end + +windower.register_event('unload', function() + close_dats() +end); + +return icon_extractor diff --git a/Data/DefaultContent/Libraries/addons/addons/eval/data/bootstrap.lua b/Data/DefaultContent/Libraries/addons/addons/eval/data/bootstrap.lua new file mode 100644 index 0000000..da99720 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/eval/data/bootstrap.lua @@ -0,0 +1,4 @@ +--[[Any functions or code in this file will be run upon the eval addon load. +This file is ONLY run upon load. You will need to reload the eval addon to +re-read any changes--]] + diff --git a/Data/DefaultContent/Libraries/addons/addons/eval/eval.lua b/Data/DefaultContent/Libraries/addons/addons/eval/eval.lua new file mode 100644 index 0000000..b1ff3ae --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/eval/eval.lua @@ -0,0 +1,11 @@ + +_addon.name = 'Eval' +_addon.author = 'Aureus' +_addon.command = 'eval' +_addon.version = '1.0.0.0' + +require('data/bootstrap') + +windower.register_event('addon command', function(...) + assert(loadstring(table.concat({...}, ' ')))() +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/eval/readme.md b/Data/DefaultContent/Libraries/addons/addons/eval/readme.md new file mode 100644 index 0000000..22f9faf --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/eval/readme.md @@ -0,0 +1,8 @@ +Author: Aureus +Version: 1.0 +Addon to evaluate lua code via windower commands + +Abbreviation: //eval + +Use //eval to run arbitrary lua code. For example, try: +//eval print(windower.ffxi.get_player().name) diff --git a/Data/DefaultContent/Libraries/addons/addons/ffocolor/Readme.md b/Data/DefaultContent/Libraries/addons/addons/ffocolor/Readme.md new file mode 100644 index 0000000..cadf226 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ffocolor/Readme.md @@ -0,0 +1,15 @@ +**Author:** Ricky Gall +**Version:** 2.02 +**Description:** +Addon to make ffochat show up in one of the 5 in-game chat tabs (say/shout/linkshell/party/tell), while also allowing you to show the text in a certain color to easily distinguish it from the chat that normally shows up in that tab. + +**Abbreviation:** //ffocolor + +**Commands:** + 1. //ffocolor chattab <say/shout/linkshell/party/tell> --Changes the chattab + 2. //ffocolor chatcolor <color#> --Changes the highlight color + 3. //ffocolor getcolors -- Show a list of color codes. Be aware this is 255 lines of text + 4. //ffocolor help --Shows this menu. + +**Changes:** + --removed name highlighting (highlight addon can be used for this)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/ffocolor/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/ffocolor/data/settings.xml new file mode 100644 index 0000000..6ee1d34 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ffocolor/data/settings.xml @@ -0,0 +1,10 @@ +<?xml version="1.1" ?> +<settings> + <global> + <chatColor>207</chatColor> + <chatTab>say</chatTab> + </global> + <nitroustaru> + <chatTab>shout</chatTab> + </nitroustaru> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/ffocolor/ffocolor.lua b/Data/DefaultContent/Libraries/addons/addons/ffocolor/ffocolor.lua new file mode 100644 index 0000000..a62b40a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ffocolor/ffocolor.lua @@ -0,0 +1,126 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'FFOColor' +_addon.version = '2.02' +_addon.author = 'Nitrous (Shiva)' +_addon.command = 'ffocolor' + +require('tables') +require('strings') +require('logger') +require('lists') +config = require('config') +files = require('files') +chat = require('chat') +defaults = {} + +defaults.chatTab = 'say' +defaults.chatColor = 207 + +settings = config.load(defaults) + +chatColors = T{say=1,shout=2,tell=4,party=5,linkshell=6,none=74} + +windower.register_event('addon command', function(...) + local args = {...} + if args[1] == nil then args[1] = 'help' end + if args[1] ~= nil then + comm = args[1]:lower() + if S{'chattab','chatcolor'}:contains(comm) then + if comm == 'chatcolor' then + if args[2] == nil then + print('Current Chat Color:', settings.chatColor) + elseif args[2] >= 1 and args[2] <= 509 then + settings.chatColor = tonumber(args[2]) + print('New Chat Color:', tonumber(args[2])) + else + error('Chat color must be between 1 and 509.') + end + elseif comm == 'chattab' then + if args[2] == nil then + print('Current Chat Tab:', settings.chatTab) + elseif S{'say','shout','linkshell','party','tell','none'}:contains(args[2]:lower()) then + settings.chatTab = args[2]:lower() + print('New Chat Tab:', args[2]:lower()) + else + error('Improper tab selection defaulting to say') + settings.chatTab = 'say' + end + end + settings:save() + elseif comm == 'getcolors' then + local color_redundant = S{26,33,41,71,72,89,94,109,114,164,173,181,184,186,70,84,104,127,128,129,130,131,132,133,134,135,136,137,138,139,140,64,86,91,106,111,175,178,183,81,101,16,65,87,92,107,112,174,176,182,82,102,67,68,69,170,189,15,208,18,25,32,40,163,185,23,24,27,34,35,42,43,162,165,187,188,30,31,14,205,144,145,146,147,148,149,150,151,152,153,190,13,9,253,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,284,285,286,287,292,293,294,295,300,301,301,303,308,309,310,311,316,317,318,319,324,325,326,327,332,333,334,335,340,341,342,343,344,345,346,347,348,349,350,351,355,357,358,360,361,363,366,369,372,374,375,378,381,384,395,406,409,412,415,416,418,421,424,437,450,453,456,458,459,462,479,490,493,496,499,500,502,505,507,508,10,51,52,55,58,62,66,80,83,85,88,90,93,100,103,105,108,110,113,122,168,169,171,172,177,179,180,12,11,37,291} -- 37 and 291 might be unique colors, but they are not gsubbable. + local black_colors = S{352,354,356,388,390,400,402,430,432,442,444,472,474,484,486} + local counter = 0 + local line = '' + for n = 1, 509 do + if not color_redundant:contains(n) and not black_colors:contains(n) then + if n <= 255 then + loc_col = string.char(0x1F, n) + else + loc_col = string.char(0x1E, n - 254) + end + line = line..loc_col..string.format('%03d ', n) + counter = counter + 1 + end + if counter == 16 or n == 509 then + log(line) + counter = 0 + line = '' + end + end + else + local helptext = [[FFOColor - Command List: + 1. ffocolor chattab <say/shout/linkshell/party/tell> --Changes the chattab. Default: say. + 2. ffocolor chatcolor <color#> --Changes the highlight color. + 3. ffocolor getcolors -- Show a list of color codes. + 4. ffocolor help --Shows this menu.]] + for _, line in ipairs(helptext:split('\n')) do + windower.add_to_chat(207, line..chat.controls.reset) + end + end + end +end) + +windower.register_event('incoming text', function(old,new,color,newcolor) + local stb = string.find(new,'[^%w]*%[%d+:#.-%]') or string.find(new,'^[^%w]*%[FFOChat%]') + if stb ~= nil or old:sub(2,8) == 'PrivMsg' then + if settings.chatTab ~= nil then + newcolor = chatColors[settings.chatTab] + end + new = new:gsub('\r',''):gsub('\n','') + local newsplit = new:split(' ') + for it = 1, #newsplit do + newsplit[it] = newsplit[it]:color(settings.chatColor) + end + new = newsplit:concat(' ') + return new, newcolor + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/findAll/README.md b/Data/DefaultContent/Libraries/addons/addons/findAll/README.md new file mode 100644 index 0000000..409d1ca --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/findAll/README.md @@ -0,0 +1,175 @@ +# FindAll + +This addon searches items stored on all your characters. To build the initial list, you must login and either receive any item-handling packet, logout, or input the `findall` command on any character at least once with each of them. +The list is stored on the machine on which the addon is executed, being updated everytime you look for an item, certain packets arrive, or you logout, so this will not work the best if you use multiple PCs, unless you store your Windower installation in Dropbox or somewhere. +The addon will show a warning if your entire inventory has not re-loaded since zoning, but it should still be accurate because Windower's item handling API has moved to being packet based. + +It offers an on-screen tracker that keeps track of items you specify or of your used/free space in specified bags. + +## Tracker + +The settings file has a field that you can use to define what is being tracked in a text box on the screen. +The text supports variables denoted by curly braces preceded by a dollar sign `${}`. +Each variable consists of two parts, the bag indicator and the item name, separated by a colo `:`. +For example, to track the amount of shihei in your inventory, you would do this: +```xml + <Track>${inventory:shihei}</Track> +``` + +This will merely display a number on the screen. You can add flavor text outside of the variable: +```xml + <Track>Shihei: ${inventory:shihei}</Track> +``` + +You can also use wildcards for item names: +```xml + <Track>Crystals: ${inventory:*crystal}</Track> +``` + +You can use any of the bag names defined [here](https://github.com/Windower/Resources/blob/master/lua/bags.lua) as well as the key word `all` to search all bags. Every variable name is case-insensitive. + +There are a three variables that can be used instead of item names: `$freespace`, `$usedspace` and `$maxspace` +If those are used, the respective value will be displayed: +```xml + <Track>Inventory: ${inventory:$freespace}, Wardrobe: ${wardrobe:$freespace}</Track> +``` + +### Formatting + +Since the tracker uses XML for formatting and XML is shitty, this will not work: +```xml + <Track> +Inventory: ${inventory:$freespace} +Satchel: ${satchel:$freespace} +Sack: ${sack:$freespace} +Case: ${case:$freespace} +Wardrobe: ${wardrobe:$freespace} + </Track> +``` + +The spaces and new lines will all collapse into a single space and you'll get one long and unreadable line. +To make the format appear as you have it in the XML settings file you need to wrap the entire text in `<![CDATA[` and `]]>` tags: +```xml + <Track> +<![CDATA[Inventory: ${inventory:$freespace} +Satchel: ${satchel:$freespace} +Sack: ${sack:$freespace} +Case: ${case:$freespace} +Wardrobe: ${wardrobe:$freespace}]]> + </Track> +``` + +That will correctly preserve any formatting you have inside the text. +With that, you can even do something like this: +```xml + <Track> +<![CDATA[Inventory: ${inventory:$usedspace||%2i}/${inventory:$maxspace||%2i} → ${inventory:$freespace||%2i} +Satchel: ${satchel:$usedspace||%2i}/${satchel:$maxspace||%2i} → ${satchel:$freespace||%2i} +Sack: ${sack:$usedspace||%2i}/${sack:$maxspace||%2i} → ${sack:$freespace||%2i} +Case: ${case:$usedspace||%2i}/${case:$maxspace||%2i} → ${case:$freespace||%2i} +Wardrobe: ${wardrobe:$usedspace||%2i}/${wardrobe:$maxspace||%2i} → ${wardrobe:$freespace||%2i}]]> + </Track> +``` + +And it will result in this: + + + +## Commands + +### Update ### + +``` +findall +``` + +Forces a list update + +### Search ### + +``` +findall [:<character1> [:...]] <query> [-e<filename>|--export=<filename>] +``` +* `character1`: the name of the characters to use for the search. +* `...`: variable list of character names. +* `query` the word you are looking for. +* `-e<filename>` or `--export=<filename>` exports the results to a csv file. The file will be created in the data folder. + +Looks for any item whose name (long or short) contains the specified value on the specified characters. + +## Examples ## + +``` +findall thaumas +``` + +Search for "thaumas" on all your characters. + +``` +findall :alpha :beta thaumas +``` + +Search for "thaumas" on "alpha" and "beta" characters. + +``` +findall :omega +``` + +Show all the items stored on "omega". + +---- + +## TODO + +- Use IPC to notify the addon about any change to the character's items list to reduce the amount of file rescans. +- Use IPC to synchronize the list between PCs in LAN or Internet (requires IPC update). + +---- + +## Changelog + +### v1.20170501 +* **add**: Added a setting to stop the display of keyitems. Maybe someone will add a command toggle for it later? + +### v1.20170405 +* **fix**: Adjusted the conditions for updating the shared storages.json to make it more robust. +* **add**: Added key item tracking. + +### v1.20150521 +* **fix**: Fixed after May 2015 FFXI update +* **change**: Future proofed the addon to be less prone to breaks + +### v1.20140328 +* **change**: Changed the inventory structure refresh rate using packets. +* **add**: IPC usage to track changes across simultaneously active accounts. + +### v1.20140210 +* **fix**: Fixed bug that occasionally deleted stored inventory structures. +* **change**: Increased the inventory structure refresh rate using packets. + +### v1.20131008 +* **add**: Added new case storage support. + +### v1.20130610 +* **add**: Added slips as searchable storages for the current character. +* **add**: The search results will show the long name if the short one doesn't contain the inputted search terms. + +### v1.20130605 +* **fix**: Fixed weird results names in search results. + +### v1.20130603 +* **add**: Added export function. +* **change**: Leave the case of items' names untouched + +### v1.20130529 +* **fix:** Escaped patterns in search terms. +* **change**: Aligned to Windower's addon development guidelines. + +### v1.20130524 +* **add:** Added temp items support. + +### v1.20130521 +* **add:** Added characters filter. + +### v1.20130520 +* First release. diff --git a/Data/DefaultContent/Libraries/addons/addons/findAll/data/storages.json b/Data/DefaultContent/Libraries/addons/addons/findAll/data/storages.json new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/findAll/data/storages.json diff --git a/Data/DefaultContent/Libraries/addons/addons/findAll/findAll.lua b/Data/DefaultContent/Libraries/addons/addons/findAll/findAll.lua new file mode 100644 index 0000000..83ff8d1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/findAll/findAll.lua @@ -0,0 +1,611 @@ +--[[ +Copyright © 2013-2015, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of findAll nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'findAll' +_addon.author = 'Zohno' +_addon.version = '1.20190514' +_addon.commands = {'findall'} + +require('chat') +require('lists') +require('logger') +require('sets') +require('tables') +require('strings') +require('pack') + +file = require('files') +slips = require('slips') +config = require('config') +texts = require('texts') +res = require('resources') + +defaults = {} +defaults.Track = '' +defaults.Tracker = {} +defaults.KeyItemDisplay = true + +settings = config.load(defaults) + +tracker = texts.new(settings.Track, settings.Tracker, settings) + +do + config.register(settings, function(settings) + tracker:text(settings.Track) + tracker:visible(settings.Track ~= '' and windower.ffxi.get_info().logged_in) + end) + + local bag_ids = res.bags:rekey('english'):key_map(string.lower):map(table.get-{'id'}) + + local variable_cache = S{} + tracker:register_event('reload', function() + for variable in tracker:it() do + local bag_name, search = variable:match('(.*):(.*)') + + local bag = bag_name == 'all' and 'all' or bag_ids[bag_name:lower()] + if not bag and bag_name ~= 'all' then + warning('Unknown bag: %s':format(bag_name)) + else + if not S{'$freespace', '$usedspace', '$maxspace'}:contains(search:lower()) then + local items = S(res.items:name(windower.wc_match-{search})) + S(res.items:name_log(windower.wc_match-{search})) + if items:empty() then + warning('No items matching "%s" found.':format(search)) + else + variable_cache:add({ + name = variable, + bag = bag, + type = 'item', + ids = items:map(table.get-{'id'}), + search = search, + }) + end + else + variable_cache:add({ + name = variable, + bag = bag, + type = 'info', + search = search, + }) + end + end + end + end) + + do + local update = T{} + + local search_bag = function(bag, ids) + return bag:filter(function(item) + return type(item) == 'table' and ids:contains(item.id) + end):reduce(function(acc, item) + return type(item) == 'table' and item.count + acc or acc + end, 0) + end + + local last_check = 0 + + windower.register_event('prerender', function() + if os.clock() - last_check < 0.25 then + return + end + last_check = os.clock() + + local items = T{} + for variable in variable_cache:it() do + if variable.type == 'info' then + local info + if variable.bag == 'all' then + info = { + max = 0, + count = 0 + } + for bag_info in T(windower.ffxi.get_bag_info()):it() do + info.max = info.max + bag_info.max + info.count = info.count + bag_info.count + end + else + info = windower.ffxi.get_bag_info(variable.bag) + end + + update[variable.name] = + variable.search == '$freespace' and (info.max - info.count) + or variable.search == '$usedspace' and info.count + or variable.search == '$maxspace' and info.max + or nil + elseif variable.type == 'item' then + if variable.bag == 'all' then + for id in bag_ids:it() do + if not items[id] then + items[id] = T(windower.ffxi.get_items(id)) + end + end + else + if not items[variable.bag] then + items[variable.bag] = T(windower.ffxi.get_items(variable.bag)) + end + end + + update[variable.name] = variable.bag ~= 'all' and search_bag(items[variable.bag], variable.ids) or items:reduce(function(acc, bag) + return acc + search_bag(bag, variable.ids) + end, 0) + end + end + + if not update:empty() then + tracker:update(update) + end + end) + end +end + +zone_search = windower.ffxi.get_info().logged_in +first_pass = true +item_names = T{} +key_item_names = T{} +global_storages = T{} +storages_path = 'data/' +storages_order_tokens = L{'temporary', 'inventory', 'wardrobe', 'wardrobe 2', 'safe', 'safe 2', 'storage', 'locker', 'satchel', 'sack', 'case'} +-- This is to maintain sorting order. I don't know why this was done, but omitting this will sort the bags arbitrarily, which (I guess) was not intended +storages_order = S(res.bags:map(string.gsub-{' ', ''} .. string.lower .. table.get-{'english'})):sort(function(name1, name2) + local index1 = storages_order_tokens:find(name1) + local index2 = storages_order_tokens:find(name2) + + if not index1 and not index2 then + return name1 < name2 + end + + if not index1 then + return false + end + + if not index2 then + return true + end + + return index1 < index2 +end) +storage_slips_order = L{'slip 01', 'slip 02', 'slip 03', 'slip 04', 'slip 05', 'slip 06', 'slip 07', 'slip 08', 'slip 09', 'slip 10', 'slip 11', 'slip 12', 'slip 13', 'slip 14', 'slip 15', 'slip 16', 'slip 17', 'slip 18', 'slip 19', 'slip 20', 'slip 21', 'slip 22', 'slip 23', 'slip 24', 'slip 25', 'slip 26', 'slip 27', 'slip 28'} +merged_storages_orders = storages_order + storage_slips_order + L{'key items'} + +function search(query, export) + update_global_storage() + update() + if query:length() == 0 then + return + end + + local character_set = S{} + local character_filter = S{} + local terms = '' + + for _, query_element in ipairs(query) do + local char = query_element:match('^([:!]%a+)$') + if char then + if char:sub(1, 1) == '!' then + character_filter:add(char:sub(2):lower():gsub("^%l", string.upper)) + else + character_set:add(char:sub(2):lower():gsub("^%l", string.upper)) + end + else + terms = query_element + end + end + + if character_set:length() == 0 and terms == '' then + return + end + + local new_item_ids = S{} + local new_key_item_ids = S{} + + for character_name, storages in pairs(global_storages) do + for storage_name, storage in pairs(storages) do + if storage_name == 'key items' then + for id, quantity in pairs(storage) do + id = tostring(id) + + if key_item_names[id] == nil then + new_key_item_ids:add(id) + end + end + elseif storage_name ~= 'gil' then + for id, quantity in pairs(storage) do + id = tostring(id) + + if item_names[id] == nil then + new_item_ids:add(id) + end + end + end + coroutine.yield() + end + end + + for id,_ in pairs(new_item_ids) do + local item = res.items[tonumber(id)] + if item then + item_names[id] = { + ['name'] = item.name, + ['long_name'] = item.name_log + } + end + end + + for id,_ in pairs(new_key_item_ids) do + local key_item = res.key_items[tonumber(id)] + if key_item then + key_item_names[id] = { + ['name'] = key_item.name, + ['long_name'] = key_item.name + } + end + end + + + local results_items = S{} + local results_key_items = S{} + local terms_pattern = '' + + if terms ~= '' then + terms_pattern = terms:escape():gsub('%a', function(char) return string.format("[%s%s]", char:lower(), char:upper()) end) + end + + for id, names in pairs(item_names) do + if terms_pattern == '' or item_names[id].name:find(terms_pattern) + or item_names[id].long_name:find(terms_pattern) + then + results_items:add(id) + end + end + + if settings.KeyItemDisplay then + for id in pairs(key_item_names) do + if terms_pattern == '' or key_item_names[id].name:match(terms_pattern) then + results_key_items:add(id) + end + end + end + + log('Searching: '..query:concat(' ')) + + local no_results = true + local sorted_names = global_storages:keyset():sort() + :reverse() + + if windower.ffxi.get_info().logged_in then + sorted_names = sorted_names:append(sorted_names:remove(sorted_names:find(windower.ffxi.get_player().name))) + :reverse() + end + + local export_file + + if export ~= nil then + export_file = io.open(windower.addon_path..'data/'..export, 'w') + + if export_file == nil then + error('The file "'..export..'" cannot be created.') + else + export_file:write('"char";"storage";"item";"quantity"\n') + end + end + + local total_quantity = 0 + + for _, character_name in ipairs(sorted_names) do + if (character_set:length() == 0 or character_set:contains(character_name)) and not character_filter:contains(character_name) then + local storages = global_storages[character_name] + + for _, storage_name in ipairs(merged_storages_orders) do + local results = L{} + + if storage_name~= 'gil' and storages[storage_name] ~= nil then + for id, quantity in pairs(storages[storage_name]) do + if storage_name == 'key items' and results_key_items:contains(id) then + if terms_pattern ~= '' then + total_quantity = total_quantity + quantity + results:append( + (character_name..'/'..storage_name..':'):color(259)..' '.. + key_item_names[id].name:gsub('('..terms_pattern..')', ('%1'):color(258)).. + (quantity > 1 and ' '..('('..quantity..')'):color(259) or '') + ) + else + results:append( + (character_name..'/'..storage_name..':'):color(259)..' '..key_item_names[id].name.. + (quantity > 1 and ' '..('('..quantity..')'):color(259) or '') + ) + end + + if export_file ~= nil then + export_file:write('"'..character_name..'";"'..storage_name..'";"'..key_item_names[id].name..'";"'..quantity..'"\n') + end + + no_results = false + elseif storage_name ~= 'key items' and results_items:contains(id) then + if terms_pattern ~= '' then + total_quantity = total_quantity + quantity + results:append( + (character_name..'/'..storage_name..':'):color(259)..' '.. + item_names[id].name:gsub('('..terms_pattern..')', ('%1'):color(258)).. + (item_names[id].name:match(terms_pattern) and '' or ' ['..item_names[id].long_name:gsub('('..terms_pattern..')', ('%1'):color(258))..']').. + (quantity > 1 and ' '..('('..quantity..')'):color(259) or '') + ) + else + results:append( + (character_name..'/'..storage_name..':'):color(259)..' '..item_names[id].name.. + (quantity > 1 and ' '..('('..quantity..')'):color(259) or '') + ) + end + + if export_file ~= nil then + export_file:write('"'..character_name..'";"'..storage_name..'";"'..item_names[id].name..'";"'..quantity..'"\n') + end + + no_results = false + end + end + + results:sort() + + for i, result in ipairs(results) do + log(result) + end + end + coroutine.yield() + end + end + end + + if total_quantity > 0 then + log('Total: ' .. total_quantity) + end + + if export_file ~= nil then + export_file:close() + log('The results have been saved to "'..export..'"') + end + + if no_results then + if terms ~= '' then + if character_set:length() == 0 and character_filter:length() == 0 then + log('You have no items that match \''..terms..'\'.') + else + log('You have no items that match \''..terms..'\' on the specified characters.') + end + else + log('You have no items on the specified characters.') + end + end +end + +function get_local_storage() + local items = windower.ffxi.get_items() + local storages = {} + + if not items then + return false + end + + storages.gil = items.gil + + for _, storage_name in ipairs(storages_order) do + storages[storage_name] = T{} + + for _, data in ipairs(items[storage_name]) do + if type(data) == 'table' then + if data.id ~= 0 then + local id = tostring(data.id) + storages[storage_name][id] = (storages[storage_name][id] or 0) + data.count + end + end + end + end + + local slip_storages = slips.get_player_items() + + for _, slip_id in ipairs(slips.storages) do + local slip_name = 'slip '..tostring(slips.get_slip_number_by_id(slip_id)):lpad('0', 2) + storages[slip_name] = T{} + + for _, id in ipairs(slip_storages[slip_id]) do + storages[slip_name][tostring(id)] = 1 + end + end + + local key_items= windower.ffxi.get_key_items() + + storages['key items'] = T{} + + for _, id in ipairs(key_items) do + storages['key items'][tostring(id)] = 1 + end + + return storages +end + +function encase_key(key) + if type(key) == 'number' then + return '['..tostring(key)..']' + elseif type(key) == 'string' then + return '["'..key..'"]' + else + return tostring(key) + end +end + +function make_table(tab,tab_offset) + -- Won't work for circular references or keys containing double quotes + local offset = " ":rep(tab_offset) + local ret = "{\n" + for i,v in pairs(tab) do + ret = ret..offset..encase_key(i)..' = ' + if type(v) == 'table' then + ret = ret..make_table(v,tab_offset+2)..',\n' + else + ret = ret..tostring(v)..',\n' + end + end + return ret..offset..'}' +end + +function update() + if not windower.ffxi.get_info().logged_in then + print('You have to be logged in to use this addon.') + return false + end + + if zone_search == false then + notice('findAll has not detected a fully loaded inventory yet.') + return false + end + + local player_name = windower.ffxi.get_player().name + local self_storage = file.new(storages_path..'\\'..player_name..'.lua') + + if not self_storage:exists() then + self_storage:create() + end + + local local_storage = get_local_storage() + + if local_storage then + global_storages[player_name] = local_storage + else + return false + end + + self_storage:write('return '..make_table(local_storage,0)..'\n') + collectgarbage() + return true +end + + +function update_global_storage() + local player_name = windower.ffxi.get_player().name + + global_storages = T{} -- global_storages[server str][character_name str][inventory_name str][item_id num] = count num + + for _,f in pairs(windower.get_dir(windower.addon_path.."\\"..storages_path)) do + if f:sub(-4) == '.lua' and f:sub(1,-5) ~= player_name then + local success,result = pcall(dofile,windower.addon_path..'\\'..storages_path..'\\'..f) + if success then + global_storages[f:sub(1,-5)] = result + else + warning('Unable to retrieve updated item storage for %s.':format(f:sub(1,-5))) + end + end + end +end + +windower.register_event('load', update:cond(function() return windower.ffxi.get_info().logged_in end)) + +windower.register_event('incoming chunk', function(id,original,modified,injected,blocked) + local seq = original:unpack('H',3) + if (next_sequence and seq == next_sequence) and zone_search then + update() + next_sequence = nil + end + + if id == 0x00B then -- Last packet of an old zone + zone_search = false + elseif id == 0x00A then -- First packet of a new zone, redundant because someone could theoretically load findAll between the two + zone_search = false + elseif id == 0x01D and not zone_search then + -- This packet indicates that the temporary item structure should be copied over to + -- the real item structure, accessed with get_items(). Thus we wait one packet and + -- then trigger an update. + zone_search = true + next_sequence = (seq+22)%0x10000 -- 128 packets is about 1 minute. 22 packets is about 10 seconds. + elseif (id == 0x1E or id == 0x1F or id == 0x20) and zone_search then + -- Inventory Finished packets aren't sent for trades and such, so this is more + -- of a catch-all approach. There is a subtantial delay to avoid spam writing. + -- The idea is that if you're getting a stream of incoming item packets (like you're gear swapping in an intense fight), + -- then it will keep putting off triggering the update until you're not. + next_sequence = (seq+22)%0x10000 + end +end) + +windower.register_event('ipc message', function(str) + if str == 'findAll update' then + update() + end +end) + +handle_command = function(...) + if first_pass then + first_pass = false + windower.send_ipc_message('findAll update') + windower.send_command('wait 0.05;findall '..table.concat({...},' ')) + else + first_pass = true + local params = L{...} + local query = L{} + local export = nil + + -- convert command line params (SJIS) to UTF-8 + for i, elm in ipairs(params) do + params[i] = windower.from_shift_jis(elm) + end + + while params:length() > 0 and params[1]:match('^[:!]%a+$') do + query:append(params:remove(1)) + end + + if params:length() > 0 then + export = params[params:length()]:match('^--export=(.+)$') or params[params:length()]:match('^-e(.+)$') + + if export ~= nil then + export = export:gsub('%.csv$', '')..'.csv' + + params:remove(params:length()) + + if export:match('['..('\\/:*?"<>|'):escape()..']') then + export = nil + + error('The filename cannot contain any of the following characters: \\ / : * ? " < > |') + end + end + + query:append(params:concat(' ')) + end + + search(query, export) + end +end + +windower.register_event('unhandled command', function(command, ...) + if command:lower() == 'find' then + local me = windower.ffxi.get_mob_by_target('me') + if me then + handle_command(':%s':format(me.name), ...) + else + handle_command(...) + end + end +end) + +windower.register_event('addon command', handle_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/gametime/gametime.lua b/Data/DefaultContent/Libraries/addons/addons/gametime/gametime.lua new file mode 100644 index 0000000..9ea8801 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/gametime/gametime.lua @@ -0,0 +1,510 @@ +-- Copyright © 2013-2016, Omnys of Valefor +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of Gametime nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL OMNYS BE LIABLE FOR ANY +-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'gametime' +_addon.author = 'Omnys' +_addon.version = '0.6' +_addon.commands = {'gametime','gt'} + +require('chat') +require('logger') +require('strings') +require('maths') +require('tables') + +texts = require('texts') +config = require('config') +res = require('resources') + +visible = false + +local gt = {} +gt.delimiter = ' ' +gt.days = {} +gt.days[1] = {} +gt.days[1][1] = 'Firesday' +gt.days[1][2] = 'Fi ' +gt.days[1][10] = '(255, 0, 0)' +gt.days[1][3] = 'Fire ' +--Mini mode +gt.days[1][4] = '° ' + +gt.days[2] = {} +gt.days[2][1] = 'Earthsday' +gt.days[2][2] = 'Ea ' +gt.days[2][10] = '(255, 225, 0)' +gt.days[2][3] = 'Earth ' +--Mini mode +gt.days[2][4] = '° ' + +gt.days[3] = {} +gt.days[3][1] = 'Watersday' +gt.days[3][2] = 'Wa ' +gt.days[3][10] = '(100, 100, 255)' +gt.days[3][3] = 'Water ' +--Mini mode +gt.days[3][4] = '° ' + +gt.days[4] = {} +gt.days[4][1] = 'Windsday' +gt.days[4][2] = 'Wi ' +gt.days[4][10] = '(0, 255, 0)' +gt.days[4][3] = 'Wind ' +--Mini mode +gt.days[4][4] = '° ' + +gt.days[5] = {} +gt.days[5][1] = 'Iceday' +gt.days[5][2] = 'Ic ' +gt.days[5][10] = '(150, 200, 255)' +gt.days[5][3] = 'Ice ' +--Mini mode +gt.days[5][4] = '° ' + +gt.days[6] = {} +gt.days[6][1] = 'Lightningday' +gt.days[6][2] = 'Lg ' +gt.days[6][10] = '(255, 128, 128)' +gt.days[6][3] = 'Lightning ' +--Mini mode +gt.days[6][4] = '° ' + +gt.days[7] = {} +gt.days[7][1] = 'Lightsday' +gt.days[7][2] = 'Lt ' +gt.days[7][10] = '(255, 255, 255)' +gt.days[7][3] = 'Light ' +--Mini mode +gt.days[7][4] = '° ' + +gt.days[8] = {} +gt.days[8][1] = 'Darksday' +gt.days[8][2] = 'Dk ' +gt.days[8][10] = '(128, 128, 128)' +gt.days[8][3] = 'Dark ' +--Mini mode +gt.days[8][4] = '° ' + +gt.WeekReport = '' +gt.MoonPct = '' +gt.MoonPhase = '' + +defaults = {} +defaults.saved = 0 +defaults.mode = 1 +defaults.zero = false +defaults.time = {} +defaults.time.pos = {} +defaults.time.pos.x = 0 -- left +defaults.time.pos.y = 0 -- top +defaults.time.text = {} +defaults.time.text.alpha = 255 +defaults.time.text.red = 255 +defaults.time.text.green = 255 +defaults.time.text.blue = 255 +defaults.time.text.size = 12 +defaults.time.text.font = 'Consolas' +defaults.time.bg = {} +defaults.time.bg.alpha = 25 +defaults.time.bg.red = 100 +defaults.time.bg.green = 100 +defaults.time.bg.blue = 100 +defaults.time.bg.visible = true + +defaults.days = {} +defaults.days.pos = {} +defaults.days.pos.x = 100 +defaults.days.pos.y = 0 -- top +defaults.days.text = {} +defaults.days.text.alpha = 255 +defaults.days.text.size = 12 +defaults.days.text.font = 'Consolas' +defaults.days.bg = {} +defaults.days.bg.alpha = 100 +defaults.days.bg.red = 0 +defaults.days.bg.green = 0 +defaults.days.bg.blue = 0 +defaults.days.axis = 'horizontal' +defaults.numdays = 8 +defaults.moon = {} +defaults.alert = true +settings = config.load(defaults) + + +Cycles = T{} +Cycles.selbina = T{} +Cycles.selbina.rname = "Ships between Mhaura and Selbina" +Cycles.selbina.route = T{} +Cycles.selbina.route[1] = "Arrives in Mhaura and Selbina|22:40" +Cycles.selbina.route[2] = "Arrives in Mhaura and Selbina|6:40" +Cycles.selbina.route[3] = "Arrives in Mhaura and Selbina|14:40" + +Cycles.bibiki = T{} +Cycles.bibiki.rname = "Ship departing Bibiki Bay for Purgonorgo Isle" +Cycles.bibiki.route = T{} +Cycles.bibiki.route[1] = "Arrives in Bibiki|4:50" +Cycles.bibiki.route[2] = "Arrives in Bibiki|16:50" + +Cycles.nashmau = T{} +Cycles.nashmau.rname = "Aht Urhgan / Nashmau Ship" +Cycles.nashmau.route = T{} +Cycles.nashmau.route[1] = "Arrives in Whitegate and Nashmau|05:00" +Cycles.nashmau.route[2] = "Arrives in Whitegate and Nashmau|13:00" +Cycles.nashmau.route[3] = "Arrives in Whitegate and Nashmau|21:00" + +Cycles.whitegate = T{} +Cycles.whitegate.rname = "Aht Urhgan / whitegate Ship" +Cycles.whitegate.route = T{} +Cycles.whitegate.route[1] = "Arrives in Whitegate and Mhaura|10:40" +Cycles.whitegate.route[2] = "Arrives in Whitegate and Mhaura|18:40" +Cycles.whitegate.route[3] = "Arrives in Whitegate and Mhaura|2:40" + +Cycles.windurst = T{} +Cycles.windurst.rname = "Ship between Windurst and Jeuno" +Cycles.windurst.route = T{} +Cycles.windurst.route[1] = "Arrives in Windurst|4:47" +Cycles.windurst.route[2] = "Arrives in Jeuno|7:41" +Cycles.windurst.route[3] = "Arrives in Windurst|10:47" +Cycles.windurst.route[4] = "Arrives in Jeuno|13:41" +Cycles.windurst.route[5] = "Arrives in Windurst|16:47" +Cycles.windurst.route[6] = "Arrives in Jeuno|19:41" +Cycles.windurst.route[7] = "Arrives in Windurst|22:47" +Cycles.windurst.route[8] = "Arrives in Jeuno|1:41" + +Cycles.bastok = T{} +Cycles.bastok.rname = "Ship between Bastok and Jeuno" +Cycles.bastok.route = T{} +Cycles.bastok.route[1] = "Arrives in Bastok|0:13" +Cycles.bastok.route[2] = "Arrives in Jeuno|3:11" +Cycles.bastok.route[3] = "Arrives in Bastok|6:13" +Cycles.bastok.route[4] = "Arrives in Jeuno|9:11" +Cycles.bastok.route[5] = "Arrives in Bastok|12:13" +Cycles.bastok.route[6] = "Arrives in Jeuno|15:11" +Cycles.bastok.route[7] = "Arrives in Bastok|18:13" +Cycles.bastok.route[8] = "Arrives in Jeuno|21:41" + +Cycles.sandy = T{} +Cycles.sandy.rname = "Ship between San d'Oria and Jeuno" +Cycles.sandy.route = T{} +Cycles.sandy.route[1] = "Arrives in San d'Oria|7:10" +Cycles.sandy.route[2] = "Arrives in Jeuno|6:11" +Cycles.sandy.route[3] = "Arrives in San d'Oria|9:10" +Cycles.sandy.route[4] = "Arrives in Jeuno|12:11" +Cycles.sandy.route[5] = "Arrives in San d'Oria|15:10" +Cycles.sandy.route[6] = "Arrives in Jeuno|18:11" +Cycles.sandy.route[7] = "Arrives in San d'Oria|21:10" +Cycles.sandy.route[8] = "Arrives in Jeuno|00:41" + +Cycles.kazham = T{} +Cycles.kazham.rname = "Ship between Kazham and Jeuno" +Cycles.kazham.route = T{} +Cycles.kazham.route[1] = "Arrives in Kazham|1:48" +Cycles.kazham.route[2] = "Arrives in Jeuno|4:49" +Cycles.kazham.route[3] = "Arrives in Kazham|7:48" +Cycles.kazham.route[4] = "Arrives in Jeuno|10:49" +Cycles.kazham.route[5] = "Arrives in Kazham|13:48" +Cycles.kazham.route[6] = "Arrives in Jeuno|14:49" +Cycles.kazham.route[7] = "Arrives in Kazham|19:48" +Cycles.kazham.route[8] = "Arrives in Jeuno|20:49" + +gt.gtt = texts.new('', settings.time) +gt.gtd = texts.new('', settings.days) + +config.register(settings, function() + if settings.days.axis == 'horizontal' then + gt.delimiter = ' ' + else + gt.delimiter = '\n' + end + + if settings.zero then + gt.gtt:text('${hours|XX|%.2d}:${minutes|XX|%.2d}') + else + gt.gtt:text('${hours|XX}:${minutes|XX|%.2d}') + end + gt.gtd:text('${day|} ${MoonPhase|Unknown} (${MoonPct|-}%); ${WeekReport|}') + + if settings.numdays > 0 and settings.numdays < 9 then + gt.numdays = settings.numdays + else + gt.numdays = 8 + end + + + gt.gtd:text('${day|} ${MoonPhase|Unknown} (${MoonPct|-}%); ${WeekReport|}') + local info = windower.ffxi.get_info() + if info.logged_in then + day_change(info.day) + + gt.gtt:show() + gt.gtd:show() + end +end) + +function getroutes(route) + for ckey, cval in pairs(Cycles) do + if route == nil or ckey == route then + log('\30\02'..Cycles[ckey].rname..' (shortcode: //gt route '..ckey..')') + for ri = 1, #Cycles[ckey].route do + ro = Cycles[ckey].route[ri]:split('|') + rtime = timeconvert(ro[2]) + rdelay = math.round(rtime-gt.dectime,2) + if rdelay < 0 then rdelay = rdelay + 24 end + rdelay = 2.4 * rdelay + log(ro[1]..' @ '..ro[2]..' \30\02Arrival in '..(timeconvert2(rdelay))..'') + end + end + end +end + +function default_settings() + settings:save('all') +end + +windower.register_event('time change', function(new, old) + gt.hours = (new / 60):floor() + gt.minutes = new % 60 + gt.gtt:update(gt) +end) + +function timeconvert(basetime) + basetable = basetime:split(':') + return basetable[1]..'.'..math.round(basetable[2] * (100/60)) +end + +function timeconvert2(basetime) + basetable = tostring(basetime):split('.') + return basetable[1]..':'..tostring(math.round(tostring(basetable[2]):slice(1,2) / (100/60))):zfill(2) +end + +function moon_change() + local info = windower.ffxi.get_info() + gt.MoonPhase = res.moon_phases[info.moon_phase].english + gt.MoonPct = info.moon + gt.gtd:update(gt) +end + +function day_change(day) + day = res.days[day].english + if (day == 'Firesday') then + dlist = {'1','2','3','4','5','6','7','8'} + elseif (day == 'Earthsday') then + dlist = {'2','3','4','5','6','7','8','1'} + elseif (day == 'Watersday') then + dlist = {'3','4','5','6','7','8','1','2'} + elseif (day == 'Windsday') then + dlist = {'4','5','6','7','8','1','2','3'} + elseif (day == 'Iceday') then + dlist = {'5','6','7','8','1','2','3','4'} + elseif (day == 'Lightningsday') then + dlist = {'6','7','8','1','2','3','4','5'} + elseif (day == 'Lightsday') then + dlist = {'7','8','1','2','3','4','5','6'} + elseif (day == 'Darksday') then + dlist = {'8','1','2','3','4','5','6','7'} + end + + dpos = 0 + daystring = '' + while dpos < gt.numdays do + dpos = dpos + 1 + dval = dlist[dpos] + daystring = ''..daystring..gt.delimiter..' \\cs'..gt.days[(dval+0)][10]..gt.days[(dval+0)][settings.mode] + end + + gt.day = day + gt.WeekReport = daystring + gt.gtd:update(gt) + moon_change() +end + +function tolog() + if settings.alert == true then + log('Day: '..gt.day..'; Moon: '..gt.MoonPhase..' ('..gt.MoonPct..'%);') + end +end + +windower.register_event('day change', moon_change .. day_change) + +windower.register_event('moon change', moon_change) + +windower.register_event('addon command', function (...) + local args = T{...}:map(string.lower) + if args[1] == nil or args[1] == "help" then + log('Use //gametime or //gt as follows:') + log('Positioning:') + log('//gt [timex/timey/daysx/daysy] <pos> :: example: //gt timex 125') + log('//gt [time/days] reset :: example: //gt days reset') + + log('Text features:') + log('//gt timeSize <size> :: example: //gt timeSize 10') + log('//gt timeFont <fontName> :: example: //gt timeFont Verdana') + log('//gt daySize <size> :: example: //gt daySize 10') + log('//gt dayFont <fontName> :: example: //gt dayFont Verdana') + + log('Visibility:') + log('//gt [time/days] [show/hide] :: example //gt time hide') + log('//gt axis [horizontal/vertical] :: week display axis') + log('//gt [time/days] alpha 1-255. :: Sets the transparency. Lowest numbers = more transparent.') + log('//gt mode 1-4 :: Fullday; Abbreviated; Element names; Compact') + + log('Routes:') + log('//gt route :: Displays route names.') + log('//gt route [route name] :: Displays arrival time for route.') + + log('Misc:') + log('//gt zero [on/off] :: Displays the time with leading zeros. 04:05 instead of 4:05') + log('//gt days [1-8] :: Limits the number of days displayed') + log('//gt alert :: Toggle display in chat log for day and moon changes') + log('Remember to //gt save when you\'re happy with your settings.') + elseif args[1] == 'routes' or args[1] == 'route' then + if args[2] == nil then + local ckeys = '' + for ckey, cval in pairs(Cycles) do + ckeys = ckeys..', '..ckey + end + ckeys = ckeys:slice(3,#ckeys) + log('Use //gt route [shortcode] ('..ckeys..')') + else + getroutes(args[2]) + end + + + + ---CLI Arguments for Time font Size + elseif args[1] == 'timeSize' then + gt.gtt:size(tonumber(args[2])) + + ---CLI Arguments for Time font type + elseif args[1] == 'timeFont' then + gt.gtt:font(args[2]) + + ---CLI Arguments for Day font Size + elseif args[1] == 'daySize' then + gt.gtd:size(tonumber(args[2])) + + ---CLI Arguments for Day font type + elseif args[1] == 'dayFont' then + gt.gtd:font(args[2]) + + elseif args[1] == 'timex' then + gt.gtt:pos_x(tonumber(args[2])) + + elseif args[1] == 'timey' then + gt.gtt:pos_y(tonumber(args[2])) + + elseif args[1] == 'daysx' then + gt.gtd:pos_x(tonumber(args[2])) + + elseif args[1] == 'daysy' then + gt.gtd:pos_y(tonumber(args[2])) + + elseif args[1] == 'alert' then + settings.alert = (settings.alert == false) + gt.alert = settings.alert + log('Show alerts in log when day / moon-phase changes: '..tostring(settings.alert)) + tolog() + + elseif args[1] == 'time' then + if args[2] == 'alpha' then + inalpha = tostring(args[3]):zfill(3) + inalpha = inalpha+0 + if (inalpha > 0 and inalpha < 256) then + gt.gtt:alpha(inalpha) + log('Time transparency set to '..inalpha..' ('..math.round(100-(inalpha/2.55),0)..'%).') + end + elseif args[2] == 'x' or args[2] == 'posx' then + windower.send_command('gt timex '..args[3]) + elseif args[2] == 'y' or args[2] == 'posy' then + windower.send_command('gt timey '..args[3]) + elseif args[2] == 'hide' then + gt.gtt:hide() + log('Time display hidden.') + elseif args[2] == 'reset' then + gt.gtt:pos(0,0) + else + gt.gtt:show() + log('Showing time display.') + end + elseif args[1] == 'days' then + if args[2] == 'alpha' then + inalpha = tostring(args[3]):zfill(3) + inalpha = inalpha+0 + if (inalpha > 0 and inalpha < 256) then + gt.gtd:alpha(inalpha) + log('Day transparency set to '..inalpha..' ('..math.round(100-(inalpha/2.55),0)..'%).') + end + elseif tonumber(args[2]) > 0 and tonumber(args[2]) < 9 then + gt.numdays = tonumber(args[2]) + settings.numdays = tonumber(args[2]) + day_change(windower.ffxi.get_info().day) + elseif args[2] == 'x' or args[2] == 'posx' then + windower.send_command('gt daysx '..args[3]) + elseif args[2] == 'y' or args[2] == 'posy' then + windower.send_command('gt daysy '..args[3]) + elseif args[2] == 'hide' then + gt.gtd:hide() + log('Day display hidden.') + elseif args[2] == 'reset' then + gt.gtd:pos(0,0) + else + gt.gtd:show() + log('Showing day display.') + end + elseif args[1] == 'axis' then + if args[2] == 'vertical' then + gt.delimiter = "\n" + log('Week display axis set to vertical.') + else + gt.delimiter = " " + log('Week display axis set to horizontal.') + end + day_change(windower.ffxi.get_info().day) + elseif args[1] == 'mode' then + inmode = args[2]:zfill(1) + inmode = inmode+0 + if inmode > 4 then + return + else + settings.mode = inmode + log('mode updated') + end + day_change(windower.ffxi.get_info().day) + elseif args[1] == 'zero' then + if args[2] == 'on' then + settings.zero = true + config.save(settings) + log('zero padding enabled.') + elseif args[2] == 'off' then + settings.zero = false + config.save(settings) + log('zero padding disabled.') + end + elseif args[1] == 'save' then + config.save(settings, 'all') + log('Settings saved.') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/giltracker/README.md b/Data/DefaultContent/Libraries/addons/addons/giltracker/README.md new file mode 100644 index 0000000..2c6e98d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/giltracker/README.md @@ -0,0 +1,12 @@ +# giltracker +This addon displays the current gil, similar to the FFXIV Gil HUD widget. + + + +## How to edit the settings +1. Login to your character in FFXI +2. Edit the addon settings file: **_Windower4\addons\giltracker\data\settings.xml_** +3. Save the file +4. Press Insert in FFXI to access the windower console +5. Type ``` lua r giltracker ``` to reload the addon +6. Press Insert in FFXI again to close the windower console diff --git a/Data/DefaultContent/Libraries/addons/addons/giltracker/gil.png b/Data/DefaultContent/Libraries/addons/addons/giltracker/gil.png Binary files differnew file mode 100644 index 0000000..df56142 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/giltracker/gil.png diff --git a/Data/DefaultContent/Libraries/addons/addons/giltracker/giltracker.lua b/Data/DefaultContent/Libraries/addons/addons/giltracker/giltracker.lua new file mode 100644 index 0000000..3612a0e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/giltracker/giltracker.lua @@ -0,0 +1,239 @@ +--[[ BSD License Disclaimer + Copyright © 2017, sylandro + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of giltracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL sylandro BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'giltracker' +_addon.author = 'sylandro' +_addon.version = '1.0.0' +_addon.language = 'English' + +config = require('config') +images = require('images') +texts = require('texts') +packets = require('packets') + +local GIL_ITEM_ID = 65535 +local CUTSCENE_STATUS_ID = 4 +local SCROLL_LOCK_KEY = 70 +local INVENTORY_FINISH_PACKET = 0x1D +local TREASURE_FIND_ITEM_PACKET = 0xD2 +local LOGIN_ZONE_PACKET = 0x0A +local ITEM_UPDATE_PACKET = 0x20 +local ITEM_MODIFY_PACKET = 0x1F + +local hideKey = SCROLL_LOCK_KEY +local is_hidden_by_cutscene = false +local is_hidden_by_key = false + +defaults = {} +defaults.hideKey = SCROLL_LOCK_KEY +defaults.gilText = {} +defaults.gilText.bg = {} +defaults.gilText.bg.alpha = 100 +defaults.gilText.bg.red = 0 +defaults.gilText.bg.green = 0 +defaults.gilText.bg.blue = 0 +defaults.gilText.bg.visible = false +defaults.gilText.text = {} +defaults.gilText.text.font = 'sans-serif' +defaults.gilText.text.fonts = {'Arial','Trebuchet MS'} +defaults.gilText.text.size = 9 +defaults.gilText.flags = {} +defaults.gilText.flags.italic = true +defaults.gilText.flags.bold = false +defaults.gilText.flags.right = true +defaults.gilText.flags.bottom = true +defaults.gilText.pos = {} +defaults.gilText.pos.x = -285 +defaults.gilText.pos.y = -35 +defaults.gilText.text.alpha = 255 +defaults.gilText.text.red = 253 +defaults.gilText.text.green = 252 +defaults.gilText.text.blue = 250 +defaults.gilText.text.stroke = {} +defaults.gilText.text.stroke.alpha = 200 +defaults.gilText.text.stroke.red = 50 +defaults.gilText.text.stroke.green = 50 +defaults.gilText.text.stroke.blue = 50 +defaults.gilText.text.stroke.width = 2 +defaults.gilText.text.visible = true +defaults.gilImage = {} +defaults.gilImage.color = {} +defaults.gilImage.color.alpha = 255 +defaults.gilImage.color.red = 255 +defaults.gilImage.color.green = 255 +defaults.gilImage.color.blue = 255 +defaults.gilImage.visible = true + +local settings = config.load(defaults) +config.save(settings) + +settings.gilImage.texture = {} +settings.gilImage.texture.path = windower.addon_path..'gil.png' +settings.gilImage.texture.fit = true +settings.gilImage.size = {} +settings.gilImage.size.height = 23 +settings.gilImage.size.width = 23 +settings.gilImage.draggable = false +settings.gilImage.repeatable = {} +settings.gilImage.repeatable.x = 1 +settings.gilImage.repeatable.y = 1 + +local gil_image = images.new(settings.gilImage) +local gil_text = texts.new(settings.gilText) +local inventory_loaded = false +local ready = false + +config.register(settings, function(settings) + hideKey = settings.hideKey + local windower_settings = windower.get_windower_settings() + local xRes = windower_settings.ui_x_res + local yRes = windower_settings.ui_y_res + gil_image:pos(xRes + settings.gilText.pos.x + 1, + yRes + settings.gilText.pos.y - (settings.gilImage.size.height/6)) +end) + +windower.register_event('load',function() + if windower.ffxi.get_info().logged_in then + initialize() + end +end) + +windower.register_event('login',function() + gil_text:text('Loading...') +end) + +windower.register_event('logout', function(...) + inventory_loaded = false + hide() +end) + +windower.register_event('add item', function(_bag,_index,id,...) + if (id == GIL_ITEM_ID) then ready = true end +end) + +windower.register_event('remove item', function(_bag,_index,id,...) + if (id == GIL_ITEM_ID) then ready = true end +end) + +windower.register_event('incoming chunk',function(id,org,_modi,_is_injected,_is_blocked) + if (id == LOGIN_ZONE_PACKET) then + inventory_loaded = false + elseif (id == TREASURE_FIND_ITEM_PACKET) then + ready_if_valid_treasure_packet(org) + elseif (id == ITEM_UPDATE_PACKET) then + update_if_valid_item_packet(org) + elseif (id == INVENTORY_FINISH_PACKET) then + refresh_gil() + elseif (id == ITEM_MODIFY_PACKET) then + update_if_valid_item_packet(org) + end +end) + +windower.register_event('status change', function(new_status_id) + local is_cutscene_playing = is_cutscene(new_status_id) + toggle_display_if_cutscene(is_cutscene_playing) +end) + +windower.register_event('keyboard', function(dik, down, _flags, _blocked) + toggle_display_if_hide_key_is_pressed(dik, down) +end) + +function ready_if_valid_treasure_packet(packet_data) + local p = packets.parse('incoming',packet_data) + if (p.Count > 0) then ready = true end +end + +function update_if_valid_item_packet(packet_data) + local p = packets.parse('incoming',packet_data) + if (p.Item == GIL_ITEM_ID and p.Count >= 0) then + update_gil() + end +end + +function refresh_gil() + if (ready and inventory_loaded) then + update_gil() + ready = false + elseif (not inventory_loaded) then + initialize() + end +end + +function initialize() + inventory_loaded = true + update_gil() + if not is_hidden_by_key and not is_hidden_by_cutscene then show() end +end + +function update_gil() + local gil = windower.ffxi.get_items('gil') + gil_text:text(comma_value(gil)) +end + +function show() + gil_text:show() + gil_image:show() +end + +function hide() + gil_text:hide() + gil_image:hide() +end + +function comma_value(amount) + local formatted = tostring(amount) + while true do + formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') + if (k==0) then break end + end + return formatted +end + +function is_cutscene(status_id) + return status_id == CUTSCENE_STATUS_ID +end + +function toggle_display_if_cutscene(is_cutscene_playing) + if (is_cutscene_playing) and (not is_hidden_by_key) then + is_hidden_by_cutscene = true + hide() + elseif (not is_cutscene_playing) and (not is_hidden_by_key) then + is_hidden_by_cutscene = false + show() + end +end + +function toggle_display_if_hide_key_is_pressed(key_pressed, key_down) + if (key_pressed == hideKey) and (key_down) and (is_hidden_by_key) and (not is_hidden_by_cutscene) then + is_hidden_by_key = false + show() + elseif (key_pressed == hideKey) and (key_down) and (not is_hidden_by_key) and (not is_hidden_by_cutscene) then + is_hidden_by_key = true + hide() + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/data/colors.xml b/Data/DefaultContent/Libraries/addons/addons/highlight/data/colors.xml new file mode 100644 index 0000000..a1f1174 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/data/colors.xml @@ -0,0 +1,25 @@ +<?xml version="1.1" ?> +<settings> + <global> + <p0>501</p0> + <p1>204</p1> + <p2>410</p2> + <p3>492</p3> + <p4>259</p4> + <p5>260</p5> + + <a10>205</a10> + <a11>359</a11> + <a12>167</a12> + <a13>038</a13> + <a14>125</a14> + <a15>185</a15> + + <a20>429</a20> + <a21>257</a21> + <a22>200</a22> + <a23>481</a23> + <a24>483</a24> + <a25>208</a25> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/data/mules.xml b/Data/DefaultContent/Libraries/addons/addons/highlight/data/mules.xml new file mode 100644 index 0000000..ef41816 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/data/mules.xml @@ -0,0 +1,9 @@ +<?xml version="1.1" ?> +<settings> + <global> + + <!-- Add your mule name or other names in here to colour them when they are not in party. The correct syntax for this is as below + <mulename>color</mulename> + This uses the same colours as battlemod. --> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/data/nicknames.xml b/Data/DefaultContent/Libraries/addons/addons/highlight/data/nicknames.xml new file mode 100644 index 0000000..b5baabb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/data/nicknames.xml @@ -0,0 +1,10 @@ +<?xml version="1.1" ?> +<settings> + <global> + <!-- + Nicknames should be written as follows: + <playername>Nickname,Nickname,Nickname</playername> + nicknames are split by commas + --> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/highlight/data/settings.xml new file mode 100644 index 0000000..cf6d0c1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/data/settings.xml @@ -0,0 +1,7 @@ +<?xml version="1.1" ?> +<settings> + <global> + <!-- This setting refers to party highlighting, turning this off will mean that only your name is highlighted when mentioned --> + <highlighting>true</highlighting> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/highlight.lua b/Data/DefaultContent/Libraries/addons/addons/highlight/highlight.lua new file mode 100644 index 0000000..f2a7f60 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/highlight.lua @@ -0,0 +1,228 @@ +_addon.author = 'Balloon' +_addon.name = 'Highlight' +_addon.version = '1.0.0.1' +_addon.command = 'highlight' + +file = require('files') +chat = require('chat') +chars = require('chat.chars') +require('tables') +require('strings') + +members={} +mulenames={} +modmember={} +nicknames={} +color={} +mulecolor={} +previousmentions={} + +config = require('config') + +defaults = {} +defaults.p0 = 501 +defaults.p1 = 204 +defaults.p2 = 410 +defaults.p3 = 492 +defaults.p4 = 259 +defaults.p5 = 260 +defaults.a10 = 205 +defaults.a11 = 359 +defaults.a12 = 167 +defaults.a13 = 038 +defaults.a14 = 125 +defaults.a15 = 185 +defaults.a20 = 429 +defaults.a21 = 257 +defaults.a22 = 200 +defaults.a23 = 481 +defaults.a24 = 483 +defaults.a25 = 208 + + +settingdefaults = {} +settingdefaults.highlighting = true + +settings = config.load(settingdefaults) +if file.exists('../battlemod/data/colors.xml') then + color = config.load('../battlemod/data/colors.xml') + print('Colors loaded from battlemod') +else + color = config.load('/data/colors.xml', defaults) +end + + + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + args = {...} + + if command == 'write' then + io.open(windower.addon_path..'/logs/'..player..'.txt',"a"):write('\n =='..string.sub(os.date(), 0, 8)..'== \n'..table.concat(previousmentions, '\n')):close() + + elseif command == 'view' then + if not args[1] then + windower.add_to_chat(4, "==Recent Mentions==") + if #previousmentions > 20 then + for i = 1, 20 do + windower.add_to_chat(4, previousmentions[i]) + end + else + for i = 1, #previousmentions do + windower.add_to_chat(4, previousmentions[i]) + end + end + + else + if tonumber(args[1]) > #previousmentions then + print('Not that many mentions, type //highlight view to show them all') + else + windower.add_to_chat(4, '==Last '..args[1]..' Mentions==') + for i = 1, tonumber(args[1]) do + windower.add_to_chat(4, previousmentions[i]) + end + end + end + + elseif command == 'help' then + print('To view your last mentions type //highlight view <last number>') + end +end) + +windower.register_event('login', 'load', function() + if windower.ffxi.get_info().logged_in then + coroutine.sleep(1) + initialize() + end +end) + +function initialize() + prevCount = 0 + colour = {} + + nicknames = config.load('/data/nicknames.xml') + mules = config.load('/data/mules.xml') + settings = config.load(settingdefaults) + + for i, v in pairs(nicknames) do + nicknames[i] = string.split(v, ',') + end + for mule, name in pairs(mules) do + mulenames[mule] = name + end + for i, v in pairs(color) do + colour[i] = colconv(v,i) + end + for i, v in pairs(mules) do + mulecolor[i] = colconv(v,i) + end + + player = windower.ffxi.get_player().name + + get_party_members() +end + +windower.register_event('incoming text', function(original, modified, color, newcolor) + if not original:match('%[.*%] .* '..string.char(129, 168)..'.*') and not original:match('.* '..chars['implies']..'.*') then + for names in modified:gmatch('%w+') do + for name in pairs(members) do + modified = modified:igsub(members[name], modmember[name]) + end + for k,v in pairs(nicknames) do + for z = 1, #v do + modified = modified:igsub('([^%a])'..nicknames[k][z]..'([^%a])', function (pre, app) return pre..k:capitalize()..app end):igsub('([^%a])'..nicknames[k][z]..'$', function(space) return space..k:capitalize() end) + end + end + for mule, color in pairs(mulenames) do + modified = modified:igsub(mule, mulecolor[mule]..mule:capitalize()..chat.controls.reset) + end + if not settings.highlighting then + modified = modified:gsub('%(['..string.char(0x1e, 0x1f)..'].(%w+)'..'['..string.char(0x1e, 0x1f)..'].%)(.*)', function(name, rest) return '('..name..')'..rest end) + modified = modified:gsub('<['..string.char(0x1e, 0x1f)..'].(%w+)'..'['..string.char(0x1e, 0x1f)..'].>(.*)', function(name, rest) return '<'..name..'>'..rest end) + end + end + + end + --Not rolltracker and not battlemod + if not original:match('.* '..string.char(129, 168)..'.*') and not original:match('.* '..chars['implies']..'.*') and color ~= 4 then + --Chat modes not empty + if original:match('^%(.*%)') or original:match('^<.*>') or original:match('^%[%d:#%w+%]%w+(%[?%w-%]?):') then + --Not myself + if not original:match('^%('..player..'%)') and not original:match('^<'..player..'>') and not original:match('^'..player..' :') and not original:match('^%[%d:#%w+%]'..player..'(%[?%w-%]?):') then + if modified:match(player) then + table.insert(previousmentions, 1, '['..string.sub(os.date(), 10).."]>> "..colconv(color)..original ) + end + end + end + end + + return modified, newcolor +end) + +windower.register_event('incoming chunk', function(id, data) + if id == 0x0C8 then + prevCount = count + count = GetPartyCount(data:sub(0x09, 0xE0)) + if(prevCount ~= count) then + modmember = {} + members = {} + coroutine.sleep(0.1) + get_party_members() + end + end +end) + +function GetPartyCount(data) + local count = 0 + local test = 0 + local offset = 0 + while offset < 216 do + local x = data:sub(offset, offset + 11) + if x ~= '\0\0\0\0\0\0\0\0\0\0\0\0' then + count = count +1 + end + offset = offset+12 + end + return count +end + +function colconv(str, key) + -- Taken from Battlemod + strnum = tonumber(str) + if strnum >= 256 and strnum < 509 then + return string.char(0x1E, strnum - 254) + elseif strnum > 0 then + return string.char(0x1F, strnum) + elseif strnum ~= 0 then + print('You have an invalid color: ' .. key) + end + return chat.controls.reset +end + +function get_party_members() + if settings.highlighting then + local party = windower.ffxi.get_party() + for member, mob in pairs(party) do + if type(mob) == 'table' and not mulenames[mob.name:lower()] then + members[member] = mob.name + modmember[member] = colour[member] .. mob.name .. chat.controls.reset + end + end + else + members.p0 = player + modmember.p0 = colour.p0 .. player .. chat.controls.reset + end +end + +--[[ +Copyright © 2013-2015, Thomas Rogers +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of highlight nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Thomas Rogers 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. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/highlight/readme.md b/Data/DefaultContent/Libraries/addons/addons/highlight/readme.md new file mode 100644 index 0000000..ac11513 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/highlight/readme.md @@ -0,0 +1,15 @@ + +##Description +This addon highlights the names of the people in your party, and highlights their name when it appears in chat. + +The colors you select in this file will override any colours specified by other addons, for instance, Rolltracker and Battlemod + +##Features +The colors of this addon are based on the default Battlemod colors, and they will be pulled in from your battlemod file. If you do not use battlemod, it will pull the colors from settings file in the data folder. + +It also changes any common mispellings of peoples names, or nicknames uses into their correct name, and colours them accordingly. This feature will eventually plugin to the chatmon functionality. + +##Previous Mentions + +Highlight also tracks when people mentions you. You can view these back at any time by typing //highlight view <optional number of views>. +If you want to add these mentions to a text file you can do this by typing //highlight write.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/indinope/README.md b/Data/DefaultContent/Libraries/addons/addons/indinope/README.md new file mode 100644 index 0000000..16c82a4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/indinope/README.md @@ -0,0 +1,15 @@ +# IndiNope 1.0.4 +Hides visual effects from geomancy on players. + +Currently does not hide geomancy effect around luopans. + +**No commands.** Load it and it's on, unload and it's off. + +### Changelog: + +1.0.4 - More tweaks. +1.0.3 - A few tweaks. +1.0.1 - Fixed a bug where Indi-Nope would make Master stars disappear. Thanks Kenshi for finding out. +1.0.0 - Initial release. + +Thanks to Thorny, this addon is a port to windower of his Ashita code with the same functionality. diff --git a/Data/DefaultContent/Libraries/addons/addons/indinope/indinope.lua b/Data/DefaultContent/Libraries/addons/addons/indinope/indinope.lua new file mode 100644 index 0000000..e085b2b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/indinope/indinope.lua @@ -0,0 +1,48 @@ +--[[ +Copyright © Lili, 2020 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of IndiNope nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL LILI BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'IndiNope' +_addon.author = 'Lili' +_addon.version = '1.0.4' + +require('bit') + +offsets = { [0x00D] = 67, [0x037] = 89, } + +windower.register_event('incoming chunk', function(id, original, modified, injected, blocked) + if injected or blocked or not offsets[id] then return end + + offset = offsets[id] + flags = original:byte(offsets[id]) + + -- if any of the bits 0 through 7 are set, a bubble is shown and we want to block it. + if bit.band(flags, 0x7F) ~= 0 then + packet = original:sub(1, offset - 1) .. string.char(bit.band(flags, 0x80)) .. original:sub(offset + 1) -- preserve bit 8 (Job Master stars) + return packet + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/instaLS/instaLS.lua b/Data/DefaultContent/Libraries/addons/addons/instaLS/instaLS.lua new file mode 100644 index 0000000..26b7e8b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/instaLS/instaLS.lua @@ -0,0 +1,93 @@ +--Copyright (c) 2015, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'instaLS' +_addon.version = 0.160309 +_addon.author = 'Byrth' + +flag=false +chatmode = {} +chatcolor = {} +message = false +require 'strings' + + +function translate_escape(str) + return str:escape():gsub(string.char(0xFD)..".-"..string.char(0xFD),string.char(0xEF,0x27).."(.-)"..string.char(0xEF,0x25,0x25,0x28)) +end + +windower.register_event('zone change',function() + flag=false +end) + +windower.register_event('incoming chunk',function(id) + if id == 0x1D then + flag = true + end +end) + +windower.register_event('outgoing chunk',function(id,org,mod,inj) + if id == 0xB5 and not inj and #chatmode>0 and mod:byte(5) == 0 then -- and org:unpack('z',7) == message + -- Not injected, message currently queued + local outpack = mod:sub(1,4)..string.char(table.remove(chatmode,1))..mod:sub(6) + return outpack + end +end) + +windower.register_event('incoming text',function(org, mod, col) + if message and #chatcolor>0 and string.find(org,translate_escape(message)) then + local a,b = string.find(mod,windower.ffxi.get_player().name) + mod = mod:sub(1,a-1)..'['..(chatcolor[1]==6 and '1' or '2')..']<'..mod:sub(a,b)..'>'..mod:sub(b+3) + local retarr = {mod, table.remove(chatcolor,1)} + message = nil + return unpack(retarr) + end +end) + +windower.register_event('outgoing text',function(org,mod,bool) + if bool or flag then return end + if mod:sub(1,3) == '/l ' then + chatmode[#chatmode+1] = 0x05 + chatcolor[#chatcolor+1] = 6 + message = mod:sub(4) + elseif mod:sub(1,11) == '/linkshell ' then + chatmode[#chatmode+1] = 0x05 + chatcolor[#chatcolor+1] = 6 + message = mod:sub(12) + elseif mod:sub(1,4) == '/l2 ' then + chatmode[#chatmode+1] = 0x1B + chatcolor[#chatcolor+1] = 213 + message = mod:sub(5) + elseif mod:sub(1,12) == '/linkshell2 ' then + chatmode[#chatmode+1] = 0x1B + chatcolor[#chatcolor+1] = 213 + message = mod:sub(13) + else + return + end + + return '/s '..message +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/invtracker/README.md b/Data/DefaultContent/Libraries/addons/addons/invtracker/README.md new file mode 100644 index 0000000..4c327fa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/invtracker/README.md @@ -0,0 +1,15 @@ +# invtracker +This addon displays a grid detailing empty and filled inventory slots, similar to the FFXIV Inventory Grid HUD widget. + + + +## How to edit the settings +1. Login to your character in FFXI +2. Edit the addon settings file: **_Windower4\addons\invtracker\data\settings.xml_** +3. Save the file +4. Press Insert in FFXI to access the windower console +5. Type ``` lua r invtracker ``` to reload the addon +6. Press Insert in FFXI again to close the windower console + +## Issues: +1. There is no way to get the inventory sort order, so all items in the grid will be ordered by status and item count. diff --git a/Data/DefaultContent/Libraries/addons/addons/invtracker/invtracker.lua b/Data/DefaultContent/Libraries/addons/addons/invtracker/invtracker.lua new file mode 100644 index 0000000..2ef1d57 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/invtracker/invtracker.lua @@ -0,0 +1,689 @@ +--[[ BSD License Disclaimer + Copyright © 2017, sylandro + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of invtracker nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL sylandro BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'invtracker' +_addon.author = 'sylandro' +_addon.version = '1.0.0' +_addon.language = 'English' + +config = require('config') +images = require('images') +texts = require('texts') +res = require('resources') +packets = require('packets') + +local CUTSCENE_STATUS_ID = 4 +local SCROLL_LOCK_KEY = 70 +local MAX_EQUIPMENT_SIZE = 16 +local DEFAULT_ITEM_STATUS = 0 +local EQUIPPED_ITEM_STATUS = 5 +local LINKSHELL_EQUIPPED_ITEM_STATUS = 19 +local BAZAAR_ITEM_STATUS = 25 +local EQUIPMENT_CHANGED_PACKET = 0x50 +local BAZAAR_PRICE_PACKET = 0x10A +local EQUIPSET_CHANGED_PACKET = 0x051 +local INVENTORY_FINISH_PACKET = 0x1D +local LOGIN_ZONE_PACKET = 0x0A +local TREASURE_FIND_ITEM_PACKET = 0xD2 +local TREASURE_LOT_ITEM_PACKET = 0xD3 +local EQUIP_LINKSHELL_PACKET = 0xC4 +local INVENTORY_SIZE_PACKET = 0x1C +local GIL_ITEM_ID = 65535 +local NO_ITEM_ID = 0 + +local hideKey = SCROLL_LOCK_KEY +local is_hidden_by_cutscene = false +local is_hidden_by_key = false + +defaults = {} +defaults.HideKey = SCROLL_LOCK_KEY +defaults.slotImage = {} +defaults.slotImage.sort = true +defaults.slotImage.spacing = 4 +defaults.slotImage.blockSpacing = 4 +defaults.slotImage.visible = true +defaults.slotImage.pos = {} +defaults.slotImage.pos.x = -365 +defaults.slotImage.pos.y = -50 +defaults.slotImage.equipment = {} +defaults.slotImage.equipment.visible = true +defaults.slotImage.equipment.maxColumns = 4 +defaults.slotImage.inventory = {} +defaults.slotImage.inventory.visible = true +defaults.slotImage.inventory.maxColumns = 5 +defaults.slotImage.mogSafe = {} +defaults.slotImage.mogSafe.visible = false +defaults.slotImage.mogSafe.maxColumns = 5 +defaults.slotImage.mogStorage = {} +defaults.slotImage.mogStorage.visible = false +defaults.slotImage.mogStorage.maxColumns = 4 +defaults.slotImage.mogLocker = {} +defaults.slotImage.mogLocker.visible = false +defaults.slotImage.mogLocker.maxColumns = 5 +defaults.slotImage.mogSatchel = {} +defaults.slotImage.mogSatchel.visible = true +defaults.slotImage.mogSatchel.maxColumns = 5 +defaults.slotImage.mogSack = {} +defaults.slotImage.mogSack.visible = true +defaults.slotImage.mogSack.maxColumns = 5 +defaults.slotImage.mogCase = {} +defaults.slotImage.mogCase.visible = true +defaults.slotImage.mogCase.maxColumns = 5 +defaults.slotImage.mogWardrobe = {} +defaults.slotImage.mogWardrobe.visible = false +defaults.slotImage.mogWardrobe.maxColumns = 5 +defaults.slotImage.tempInventory = {} +defaults.slotImage.tempInventory.maxColumns = 1 +defaults.slotImage.tempInventory.visible = true +defaults.slotImage.treasury = {} +defaults.slotImage.treasury.visible = true +defaults.slotImage.treasury.maxColumns = 1 +defaults.slotImage.status = {} +defaults.slotImage.status.default = {} +defaults.slotImage.status.default.color = {} +defaults.slotImage.status.default.color.alpha = 255 +defaults.slotImage.status.default.color.red = 0 +defaults.slotImage.status.default.color.green = 170 +defaults.slotImage.status.default.color.blue = 170 +defaults.slotImage.status.default.background = {} +defaults.slotImage.status.default.background.color = {} +defaults.slotImage.status.default.background.color.alpha = 200 +defaults.slotImage.status.default.background.color.red = 0 +defaults.slotImage.status.default.background.color.green = 60 +defaults.slotImage.status.default.background.color.blue = 60 +defaults.slotImage.status.fullStack = {} +defaults.slotImage.status.fullStack.color = {} +defaults.slotImage.status.fullStack.color.alpha = 255 +defaults.slotImage.status.fullStack.color.red = 245 +defaults.slotImage.status.fullStack.color.green = 40 +defaults.slotImage.status.fullStack.color.blue = 40 +defaults.slotImage.status.fullStack.background = {} +defaults.slotImage.status.fullStack.background.color = {} +defaults.slotImage.status.fullStack.background.color.alpha = 200 +defaults.slotImage.status.fullStack.background.color.red = 100 +defaults.slotImage.status.fullStack.background.color.green = 0 +defaults.slotImage.status.fullStack.background.color.blue = 0 +defaults.slotImage.status.equipment = {} +defaults.slotImage.status.equipment.color = {} +defaults.slotImage.status.equipment.color.alpha = 255 +defaults.slotImage.status.equipment.color.red = 253 +defaults.slotImage.status.equipment.color.green = 252 +defaults.slotImage.status.equipment.color.blue = 250 +defaults.slotImage.status.equipment.background = {} +defaults.slotImage.status.equipment.background.color = {} +defaults.slotImage.status.equipment.background.color.alpha = 200 +defaults.slotImage.status.equipment.background.color.red = 50 +defaults.slotImage.status.equipment.background.color.green = 50 +defaults.slotImage.status.equipment.background.color.blue = 50 +defaults.slotImage.status.equipped = {} +defaults.slotImage.status.equipped.color = {} +defaults.slotImage.status.equipped.color.alpha = 255 +defaults.slotImage.status.equipped.color.red = 150 +defaults.slotImage.status.equipped.color.green = 255 +defaults.slotImage.status.equipped.color.blue = 150 +defaults.slotImage.status.equipped.background = {} +defaults.slotImage.status.equipped.background.color = {} +defaults.slotImage.status.equipped.background.color.alpha = 200 +defaults.slotImage.status.equipped.background.color.red = 0 +defaults.slotImage.status.equipped.background.color.green = 100 +defaults.slotImage.status.equipped.background.color.blue = 0 +defaults.slotImage.status.linkshellEquipped = {} +defaults.slotImage.status.linkshellEquipped.color = {} +defaults.slotImage.status.linkshellEquipped.color.alpha = 255 +defaults.slotImage.status.linkshellEquipped.color.red = 150 +defaults.slotImage.status.linkshellEquipped.color.green = 255 +defaults.slotImage.status.linkshellEquipped.color.blue = 150 +defaults.slotImage.status.linkshellEquipped.background = {} +defaults.slotImage.status.linkshellEquipped.background.color = {} +defaults.slotImage.status.linkshellEquipped.background.color.alpha = 200 +defaults.slotImage.status.linkshellEquipped.background.color.red = 0 +defaults.slotImage.status.linkshellEquipped.background.color.green = 100 +defaults.slotImage.status.linkshellEquipped.background.color.blue = 0 +defaults.slotImage.status.bazaar = {} +defaults.slotImage.status.bazaar.color = {} +defaults.slotImage.status.bazaar.color.alpha = 255 +defaults.slotImage.status.bazaar.color.red = 225 +defaults.slotImage.status.bazaar.color.green = 160 +defaults.slotImage.status.bazaar.color.blue = 30 +defaults.slotImage.status.bazaar.background = {} +defaults.slotImage.status.bazaar.background.color = {} +defaults.slotImage.status.bazaar.background.color.alpha = 200 +defaults.slotImage.status.bazaar.background.color.red = 100 +defaults.slotImage.status.bazaar.background.color.green = 100 +defaults.slotImage.status.bazaar.background.color.blue = 0 +defaults.slotImage.status.tempItem = {} +defaults.slotImage.status.tempItem.color = {} +defaults.slotImage.status.tempItem.color.alpha = 255 +defaults.slotImage.status.tempItem.color.red = 255 +defaults.slotImage.status.tempItem.color.green = 130 +defaults.slotImage.status.tempItem.color.blue = 255 +defaults.slotImage.status.tempItem.background = {} +defaults.slotImage.status.tempItem.background.color = {} +defaults.slotImage.status.tempItem.background.color.alpha = 200 +defaults.slotImage.status.tempItem.background.color.red = 100 +defaults.slotImage.status.tempItem.background.color.green = 0 +defaults.slotImage.status.tempItem.background.color.blue = 100 +defaults.slotImage.status.empty = {} +defaults.slotImage.status.empty.color = {} +defaults.slotImage.status.empty.color.alpha = 150 +defaults.slotImage.status.empty.color.red = 0 +defaults.slotImage.status.empty.color.green = 0 +defaults.slotImage.status.empty.color.blue = 0 +defaults.slotImage.status.empty.background = {} +defaults.slotImage.status.empty.background.color = {} +defaults.slotImage.status.empty.background.color.alpha = 150 +defaults.slotImage.status.empty.background.color.red = 0 +defaults.slotImage.status.empty.background.color.green = 0 +defaults.slotImage.status.empty.background.color.blue = 0 + +local settings = config.load(defaults) +config.save(settings) + +settings.slotImage.box = {} +settings.slotImage.box.size = {} +settings.slotImage.box.size.height = 2 +settings.slotImage.box.size.width = 2 +settings.slotImage.box.texture = {} +settings.slotImage.box.texture.path = windower.addon_path..'slot.png' +settings.slotImage.box.texture.fit = false +settings.slotImage.box.repeatable = {} +settings.slotImage.box.repeatable.x = 1 +settings.slotImage.box.repeatable.y = 1 +settings.slotImage.background = {} +settings.slotImage.background.size = {} +settings.slotImage.background.size.height = 3 +settings.slotImage.background.size.width = 3 +settings.slotImage.background.texture = {} +settings.slotImage.background.texture.path = windower.addon_path..'slot.png' +settings.slotImage.background.texture.fit = false +settings.slotImage.background.repeatable = {} +settings.slotImage.background.repeatable.x = 1 +settings.slotImage.background.repeatable.y = 1 + +local windower_settings = windower.get_windower_settings() +local yRes = windower_settings.ui_y_res +local xRes = windower_settings.ui_x_res +local current_block = 0 +local current_slot = 1 +local current_row = 1 +local current_column = 1 +local last_column = 1 +local items = {} +local slot_images = {} +local inventory_loaded = false +local refresh_all = false +local refresh_items = false +local refresh_inventory = false +local refresh_linkshell = false +local last_treasure_count = 0 + +config.register(settings, function(settings) + hideKey = settings.HideKey + xBase = settings.slotImage.pos.x + yBase = settings.slotImage.pos.y +end) + +windower.register_event('load',function() + if windower.ffxi.get_info().logged_in then + initialize() + end +end) + +windower.register_event('login',function() + update_all() + hide() +end) + +windower.register_event('logout', function(...) + inventory_loaded = false + hide() + clear() +end) + +windower.register_event('add item', function(_bag,_index,id,...) + if (id ~= GIL_ITEM_ID and id ~= NO_ITEM_ID) then refresh_items = true end +end) + +windower.register_event('remove item', function(_bag,_index,id,...) + if (id ~= GIL_ITEM_ID and id ~= NO_ITEM_ID) then refresh_items = true end +end) + +windower.register_event('linkshell change', function(new,old) + if (refresh_linkshell) then + refresh_inventory = true + refresh_linkshell = false + end +end) + +windower.register_event('incoming chunk',function(id,org,_modi,_is_injected,_is_blocked) + if (id == LOGIN_ZONE_PACKET) then + inventory_loaded = false + elseif (id == INVENTORY_FINISH_PACKET) then + update() + elseif (id == TREASURE_FIND_ITEM_PACKET) then + update_treasure_only() + elseif (id == TREASURE_LOT_ITEM_PACKET) then + update_treasure_only() + elseif (id == INVENTORY_SIZE_PACKET) then + update_if_different_size(org) + end +end) + +windower.register_event('outgoing chunk',function(id,org,_modi,_is_injected,_is_blocked) + if (id == EQUIPMENT_CHANGED_PACKET or id == EQUIPSET_CHANGED_PACKET) then + refresh_all = true + elseif (id == BAZAAR_PRICE_PACKET) then + refresh_inventory = true + elseif (id == EQUIP_LINKSHELL_PACKET) then + refresh_linkshell = true + end +end) + +windower.register_event('status change', function(new_status_id) + local is_cutscene_playing = is_cutscene(new_status_id) + toggle_display_if_cutscene(is_cutscene_playing) +end) + +windower.register_event('keyboard', function(dik, down, _flags, _blocked) + toggle_display_if_hide_key_is_pressed(dik, down) +end) + +function update() + if (inventory_loaded) then + update_if_event() + else + initialize() + end +end + +function initialize() + inventory_loaded = true + update_all() + refresh_all = false + refresh_items = false + refresh_inventory = false + if not is_hidden_by_key and not is_hidden_by_cutscene then show() end +end + +function update_all() + setup_indexes() + update_equipment() + update_items() + update_treasure_bag(settings.slotImage.treasury,items.treasure) +end + +function update_if_event() + if (refresh_all) then + update_all() + refresh_all = false + refresh_items = false + refresh_inventory = false + elseif (refresh_items) then + update_items_only() + refresh_items = false + refresh_inventory = false + elseif (refresh_inventory) then + update_inventory_only() + refresh_inventory = false + end +end + +function update_inventory_only() + setup_indexes() + skip_block_if_enabled(settings.slotImage.equipment.visible, true,settings.slotImage.equipment.maxColumns,MAX_EQUIPMENT_SIZE) + update_bag(settings.slotImage.inventory,items.inventory,items.max_inventory,items.enabled_inventory) +end + +function update_items_only() + setup_indexes() + skip_block_if_enabled(settings.slotImage.equipment.visible, true,settings.slotImage.equipment.maxColumns,MAX_EQUIPMENT_SIZE) + update_items() +end + +function update_treasure_only() + local s = settings.slotImage + if (inventory_loaded and s.treasury.visible) then + setup_indexes() + skip_block_if_enabled(s.equipment.visible, true,s.equipment.maxColumns,MAX_EQUIPMENT_SIZE) + skip_block_if_enabled(s.inventory.visible,items.enabled_inventory,s.inventory.maxColumns,items.max_inventory) + skip_block_if_enabled(s.mogSafe.visible,items.safe.enabled,s.mogSafe.maxColumns,items.max_safe) + skip_block_if_enabled(s.mogSafe.visible,items.safe2.enabled,s.mogSafe.maxColumns,items.max_safe2) + skip_block_if_enabled(s.mogStorage.visible,items.storage.enabled,s.mogStorage.maxColumns,items.storage.max) + skip_block_if_enabled(s.mogLocker.visible,items.enabled_locker,s.mogLocker.maxColumns,items.max_locker) + skip_block_if_enabled(s.mogSatchel.visible,items.enabled_satchel,s.mogSatchel.maxColumns,items.max_satchel) + skip_block_if_enabled(s.mogSack.visible,items.enabled_sack,s.mogSack.maxColumns,items.max_sack) + skip_block_if_enabled(s.mogCase.visible,items.enabled_case,s.mogCase.maxColumns,items.max_case) + skip_block_if_enabled(s.mogWardrobe.visible,items.enabled_wardrobe,s.mogWardrobe.maxColumns,items.max_wardrobe) + skip_block_if_enabled(s.mogWardrobe.visible,items.enabled_wardrobe2,s.mogWardrobe.maxColumns,items.max_wardrobe2) + skip_block_if_enabled(s.mogWardrobe.visible,items.enabled_wardrobe3,s.mogWardrobe.maxColumns,items.max_wardrobe3) + skip_block_if_enabled(s.mogWardrobe.visible,items.enabled_wardrobe4,s.mogWardrobe.maxColumns,items.max_wardrobe4) + skip_block_if_enabled(s.tempInventory.visible,true,s.tempInventory.maxColumns,items.temporary.max) + update_treasure_bag(s.treasury,items.treasure) + end +end + +function update_if_different_size(packet_data) + if (inventory_loaded) then + local p = packets.parse('incoming',packet_data) + local s = settings.slotImage + if size_changed(s.inventory.visible,items.enabled_inventory,items.max_inventory,p['Inventory Size']) + or size_changed(s.mogSafe.visible,items.safe.enabled,items.max_safe,p['Safe Size']) + or size_changed(s.mogSafe.visible,items.safe2.enabled,items.max_safe2,p['Safe 2 Size']) + or size_changed(s.mogStorage.visible,items.storage.enabled,items.storage.max,p['Storage Size']) + or size_changed(s.mogLocker.visible,items.enabled_locker,items.max_locker,p['Locker Size']) + or size_changed(s.mogSatchel.visible,items.enabled_satchel,items.max_satchel,p['Satchel Size']) + or size_changed(s.mogSack.visible,items.enabled_sack,items.max_sack,p['Sack Size']) + or size_changed(s.mogCase.visible,items.enabled_case,items.max_case,p['Case Size']) + or size_changed(s.mogWardrobe.visible,items.enabled_wardrobe,items.max_wardrobe,p['Wardrobe Size']) + or size_changed(s.mogWardrobe.visible,items.enabled_wardrobe2,items.max_wardrobe2,p['Wardrobe 2 Size']) + or size_changed(s.mogWardrobe.visible,items.enabled_wardrobe3,items.max_wardrobe3,p['Wardrobe 3 Size']) + or size_changed(s.mogWardrobe.visible,items.enabled_wardrobe4,items.max_wardrobe4,p['Wardrobe 4 Size']) then + refresh_all = true + end + end +end + +function size_changed(visible, enabled, current_size, new_size) + return (visible and enabled and ((current_size+1) ~= new_size)) +end + +function setup_indexes() + current_block = 0 + last_column = 1 + items = windower.ffxi.get_items() +end + +function update_equipment() + if (settings.slotImage.equipment.visible) then + initialize_block() + print_equipment(items.equipment.back) + print_equipment(items.equipment.waist) + print_equipment(items.equipment.legs) + print_equipment(items.equipment.feet) + print_equipment(items.equipment.body) + print_equipment(items.equipment.hands) + print_equipment(items.equipment.left_ring) + print_equipment(items.equipment.right_ring) + print_equipment(items.equipment.head) + print_equipment(items.equipment.neck) + print_equipment(items.equipment.left_ear) + print_equipment(items.equipment.right_ear) + print_equipment(items.equipment.main) + print_equipment(items.equipment.sub) + print_equipment(items.equipment.range) + print_equipment(items.equipment.ammo) + end +end + +function update_items() + local s = settings.slotImage + update_bag(s.inventory,items.inventory,items.max_inventory,items.enabled_inventory) + update_bag(s.mogSafe,items.safe,items.max_safe,items.safe.enabled) + update_bag(s.mogSafe,items.safe2,items.max_safe2,items.safe2.enabled) + update_bag(s.mogStorage,items.storage,items.storage.max,items.storage.enabled) + update_bag(s.mogLocker,items.locker,items.max_locker,items.enabled_locker) + update_bag(s.mogSatchel,items.satchel,items.max_satchel,items.enabled_satchel) + update_bag(s.mogSack,items.sack,items.max_sack,items.enabled_sack) + update_bag(s.mogCase,items.case,items.max_case,items.enabled_case) + update_bag(s.mogWardrobe,items.wardrobe,items.max_wardrobe,items.enabled_wardrobe) + update_bag(s.mogWardrobe,items.wardrobe2,items.max_wardrobe2,items.enabled_wardrobe2) + update_bag(s.mogWardrobe,items.wardrobe3,items.max_wardrobe3,items.enabled_wardrobe3) + update_bag(s.mogWardrobe,items.wardrobe4,items.max_wardrobe4,items.enabled_wardrobe4) + update_temp_bag(s.tempInventory,items.temporary) +end + +function update_treasure_bag(config,bag) + if (config.visible) then + local treasure_count = count_treasure() + if (treasure_count ~= last_treasure_count) then + initialize_block() + for k, _v in ipairs(slot_images[current_block]) do + slot_images[current_block][k].background:alpha(0) + slot_images[current_block][k].box:alpha(0) + end + for _k, _v in pairs(bag) do + print_slot(settings.slotImage.status.tempItem,config.maxColumns,80) + end + last_treasure_count = treasure_count + end + end +end + +function update_bag(config, bag, max, enabled) + if (config.visible and enabled) then + initialize_block() + print_bag(config, bag, max) + end +end + +function update_temp_bag(config, bag) + if (config.visible) then + initialize_block() + local occupied_slots = 0 + for key=1,bag.max,1 do + if bag[key].count > 0 then + occupied_slots = occupied_slots + 1 + end + end + for k, _v in ipairs(slot_images[current_block]) do + slot_images[current_block][k].background:alpha(0) + slot_images[current_block][k].box:alpha(0) + end + for _k=1,occupied_slots,1 do + print_slot(settings.slotImage.status.tempItem,config.maxColumns,bag.max) + end + current_slot = bag.max + current_column = config.maxColumns + update_indexes(config.maxColumns,bag.max) + end +end + +function count_treasure() + local count = 0 + for _k, _v in pairs(items.treasure) do count = count + 1 end + return count +end + +function skip_block_if_enabled(visible,enabled,max_columns,last_index) + if visible and enabled then + initialize_block() + current_slot = last_index + current_column = max_columns + update_indexes(max_columns,last_index) + end +end + +function initialize_block() + current_block = current_block + 1 + if slot_images[current_block] == nil then + slot_images[current_block] = {} + end + current_slot = 1 + current_row = 1 + current_column = 1 +end + +function print_equipment(status) + if (status > 0) then + print_slot(settings.slotImage.status.equipment,settings.slotImage.equipment.maxColumns,MAX_EQUIPMENT_SIZE) + else + print_slot(settings.slotImage.status.empty,settings.slotImage.equipment.maxColumns,MAX_EQUIPMENT_SIZE) + end +end + +function print_bag(config, bag, max) + sort_table(bag) + for key=1,max,1 do + if (bag[key].count > 0) then + print_item(config,bag[key],max) + else + print_slot(settings.slotImage.status.empty,config.maxColumns,max) + end + end +end + +function sort_table(bag) + if (settings.slotImage.sort) then + table.sort(bag, function(a,b) + if (a.status ~= b.status) then + return a.status > b.status + end + if (a.count > 0 and b.count > 0) then + full_stack_a = res.items[a.id].stack - a.count + full_stack_b = res.items[b.id].stack - b.count + if (full_stack_a ~= full_stack_b) then + return full_stack_a < full_stack_b + end + end + return a.count > b.count + end) + end +end + +function print_item(config, item, last_index) + local s = settings.slotImage + if (item.status == DEFAULT_ITEM_STATUS) then + if (item.count == res.items[item.id].stack) then + print_slot(s.status.fullStack,config.maxColumns,last_index) + else + print_slot(s.status.default,config.maxColumns,last_index) + end + elseif (item.status == EQUIPPED_ITEM_STATUS) then + print_slot(s.status.equipped,config.maxColumns,last_index) + elseif (item.status == LINKSHELL_EQUIPPED_ITEM_STATUS) then + print_slot(s.status.linkshellEquipped,config.maxColumns,last_index) + elseif (item.status == BAZAAR_ITEM_STATUS) then + print_slot(s.status.bazaar,config.maxColumns,last_index) + else + print_slot(s.status.empty,config.maxColumns,last_index) + end +end + +function print_slot(status, max_columns, last_index) + update_coordinates() + if slot_images[current_block][current_slot] == nil then + slot_images[current_block][current_slot] = {} + end + print_slot_background(status.background.color) + print_slot_box(status.color) + update_indexes(max_columns,last_index) +end + +function print_slot_background(slot_color, max_columns, last_index) + local slot_image = slot_images[current_block][current_slot] + local s = settings.slotImage + if slot_image.background == nil then + slot_image.background = images.new(s.background) + slot_image.background:pos(current_x,current_y) + end + slot_image.background:width(s.background.size.width) + slot_image.background:height(s.background.size.height) + slot_image.background:alpha(slot_color.alpha) + slot_image.background:color(slot_color.red,slot_color.green,slot_color.blue) +end + +function print_slot_box(slot_color, max_columns, last_index) + local slot_image = slot_images[current_block][current_slot] + local s = settings.slotImage + if slot_image.box == nil then + slot_image.box = images.new(s.box) + slot_image.box:pos(current_x,current_y) + end + slot_image.box:width(s.box.size.width) + slot_image.box:height(s.box.size.height) + slot_image.box:color(slot_color.red,slot_color.green,slot_color.blue) + slot_image.box:alpha(slot_color.alpha) +end + +function update_coordinates() + local s = settings.slotImage + current_x = xRes + xBase + + ((current_column - 1) * s.spacing) + + ((current_block - 1) * s.blockSpacing) + + ((last_column - 1) * s.spacing) + current_y = yRes + yBase - ((current_row - 1) * s.spacing) +end + +function update_indexes(max_columns, last_index) + if (current_slot % max_columns) == 0 then + if (current_slot == last_index) then + last_column = last_column + current_column + end + current_column = 1 + current_row = current_row + 1 + else + current_column = current_column + 1 + end + current_slot = current_slot + 1 +end + +function show() + for key,block in ipairs(slot_images) do + for key,slot in ipairs(block) do + slot.background:show() + slot.box:show() + end + end +end + +function hide() + for key,block in ipairs(slot_images) do + for key,slot in ipairs(block) do + slot.background:hide() + slot.box:hide() + end + end +end + +function clear() + slot_images = {} +end + +function is_cutscene(status_id) + return status_id == CUTSCENE_STATUS_ID +end + +function toggle_display_if_cutscene(is_cutscene_playing) + if (is_cutscene_playing) and (not is_hidden_by_key) then + is_hidden_by_cutscene = true + hide() + elseif (not is_cutscene_playing) and (not is_hidden_by_key) then + is_hidden_by_cutscene = false + show() + end +end + +function toggle_display_if_hide_key_is_pressed(key_pressed, key_down) + if (key_pressed == hideKey) and (key_down) and (is_hidden_by_key) and (not is_hidden_by_cutscene) then + is_hidden_by_key = false + show() + elseif (key_pressed == hideKey) and (key_down) and (not is_hidden_by_key) and (not is_hidden_by_cutscene) then + is_hidden_by_key = true + hide() + end +end diff --git a/Data/DefaultContent/Libraries/addons/addons/invtracker/slot.png b/Data/DefaultContent/Libraries/addons/addons/invtracker/slot.png Binary files differnew file mode 100644 index 0000000..f2d13ec --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/invtracker/slot.png diff --git a/Data/DefaultContent/Libraries/addons/addons/itemizer/itemizer.lua b/Data/DefaultContent/Libraries/addons/addons/itemizer/itemizer.lua new file mode 100644 index 0000000..5074ee9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/itemizer/itemizer.lua @@ -0,0 +1,380 @@ +_addon.name = 'Itemizer' +_addon.author = 'Ihina' +_addon.version = '3.0.1.3' +_addon.command = 'itemizer' + +require('luau') + +defaults = {} +defaults.AutoNinjaTools = true +defaults.AutoItems = true +defaults.Delay = 0.5 +defaults.version = "3.0.1.1" +defaults.UseUniversalTools = {} + +defaults.UseUniversalTools.Katon = false +defaults.UseUniversalTools.Hyoton = false +defaults.UseUniversalTools.Huton = false +defaults.UseUniversalTools.Doton = false +defaults.UseUniversalTools.Raiton = false +defaults.UseUniversalTools.Suiton = false +defaults.UseUniversalTools.Utsusemi = false +defaults.UseUniversalTools.Jubaku = false +defaults.UseUniversalTools.Hojo = false +defaults.UseUniversalTools.Kurayami = false +defaults.UseUniversalTools.Dokumori = false +defaults.UseUniversalTools.Tonko = false +defaults.UseUniversalTools.Monomi = false +defaults.UseUniversalTools.Aisha = false +defaults.UseUniversalTools.Yurin = false +defaults.UseUniversalTools.Myoshu = false +defaults.UseUniversalTools.Migawari = false +defaults.UseUniversalTools.Kakka = false +defaults.UseUniversalTools.Gekka = false +defaults.UseUniversalTools.Yain = false + +settings = config.load(defaults) +bag_ids = res.bags:key_map(string.gsub-{' ', ''} .. string.lower .. table.get-{'english'} .. table.get+{res.bags}):map(table.get-{'id'}) +-- Remove temporary bag, because items cannot be moved from/to there, as such it's irrelevant to Itemizer +bag_ids.temporary = nil + +--Added this function for first load on new version. Because of the newly added features that weren't there before. +windower.register_event("load", function() + if settings.version == "3.0.1.1" then + windower.add_to_chat(207,"Itemizer v3.0.1.2: New features added. (use //itemizer help to find out about them)") + settings.version = "3.0.1.2" + settings:save() + end +end) + +find_items = function(ids, bag, limit) + local res = S{} + local found = 0 + + for bag_index, bag_name in bag_ids:filter(table.get-{'enabled'} .. windower.ffxi.get_bag_info):it() do + if not bag or bag_index == bag then + for _, item in ipairs(windower.ffxi.get_items(bag_index)) do + if ids:contains(item.id) then + local count = limit and math.min(limit, item.count) or item.count + found = found + count + + res:add({ + bag = bag_index, + slot = item.slot, + count = count, + }) + + if limit then + limit = limit - count + + if limit == 0 then + return res, found + end + end + end + end + end + end + + return res, found +end + +windower.register_event("addon command", function(command, arg2, ...) + if command == 'help' then + local helptext = [[Itemizer - Command List:') + 1. Delay <delay> - Sets the time delay. + 2. Autoninjatools - toggles Automatically getting ninja tools (Shortened ant) + 3. Autoitems - Toggles automatically getting items from bags (shortened ai) + 4. Useuniversaltool <spell> - toggles using universal ninja tools for <spell> (shortened uut) + i.e. uut katon - will toggle katon either true or false depending on your setting + all defaulted false. + 5. help --Shows this menu.]] + for _, line in ipairs(helptext:split('\n')) do + windower.add_to_chat(207, line) + end + elseif command:lower() == "delay" and arg2 ~= nil then + if type(arg2) == 'number' then + settings.delay = arg2 + settings:save() + else + error('The delay must be a number') + end + elseif T{'autoninjatools','ant'}:contains(command:lower()) then + settings.AutoNinjaTools = not settings.AutoNinjaTools + settings:save() + elseif T{'autoitems','ai'}:contains(command:lower()) then + settings.AutoItems = not settings.AutoItems + settings:save() + elseif T{'useuniversaltool','uut'}:contains(command:lower()) then + if settings.UseUniversalTools[arg2:ucfirst()] ~= nil then + settings.UseUniversalTools[arg2:ucfirst()] = not settings.UseUniversalTools[arg2:ucfirst()] + settings:save() + else + error('Argument 2 must be a ninjutsu spell (sans :ichi or :ni) i.e. uut katon') + end + end +end) + + +windower.register_event('unhandled command', function(command, ...) + local args = L{...}:map(string.lower) + + if command == 'get' or command == 'put' or command == 'gets' or command == 'puts' then + local count + if command == 'gets' or command == 'puts' then + command = command:sub(1, -2) + else + local last = args[#args] + if last == 'all' then + args:remove() + elseif tonumber(last) then + count = tonumber(last) + args:remove() + else + count = 1 + end + end + + local bag = args[#args] + local specified_bag = rawget(bag_ids, bag) + if specified_bag then + if not windower.ffxi.get_bag_info(specified_bag).enabled then + error('%s currently not enabled':format(res.bags[specified_bag].name)) + return + end + + args:remove() + elseif command == 'put' and not specified_bag then + error('Specify a valid destination bag to put items in.') + return + end + + local source_bag + local destination_bag + if command == 'get' then + source_bag = specified_bag + destination_bag = bag_ids.inventory + else + destination_bag = specified_bag + source_bag = bag_ids.inventory + end + + local destination_bag_info = windower.ffxi.get_bag_info(destination_bag) + if destination_bag_info.max - destination_bag_info.count == 0 then + error('Not enough space in %s to move items.':format(res.bags[destination_bag].name)) + return + end + + local item_name = args:concat(' ') + + local item_ids = (S(res.items:name(windower.wc_match-{item_name})) + S(res.items:name_log(windower.wc_match-{item_name}))):map(table.get-{'id'}) + if item_ids:length() == 0 then + error('Unknown item: %s':format(item_name)) + return + end + + local matches, results = find_items(item_ids, source_bag, count) + if results == 0 then + error('Item "%s" not found in %s.':format(item_name, source_bag and res.bags[source_bag].name or 'any accessible bags')) + return + end + + if count and results < count then + warning('Only %u "%s" found in %s.':format(results, item_name, source_bag and res.bags[source_bag].name or 'all accessible bags')) + end + + for match in matches:it() do + windower.ffxi[command .. '_item'](command == 'get' and match.bag or destination_bag, match.slot, match.count) + end + end +end) + +ninjutsu = res.spells:type('Ninjutsu') +patterns = L{'"(.+)"', '\'(.+)\'', '.- (.+) .-', '.- (.+)'} +spec_tools = T{ + Katon = 1161, + Hyoton = 1164, + Huton = 1167, + Doton = 1170, + Raiton = 1173, + Suiton = 1176, + Utsusemi = 1179, + Jubaku = 1182, + Hojo = 1185, + Kurayami = 1188, + Dokumori = 1191, + Tonko = 1194, + Monomi = 2553, + Aisha = 2555, + Yurin = 2643, + Myoshu = 2642, + Migawari = 2970, + Kakka = 2644, + Gekka = 8803, + Yain = 8804 +} +gen_tools = T{ + Katon = 2971, + Hyoton = 2971, + Huton = 2971, + Doton = 2971, + Raiton = 2971, + Suiton = 2971, + Utsusemi = 2972, + Jubaku = 2973, + Hojo = 2973, + Kurayami = 2973, + Dokumori = 2973, + Tonko = 2972, + Monomi = 2972, + Aisha = 2973, + Yurin = 2973, + Myoshu = 2972, + Migawari = 2972, + Kakka = 2972, + Gekka = 2972, + Yain = 2972 +} + +active = S{} + +-- Returning true resends the command in settings.Delay seconds +-- Returning false doesn't resend the command and executes it +collect_item = function(id, items) + items = items or {inventory = windower.ffxi.get_items(bag_ids.inventory)} + + local item = T(items.inventory):with('id', id) + if item then + active = active:remove(id) + return false + end + + -- Current ID already being processed? + if active:contains(id) then + return true + end + + -- Check for all items + local match = find_items(S{id}, nil, 1):it()() + + if match then + windower.ffxi.get_item(match.bag, match.slot, match.count) + + -- Add currently processing ID to set of active IDs + active:add(id) + else + error('Item "%s" not found in any accessible bags':format(res.items[id].name)) + end + + return match ~= nil +end + +reschedule = function(text, ids, items) + if not items then + local info = windower.ffxi.get_bag_info(bag_ids.inventory) + items = {inventory = windower.ffxi.get_items(bag_ids.inventory)} + items.max_inventory = info.max + items.count_inventory = info.count + end + + -- Inventory full? + if items.max_inventory - items.count_inventory == 0 then + return false + end + + for id in L(ids):it() do + if collect_item(id, items) then + windower.send_command:prepare('input %s':format(text)):schedule(settings.Delay) + return true + end + end +end + +windower.register_event('outgoing text', function() + local item_names = T{} + + return function(text) + -- Ninjutsu + if settings.AutoNinjaTools and (text:startswith('/ma ') or text:startswith('/nin ') or text:startswith('/magic ') or text:startswith('/ninjutsu ')) then + local name + for pattern in patterns:it() do + local match = text:match(pattern) + if match then + if ninjutsu:with('name', string.imatch-{match}) then + name = match:lower():capitalize():match('%w+') + break + end + end + end + + if name then + if settings.UseUniversalTools[name] == false or windower.ffxi.get_player().main_job ~= 'NIN' then + return reschedule(text, {spec_tools[name], windower.ffxi.get_player().main_job == 'NIN' and gen_tools[name] or nil}) + else + return reschedule(text, {windower.ffxi.get_player().main_job == 'NIN' and gen_tools[name] or nil}) + end + end + + -- Item usage + elseif settings.AutoItems and text:startswith('/item ') then + local items = windower.ffxi.get_items() + local inventory_items = S{} + local wardrobe_items = S{} + for bag in bag_ids:keyset():it() do + for _, item in ipairs(items[bag]) do + if item.id > 0 and not item_names[item.id] then + item_names[item.id] = res.items[item.id].name + end + + if bag == 'inventory' then + inventory_items:add(item.id) + elseif S{'wardrobe','wardrobe2','wardrobe3','wardrobe4'}:contains(bag) then + wardrobe_items:add(item.id) + end + end + end + + local parsed_text = item_count and text:match(' (.+) (%d+)$') or text:match(' (.+)') + local mid_name = parsed_text:match('"(.+)"') or parsed_text:match('\'(.+)\'') or parsed_text:match('(.+) ') + local full_name = parsed_text:match('(.+)') + local id = item_names:find(string.imatch-{mid_name}) or item_names:find(string.imatch-{full_name}) + if id then + if not inventory_items:contains(id) and not wardrobe_items:contains(id) then + return reschedule(text, {id}, items) + else + active:remove(id) + end + end + + end + end +end()) + +--[[ +Copyright © 2013-2015, Ihina +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Silence nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL IHINA 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. +]] +--Original plugin by Aureus diff --git a/Data/DefaultContent/Libraries/addons/addons/itemizer/readme.md b/Data/DefaultContent/Libraries/addons/addons/itemizer/readme.md new file mode 100644 index 0000000..954ef68 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/itemizer/readme.md @@ -0,0 +1,64 @@ +#Itemizer + +Provides a chat/console interface for moving items around between bags. Optionally also automatically fetches items into the main inventory before item usage (both regular usage and Ninjutsu tools). + +### Commands +``` +itemizer delay <delay> +``` + +Sets the delay for pulling items into your inventory to 'delay'. (Default 0.5) + +``` +itemizer autoitems (shortened: ai) +``` + +Toggles the auto-pulling of items from bags if not in your inventory. (Default: True) + +``` +itemizer autoninjatools (shortened: ant) +``` + +Toggles the auto-pulling of ninja tools from bags when not in your bag. Prioritizes specific tools then moves to universal unless the next command is used to set the tools to only use universal tools for specific spells. (Default: True) + +``` +itemizer useuniversaltools <spell> (shortened: uut) +``` + +Toggles the use of only universal tools for `spell`. Does not use :ichi or :ni suffixes. (Default: False for all spells) +For Example: itemiser uut katon + +``` +get <item> [bag] [count] +``` + +Retrieves the specified item from the specified bag. If `bag` is omitted it will fetch the item from any accessible bag, if available. If `count` is omitted only a single item is fetched. If `count` is `all` all items will be fetched. + +``` +put <item> <bag> [count] +``` + +Places the specified item into the specified bag. If `count` is omitted only a single item is put away. If `count` is `all` all items will be put away. + +``` +gets <item> [bag] +puts <item> <bag> +``` + +Same as the `get` and `put` variants if `all` was specified for the `count` argument. + +### Notes + +Both the full name and the abbrevited names are valid entries. Wildcards are allowed. For example, `//get *ore` would fetch all ores from all accessible bags to the main inventory. + +### Examples + +``` +//put Mandau sack +//put Whirlpool mask satchel +//get Hct. Subligar +1 storage +//get Raider's armlets +2 safe +//get "HoPe toRQue" locker +//get earth crystal 5 +//get *crystal +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/latentchecker/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/latentchecker/ReadMe.md new file mode 100644 index 0000000..834f4c3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/latentchecker/ReadMe.md @@ -0,0 +1,8 @@ +LatentChecker +============= + +Description: Checks weapon skill points of valid weapons. + +Instructions: Use "//latentchecker run" or "//lc run" to check the weapon skill points of all valid weapons in your inventory. + +Warning: Using it will unequip your main/sub/ranged weapons, and it takes about 5 seconds to check each weapon in your inventory.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/latentchecker/latentchecker.lua b/Data/DefaultContent/Libraries/addons/addons/latentchecker/latentchecker.lua new file mode 100644 index 0000000..354ad77 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/latentchecker/latentchecker.lua @@ -0,0 +1,107 @@ +--[[Copyright © 2014-2016, smd111 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Byrth or smd111 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.]] + +_addon.name = 'latentchecker' +_addon.author = 'smd111' +_addon.command = 'latentchecker' +_addon.commands = {'lc'} +_addon.version = '1.1' + +extdata = require 'extdata' +res = require 'resources' +bag = 'Satchel' +bag_id = 5 + +function validate_bag(id) + local bag_info = windower.ffxi.get_bag_info(id) + if bag_info.enabled and bag_info.max > bag_info.count then + return true + end + return false +end + +function check_space() + if validate_bag(bag_id) then + return bag_id + else + for i=5,8 do + if validate_bag(i) then + bag_id = i -- Update bag ID to be the bag that will work + return bag_id + end + end + end + return false +end + +function match_item(target_item,m) + return type(m) == 'table' and m.id and res.items[m.id] and (res.items[m.id].en:lower() == target_item:lower() or res.items[m.id].enl:lower() == target_item:lower()) +end + + +windower.register_event('addon command', function(command, ...) + local trial_weapons = {"axe of trials","gun of trials","sword of trials","knuckles of trials","spear of trials","scythe of trials","sapara of trials", + "bow of trials","club of trials","pole of trials","pick of trials","dagger of trials","tachi of trials","kodachi of trials","sturdy axe","burning fists", + "werebuster","mage's staff","vorpal sword","swordbreaker","brave blade","death sickle","double axe","dancing dagger","killer bow","windslicer", + "sasuke katana","radiant lance","scepter staff","wightslayer","quicksilver","inferno claws","main gauche","elder staff","destroyers","senjuinrikio", + "heart snatcher","subduer","dissector","expunger","morgenstern","gravedigger","rampager","coffinmaker","gonzo-shizunori","retributor","michishiba","thyrsusstab", + "trial wand","trial blade","chaosbringer"} + if command == 'run' then + windower.add_to_chat(121,'latentchecker: Starting...') + windower.ffxi.set_equip(0, 0, 0) -- Remove main/sub weapons + windower.ffxi.set_equip(0, 2, 0) -- Remove ranged weapons + coroutine.sleep(1.2) + for _,target_item in pairs(trial_weapons) do + if not check_space() then + windower.add_to_chat(123,'latentchecker: not able to swap item. No available space found in bags.') + return + end + + for n,m in pairs(windower.ffxi.get_items(0)) do -- Iterate over inventory + if match_item(target_item,m) then + windower.ffxi.put_item(bag_id,n) + coroutine.sleep(1.2) + windower.add_to_chat(55,'latentchecker: '..res.items[m.id].en..' has '..tostring(extdata.decode(windower.ffxi.get_items(0,n)).ws_points)..' WS points') + coroutine.sleep(1.2) + + if not validate_bag(0) then + windower.add_to_chat(123,'latentchecker: Inventory became full while running.\nlatentchecker: Stopping.') + return + end + for j,k in pairs(windower.ffxi.get_items(bag_id)) do + if match_item(target_item,k) then + windower.ffxi.get_item(bag_id,j) + break + end + end + end + end + end + windower.add_to_chat(121,'latentchecker: Done! Remember to re-dress yourself!') + else + print('latentchecker: My only valid command is "run", which will reset your TP.') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/actions.lua b/Data/DefaultContent/Libraries/addons/addons/libs/actions.lua new file mode 100644 index 0000000..14254ce --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/actions.lua @@ -0,0 +1,625 @@ +--[[ +A library to make the manipulation of the action packet easier. + +The primary functionality provided here are iterators which allow for +easy traversal of the sub-tables within the packet. Example: + +======================================================================================= +require('actions') + +function event_action(act) + action = Action(act) -- constructor + + -- print out all melee hits to the console + if actionpacket:get_category_string() == 'melee' then + for target in actionpacket:get_targets() do -- target iterator + for action in target:get_actions() do -- subaction iterator + if action.message == 1 then -- 1 is the code for messages + print(string.format("%s hit %s for %d damage", + actionpacket:get_actor_name(), target:get_name(), action.param)) + end + end + end + end +end +======================================================================================= + +]] + +_libs = _libs or {} + +require('tables') + +local table = _libs.tables +local res = require('resources') + +_libs.actions = true + +local category_strings = { + 'melee', + 'ranged_finish', + 'weaponskill_finish', + 'spell_finish', + 'item_finish', + 'job_ability', + 'weaponskill_begin', + 'casting_begin', + 'item_begin', + 'unknown', + 'mob_tp_finish', + 'ranged_begin', + 'avatar_tp_finish', + 'job_ability_unblinkable', + 'job_ability_run' +} + +-- ActionPacket operations +ActionPacket = {} + +local actionpacket = {} +-- Constructor for Actions. +-- Usage: actionpacket = ActionPacket(raw_action) + +function ActionPacket.new(a) + if a == nil then + return + end + + local new_instance = {} + new_instance.raw = a + + return setmetatable(new_instance, {__index = function(t, k) if rawget(t, k) ~= nil then return t[k] else return actionpacket[k] end end}) +end + +local function act_to_string(original,act) + if type(act) ~= 'table' then return act end + + function assemble_bit_packed(init,val,initial_length,final_length) + if not init then return init end + + if type(val) == 'boolean' then + if val then val = 1 else val = 0 end + elseif type(val) ~= 'number' then + return false + end + local bits = initial_length%8 + local byte_length = math.ceil(final_length/8) + + local out_val = 0 + if bits > 0 then + out_val = init:byte(#init) -- Initialize out_val to the remainder in the active byte. + init = init:sub(1,#init-1) -- Take off the active byte + end + out_val = out_val + val*2^bits -- left-shift val by the appropriate amount and add it to the remainder (now the lsb-s in val) + + while out_val > 0 do + init = init..string.char(out_val%256) + out_val = math.floor(out_val/256) + end + while #init < byte_length do + init = init..string.char(0) + end + return init + end + + local react = assemble_bit_packed(original:sub(1,4),act.size,32,40) + react = assemble_bit_packed(react,act.actor_id,40,72) + react = assemble_bit_packed(react,act.target_count,72,82) + react = assemble_bit_packed(react,act.category,82,86) + react = assemble_bit_packed(react,act.param,86,102) + react = assemble_bit_packed(react,act.unknown,102,118) + react = assemble_bit_packed(react,act.recast,118,150) + + local offset = 150 + for i = 1,act.target_count do + react = assemble_bit_packed(react,act.targets[i].id,offset,offset+32) + react = assemble_bit_packed(react,act.targets[i].action_count,offset+32,offset+36) + offset = offset + 36 + for n = 1,act.targets[i].action_count do + react = assemble_bit_packed(react,act.targets[i].actions[n].reaction,offset,offset+5) + react = assemble_bit_packed(react,act.targets[i].actions[n].animation,offset+5,offset+17) + react = assemble_bit_packed(react,act.targets[i].actions[n].effect,offset+17,offset+21) + react = assemble_bit_packed(react,act.targets[i].actions[n].stagger,offset+21,offset+24) + react = assemble_bit_packed(react,act.targets[i].actions[n].knockback,offset+24,offset+27) + react = assemble_bit_packed(react,act.targets[i].actions[n].param,offset+27,offset+44) + react = assemble_bit_packed(react,act.targets[i].actions[n].message,offset+44,offset+54) + react = assemble_bit_packed(react,act.targets[i].actions[n].unknown,offset+54,offset+85) + + react = assemble_bit_packed(react,act.targets[i].actions[n].has_add_effect,offset+85,offset+86) + offset = offset + 86 + if act.targets[i].actions[n].has_add_effect then + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_animation,offset,offset+6) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_effect,offset+6,offset+10) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_param,offset+10,offset+27) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_message,offset+27,offset+37) + offset = offset + 37 + end + react = assemble_bit_packed(react,act.targets[i].actions[n].has_spike_effect,offset,offset+1) + offset = offset + 1 + if act.targets[i].actions[n].has_spike_effect then + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_animation,offset,offset+6) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_effect,offset+6,offset+10) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_param,offset+10,offset+24) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_message,offset+24,offset+34) + offset = offset + 34 + end + end + end + if react then + while #react < #original do + react = react..original:sub(#react+1,#react+1) + end + else + print('Action Library failure in '..(_addon.name or 'Unknown Addon')..': Invalid Act table returned.') + end + return react +end + +-- Opens a listener event for the action packet at the incoming chunk level before modifications. +-- Passes in the documented act structures for the original and modified packets. +-- If a table is returned, the library will treat it as a modified act table and recompose the packet string from it. +-- If an invalid act table is passed, it will silently fail to be returned. +function ActionPacket.open_listener(funct) + if not funct or type(funct) ~= 'function' then return end + local id = windower.register_event('incoming chunk',function(id, org, modi, is_injected, is_blocked) + if id == 0x28 then + local act_org = windower.packets.parse_action(org) + act_org.size = org:byte(5) + local act_mod = windower.packets.parse_action(modi) + act_mod.size = modi:byte(5) + return act_to_string(org,funct(act_org,act_mod)) + end + end) + return id +end + +function ActionPacket.close_listener(id) + if not id or type(id) ~= 'number' then return end + windower.unregister_event(id) +end + + +local actor_animation_twoCC = { + wh='White Magic', + bk='Black Magic', + bl='Blue Magic', + sm='Summoning Magic', + te='TP Move', + ['k0']='Melee Attack', + ['lg']='Ranged Attack', + } + +function actionpacket:get_animation_string() + return actor_animation_twoCC[string.char(actor_animation_twoCC[self.raw['unknown']]%256,math.floor(actor_animation_twoCC[self.raw['unknown']]/256))] +end + + +function actionpacket:get_category_string() + return category_strings[self.raw['category']] +end + +function actionpacket:get_spell() + local info = self:get_targets()():get_actions()():get_basic_info() + if rawget(info,'resource') and rawget(info,'spell_id') and rawget(rawget(res,rawget(info,'resource')),rawget(info,'spell_id')) then + local copied_line = {} + for i,v in pairs(rawget(rawget(res,rawget(info,'resource')),rawget(info,'spell_id'))) do + rawset(copied_line,i,v) + end + setmetatable(copied_line,getmetatable(res[rawget(info,'resource')][rawget(info,'spell_id')])) + return copied_line + end +end + +-- Returns the name of this actor if there is one +function actionpacket:get_actor_name() + local mob = windower.ffxi.get_mob_by_id(self.raw['actor_id']) + + if mob then + return mob['name'] + else + return nil + end +end + +--Returns the id of the actor +function actionpacket:get_id() + return self.raw['actor_id'] +end + +-- Returns an iterator for this actionpacket's targets +function actionpacket:get_targets() + local targets = self.raw['targets'] + local target_count = self.raw['target_count'] + local i = 0 + return function () + i = i + 1 + if i <= target_count then + return Target(self.raw['category'],self.raw['param'],targets[i]) + end + end +end + +local target = {} + +-- Constructor for target wrapper +function Target(category,top_level_param,t) + if t == nil then + return + end + + local new_instance = {} + new_instance.raw = t + new_instance.category = category + new_instance.top_level_param = top_level_param + new_instance.id = t.id + + return setmetatable(new_instance, {__index = function (t, k) if rawget(t, k) ~= nil then return t[k] else return target[k] end end}) +end + +-- Returns an iterator for this target's actions +function target:get_actions() + local action_count = self.raw['action_count'] + local i = 0 + return function () + i = i + 1 + if i <= action_count then + return Action(self.category,self.top_level_param,self.raw['actions'][i]) + end + end +end + +-- Returns the name of this target if there is one +function target:get_name() + local mob = windower.ffxi.get_mob_by_id(self.raw['id']) + + if mob then + return mob['name'] + else + return nil + end +end + +local reaction_strings = { + [1] = 'evade', + [2] = 'parry', + [4] = 'block/guard', + [8] = 'hit' + -- 12 = blocked? + } + +local animation_strings = { + [0] = 'main hand', + [1] = 'off hand', + [2] = 'left kick', + [3] = 'right kick', + [4] = 'daken throw' + } + +local effect_strings = { + [2] = 'critical hit' + } + +local stagger_strings = { + } + +local action = {} + +function Action(category,top_level_param,t) + if category == nil or t == nil then + return + end + + local new_instance = {} + new_instance.raw = t + new_instance.raw.category = category_strings[category] or category + new_instance.raw.top_level_param = top_level_param + + return setmetatable(new_instance, {__index = function (t, k) if rawget(t, k) ~= nil then return t[k] else return action[k] or rawget(rawget(t,'raw'),k) end end}) +end + +function action:get_basic_info() + local reaction = self:get_reaction_string() + local animation = self:get_animation_string() + local effect = self:get_effect_string() + local stagger = self:get_stagger_string() + local message_id = self:get_message_id() + + local param, resource, spell_id, interruption, conclusion = self:get_spell() + + return {reaction = reaction, animation = animation, effect=effect, message_id = message_id, + stagger = stagger, param = param, resource = resource, spell_id = spell_id, + interruption = interruption, conclusion = conclusion} +end + +function action:get_reaction_string() + local reaction = rawget(rawget(self,'raw'),'reaction') + return rawget(reaction_strings,reaction) or reaction +end + +function action:get_animation_string() + local animation = rawget(rawget(self,'raw'),'animation') + return rawget(animation_strings,animation) or animation +end + +function action:get_effect_string() + local effect = rawget(rawget(self,'raw'),'effect') + return rawget(effect_strings,effect) or effect +end + +function action:get_stagger_string() + local stagger = rawget(rawget(self,'raw'),'stagger') + return rawget(stagger_strings,stagger) or stagger +end + +local cat_to_res_map = {['weaponskill_finish']='weapon_skills', ['spell_finish']='spells', + ['item_finish']='items', ['job_ability']='job_abilities', ['weaponskill_begin']='weapon_skills', + ['casting_begin']='spells', ['item_begin']='items', ['mob_tp_finish']='monster_abilities', + ['avatar_tp_finish']='job_abilities', ['job_ability_unblinkable']='job_abilities', + ['job_ability_run']='job_abilities'} +local begin_categories = {['weaponskill_begin']=true, ['casting_begin']=true, ['item_begin']=true, ['ranged_begin']=true} +local finish_categories = {['melee']=true,['ranged_finish']=true,['weaponskill_finish']=true, ['spell_finish']=true, ['item_finish']=true, + ['job_ability']=true, ['mob_tp_finish']=true, ['avatar_tp_finish']=true, ['job_ability_unblinkable']=true, + ['job_ability_run']=true} +local msg_id_to_conclusion_map = { + [26] = {subject="target", verb="gains", objects={"HP","MP"} }, + [31] = {subject="target", verb="loses", objects={"shadows"} }, + [112] = {subject="target", verb="count", objects={"doom"} }, + [120] = {subject="actor", verb="gains", objects={"Gil"} }, + [132] = {subject="target", verb="steals", objects={"HP"} }, + [133] = {subject="actor", verb="steals", objects={"Petra"} }, + [152] = {subject="actor", verb="gains", objects={"MP"} }, + [229] = {subject="target", verb="loses", objects={"HP"} }, + [231] = {subject="actor", verb="loses", objects={"effects"} }, + [530] = {subject="target", verb="count", objects={"petrify"} }, -- Gradual Petrify + [557] = {subject="actor", verb="gains", objects={"Alexandrite"} }, -- Using a pouch + [560] = {subject="actor", verb="gains", objects={"FMs"} }, -- No Foot Rise + [572] = {subject="actor", verb="steals", objects={"ailments"} }, -- Sacrifice + [585] = {subject="actor", verb="has", objects={"enmity"} }, -- Libra with actor + [586] = {subject="target", verb="has", objects={"enmity"} }, -- Libra without actor + [674] = {subject="actor", verb="gains", objects={"items"} }, -- Scavenge + [730] = {subject="target", verb="has", objects={"TP"} }, + } +local expandable = {} +expandable[{1, 2, 67, 77, 110,157, + 163,185,196,197,223,252, + 264,265,288,289,290,291, + 292,293,294,295,296,297, + 298,299,300,301,302,317, + 352,353,379,419,522,576, + 577,648,650,732,767,768}] = {subject="target", verb="loses", objects={"HP"} } +expandable[{122,167,383}] = {subject="actor", verb="gains", objects={"HP"} } +expandable[{7, 24, 102,103,238,263, + 306,318,357,367,373,382,384, + 385,386,387,388,389,390,391, + 392,393,394,395,396,397,398, + 539,587,606,651,769,770}] = {subject="target", verb="gains", objects={"HP"} } +expandable[{25, 224,276,358,451,588}] = {subject="target", verb="gains", objects={"MP"} } +expandable[{161,187,227,274,281}] = {subject="actor", verb="steals", objects={"HP"} } +expandable[{165,226,454,652}] = {subject="actor", verb="steals", objects={"TP"} } +expandable[{162,225,228,275,366}] = {subject="actor", verb="steals", objects={"MP"} } +expandable[{362,363}] = {subject="target", verb="loses", objects={"TP"} } +expandable[{369,403,417}] = {subject="actor", verb="steals", objects={"attributes"} } +expandable[{370,404,642}] = {subject="actor", verb="steals", objects={"effects"} } +expandable[{400,570,571,589,607}] = {subject="target", verb="loses", objects={"ailments"} } +expandable[{401,405,644}] = {subject="target", verb="loses", objects={"effects"} } +expandable[{409,452,537}] = {subject="target", verb="gains", objects={"TP"} } +expandable[{519,520,521,591}] = {subject="target", verb="gains", objects={"daze"} } +expandable[{14, 535}] = {subject="actor", verb="loses", object={"shadows"} } +expandable[{603,608}] = {subject="target", verb="gains", objects={"TH"} } +expandable[{33, 44, 536,}] = {subject="actor", verb="loses", objects={"HP"} } +for ids,tab in pairs(expandable) do + for _,id in pairs(ids) do + msg_id_to_conclusion_map[id] = tab + end +end +local function msg_id_to_conclusion(msg_id) + return rawget(msg_id_to_conclusion_map,msg_id) or false +end + +function action:get_spell() + local category = rawget(rawget(self,'raw'),'category') + -- It's far more accurate to filter by the resources line. + + local function fieldsearch(message_id) + if not message_id or not res.action_messages[message_id] or not res.action_messages[message_id].en then return false end + local fields = {} + res.action_messages[message_id].en:gsub("${(.-)}", function(a) if a ~= "actor" and a ~= "target" and a ~= 'lb' then rawset(fields,a,true) end end) + return fields + end + + local message_id = self:get_message_id() + local fields = fieldsearch(message_id) + local param = rawget(finish_categories, category) and rawget(rawget(self, 'raw'), 'param') + local spell_id = rawget(begin_categories, category) and rawget(rawget(self, 'raw'), 'param') or + rawget(finish_categories, category) and rawget(rawget(self, 'raw'), 'top_level_param') + local interruption = rawget(begin_categories, category) and rawget(rawget(self, 'raw'), 'top_level_param') == 28787 + if interruption == nil then interruption = false end + + local conclusion = msg_id_to_conclusion(message_id) + + local resource + if not fields or message_id == 31 then + -- If there is no message, assume the resources type based on the category. + if category == 'weaponskill_begin' and spell_id <= 256 then + resource = 'weapon_skills' + elseif category == 'weaponskill_begin' then + resource = 'monster_abilities' + else + resource = rawget(cat_to_res_map,category) or false + end + else + local msgID_to_res_map = { + [244] = 'job_abilities', -- Mug + [328] = 'job_abilities', -- BPs that are out of range + } + -- If there is a message, interpret the fields. + resource = msgID_to_res_map[message_id] or fields.spell and 'spells' or + fields.weapon_skill and spell_id <= 256 and 'weapon_skills' or + fields.weapon_skill and spell_id > 256 and 'monster_abilities' or + fields.ability and 'job_abilities' or + fields.item and 'items' or rawget(cat_to_res_map,category) + local msgID_to_spell_id_map = { + [240] = 43, -- Hide + [241] = 43, -- Hide failing + [303] = 74, -- Divine Seal + [304] = 75, -- Elemental Seal + [305] = 76, -- Trick Attack + [311] = 79, -- Cover + } + spell_id = msgID_to_spell_id_map[message_id] or spell_id + end + + -- param will be a number or false + -- resource will be a string or false + -- spell_id will either be a number or false + -- interruption will be true or false + -- conclusion will either be a table or false + + return param, resource, spell_id, interruption, conclusion +end + +function action:get_message_id() + local message_id = rawget(rawget(self,'raw'),'message') + return message_id or 0 +end + +---------------------------------------- Additional Effects ---------------------------------------- +local add_effect_animation_strings = {} + +add_effect_animation_strings['melee'] = { + [1] = 'enfire', + [2] = 'enblizzard', + [3] = 'enaero', + [4] = 'enstone', + [5] = 'enthunder', + [6] = 'enwater', + [7] = 'enlight', + [8] = 'endark', + [12] = 'enblind', + [14] = 'enpetrify', + [21] = 'endrain', + [22] = 'enaspir', + [23] = 'enhaste', + } + +add_effect_animation_strings['ranged_finish'] = add_effect_animation_strings['melee'] + +add_effect_animation_strings['weaponskill_finish'] = { + [1] = 'light', + [2] = 'darkness', + [3] = 'gravitation', + [4] = 'fragmentation', + [5] = 'distortion', + [6] = 'fusion', + [7] = 'compression', + [8] = 'liquefaction', + [9] = 'induration', + [10] = 'reverberation', + [11] = 'transfixion', + [12] = 'scission', + [13] = 'detonation', + [14] = 'impaction', + [15] = 'radiance', + [16] = 'umbra', + } + +add_effect_animation_strings['spell_finish'] = add_effect_animation_strings['weaponskill_finish'] +add_effect_animation_strings['mob_tp_finish'] = add_effect_animation_strings['weaponskill_finish'] +add_effect_animation_strings['avatar_tp_finish'] = add_effect_animation_strings['weaponskill_finish'] + +local add_effect_effect_strings = {} + +function action:get_add_effect() + if not rawget(rawget(self,'raw'),'has_add_effect') then return false end + local animation = self:get_add_effect_animation_string() + local effect = self:get_add_effect_effect_string() + local param = rawget(rawget(self,'raw'),'add_effect_param') + local message_id = rawget(rawget(self,'raw'),'add_effect_message') + local conclusion = msg_id_to_conclusion(message_id) + return {animation = animation, effect = effect, param = param, + message_id = message_id,conclusion = conclusion} +end + +function action:get_add_effect_animation_string() + local add_effect_animation = rawget(rawget(self,'raw'),'add_effect_animation') + local add_eff_animation_tab = rawget(add_effect_animation_strings,rawget(rawget(self,'raw'),'category')) + return add_eff_animation_tab and rawget(add_eff_animation_tab,add_effect_animation) or add_effect_animation +end + +function action:get_add_effect_effect_string() + local add_effect_effect = rawget(rawget(self,'raw'),'add_effect_effect') + return rawget(add_effect_effect_strings,add_effect_effect) or add_effect_effect +end + +function action:get_add_effect_conclusion() + return msg_id_to_conclusion(rawget(rawget(self,'raw'),'add_effect_message')) +end + + +------------------------------------------- Spike Effects ------------------------------------------ +local spike_effect_animation_strings = { + [1] = 'blaze spikes', + [2] = 'ice spikes', + [3] = 'dread spikes', + [4] = 'water spikes', + [5] = 'shock spikes', + [6] = 'reprisal', + [7] = 'wind spikes', + [8] = 'stone spikes', + [63] = 'counter', + } + +local spike_effect_effect_strings = { + } +function action:get_spike_effect() + if not rawget(rawget(self,'raw'),'has_spike_effect') then return false end + local effect = self:get_spike_effect_effect_string() + local animation = self:get_spike_effect_animation_string() + local param = rawget(rawget(self,'raw'),'spike_effect_param') + local message_id = rawget(rawget(self,'raw'),'spike_effect_message') + local conclusion = msg_id_to_conclusion(message_id) + return {animation = animation, effect = effect, param = param, + message_id = message_id,conclusion = conclusion} +end + +function action:get_spike_effect_effect_string() + local spike_effect_effect = rawget(rawget(self,'raw'),'spike_effect_effect') + return rawget(spike_effect_effect_strings,spike_effect_effect) or spike_effect_effect +end + +function action:get_spike_effect_animation_string() + local spike_effect_animation = rawget(rawget(self,'raw'),'spike_effect_animation') + return rawget(spike_effect_animation_strings,spike_effect_animation) or spike_effect_animation +end + +function action:get_additional_effect_conclusion() + return msg_id_to_conclusion(rawget(rawget(self,'raw'),'spike_effect_message')) +end + +--[[ +Copyright © 2013, Suji +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of actions nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SUJI 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. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/chat.lua b/Data/DefaultContent/Libraries/addons/addons/libs/chat.lua new file mode 100644 index 0000000..89b10cd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/chat.lua @@ -0,0 +1,118 @@ +--[[ + A collection of FFXI-specific chat/text functions and character/control database. +]] + +_libs = _libs or {} + +require('tables') +require('sets') +require('strings') + +local table, set, string = _libs.tables, _libs.sets, _libs.strings + +local chat = {} + +chat.colors = require('chat/colors') +chat.controls = require('chat/controls') + +_libs.chat = chat + +-- Returns a color from a given input. +local function make_color(col) + if type(col) == 'number' then + if col <= 0x000 or col == 0x100 or col == 0x101 or col > 0x1FF then + warning('Invalid color number '..col..'. Only numbers between 1 and 511 permitted, except 256 and 257.') + col = '' + elseif col <= 0xFF then + col = chat.controls.color1..string.char(col) + else + col = chat.controls.color2..string.char(col - 256) + end + else + if #col > 2 then + local cl = col + col = chat.colors[col] + if col == nil then + warning('Color \''..cl..'\' not found.') + col = '' + end + end + end + + return col +end + +local invalids = S{0, 256, 257} + +-- Returns str colored as specified by newcolor. If oldcolor is omitted, the string color will reset. +function string.color(str, new_color, reset_color) + if new_color == nil or invalids:contains(new_color) then + return str + end + + reset_color = reset_color or chat.controls.reset + + new_color = make_color(new_color) + reset_color = make_color(reset_color) + + return str:enclose(new_color, reset_color) +end + +-- Strips a string of all colors. +function string.strip_colors(str) + return (str:gsub('['..string.char(0x1E, 0x1F, 0x7F)..'].', '')) +end + +-- Strips a string of auto-translate tags. +function string.strip_auto_translate(str) + return (str:gsub(string.char(0xEF)..'['..string.char(0x27, 0x28)..']', '')) +end + +-- Strips a string of all colors and auto-translate tags. +function string.strip_format(str) + return str:strip_colors():strip_auto_translate() +end + +--[[ + The following functions are for text object strings, since they behave differently than chatlog strings. +]] + +-- Returns str colored as specified by (new_alpha, new_red, ...). If reset values are omitted, the string color will reset. +function string.text_color(str, new_red, new_green, new_blue, reset_red, reset_green, reset_blue) + if str == '' then + return str + end + + if reset_blue then + return chat.make_text_color(new_red, new_green, new_blue)..str..chat.make_text_color(reset_red, reset_green, reset_blue) + end + + return chat.make_text_color(new_red, new_green, new_blue)..str..'\\cr' +end + +-- Returns a color string in console format. +function chat.make_text_color(red, green, blue) + return '\\cs('..red..', '..green..', '..blue..')' +end + +-- Returns a string stripped of console formatting information. +function string.text_strip_format(str) + return (str:gsub('\\cs%(%s*%d+,%s*%d+,%s*%d+%s*%)(.-)', ''):gsub('\\cr', '')) +end + +chat.text_color_reset = '\\cr' + +return chat + +--[[ +Copyright © 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/chat/chars.lua b/Data/DefaultContent/Libraries/addons/addons/libs/chat/chars.lua new file mode 100644 index 0000000..1389356 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/chat/chars.lua @@ -0,0 +1,361 @@ +return { + -- Punctuation + comma = string.char(0x81, 0x41), + period = string.char(0x81, 0x42), + colon = string.char(0x81, 0x46), + semicolon = string.char(0x81, 0x47), + query = string.char(0x81, 0x48), + exclamation = string.char(0x81, 0x49), + macron = string.char(0x81, 0x50), -- + zero = string.char(0x81, 0x5A), + bar = string.char(0x81, 0x5B), -- ? + emdash = string.char(0x81, 0x5C), -- + endash = string.char(0x81, 0x5D), -- + slash = string.char(0x81, 0x5E), + bslash = string.char(0x81, 0x5F), + cdots = string.char(0x81, 0x63), -- centered vertically + dots = string.char(0x81, 0x64), -- .. centered vertically + + -- Typography + tilde = string.char(0x81, 0x3E), -- ~ + wave = string.char(0x81, 0x60), -- ? + ditto = string.char(0x81, 0x56), -- ? + amp = string.char(0x81, 0x95), + asterisk = string.char(0x81, 0x96), + at = string.char(0x81, 0x97), + underscore = string.char(0x81, 0x51), + dagger = string.char(0x81, 0xEE), -- + ddagger = string.char(0x81, 0xEF), -- + parallel = string.char(0x81, 0x61), -- || + pipe = string.char(0x81, 0x62), -- | + hash = string.char(0x81, 0x94), + section = string.char(0x81, 0x98), -- + ref = string.char(0x81, 0xA6), -- ? + post = string.char(0x81, 0xA7), -- ? + tie = string.char(0x81, 0xD1), -- ? + para = string.char(0x81, 0xF7), -- + + -- Quotes + lsquo = string.char(0x81, 0x65), -- + rsquo = string.char(0x81, 0x66), -- + ldquo = string.char(0x81, 0x67), -- + rdquo = string.char(0x81, 0x68), -- + + -- Brackets + lpar = string.char(0x81, 0x69), -- ( + rpar = string.char(0x81, 0x6A), -- ) + ltort = string.char(0x81, 0x6B), -- ? + rtort = string.char(0x81, 0x6C), -- ? + lbrack = string.char(0x81, 0x6D), -- [ + rbrack = string.char(0x81, 0x6E), -- ] + lbrace = string.char(0x81, 0x6F), -- { + rbrace = string.char(0x81, 0x70), -- } + lang = string.char(0x81, 0x71), -- < + rang = string.char(0x81, 0x72), -- > + ldangle = string.char(0x81, 0x73), -- + rdangle = string.char(0x81, 0x74), -- + lcorner = string.char(0x81, 0x75), -- ? + rcorner = string.char(0x81, 0x76), -- ? + lwcorner = string.char(0x81, 0x77), -- ? + rwcorner = string.char(0x81, 0x78), -- ? + lblent = string.char(0x81, 0x79), -- ? + rblent = string.char(0x81, 0x7A), -- ? + laquo = string.char(0x85, 0x6B), -- + raquo = string.char(0x85, 0x7B), -- + + -- Math (General) + plus = string.char(0x81, 0x7B), + minus = string.char(0x81, 0x7C), + plusminus = string.char(0x81, 0x7D), -- + times = string.char(0x81, 0x7E), -- + div = string.char(0x81, 0x80), -- + eq = string.char(0x81, 0x81), -- = + neq = string.char(0x81, 0x82), -- ? + lt = string.char(0x81, 0x83), + gt = string.char(0x81, 0x84), + leq = string.char(0x81, 0x85), -- ? + geq = string.char(0x81, 0x86), -- ? + ll = string.char(0x81, 0xD6), -- + gg = string.char(0x81, 0xD7), -- + root = string.char(0x81, 0xD8), -- v + inf = string.char(0x81, 0x87), -- 8 + prop = string.char(0x81, 0xE5), -- ? + ninf = string.char(0x81, 0xD9), -- open infinity in the middle + nearlyeq = string.char(0x81, 0xD5), -- ? + + -- Math (Sets) + ['in'] = string.char(0x81, 0xAD), -- ? + subseteq = string.char(0x81, 0xAE), -- ? + supseteq = string.char(0x81, 0xB0), -- ? + subset = string.char(0x81, 0xB1), -- ? + supset = string.char(0x81, 0xB2), -- ? + union = string.char(0x81, 0xB3), -- ? + intersect = string.char(0x81, 0xB4), -- n + + -- Math (Analysis) + nabla = string.char(0x81, 0xD3), -- ? + integral = string.char(0x81, 0xE7), -- ? + dintegral = string.char(0x81, 0xE8), -- ?? + + -- Math (Logical) + therefore = string.char(0x81, 0x88), -- ? + bc = string.char(0x81, 0xE6), -- ? + min = string.char(0x81, 0xB5), -- ? + max = string.char(0x81, 0xB6), -- ? + neg = string.char(0x81, 0xB7), -- + implies = string.char(0x81, 0xC3), -- ? + iff = string.char(0x81, 0xC4), -- ? + foreach = string.char(0x81, 0xC5), -- ? + exists = string.char(0x81, 0xC6), -- ? + bot = string.char(0x81, 0xD0), -- ? + part = string.char(0x81, 0xD2), -- ? + equiv = string.char(0x81, 0xD4), -- = + + -- Math (Fractions) + ['1div4'] = string.char(0x85, 0x7C), -- + ['1div2'] = string.char(0x85, 0x7D), -- + ['3div4'] = string.char(0x85, 0x7E), -- + + -- Math (Geometry) + degree = string.char(0x81, 0x8B), -- + arcmin = string.char(0x81, 0x8C), -- ' + arcsec = string.char(0x81, 0x8D), -- ? + angle = string.char(0x81, 0xC7), -- ? + rangle = string.char(0x87, 0x98), -- ? + lrtriangle = string.char(0x87, 0x99), -- ? + + -- Polygons + bstar = string.char(0x81, 0x99), -- ? + wstar = string.char(0x81, 0x9A), -- ? + brhombus = string.char(0x81, 0x9E), -- black rhombus + wrhombus = string.char(0x81, 0x9F), -- white rhombus + bsquare = string.char(0x81, 0xA0), -- black square + wsquare = string.char(0x81, 0xA1), -- white square + btriangle = string.char(0x81, 0xA2), -- black triagle + wtriangle = string.char(0x81, 0xA3), -- white triangle + bustriangle = string.char(0x81, 0xA4), -- black upside triangle + wustriangle = string.char(0x81, 0xA5), -- white upside triangle + + -- Circles + bcircle = string.char(0x81, 0x9B), -- black circle + wcircle = string.char(0x81, 0x9C), -- white circle + circlejot = string.char(0x81, 0x9D), -- circle in circle + + -- Arrows + rarr = string.char(0x81, 0xA8), -- ? + larr = string.char(0x81, 0xA9), -- ? + uarr = string.char(0x81, 0xAA), -- ? + darr = string.char(0x81, 0xAB), -- ? + + -- Financial + dollar = string.char(0x81, 0x90), -- $ + cent = string.char(0x81, 0x91), -- + pound = string.char(0x81, 0x92), -- + euro = string.char(0x85, 0x40), -- + yen = string.char(0x85, 0x65), -- + + -- Musical + sharp = string.char(0x81, 0xEB), -- ? + flat = string.char(0x81, 0xEC), -- ? + note = string.char(0x81, 0xED), -- ? + + -- Misc + male = string.char(0x81, 0x89), -- ? + female = string.char(0x81, 0x8A), -- ? + percent = string.char(0x81, 0x93), + permil = string.char(0x81, 0xEA), -- + circle = string.char(0x81, 0xF8), + cdegree = string.char(0x81, 0x8E), -- C + tm = string.char(0x85, 0x59), -- + copy = string.char(0x85, 0x69), -- + + -- Alphanumeric characters (Japanese) + j0 = string.char(0x82, 0x4F), + j1 = string.char(0x82, 0x50), + j2 = string.char(0x82, 0x51), + j3 = string.char(0x82, 0x52), + j4 = string.char(0x82, 0x53), + j5 = string.char(0x82, 0x54), + j6 = string.char(0x82, 0x55), + j7 = string.char(0x82, 0x56), + j8 = string.char(0x82, 0x57), + j9 = string.char(0x82, 0x58), + jA = string.char(0x82, 0x60), + jB = string.char(0x82, 0x61), + jC = string.char(0x82, 0x62), + jD = string.char(0x82, 0x63), + jE = string.char(0x82, 0x64), + jF = string.char(0x82, 0x65), + jG = string.char(0x82, 0x66), + jH = string.char(0x82, 0x67), + jI = string.char(0x82, 0x68), + jJ = string.char(0x82, 0x69), + jK = string.char(0x82, 0x6A), + jL = string.char(0x82, 0x6B), + jM = string.char(0x82, 0x6C), + jN = string.char(0x82, 0x6D), + jO = string.char(0x82, 0x6E), + jP = string.char(0x82, 0x6F), + jQ = string.char(0x82, 0x70), + jR = string.char(0x82, 0x71), + jS = string.char(0x82, 0x72), + jT = string.char(0x82, 0x73), + jU = string.char(0x82, 0x74), + jV = string.char(0x82, 0x75), + jW = string.char(0x82, 0x76), + jX = string.char(0x82, 0x77), + jY = string.char(0x82, 0x78), + jZ = string.char(0x82, 0x79), + ja = string.char(0x82, 0x81), + jb = string.char(0x82, 0x82), + jc = string.char(0x82, 0x83), + jd = string.char(0x82, 0x84), + je = string.char(0x82, 0x85), + jf = string.char(0x82, 0x86), + jg = string.char(0x82, 0x87), + jh = string.char(0x82, 0x88), + ji = string.char(0x82, 0x89), + jj = string.char(0x82, 0x8A), + jk = string.char(0x82, 0x8B), + jl = string.char(0x82, 0x8C), + jm = string.char(0x82, 0x8D), + jn = string.char(0x82, 0x8E), + jo = string.char(0x82, 0x8F), + jp = string.char(0x82, 0x90), + jq = string.char(0x82, 0x91), + jr = string.char(0x82, 0x92), + js = string.char(0x82, 0x93), + jt = string.char(0x82, 0x94), + ju = string.char(0x82, 0x95), + jv = string.char(0x82, 0x96), + jw = string.char(0x82, 0x97), + jx = string.char(0x82, 0x98), + jy = string.char(0x82, 0x99), + jz = string.char(0x82, 0x9A), + + -- Greek letters + Alpha = string.char(0x83, 0x97), + Beta = string.char(0x83, 0x98), + Gamma = string.char(0x83, 0x99), + Delta = string.char(0x83, 0x9A), + Epsilon = string.char(0x83, 0x9B), + Zeta = string.char(0x83, 0x9C), + Eta = string.char(0x83, 0x9D), + Theta = string.char(0x83, 0x9E), + Iota = string.char(0x83, 0xA7), + Kappa = string.char(0x83, 0xA8), + Lambda = string.char(0x83, 0xA9), + Mu = string.char(0x83, 0xAA), + Nu = string.char(0x83, 0xAB), + Xi = string.char(0x83, 0xAC), + Omicron = string.char(0x83, 0xAD), + Pi = string.char(0x83, 0xAE), + Rho = string.char(0x83, 0xAF), + Sigma = string.char(0x83, 0xB0), + Tau = string.char(0x83, 0xB1), + Upsilon = string.char(0x83, 0xB2), + Phi = string.char(0x83, 0xB3), + Chi = string.char(0x83, 0xB4), + Psi = string.char(0x83, 0xB5), + Omega = string.char(0x83, 0xB6), + alpha = string.char(0x83, 0xB7), + beta = string.char(0x83, 0xB8), + gamma = string.char(0x83, 0xB9), + delta = string.char(0x83, 0xBA), + epsilon = string.char(0x83, 0xBB), + zeta = string.char(0x83, 0xBC), + eta = string.char(0x83, 0xBD), + theta = string.char(0x83, 0xBE), + iota = string.char(0x83, 0xC7), + kappa = string.char(0x83, 0xC8), + lambda = string.char(0x83, 0xC9), + mu = string.char(0x83, 0xCA), + nu = string.char(0x83, 0xCB), + xi = string.char(0x83, 0xCC), + omicron = string.char(0x83, 0xCD), + pi = string.char(0x83, 0xCE), + rho = string.char(0x83, 0xCF), + sigma = string.char(0x83, 0xD0), + tau = string.char(0x83, 0xD1), + upsilon = string.char(0x83, 0xD2), + phi = string.char(0x83, 0xD3), + chi = string.char(0x83, 0xD4), + psi = string.char(0x83, 0xD5), + omega = string.char(0x83, 0xD6), + + -- lines + hline = string.char(0x84, 0x92), -- - + vline = string.char(0x84, 0x93), -- + tl = string.char(0x84, 0x94), -- + + tr = string.char(0x84, 0x95), -- + + br = string.char(0x84, 0x96), -- + + bl = string.char(0x84, 0x97), -- + + left = string.char(0x84, 0x98), -- + + top = string.char(0x84, 0x99), -- - + right = string.char(0x84, 0x9A), -- + bottom = string.char(0x84, 0x9B), -- - + middle = string.char(0x84, 0x9C), -- + + bhline = string.char(0x84, 0xAA), -- - + bvline = string.char(0x84, 0xAB), -- + btl = string.char(0x84, 0xAB), -- + + btr = string.char(0x84, 0xAC), -- + + bbr = string.char(0x84, 0xAD), -- + + bbl = string.char(0x84, 0xAE), -- + + bleft = string.char(0x84, 0xB0), -- + + btop = string.char(0x84, 0xB1), -- - + bright = string.char(0x84, 0xB2), -- + bbottom = string.char(0x84, 0xB3), -- - + bmiddle = string.char(0x84, 0xB4), -- + + + -- sup numbers 1-4 + sup0 = string.char(0x85, 0x7A), + sup1 = string.char(0x85, 0x79), + sup2 = string.char(0x85, 0x72), + sup3 = string.char(0x85, 0x73), + + -- circled numbers 1-20 + circle1 = string.char(0x87, 0x40), + circle2 = string.char(0x87, 0x41), + circle3 = string.char(0x87, 0x42), + circle4 = string.char(0x87, 0x43), + circle5 = string.char(0x87, 0x44), + circle6 = string.char(0x87, 0x45), + circle7 = string.char(0x87, 0x46), + circle8 = string.char(0x87, 0x47), + circle9 = string.char(0x87, 0x48), + circle10 = string.char(0x87, 0x49), + circle11 = string.char(0x87, 0x4A), + circle12 = string.char(0x87, 0x4B), + circle13 = string.char(0x87, 0x4C), + circle14 = string.char(0x87, 0x4D), + circle15 = string.char(0x87, 0x4E), + circle16 = string.char(0x87, 0x4F), + circle17 = string.char(0x87, 0x50), + circle18 = string.char(0x87, 0x51), + circle19 = string.char(0x87, 0x52), + circle20 = string.char(0x87, 0x53), + + -- roman numerals 1-10 + roman1 = string.char(0x87, 0x54), + roman2 = string.char(0x87, 0x55), + roman3 = string.char(0x87, 0x56), + roman4 = string.char(0x87, 0x57), + roman5 = string.char(0x87, 0x58), + roman6 = string.char(0x87, 0x59), + roman7 = string.char(0x87, 0x5A), + roman8 = string.char(0x87, 0x5B), + roman9 = string.char(0x87, 0x5C), + roman10 = string.char(0x87, 0x5D), + + -- abbreviations + mm = string.char(0x87, 0x6F), + cm = string.char(0x87, 0x70), + km = string.char(0x87, 0x71), + mg = string.char(0x87, 0x72), + kg = string.char(0x87, 0x73), + cc = string.char(0x87, 0x74), + m2 = string.char(0x87, 0x75), + no = string.char(0x87, 0x82), + kk = string.char(0x87, 0x83), + tel = string.char(0x87, 0x84), +} diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/chat/colors.lua b/Data/DefaultContent/Libraries/addons/addons/libs/chat/colors.lua new file mode 100644 index 0000000..8ab54c0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/chat/colors.lua @@ -0,0 +1,27 @@ +return { + -- Configurable game colors + say = string.char(0x1F, 0x01), -- Menu > Font Colors > Chat > Immediate vicinity ('Say') + tell = string.char(0x1F, 0x04), -- Menu > Font Colors > Chat > Tell target only ('Tell') + party = string.char(0x1F, 0x05), -- Menu > Font Colors > Chat > All party members ('Party') + linkshell = string.char(0x1F, 0x06), -- Menu > Font Colors > Chat > Linkshell group ('Linkshell') + emote = string.char(0x1F, 0x07), -- Menu > Font Colors > Chat > Emotes + message = string.char(0x1F, 0x11), -- Menu > Font Colors > Chat > Messages ('Message') + npc = string.char(0x1F, 0x8E), -- Menu > Font Colors > Chat > NPC Conversations + shout = string.char(0x1F, 0x02), -- Menu > Font Colors > Chat > Wide area ('Shout') + yell = string.char(0x1F, 0x03), -- Menu > Font Colors > Chat > Extremely wide area ('Yell') + selfheal = string.char(0x1F, 0x1E), -- Menu > Font Colors > For Self > HP/MP you recover + selfhurt = string.char(0x1F, 0x1C), -- Menu > Font Colors > For Self > HP/MP you loose + selfbuff = string.char(0x1F, 0x38), -- Menu > Font Colors > For Self > Beneficial effects you are granted + selfdebuff = string.char(0x1F, 0x39), -- Menu > Font Colors > For Self > Detrimental effects you receive + selfresist = string.char(0x1F, 0x3B), -- Menu > Font Colors > For Self > Effects you resist + selfevade = string.char(0x1F, 0x1D), -- Menu > Font Colors > For Self > Actions you evade + otherheal = string.char(0x1F, 0x16), -- Menu > Font Colors > For Others > HP/MP others recover + otherhurt = string.char(0x1F, 0x14), -- Menu > Font Colors > For Others > HP/MP others loose + otherbuff = string.char(0x1F, 0x3C), -- Menu > Font Colors > For Others > Beneficial effects others are granted + otherdebuff = string.char(0x1F, 0x3D), -- Menu > Font Colors > For Others > Detrimental effects others receive + otherresist = string.char(0x1F, 0x3F), -- Menu > Font Colors > For Others > Effects others resist + otherevade = string.char(0x1F, 0x15), -- Menu > Font Colors > For Others > Actions others evade + cfh = string.char(0x1F, 0x08), -- Menu > Font Colors > System > Calls for help + battle = string.char(0x1F, 0x32), -- Menu > Font Colors > System > Standard battle messages + system = string.char(0x1F, 0x79), -- Menu > Font Colors > System > Basic system messages +} diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/chat/controls.lua b/Data/DefaultContent/Libraries/addons/addons/libs/chat/controls.lua new file mode 100644 index 0000000..dc68aa3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/chat/controls.lua @@ -0,0 +1,5 @@ +return { + color1 = string.char(0x1F), + color2 = string.char(0x1E), + reset = string.char(0x1E, 0x01), +} diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/chat/icons.lua b/Data/DefaultContent/Libraries/addons/addons/libs/chat/icons.lua new file mode 100644 index 0000000..6e7c571 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/chat/icons.lua @@ -0,0 +1,19 @@ +return { + -- FFXI internal icons, all starting with \xEF + fire = string.char(0xEF, 0x1F), -- Elemental fire sign + ice = string.char(0xEF, 0x20), -- Elemental ice sign + wind = string.char(0xEF, 0x21), -- Elemental wind sign + earth = string.char(0xEF, 0x22), -- Elemental earth sign + lightning = string.char(0xEF, 0x23), -- Elemental lightning sign + water = string.char(0xEF, 0x24), -- Elemental water sign + light = string.char(0xEF, 0x25), -- Elemental light sign + darkness = string.char(0xEF, 0x26), -- Elemental darkness sign + atstart = string.char(0xEF, 0x27), -- Auto-translate, green beginning brace + atend = string.char(0xEF, 0x28), -- Auto-translate, red finishing brace + on = string.char(0xEF, 0x29), -- ON sign (as used in menus + off = string.char(0xEF, 0x2A), -- OFF sign + oui = string.char(0xEF, 0x2B), -- ON sign (French) + non = string.char(0xEF, 0x2C), -- OFF sign (French) + ein = string.char(0xEF, 0x2D), -- ON sign (German) + aus = string.char(0xEF, 0x2E), -- OFF sign (German) +} diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/config.lua b/Data/DefaultContent/Libraries/addons/addons/libs/config.lua new file mode 100644 index 0000000..7829c7f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/config.lua @@ -0,0 +1,513 @@ +--[[ + Functions that facilitate loading, parsing, manipulating and storing of config files. +]] + +_libs = _libs or {} + +require('tables') +require('sets') +require('lists') +require('strings') + +local table, set, list, string = _libs.tables, _libs.sets, _libs.lists, _libs.strings +local xml = require('xml') +local files = require('files') +local json = require('json') + +local config = {} + +_libs.config = config + +local error = error or print+{'Error:'} +local warning = warning or print+{'Warning:'} +local notice = notice or print+{'Notice:'} +local log = log or print + +-- Map for different config loads. +local settings_map = T{} + +--[[ Local functions ]] + +local parse +local merge +local settings_table +local settings_xml +local nest_xml +local table_diff + +-- Loads a specified file, or alternatively a file 'settings.xml' in the current addon/data folder. +function config.load(filepath, defaults) + if type(filepath) ~= 'string' then + filepath, defaults = 'data/settings.xml', filepath + end + + local confdict_mt = getmetatable(defaults) or _meta.T + local settings = setmetatable(table.copy(defaults or {}), {__class = 'Settings', __index = function(t, k) + if config[k] ~= nil then + return config[k] + end + + return confdict_mt.__index[k] + end}) + + -- Settings member variables, in separate struct + local meta = {} + meta.file = files.new(filepath, true) + meta.original = T{global = table.copy(settings)} + meta.chars = S{} + meta.comments = {} + meta.refresh = T{} + meta.cdata = S{} + + settings_map[settings] = meta + + -- Load addon config file (Windower/addon/<addonname>/data/settings.xml). + if not meta.file:exists() then + config.save(settings, 'all') + end + + return parse(settings) +end + +-- Reloads the settings for the provided table. Needs to be the same table that was assigned to with config.load. +function config.reload(settings) + if not settings_map[settings] then + error('Config reload error: unknown settings table.') + return + end + + parse(settings) + + for t in settings_map[settings].refresh:it() do + t.fn(settings, unpack(t.args)) + end +end + +-- Resolves to the correct parser and calls the respective subroutine, returns the parsed settings table. +function parse(settings) + local parsed = T{} + local err + local meta = settings_map[settings] + + if meta.file.path:endswith('.json') then + parsed = json.read(meta.file) + + elseif meta.file.path:endswith('.xml') then + parsed, err = xml.read(meta.file) + + if not parsed then + error(err or 'XML error: Unknown error.') + return settings + end + + parsed = settings_table(parsed, settings) + end + + -- Determine all characters found in the settings file. + meta.chars = parsed:keyset() - S{'global'} + meta.original = T{} + + if table.empty(settings) then + for char in (meta.chars + S{'global'}):it() do + meta.original[char] = table.update(table.copy(settings), parsed[char], true) + end + + local full_parsed = parsed.global + local player = windower.ffxi.get_player() + if player then + full_parsed = table.update(full_parsed, rawget(parsed, player.name:lower()), true) + end + + return settings:update(full_parsed, true) + end + + -- Update the global settings with the per-player defined settings, if they exist. Save the parsed value for later comparison. + for char in (meta.chars + S{'global'}):it() do + meta.original[char] = merge(table.copy(settings), parsed[char], char) + end + for char in meta.chars:it() do + meta.original[char] = table_diff(meta.original.global, meta.original[char]) or T{} + end + + local full_parsed = parsed.global + + local player = windower.ffxi.get_player() + if player then + full_parsed = table.update(full_parsed, rawget(parsed, player.name:lower()), true) + end + + return merge(settings, full_parsed) +end + +-- Merges two tables like update would, but retains type-information and tries to work around conflicts. +function merge(t, t_merge, path) + path = type(path) == 'string' and T{path} or path + + local keys = {} + for key in pairs(t) do + keys[tostring(key):lower()] = key + end + + if not t_merge then + return t + end + + for lkey, val in pairs(t_merge) do + local key = keys[lkey:lower()] + if not key then + if type(val) == 'table' then + t[lkey] = setmetatable(table.copy(val), getmetatable(val) or _meta.T) + else + t[lkey] = val + end + + else + local err = false + local oldval = rawget(t, key) + local oldtype = type(oldval) + + if oldtype == 'table' and type(val) == 'table' then + t[key] = merge(oldval, val, path and path:copy() + key or nil) + + elseif oldtype ~= type(val) then + if oldtype == 'table' then + if type(val) == 'string' then + -- Single-line CSV parser, can possible refactor this to tables.lua + local res = {} + local current = '' + local quote = false + local last + for c in val:gmatch('.') do + if c == ',' and not quote then + res[#res + 1] = current + current = '' + last = nil + elseif c == '"' then + if last == '"' then + current = current .. c + last = nil + else + last = '"' + end + + quote = not quote + else + current = current .. c + last = c + end + end + res[#res + 1] = current + + -- TODO: Remove this after a while, not standard compliant + -- Currently needed to not mess up existing settings + res = table.map(res, string.trim) + + if class then + if class(oldval) == 'Set' then + res = S(res) + elseif class(oldval) == 'List' then + res = L(res) + elseif class(oldval) == 'Table' then + res = T(res) + end + end + t[key] = res + + else + err = true + + end + + elseif oldtype == 'number' then + local testdec = tonumber(val) + local testhex = tonumber(val, 16) + if testdec then + t[key] = testdec + elseif testhex then + t[key] = testhex + else + err = true + end + + elseif oldtype == 'boolean' then + if val == 'true' then + t[key] = true + elseif val == 'false' then + t[key] = false + else + err = true + end + + elseif oldtype == 'string' then + if type(val) == 'table' and not next(val) then + t[key] = '' + else + t[key] = val + err = true + end + + else + err = true + end + + else + t[key] = val + end + + if err then + if path then + warning('Could not safely merge values for \'%s/%s\', %s expected (default: %s), got %s (%s).':format(path:concat('/'), key, class(oldval), tostring(oldval), class(val), tostring(val))) + end + t[key] = val + end + end + end + + return t +end + +-- Parses a settings struct from a DOM tree. +function settings_table(node, settings, key, meta) + settings = settings or T{} + key = key or 'settings' + meta = meta or settings_map[settings] + + local t = T{} + if node.type ~= 'tag' then + return t + end + + if not node.children:all(function(n) + return n.type == 'tag' or n.type == 'comment' + end) and not (#node.children == 1 and node.children[1].type == 'text') then + error('Malformatted settings file.') + return t + end + + -- TODO: Type checking necessary? merge should take care of that. + if #node.children == 1 and node.children[1].type == 'text' then + local val = node.children[1].value + if node.children[1].cdata then + meta.cdata:add(key) + return val + end + + if val:lower() == 'false' then + return false + elseif val:lower() == 'true' then + return true + end + + local num = tonumber(val) + if num ~= nil then + return num + end + + return val + end + + for child in node.children:it() do + if child.type == 'comment' then + meta.comments[key] = child.value:trim() + elseif child.type == 'tag' then + key = child.name:lower() + local childdict + if table.containskey(settings, key) then + childdict = table.copy(settings) + else + childdict = settings + end + t[child.name:lower()] = settings_table(child, childdict, key, meta) + end + end + + return t +end + +-- Writes the passed config table to the spcified file name. +-- char defaults to windower.ffxi.get_player().name. Set to "all" to apply to all characters. +function config.save(t, char) + if char ~= 'all' and not windower.ffxi.get_info().logged_in then + return + end + + char = (char or windower.ffxi.get_player().name):lower() + local meta = settings_map[t] + + if char == 'all' then + char = 'global' + elseif char ~= 'global' and not meta.chars:contains(char) then + meta.chars:add(char) + meta.original[char] = T{} + end + + meta.original[char]:update(t) + + if char == 'global' then + meta.original = T{global = meta.original.global} + meta.chars = S{} + else + meta.original.global:amend(meta.original[char], true) + meta.original[char] = table_diff(meta.original.global, meta.original[char]) or T{} + + if meta.original[char]:empty(true) then + meta.original[char] = nil + meta.chars:remove(char) + end + end + + meta.file:write(settings_xml(meta)) +end + +-- Returns the table containing only elements from t_new that are different from t and not nil. +function table_diff(t, t_new) + local res = T{} + local cmp + + for key, val in pairs(t_new) do + cmp = t[key] + if cmp ~= nil then + if type(cmp) ~= type(val) then + warning('Mismatched setting types for key \''..key..'\':', type(cmp), type(val)) + else + if type(val) == 'table' then + if class(val) == 'Set' or class(val) == 'List' then + if not cmp:equals(val) then + res[key] = val + end + elseif table.isarray(val) and table.isarray(cmp) then + if not table.equals(cmp, val) then + res[key] = val + end + else + res[key] = table_diff(cmp, val) + end + elseif cmp ~= val then + res[key] = val + end + end + end + end + + return not table.empty(res) and res or nil +end + +-- Converts a settings table to a XML representation. +function settings_xml(meta) + local lines = L{} + lines:append('<?xml version="1.1" ?>') + lines:append('<settings>') + + local chars = (meta.original:keyset() - S{'global'}):sort() + for char in (L{'global'} + chars):it() do + if char == 'global' and meta.comments.settings then + lines:append(' <!--') + local comment_lines = meta.comments.settings:split('\n') + for comment in comment_lines:it() do + lines:append(' %s':format(comment:trim())) + end + + lines:append(' -->') + end + + lines:append(' <%s>':format(char)) + lines:append(nest_xml(meta.original[char], meta)) + lines:append(' </%s>':format(char)) + end + + lines:append('</settings>') + lines:append('') + return lines:concat('\n') +end + +-- Converts a table to XML without headers using appropriate indentation and comment spacing. Used in settings_xml. +function nest_xml(t, meta, indentlevel) + indentlevel = indentlevel or 2 + local indent = (' '):rep(4*indentlevel) + + local inlines = T{} + local fragments = T{} + local maxlength = 0 -- For proper comment indenting + local keys = set.sort(table.keyset(t)) + local val + for _, key in ipairs(keys) do + val = t[key] + if type(val) == 'table' and not (class(val) == 'List' or class(val) == 'Set') then + fragments:append('%s<%s>':format(indent, key)) + if meta.comments[key] then + local c = '<!-- %s -->':format(meta.comments[key]:trim()):split('\n') + local pre = '' + for cstr in c:it() do + fragments:append('%s%s%s':format(indent, pre, cstr:trim())) + pre = '\t ' + end + end + fragments:append(nest_xml(val, meta, indentlevel + 1)) + fragments:append('%s</%s>':format(indent, key)) + + else + if class(val) == 'List' then + val = list.format(val, 'csv') + elseif class(val) == 'Set' then + val = set.format(val, 'csv') + elseif type(val) == 'table' then + val = table.format(val, 'csv') + elseif type(val) == 'string' and meta.cdata:contains(tostring(key):lower()) then + val = '<![CDATA[%s]]>':format(val) + else + val = tostring(val) + end + + if val == '' then + fragments:append('%s<%s />':format(indent, key)) + else + fragments:append('%s<%s>%s</%s>':format(indent, key, meta.cdata:contains(tostring(key):lower()) and val or val:xml_escape(), key)) + end + local length = fragments:last():length() - indent:length() + if length > maxlength then + maxlength = length + end + inlines[fragments:length()] = key + end + end + + for frag_key, key in pairs(inlines) do + if meta.comments[key] then + fragments[frag_key] = '%s%s<!-- %s -->':format(fragments[frag_key], ' ':rep(maxlength - fragments[frag_key]:trim():length() + 1), meta.comments[key]) + end + end + + return fragments:concat('\n') +end + +function config.register(settings, fn, ...) + local args = {...} + local key = tostring(args):sub(8) + settings_map[settings].refresh[key] = {fn=fn, args=args} + return key +end + +function config.unregister(settings, key) + settings_map[settings].refresh[key] = nil +end + +windower.register_event('load', 'logout', 'login', function() + for _, settings in settings_map:it() do + config.reload(settings) + end +end) + +return config + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/dialog.lua b/Data/DefaultContent/Libraries/addons/addons/libs/dialog.lua new file mode 100644 index 0000000..5598175 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/dialog.lua @@ -0,0 +1,227 @@ +-- This library was written to help find the ID of a known +-- action message corresponding to an entry in the dialog tables. +-- While the IDs can be collected in-game, they occasionally +-- change and would otherwise need to be manually updated. +-- It can also be used to find and decode an entry given the ID. + +-- Common parameters: +-- +-- dat: Either the entire content of the zone dialog DAT file or +-- a file descriptor. +-- i.e. either local dat = io.open('path/to/dialog/DAT', 'rb') +-- or dat = dat:read('*a') +-- The functions are expected to be faster when passed a string, +-- but will use less memory when receiving a file descriptor. +-- +-- entry: The string you are looking for. Whether or not the string +-- is expected to be encoded should be indicated in the parameter's +-- name. If you do not know the entire string, use dev.find_substring +-- and serialize the result. + +local xor = require('bit').bxor +local floor = require('math').floor +local string = require('string') +local find = string.find +local sub = string.sub +local gsub = string.gsub +local format = string.format +local char = string.char +local byte = string.byte +require('pack') +local unpack = string.unpack +local pack = string.pack + +local function decode(int) + return xor(int, 0x80808080) +end +local encode = decode + +local function binary_search(pos, dat, n) + local l, r, m = 1, n + while l < r do + m = floor((l + r) / 2) + if decode(unpack('<I', dat, 1 + 4 * m)) < pos then + -- offset given by mth ID < offset to string + l = m + 1 + else + r = m + end + end + return l - 2 -- we want the index to the left of where "pos" would be placed +end + +local function plain_text_gmatch(text, substring, n) + n = n or 1 + return function() + local head, tail = find(text, substring, n, true) + if head then n = head + 1 end + return head, tail + end +end + +local dialog = {} + +-- Returns the number of entries in the given dialog DAT file +function dialog.entry_count(dat) + if type(dat) == 'userdata' then + dat:seek('set', 4) + return decode(unpack('<I', dat:read(4))) / 4 + end + return decode(unpack('<I', dat, 5)) / 4 +end + +-- Returns an array-like table containing every ID which matched +-- the given entry. Note that the tables contain an enormous +-- number of duplicate entries. +function dialog.get_ids_matching_entry(dat, encoded_entry) + local res = {} + local n = 0 + if type(dat) == 'string' then + local last_offset = decode(unpack('<I', dat, 5)) + local start = 5 + for head, tail in plain_text_gmatch(dat, encoded_entry, last_offset) do + local encoded_pos = pack('<I', encode(head - 5)) + local offset = find(dat, encoded_pos, start, true) + if offset then + offset = offset - 1 + local next_pos + if offset > last_offset then + break + elseif offset == last_offset then + next_pos = #dat + 1 + else + next_pos = decode(unpack('<I', dat, offset + 5)) + 5 + end + + if next_pos - head == tail - head + 1 then + n = n + 1 + res[n] = (offset - 4) / 4 + end + start = offset + 1 + end + end + + elseif type(dat) == 'userdata' then + dat:seek('set', 4) + local offset = decode(unpack('<I', dat:read(4))) + local entry_count = offset / 4 + local entry_length = #encoded_entry + for i = 1, entry_count - 1 do + dat:seek('set', 4 * i + 4) + local next_offset = decode(unpack('<I', dat:read(4))) + if next_offset - offset == entry_length then + dat:seek('set', offset + 4) + if dat:read(entry_length) == encoded_entry then + n = n + 1 + res[n] = i - 1 + end + end + + offset = next_offset + end + local m = dat:seek('end') + if m - offset - 4 == entry_length then + dat:seek('set', offset + 4) + if dat:read(entry_length) == encoded_entry then + n = n + 1 + res[n] = entry_count - 1 + end + end + end + + return res +end + +-- Returns the encoded entry from a given dialog table. If you +-- want to decode the entry, use dialog.decode_string. +function dialog.get_entry(dat, id) + local entry_count, offset, next_offset + if type(dat) == 'string' then + entry_count = decode(unpack('<I', dat, 5)) / 4 + if id == entry_count - 1 then + offset = decode(unpack('<I', dat, 4 * id + 5)) + 5 + next_offset = #dat + 1 + else + offset, next_offset = unpack('<II', dat, 4 * id + 5) + offset, next_offset = decode(offset) + 5, decode(next_offset) + 5 + end + + return sub(dat, offset, next_offset - 1) + elseif type(dat) == 'userdata' then + dat:seek('set', 4) + entry_count = decode(unpack('<I', dat:read(4))) / 4 + dat:seek('set', 4 * id + 4) + if id == entry_count - 1 then + offset = decode(unpack('<I', dat:read(4))) + next_offset = dat:seek('end') + 1 + else + offset, next_offset = unpack('<II', dat:read(8)) + offset, next_offset = decode(offset), decode(next_offset) + end + + dat:seek('set', offset + 4) + return dat:read(next_offset - offset) + end +end + +-- Creates a serialized representation of a string which can +-- be copied and pasted into the contents of an addon. +function dialog.serialize(entry) + return 'string.char(' + .. sub(gsub(entry, '.', function(c) + return tostring(string.byte(c)) .. ',' + end), 1, -2) + ..')' +end + +function dialog.encode_string(s) + return gsub(s, '.', function(c) + return char(xor(byte(c), 0x80)) + end) +end + +dialog.decode_string = dialog.encode_string + +dialog.dev = {} + +-- Returns the hex offset of the dialog entry with the given ID. +-- May be useful if you are viewing the file in a hex editor. +function dialog.dev.get_offset(dat, id) + local offset + if type(dat) == 'string' then + offset = unpack('<I', dat, 5 + 4 * id) + elseif type(dat) == 'userdata' then + dat:seek('set', 4 * id + 4) + offset = unpack('<I', dat:read(4)) + end + return format('0x%08X', decode(offset)) +end + +-- This function is intended to be used only during development +-- to find the ID of a dialog entry given a substring. +-- This is necessary because SE uses certain bytes to indicate +-- things like placeholders or pauses and it is unlikely you +-- will know the entire content of the entry you're looking for +-- from the get-go. +-- Returns an array-like table which contains the ID of every entry +-- containing a given substring. +function dialog.dev.find_substring(dat, unencoded_string) + local last_offset = decode(unpack('<I', dat, 5)) + 5 + local res = {} + -- local pos = find(dat, unencoded_string), last_offset, true) + local n = 0 + for i in plain_text_gmatch(dat, dialog.encode_string(unencoded_string), last_offset) do + n = n + 1 + res[n] = i + end + if n == 0 then print('No results for ', unencoded_string) return end + local entry_count = (last_offset - 5) / 4 + for i = 1, n do + res[i] = binary_search(res[i] - 1, dat, entry_count) + end + + return res +end + +return dialog + diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/extdata.lua b/Data/DefaultContent/Libraries/addons/addons/libs/extdata.lua new file mode 100644 index 0000000..0537a43 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/extdata.lua @@ -0,0 +1,2246 @@ +-- Extdata lib first pass + +_libs = _libs or {} + +require('tables') +require('strings') +require('functions') +require('pack') + +local table, string, functions = _libs.tables, _libs.strings, _libs.functions +local math = require('math') +local res = require('resources') + +-- MASSIVE LOOKUP TABLES AND OTHER CONSTANTS + +local decode = {} + +potencies = { + zeros = {[0]=0,[1]=0,[2]=0,[3]=0,[4]=0,[5]=0,[6]=0,[7]=0,[8]=0,[9]=0,[10]=0,[11]=0,[12]=0,[13]=0,[14]=0,[15]=0}, + family = { + attack = {[0]=4,[1]=5,[2]=6,[3]=7,[4]=8,[5]=9,[6]=10,[7]=12,[8]=12,[9]=12,[10]=12,[11]=12,[12]=12,[13]=12,[14]=12,[15]=12}, -- Atk and RAtk + defense = {[0]=2,[1]=4,[2]=6,[3]=8,[4]=10,[5]=12,[6]=15,[7]=18,[8]=18,[9]=18,[10]=18,[11]=18,[12]=18,[13]=18,[14]=18,[15]=18}, + accuracy = {[0]=2,[1]=3,[2]=4,[3]=5,[4]=7,[5]=9,[6]=12,[7]=15,[8]=15,[9]=15,[10]=15,[11]=15,[12]=15,[13]=15,[14]=15,[15]=15}, -- Acc, RAcc, and MEva + evasion = {[0]=3,[1]=4,[2]=5,[3]=6,[4]=8,[5]=10,[6]=13,[7]=16,[8]=16,[9]=16,[10]=16,[11]=16,[12]=16,[13]=16,[14]=16,[15]=16}, + magic_bonus = {[0]=1,[1]=1,[2]=1,[3]=2,[4]=2,[5]=3,[6]=4,[7]=6,[8]=6,[9]=6,[10]=6,[11]=6,[12]=6,[13]=6,[14]=6,[15]=6}, -- MAB and MDB + magic_accuracy = {[0]=2,[1]=2,[2]=3,[3]=4,[4]=5,[5]=6,[6]=7,[7]=9,[8]=9,[9]=9,[10]=9,[11]=9,[12]=9,[13]=9,[14]=9,[15]=9}, + }, + sp_recast = {[0]=-1,[1]=-1,[2]=-1,[3]=-2,[4]=-2,[5]=-3,[6]=-3,[7]=-4,[8]=-4,[9]=-4,[10]=-4,[11]=-4,[12]=-4,[13]=-4,[14]=-4,[15]=-4}, + } + +sp_390_augments = { + [553] = {{stat="Occ. atk. twice", offset=0}}, + [555] = {{stat="Occ. atk. twice", offset=0}}, + [556] = {{stat="Occ. atk. 2-3 times", offset=0}}, + [557] = {{stat="Occ. atk. 2-4 times", offset=0}}, + [558] = {{stat="Occ. deals dbl. dmg.", offset=0}}, + [563] = {{stat="Movement speed +8%", offset=0}}, + [593] = {{stat="Fire Affinity +1", offset=0}}, + [594] = {{stat="Ice Affinity +1", offset=0}}, + [595] = {{stat="Wind Affinity +1", offset=0}}, + [596] = {{stat="Earth Affinity +1", offset=0}}, + [597] = {{stat="Lightning Affinity +1", offset=0}}, + [598] = {{stat="Water Affinity +1", offset=0}}, + [599] = {{stat="Light Affinity +1", offset=0}}, + [600] = {{stat="Dark Affinity +1", offset=0}}, + [601] = {{stat="Fire Affinity: Magic Accuracy +1", offset=0}}, + [602] = {{stat="Ice Affinity: Magic Accuracy +1", offset=0}}, + [603] = {{stat="Wind Affinity: Magic Accuracy +1", offset=0}}, + [604] = {{stat="Earth Affinity: Magic Accuracy +1", offset=0}}, + [605] = {{stat="Lightning Affinity: Magic Accuracy +1", offset=0}}, + [606] = {{stat="Water Affinity: Magic Accuracy +1", offset=0}}, + [607] = {{stat="Light Affinity: Magic Accuracy +1", offset=0}}, + [608] = {{stat="Dark Affinity: Magic Accuracy +1", offset=0}}, + } + + +augment_values = { + [1] = { + [0x000] = {{stat="none",offset=0}}, + [0x001] = {{stat="HP", offset=1}}, + [0x002] = {{stat="HP", offset=33}}, + [0x003] = {{stat="HP", offset=65}}, + [0x004] = {{stat="HP", offset=97}}, + [0x005] = {{stat="HP", offset=1,multiplier=-1}}, + [0x006] = {{stat="HP", offset=33,multiplier=-1}}, + [0x007] = {{stat="HP", offset=65,multiplier=-1}}, + [0x008] = {{stat="HP", offset=97,multiplier=-1}}, + [0x009] = {{stat="MP", offset=1}}, + [0x00A] = {{stat="MP", offset=33}}, + [0x00B] = {{stat="MP", offset=65}}, + [0x00C] = {{stat="MP", offset=97}}, + [0x00D] = {{stat="MP", offset=1,multiplier=-1}}, + [0x00E] = {{stat="MP", offset=33,multiplier=-1}}, + [0x00F] = {{stat="MP", offset=65,multiplier=-1}}, + [0x010] = {{stat="MP", offset=97,multiplier=-1}}, + [0x011] = {{stat="HP", offset=1}, {stat="MP", offset=1}}, + [0x012] = {{stat="HP", offset=33}, {stat="MP", offset=33}}, + [0x013] = {{stat="HP", offset=1}, {stat="MP", offset=1,multiplier=-1}}, + [0x014] = {{stat="HP", offset=33}, {stat="MP", offset=33,multiplier=-1}}, + [0x015] = {{stat="HP", offset=1,multiplier=-1}, {stat="MP", offset=1}}, + [0x016] = {{stat="HP", offset=33,multiplier=-1}, {stat="MP", offset=33}}, + [0x017] = {{stat="Accuracy", offset=1}}, + [0x018] = {{stat="Accuracy", offset=1,multiplier=-1}}, + [0x019] = {{stat="Attack", offset=1}}, + [0x01A] = {{stat="Attack", offset=1,multiplier=-1}}, + [0x01B] = {{stat="Rng.Acc.", offset=1}}, + [0x01C] = {{stat="Rng.Acc.", offset=1,multiplier=-1}}, + [0x01D] = {{stat="Rng.Atk.", offset=1}}, + [0x01E] = {{stat="Rng.Atk.", offset=1,multiplier=-1}}, + [0x01F] = {{stat="Evasion", offset=1}}, + [0x020] = {{stat="Evasion", offset=1,multiplier=-1}}, + [0x021] = {{stat="DEF", offset=1}}, + [0x022] = {{stat="DEF", offset=1,multiplier=-1}}, + [0x023] = {{stat="Mag. Acc.", offset=1}}, + [0x024] = {{stat="Mag. Acc.", offset=1,multiplier=-1}}, + [0x025] = {{stat="Mag. Evasion", offset=1}}, + [0x026] = {{stat="Mag. Evasion", offset=1,multiplier=-1}}, + [0x027] = {{stat="Enmity", offset=1}}, + [0x028] = {{stat="Enmity", offset=1,multiplier=-1}}, + [0x029] = {{stat="Crit.hit rate", offset=1}}, + [0x02A] = {{stat="Enemy crit. hit rate ", offset=1,multiplier=-1}}, + [0x02B] = {{stat='"Charm"', offset=1}}, + [0x02C] = {{stat='"Store TP"', offset=1}, {stat='"Subtle Blow"', offset=1}}, + [0x02D] = {{stat="DMG:", offset=1}}, + [0x02E] = {{stat="DMG:", offset=1,multiplier=-1}}, + [0x02F] = {{stat="Delay:", offset=1,percent=true}}, + [0x030] = {{stat="Delay:", offset=1,multiplier=-1,percent=true}}, + [0x031] = {{stat="Haste", offset=1}}, + [0x032] = {{stat='"Slow"', offset=1}}, + [0x033] = {{stat="HP recovered while healing ", offset=1}}, + [0x034] = {{stat="MP recovered while healing ", offset=1}}, + [0x035] = {{stat="Spell interruption rate down ", offset=1,multiplier=-1,percent=true}}, + [0x036] = {{stat="Phys. dmg. taken ", offset=1,multiplier=-1,percent=true}}, + [0x037] = {{stat="Magic dmg. taken ", offset=1,multiplier=-1,percent=true}}, + [0x038] = {{stat="Breath dmg. taken ", offset=1,multiplier=-1,percent=true}}, + [0x039] = {{stat="Magic crit. hit rate ", offset=1}}, + [0x03A] = {{stat='"Mag.Def.Bns."', offset=1,multiplier=-1}}, + [0x03B] = {{stat='Latent effect: "Regain"', offset=1}}, + [0x03C] = {{stat='Latent effect: "Refresh"', offset=1}}, + [0x03D] = {{stat="Occ. inc. resist. to stat. ailments ", offset=1}}, + [0x03E] = {{stat="Accuracy", offset=33}}, + [0x03F] = {{stat="Rng.Acc.", offset=33}}, + [0x040] = {{stat="Mag. Acc.", offset=33}}, + [0x041] = {{stat="Attack", offset=33}}, + [0x042] = {{stat="Rng.Atk.", offset=33}}, + [0x043] = {{stat="All Songs", offset=1}}, + [0x044] = {{stat="Accuracy", offset=1},{stat="Attack", offset=1}}, + [0x045] = {{stat="Rng.Acc.", offset=1},{stat="Rng.Atk.", offset=1}}, + [0x046] = {{stat="Mag. Acc.", offset=1},{stat='"Mag.Atk.Bns."', offset=1}}, + [0x047] = {{stat="Damage taken", offset=1,multiplier=-1,percent=true}}, + + [0x04A] = {{stat="Cap. Point", offset=1,percent=true}}, + [0x04B] = {{stat="Cap. Point", offset=33,percent=true}}, + [0x04C] = {{stat="DMG:", offset=33}}, + [0x04D] = {{stat="Delay:", offset=33,multiplier=-1,percent=true}}, + [0x04E] = {{stat="HP", offset=1,multiplier=2}}, + [0x04F] = {{stat="HP", offset=1,multiplier=3}}, + [0x050] = {{stat="Mag. Acc", offset=1}, {stat="/Mag. Dmg.", offset=1}}, + [0x051] = {{stat="Eva.", offset=1}, {stat="/Mag. Eva.", offset=1}}, + [0x052] = {{stat="MP", offset=1,multiplier=2}}, + [0x053] = {{stat="MP", offset=1,multiplier=3}}, + + + -- Need to figure out how to handle this section. The Pet: prefix is only used once despite how many augments are used. + [0x060] = {{stat="Pet: Accuracy", offset=1}, {stat="Pet: Rng. Acc.", offset=1}}, -- Pet: Accuracy+5 Rng.Acc.+5 + [0x061] = {{stat="Pet: Attack", offset=1}, {stat="Pet: Rng.Atk.", offset=1}}, -- Pet: Attack +5 Rng.Atk.+5 + [0x062] = {{stat="Pet: Evasion", offset=1}}, + [0x063] = {{stat="Pet: DEF", offset=1}}, + [0x064] = {{stat="Pet: Mag. Acc.", offset=1}}, + [0x065] = {{stat='Pet: "Mag.Atk.Bns."', offset=1}}, + [0x066] = {{stat="Pet: Crit.hit rate ", offset=1}}, + [0x067] = {{stat="Pet: Enemy crit. hit rate ", offset=1,multiplier=-1}}, + [0x068] = {{stat="Pet: Enmity", offset=1}}, + [0x069] = {{stat="Pet: Enmity", offset=1,multiplier=-1}}, + [0x06A] = {{stat="Pet: Accuracy", offset=1}, {stat="Pet: Rng. Acc.", offset=1}}, + [0x06B] = {{stat="Pet: Attack", offset=1}, {stat="Pet: Rng.Atk.", offset=1}}, + [0x06C] = {{stat="Pet: Mag. Acc.", offset=1}, {stat='Pet: "Mag.Atk.Bns."', offset=1}}, + [0x06D] = {{stat='Pet: "Dbl.Atk."', offset=1}, {stat="Pet: Crit.hit rate ", offset=1}}, + [0x06E] = {{stat='Pet: "Regen"', offset=1}}, + [0x06F] = {{stat="Pet: Haste", offset=1}}, + [0x070] = {{stat="Pet: Damage taken ", offset=1,multiplier=-1,percent=true}}, + [0x071] = {{stat="Pet: Rng.Acc.", offset=1}}, + [0x072] = {{stat="Pet: Rng.Atk.", offset=1}}, + [0x073] = {{stat='Pet: "Store TP"', offset=1}}, + [0x074] = {{stat='Pet: "Subtle Blow"', offset=1}}, + [0x075] = {{stat="Pet: Mag. Evasion", offset=1}}, + [0x076] = {{stat="Pet: Phys. dmg. taken ", offset=1,multiplier=-1,percent=true}}, + [0x077] = {{stat='Pet: "Mag.Def.Bns."', offset=1}}, + [0x078] = {{stat='Avatar: "Mag.Atk.Bns."', offset=1}}, + [0x079] = {{stat='Pet: Breath', offset=1}}, + [0x07A] = {{stat='Pet: TP Bonus', offset=1, multiplier=20}}, + [0x07B] = {{stat='Pet: "Dbl. Atk."', offset=1}}, + [0x07C] = {{stat="Pet: Acc.", offset=1}, {stat="Pet: R.Acc.", offset=1}, {stat="Pet: Atk.", offset=1}, {stat="Pet: R.Atk.", offset=1}}, + [0x07D] = {{stat="Pet: M.Acc.", offset=1}, {stat="Pet: M.Dmg.", offset=1}}, + [0x07E] = {{stat='Pet: Magic Damage', offset=1}}, + [0x07F] = {{stat="Pet: Magic dmg. taken ", offset=1,multiplier=-1,percent=true}}, + + + [0x080] = {{stat="Pet:",offset = 0}}, + --[0x081: Accuracy +1 Ranged Acc. +0 | value + 1 + --[0x082: Attack +1 Ranged Atk. +0 | value + 1 + --[0x083: Mag. Acc. +1 "Mag.Atk.Bns."+0 | value + 1 + --[0x084: "Double Atk."+1 "Crit. hit +0 | value + 1 + + --0x080~0x084 are pet augs with a pair of stats with 0x080 being just "Pet:" + --the second stat starts at 0. the previous pet augs add +2. the first previous non pet aug adds +2. any other non pet aug will add +1. + --any aug >= 0x032 will be added after the pet stack and will not be counted to increase the 2nd pet's aug stat and will be prolly assigned to the pet. + --https://gist.github.com/giulianoriccio/6df4fbd1f2a166fed041/raw/4e1d1103e7fe0e69d25f8264387506b5e38296a7/augs + + -- Byrth's note: These augments are just weird and I have no evidence that SE actually uses them. + -- The first argument of the augment has its potency calculated normally (using the offset). The second argument + -- has its potency calculated using an offset equal to 2*its position in the augment list (re-ordered from biggest to lowest IDs) + -- So having 0x80 -> 0x81 -> 0x82 results in the same augments as 0x80 -> 0x82 -> 0x81 + -- In that case, Acc/Atk would be determined by the normal offset, but Racc would be +2 and RAtk would be +4 + + [0x085] = {{stat='"Mag.Atk.Bns."', offset=1}}, + [0x086] = {{stat='"Mag.Def.Bns."', offset=1}}, + [0x087] = {{stat="Avatar:",offset=0}}, + + [0x089] = {{stat='"Regen"', offset=1}}, + [0x08A] = {{stat='"Refresh"', offset=1}}, + [0x08B] = {{stat='"Rapid Shot"', offset=1}}, + [0x08C] = {{stat='"Fast Cast"', offset=1}}, + [0x08D] = {{stat='"Conserve MP"', offset=1}}, + [0x08E] = {{stat='"Store TP"', offset=1}}, + [0x08F] = {{stat='"Dbl.Atk."', offset=1}}, + [0x090] = {{stat='"Triple Atk."', offset=1}}, + [0x091] = {{stat='"Counter"', offset=1}}, + [0x092] = {{stat='"Dual Wield"', offset=1}}, + [0x093] = {{stat='"Treasure Hunter"', offset=1}}, + [0x094] = {{stat='"Gilfinder"', offset=1}}, + + [0x097] = {{stat='"Martial Arts"', offset=1}}, + + [0x099] = {{stat='"Shield Mastery"', offset=1}}, + + [0x0B0] = {{stat='"Resist Sleep"', offset=1}}, + [0x0B1] = {{stat='"Resist Poison"', offset=1}}, + [0x0B2] = {{stat='"Resist Paralyze"', offset=1}}, + [0x0B3] = {{stat='"Resist Blind"', offset=1}}, + [0x0B4] = {{stat='"Resist Silence"', offset=1}}, + [0x0B5] = {{stat='"Resist Petrify"', offset=1}}, + [0x0B6] = {{stat='"Resist Virus"', offset=1}}, + [0x0B7] = {{stat='"Resist Curse"', offset=1}}, + [0x0B8] = {{stat='"Resist Stun"', offset=1}}, + [0x0B9] = {{stat='"Resist Bind"', offset=1}}, + [0x0BA] = {{stat='"Resist Gravity"', offset=1}}, + [0x0BB] = {{stat='"Resist Slow"', offset=1}}, + [0x0BC] = {{stat='"Resist Charm"', offset=1}}, + + [0x0C2] = {{stat='"Kick Attacks"', offset=1}}, + [0x0C3] = {{stat='"Subtle Blow"', offset=1}}, + + [0x0C6] = {{stat='"Zanshin"', offset=1}}, + + [0x0D3] = {{stat='"Snapshot"', offset=1}}, + [0x0D4] = {{stat='"Recycle"', offset=1}}, + + [0x0D7] = {{stat='"Ninja tool expertise"', offset=1}}, + + [0x0E9] = {{stat='"Blood Boon"', offset=1}}, + + [0x0ED] = {{stat='"Occult Acumen"', offset=1}}, + + [0x101] = {{stat="Hand-to-Hand skill ", offset=1}}, + [0x102] = {{stat="Dagger skill ", offset=1}}, + [0x103] = {{stat="Sword skill ", offset=1}}, + [0x104] = {{stat="Great Sword skill ", offset=1}}, + [0x105] = {{stat="Axe skill ", offset=1}}, + [0x106] = {{stat="Great Axe skill ", offset=1}}, + [0x107] = {{stat="Scythe skill ", offset=1}}, + [0x108] = {{stat="Polearm skill ", offset=1}}, + [0x109] = {{stat="Katana skill ", offset=1}}, + [0x10A] = {{stat="Great Katana skill ", offset=1}}, + [0x10B] = {{stat="Club skill ", offset=1}}, + [0x10C] = {{stat="Staff skill ", offset=1}}, + + [0x116] = {{stat="Melee skill ", offset=1}}, -- Automaton + [0x117] = {{stat="Ranged skill ", offset=1}}, -- Automaton + [0x118] = {{stat="Magic skill ", offset=1}}, -- Automaton + [0x119] = {{stat="Archery skill ", offset=1}}, + [0x11A] = {{stat="Marksmanship skill ", offset=1}}, + [0x11B] = {{stat="Throwing skill ", offset=1}}, + + [0x11E] = {{stat="Shield skill ", offset=1}}, + + [0x120] = {{stat="Divine magic skill ", offset=1}}, + [0x121] = {{stat="Healing magic skill ", offset=1}}, + [0x122] = {{stat="Enha.mag. skill ", offset=1}}, + [0x123] = {{stat="Enfb.mag. skill ", offset=1}}, + [0x124] = {{stat="Elem. magic skill ", offset=1}}, + [0x125] = {{stat="Dark magic skill ", offset=1}}, + [0x126] = {{stat="Summoning magic skill ", offset=1}}, + [0x127] = {{stat="Ninjutsu skill ", offset=1}}, + [0x128] = {{stat="Singing skill ", offset=1}}, + [0x129] = {{stat="String instrument skill ", offset=1}}, + [0x12A] = {{stat="Wind instrument skill ", offset=1}}, + [0x12B] = {{stat="Blue Magic skill ", offset=1}}, + [0x12C] = {{stat="Geomancy Skill ", offset=1}}, + [0x12D] = {{stat="Handbell Skill ", offset=1}}, + + [0x140] = {{stat='"Blood Pact" ability delay ', offset=1,multiplier=-1}}, + [0x141] = {{stat='"Avatar perpetuation cost" ', offset=1,multiplier=-1}}, + [0x142] = {{stat="Song spellcasting time ", offset=1,multiplier=-1,percent=true}}, + [0x143] = {{stat='"Cure" spellcasting time ', offset=1,multiplier=-1,percent=true}}, + [0x144] = {{stat='"Call Beast" ability delay ', offset=1,multiplier=-1}}, + [0x145] = {{stat='"Quick Draw" ability delay ', offset=1,multiplier=-1}}, + [0x146] = {{stat="Weapon Skill Acc.", offset=1}}, + [0x147] = {{stat="Weapon skill damage ", offset=1,percent=true}}, + [0x148] = {{stat="Crit. hit damage ", offset=1,percent=true}}, + [0x149] = {{stat='"Cure" potency ', offset=1,percent=true}}, + [0x14A] = {{stat='"Waltz" potency ', offset=1,percent=true}}, + [0x14B] = {{stat='"Waltz" ability delay ', offset=1,multiplier=-1}}, + [0x14C] = {{stat="Sklchn.dmg.", offset=1,percent=true}}, + [0x14D] = {{stat='"Conserve TP"', offset=1}}, + [0x14E] = {{stat="Magic burst dmg.", offset=1,percent=true}}, + [0x14F] = {{stat="Mag. crit. hit dmg. ", offset=1,percent=true}}, + [0x150] = {{stat='"Sic" and "Ready" ability delay ', offset=1,multiplier=-1}}, + [0x151] = {{stat="Song recast delay ", offset=1,multiplier=-1}}, + [0x152] = {{stat='"Barrage"', offset=1}}, + [0x153] = {{stat='"Elemental Siphon"', offset=1, multiplier=5}}, + [0x154] = {{stat='"Phantom Roll" ability delay ', offset=1,multiplier=-1}}, + [0x155] = {{stat='"Repair" potency ', offset=1,percent=true}}, + [0x156] = {{stat='"Waltz" TP cost ', offset=1,multiplier=-1}}, + [0x157] = {{stat='"Drain" and "Aspir" potency ', offset=1}}, + + [0x15E] = {{stat="Occ. maximizes magic accuracy ", offset=1,percent=true}}, + [0x15F] = {{stat="Occ. quickens spellcasting ", offset=1,percent=true}}, + [0x160] = {{stat="Occ. grants dmg. bonus based on TP ", offset=1,percent=true}}, + [0x161] = {{stat="TP Bonus ", offset=1, multiplier=50}}, + [0x162] = {{stat="Quadruple Attack ", offset=1}}, + + [0x164] = {{stat='Potency of "Cure" effect received', offset=1, percent=true}}, + + [0x168] = {{stat="Save TP ", offset=1, multiplier=10}}, + + [0x16A] = {{stat="Magic Damage ", offset=1}}, + [0x16B] = {{stat="Chance of successful block ", offset=1}}, + [0x16E] = {{stat="Blood Pact ab. del. II ", offset=1, multiplier=-1}}, + [0x170] = {{stat="Phalanx ", offset=1}}, + [0x171] = {{stat="Blood Pact Dmg.", offset=1}}, + [0x172] = {{stat='"Rev. Flourish"', offset=1}}, + [0x173] = {{stat='"Regen" potency', offset=1}}, + [0x174] = {{stat='"Embolden"', offset=1}}, + -- Empties are Numbered up to 0x17F. Their stat is their index + 1 + [0x200] = {{stat="STR", offset=1}}, + [0x201] = {{stat="DEX", offset=1}}, + [0x202] = {{stat="VIT", offset=1}}, + [0x203] = {{stat="AGI", offset=1}}, + [0x204] = {{stat="INT", offset=1}}, + [0x205] = {{stat="MND", offset=1}}, + [0x206] = {{stat="CHR", offset=1}}, + [0x207] = {{stat="STR", offset=1,multiplier=-1}}, + [0x208] = {{stat="DEX", offset=1,multiplier=-1}}, + [0x209] = {{stat="VIT", offset=1,multiplier=-1}}, + [0x20A] = {{stat="AGI", offset=1,multiplier=-1}}, + [0x20B] = {{stat="INT", offset=1,multiplier=-1}}, + [0x20C] = {{stat="MND", offset=1,multiplier=-1}}, + [0x20D] = {{stat="CHR", offset=1,multiplier=-1}}, + -- The below values aren't really right + -- They need to be "Ceiling'd" + [0x20E] = {{stat="STR", offset=1}, {stat="DEX", offset=1, multiplier=-0.5}, {stat="VIT", offset=1, multiplier=-0.5}}, + [0x20F] = {{stat="STR", offset=1}, {stat="DEX", offset=1, multiplier=-0.5}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x210] = {{stat="STR", offset=1}, {stat="VIT", offset=1, multiplier=-0.5}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x211] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="DEX", offset=1}, {stat="VIT", offset=1, multiplier=-0.5}}, + [0x212] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="DEX", offset=1}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x213] = {{stat="DEX", offset=1}, {stat="VIT", offset=1, multiplier=-0.5}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x214] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="DEX", offset=1, multiplier=-0.5}, {stat="VIT", offset=1}}, + [0x215] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="VIT", offset=1}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x216] = {{stat="DEX", offset=1, multiplier=-0.5}, {stat="VIT", offset=1}, {stat="AGI", offset=1, multiplier=-0.5}}, + [0x217] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="DEX", offset=1, multiplier=-0.5}, {stat="AGI", offset=1}}, + [0x218] = {{stat="STR", offset=1, multiplier=-0.5}, {stat="VIT", offset=1, multiplier=-0.5}, {stat="AGI", offset=1}}, + [0x219] = {{stat="DEX", offset=1, multiplier=-0.5}, {stat="VIT", offset=1, multiplier=-0.5}, {stat="AGI", offset=1}}, + [0x21A] = {{stat="AGI", offset=1}, {stat="INT", offset=1, multiplier=-0.5}, {stat="MND", offset=1, multiplier=-0.5}}, + [0x21B] = {{stat="AGI", offset=1}, {stat="INT", offset=1, multiplier=-0.5}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x21C] = {{stat="AGI", offset=1}, {stat="MND", offset=1, multiplier=-0.5}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x21D] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="INT", offset=1}, {stat="MND", offset=1, multiplier=-0.5}}, + [0x21E] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="INT", offset=1}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x21F] = {{stat="INT", offset=1}, {stat="MND", offset=1, multiplier=-0.5}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x220] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="INT", offset=1, multiplier=-0.5}, {stat="MND", offset=1}}, + [0x221] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="MND", offset=1}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x222] = {{stat="INT", offset=1, multiplier=-0.5}, {stat="MND", offset=1}, {stat="CHR", offset=1, multiplier=-0.5}}, + [0x223] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="INT", offset=1, multiplier=-0.5}, {stat="CHR", offset=1}}, + [0x224] = {{stat="AGI", offset=1, multiplier=-0.5}, {stat="MND", offset=1, multiplier=-0.5}, {stat="CHR", offset=1}}, + [0x225] = {{stat="INT", offset=1, multiplier=-0.5}, {stat="MND", offset=1, multiplier=-0.5}, {stat="CHR", offset=1}}, + [0x226] = {{stat="STR", offset=1}, {stat="DEX", offset=1}}, + [0x227] = {{stat="STR", offset=1}, {stat="VIT", offset=1}}, + [0x228] = {{stat="STR", offset=1}, {stat="AGI", offset=1}}, + [0x229] = {{stat="DEX", offset=1}, {stat="AGI", offset=1}}, + [0x22A] = {{stat="INT", offset=1}, {stat="MND", offset=1}}, + [0x22B] = {{stat="MND", offset=1}, {stat="CHR", offset=1}}, + [0x22C] = {{stat="INT", offset=1}, {stat="MND", offset=1}, {stat="CHR", offset=1}}, + [0x22D] = {{stat="STR", offset=1}, {stat="CHR", offset=1}}, + [0x22E] = {{stat="STR", offset=1}, {stat="INT", offset=1}}, + [0x22F] = {{stat="STR", offset=1}, {stat="MND", offset=1}}, + + [0x2E4] = {{stat="DMG:", offset=1}}, + [0x2E5] = {{stat="DMG:", offset=33}}, + [0x2E6] = {{stat="DMG:", offset=65}}, + [0x2E7] = {{stat="DMG:", offset=97}}, + [0x2E8] = {{stat="DMG:", offset=1,multiplier=-1}}, + [0x2E9] = {{stat="DMG:", offset=33,multiplier=-1}}, + [0x2EA] = {{stat="DMG:", offset=1}}, + [0x2EB] = {{stat="DMG:", offset=33}}, + [0x2EC] = {{stat="DMG:", offset=65}}, + [0x2ED] = {{stat="DMG:", offset=97}}, + [0x2EE] = {{stat="DMG:", offset=1,multiplier=-1}}, + [0x2EF] = {{stat="DMG:", offset=33,multiplier=-1}}, + [0x2F0] = {{stat="Delay:", offset=1}}, + [0x2F1] = {{stat="Delay:", offset=33}}, + [0x2F2] = {{stat="Delay:", offset=65}}, + [0x2F3] = {{stat="Delay:", offset=97}}, + [0x2F4] = {{stat="Delay:", offset=1,multiplier=-1}}, + [0x2F5] = {{stat="Delay:", offset=33,multiplier=-1}}, + [0x2F6] = {{stat="Delay:", offset=65,multiplier=-1}}, + [0x2F7] = {{stat="Delay:", offset=97,multiplier=-1}}, + [0x2F8] = {{stat="Delay:", offset=1}}, + [0x2F9] = {{stat="Delay:", offset=33}}, + [0x2FA] = {{stat="Delay:", offset=65}}, + [0x2FB] = {{stat="Delay:", offset=97}}, + [0x2FC] = {{stat="Delay:", offset=1,multiplier=-1}}, + [0x2FD] = {{stat="Delay:", offset=33,multiplier=-1}}, + [0x2FE] = {{stat="Delay:", offset=65,multiplier=-1}}, + [0x2FF] = {{stat="Delay:", offset=97,multiplier=-1}}, + [0x300] = {{stat="Fire resistance", offset=1}}, + [0x301] = {{stat="Ice resistance", offset=1}}, + [0x302] = {{stat="Wind resistance", offset=1}}, + [0x303] = {{stat="Earth resistance", offset=1}}, + [0x304] = {{stat="Lightning resistance", offset=1}}, + [0x305] = {{stat="Water resistance", offset=1}}, + [0x306] = {{stat="Light resistance", offset=1}}, + [0x307] = {{stat="Dark resistance", offset=1}}, + [0x308] = {{stat="Fire resistance", offset=1,multiplier=-1}}, + [0x309] = {{stat="Ice resistance", offset=1,multiplier=-1}}, + [0x30A] = {{stat="Wind resistance", offset=1,multiplier=-1}}, + [0x30B] = {{stat="Earth resistance", offset=1,multiplier=-1}}, + [0x30C] = {{stat="Lightning resistance", offset=1,multiplier=-1}}, + [0x30D] = {{stat="Water resistance", offset=1,multiplier=-1}}, + [0x30E] = {{stat="Light resistance", offset=1,multiplier=-1}}, + [0x30F] = {{stat="Dark resistance", offset=1,multiplier=-1}}, + [0x310] = {{stat="Fire resistance", offset=1}, {stat="Water resistance", offset=1,multiplier=-1}}, + [0x311] = {{stat="Fire resistance", offset=1,multiplier=-1}, {stat="Ice resistance", offset=1}}, + [0x312] = {{stat="Ice resistance", offset=1,multiplier=-1}, {stat="Wind resistance", offset=1}}, + [0x313] = {{stat="Wind resistance", offset=1,multiplier=-1}, {stat="Earth resistance", offset=1}}, + [0x314] = {{stat="Earth resistance", offset=1,multiplier=-1}, {stat="Lightning resistance", offset=1}}, + [0x315] = {{stat="Lightning resistance", offset=1,multiplier=-1}, {stat="Water resistance", offset=1}}, + [0x316] = {{stat="Light resistance", offset=1}, {stat="Dark resistance", offset=1,multiplier=-1}}, + [0x317] = {{stat="Light resistance", offset=1,multiplier=-1}, {stat="Dark resistance", offset=1}}, + [0x318] = {{stat="Fire resistance", offset=1}, {stat="Wind resistance", offset=1}, {stat="Lightning resistance", offset=1}, {stat="Light resistance", offset=1}}, + [0x319] = {{stat="Ice resistance", offset=1}, {stat="Earth resistance", offset=1}, {stat="Water resistance", offset=1}, {stat="Dark resistance", offset=1}}, + [0x31A] = {{stat="Fire resistance", offset=1}, {stat="Ice resistance", offset=1,multiplier=-1}, {stat="Wind resistance", offset=1}, {stat="Earth resistance", offset=1,multiplier=-1}, {stat="Lightning resistance", offset=1}, {stat="Water resistance", offset=1,multiplier=-1}, {stat="Light resistance", offset=1}, {stat="Dark resistance", offset=1,multiplier=-1}}, + [0x31B] = {{stat="Fire resistance", offset=1,multiplier=-1}, {stat="Ice resistance", offset=1}, {stat="Wind resistance", offset=1,multiplier=-1}, {stat="Earth resistance", offset=1}, {stat="Lightning resistance", offset=1,multiplier=-1}, {stat="Water resistance", offset=1}, {stat="Light resistance", offset=1,multiplier=-1}, {stat="Dark resistance", offset=1}}, + [0x31C] = {{stat="Fire resistance", offset=1}, {stat="Ice resistance", offset=1}, {stat="Wind resistance", offset=1}, {stat="Earth resistance", offset=1}, {stat="Lightning resistance", offset=1}, {stat="Water resistance", offset=1}, {stat="Light resistance", offset=1}, {stat="Dark resistance", offset=1}}, + [0x31D] = {{stat="Fire resistance", offset=1,multiplier=-1}, {stat="Ice resistance", offset=1,multiplier=-1}, {stat="Wind resistance", offset=1,multiplier=-1}, {stat="Earth resistance", offset=1,multiplier=-1}, {stat="Lightning resistance", offset=1,multiplier=-1}, {stat="Water resistance", offset=1,multiplier=-1}, {stat="Light resistance", offset=1,multiplier=-1}, {stat="Dark resistance", offset=1,multiplier=-1}}, + + [0x340] = {{stat="Add.eff.:Fire Dmg.", offset=5}}, + [0x341] = {{stat="Add.eff.:Ice Dmg.", offset=5}}, + [0x342] = {{stat="Add.eff.:Wind Dmg.", offset=5}}, + [0x343] = {{stat="Add.eff.:Earth Dmg.", offset=5}}, + [0x344] = {{stat="Add.eff.:Lightning Dmg.", offset=5}}, + [0x345] = {{stat="Add.eff.:Water Dmg.", offset=5}}, + [0x346] = {{stat="Add.eff.:Light Dmg.", offset=5}}, + [0x347] = {{stat="Add.eff.:Dark Dmg.", offset=5}}, + [0x348] = {{stat="Add.eff.:Disease", offset=1}}, + [0x349] = {{stat="Add.eff.:Paralysis", offset=1}}, + [0x34A] = {{stat="Add.eff.:Silence", offset=1}}, + [0x34B] = {{stat="Add.eff.:Slow", offset=1}}, + [0x34C] = {{stat="Add.eff.:Stun", offset=1}}, + [0x34D] = {{stat="Add.eff.:Poison", offset=1}}, + [0x34E] = {{stat="Add.eff.:Flash", offset=1}}, + [0x34F] = {{stat="Add.eff.:Blindness", offset=1}}, + [0x350] = {{stat="Add.eff.:Weakens def.", offset=1}}, + [0x351] = {{stat="Add.eff.:Sleep", offset=1}}, + [0x352] = {{stat="Add.eff.:Weakens atk.", offset=1}}, + [0x353] = {{stat="Add.eff.:Impairs evasion", offset=1}}, + [0x354] = {{stat="Add.eff.:Lowers acc.", offset=1}}, + [0x355] = {{stat="Add.eff.:Lowers mag.eva.", offset=1}}, + [0x356] = {{stat="Add.eff.:Lowers mag.atk.", offset=1}}, + [0x357] = {{stat="Add.eff.:Lowers mag.def.", offset=1}}, + [0x358] = {{stat="Add.eff.:Lowers mag.acc.", offset=1}}, + -- 0x359 = 475 + [0x380] = {{stat="Sword enhancement spell damage ", offset=1}}, + [0x381] = {{stat='Enhances "Souleater" effect ', offset=1,percent=true}}, + + -- This is actually a range for static augments that uses all the bits. + + [0x390] = {Secondary_Handling = true}, + [0x391] = {Secondary_Handling = true}, + [0x392] = {Secondary_Handling = true}, + -- The below enhancements aren't visible if their value is 0. + [0x3A0] = {{stat="Fire Affinity ", offset=0}}, + [0x3A1] = {{stat="Ice Affinity ", offset=0}}, + [0x3A2] = {{stat="Wind Affinity ", offset=0}}, + [0x3A3] = {{stat="Earth Affinity ", offset=0}}, + [0x3A4] = {{stat="Lightning Affinity ", offset=0}}, + [0x3A5] = {{stat="Water Affinity ", offset=0}}, + [0x3A6] = {{stat="Light Affinity ", offset=0}}, + [0x3A7] = {{stat="Dark Affinity ", offset=0}}, + [0x3A8] = {{stat="Fire Affinity: Magic Accuracy", offset=0}}, + [0x3A9] = {{stat="Ice Affinity: Magic Accuracy", offset=0}}, + [0x3AA] = {{stat="Wind Affinity: Magic Accuracy", offset=0}}, + [0x3AB] = {{stat="Earth Affinity: Magic Accuracy", offset=0}}, + [0x3AC] = {{stat="Lightning Affinity: Magic Accuracy", offset=0}}, + [0x3AD] = {{stat="Water Affinity: Magic Accuracy", offset=0}}, + [0x3AE] = {{stat="Light Affinity: Magic Accuracy", offset=0}}, + [0x3AF] = {{stat="Dark Affinity: Magic Accuracy", offset=0}}, + [0x3B0] = {{stat="Fire Affinity: Magic Damage", offset=0}}, + [0x3B1] = {{stat="Ice Affinity: Magic Damage", offset=0}}, + [0x3B2] = {{stat="Wind Affinity: Magic Damage", offset=0}}, + [0x3B3] = {{stat="Earth Affinity: Magic Damage", offset=0}}, + [0x3B4] = {{stat="Lightning Affinity: Magic Damage", offset=0}}, + [0x3B5] = {{stat="Water Affinity: Magic Damage", offset=0}}, + [0x3B6] = {{stat="Light Affinity: Magic Damage", offset=0}}, + [0x3B7] = {{stat="Dark Affinity: Magic Damage", offset=0}}, + [0x3B8] = {{stat="Fire Affinity: Avatar perp. cost", offset=0}}, + [0x3B9] = {{stat="Ice Affinity: Avatar perp. cost", offset=0}}, + [0x3BA] = {{stat="Wind Affinity: Avatar perp. cost", offset=0}}, + [0x3BB] = {{stat="Earth Affinity: Avatar perp. cost", offset=0}}, + [0x3BC] = {{stat="Lightning Affinity: Avatar perp. cost", offset=0}}, + [0x3BD] = {{stat="Water Affinity: Avatar perp. cost", offset=0}}, + [0x3BE] = {{stat="Light Affinity: Avatar perp. cost", offset=0}}, + [0x3BF] = {{stat="Dark Affinity: Avatar perp. cost", offset=0}}, + [0x3C0] = {{stat="Fire Affinity: Magic Accuracy", offset=0},{stat="Fire Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C1] = {{stat="Ice Affinity: Magic Accuracy", offset=0},{stat="Ice Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C2] = {{stat="Wind Affinity: Magic Accuracy", offset=0},{stat="Wind Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C3] = {{stat="Earth Affinity: Magic Accuracy", offset=0},{stat="Earth Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C4] = {{stat="Lightning Affinity: Magic Accuracy", offset=0},{stat="Lightning Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C5] = {{stat="Water Affinity: Magic Accuracy", offset=0},{stat="Water Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C6] = {{stat="Light Affinity: Magic Accuracy", offset=0},{stat="Light Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C7] = {{stat="Dark Affinity: Magic Accuracy", offset=0},{stat="Dark Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3C8] = {{stat="Fire Affinity: Magic Damage", offset=0},{stat="Fire Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3C9] = {{stat="Ice Affinity: Magic Damage", offset=0},{stat="Ice Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CA] = {{stat="Wind Affinity: Magic Damage", offset=0},{stat="Wind Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CB] = {{stat="Earth Affinity: Magic Damage", offset=0},{stat="Earth Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CC] = {{stat="Lightning Affinity: Magic Damage", offset=0},{stat="Lightning Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CD] = {{stat="Water Affinity: Magic Damage", offset=0},{stat="Water Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CE] = {{stat="Light Affinity: Magic Damage", offset=0},{stat="Light Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3CF] = {{stat="Dark Affinity: Magic Damage", offset=0},{stat="Dark Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3D0] = {{stat='Fire Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D1] = {{stat='Ice Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D2] = {{stat='Wind Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D3] = {{stat='Earth Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D4] = {{stat='Lightning Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D5] = {{stat='Water Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D6] = {{stat='Light Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D7] = {{stat='Dark Affin.: "Blood Pact" delay ', offset=1}}, + [0x3D8] = {{stat="Fire Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3D9] = {{stat="Ice Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DA] = {{stat="Wind Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DB] = {{stat="Earth Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DC] = {{stat="Lightning Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DD] = {{stat="Water Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DE] = {{stat="Light Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3DF] = {{stat="Dark Affinity: Recast time", offset=1, multiplier=-2, percent=true}}, + [0x3E0] = {{stat="Fire Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E1] = {{stat="Ice Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E2] = {{stat="Wind Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E3] = {{stat="Earth Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E4] = {{stat="Lightning Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E5] = {{stat="Water Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E6] = {{stat="Light Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E7] = {{stat="Dark Affinity: Casting time", offset=1, multiplier=-2, percent=true}}, + [0x3E8] = {{stat="Fire Affinity: Magic Accuracy", offset=0},{stat="Fire Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3E9] = {{stat="Ice Affinity: Magic Accuracy", offset=0},{stat="Ice Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3EA] = {{stat="Wind Affinity: Magic Accuracy", offset=0},{stat="Wind Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3EB] = {{stat="Earth Affinity: Magic Accuracy", offset=0},{stat="Earth Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3EC] = {{stat="Lightning Affinity: Magic Accuracy", offset=0},{stat="Lightning Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3ED] = {{stat="Water Affinity: Magic Accuracy", offset=0},{stat="Water Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3EE] = {{stat="Light Affinity: Magic Accuracy", offset=0},{stat="Light Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3EF] = {{stat="Dark Affinity: Magic Accuracy", offset=0},{stat="Dark Affinity: Casting time", offset=1, multiplier=-6, percent=true}}, + [0x3F0] = {{stat="Fire Affinity: Magic Damage", offset=0},{stat="Fire Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F1] = {{stat="Ice Affinity: Magic Damage", offset=0},{stat="Ice Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F2] = {{stat="Wind Affinity: Magic Damage", offset=0},{stat="Wind Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F3] = {{stat="Earth Affinity: Magic Damage", offset=0},{stat="Earth Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F4] = {{stat="Lightning Affinity: Magic Damage", offset=0},{stat="Lightning Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F5] = {{stat="Water Affinity: Magic Damage", offset=0},{stat="Water Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F6] = {{stat="Light Affinity: Magic Damage", offset=0},{stat="Light Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + [0x3F7] = {{stat="Dark Affinity: Magic Damage", offset=0},{stat="Dark Affinity: Recast time", offset=1, multiplier=-6, percent=true}}, + + [0x400] = {{stat="Backhand Blow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x401] = {{stat="Spinning Attack:DMG:", offset=1,multiplier=5,percent=true}}, + [0x402] = {{stat="Howling Fist:DMG:", offset=1,multiplier=5,percent=true}}, + [0x403] = {{stat="Dragon Kick:DMG:", offset=1,multiplier=5,percent=true}}, + [0x404] = {{stat="Viper Bite:DMG:", offset=1,multiplier=5,percent=true}}, + [0x405] = {{stat="Shadowstitch:DMG:", offset=1,multiplier=5,percent=true}}, + [0x406] = {{stat="Cyclone:DMG:", offset=1,multiplier=5,percent=true}}, + [0x407] = {{stat="Evisceration:DMG:", offset=1,multiplier=5,percent=true}}, + [0x408] = {{stat="Burning Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x409] = {{stat="Shining Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40A] = {{stat="Circle Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40B] = {{stat="Savage Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40C] = {{stat="Freezebite:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40D] = {{stat="Shockwave:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40E] = {{stat="Ground Strike:DMG:", offset=1,multiplier=5,percent=true}}, + [0x40F] = {{stat="Sickle Moon:DMG:", offset=1,multiplier=5,percent=true}}, + [0x410] = {{stat="Gale Axe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x411] = {{stat="Spinning Axe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x412] = {{stat="Calamity:DMG:", offset=1,multiplier=5,percent=true}}, + [0x413] = {{stat="Decimation:DMG:", offset=1,multiplier=5,percent=true}}, + [0x414] = {{stat="Iron Tempest:DMG:", offset=1,multiplier=5,percent=true}}, + [0x415] = {{stat="Sturmwind:DMG:", offset=1,multiplier=5,percent=true}}, + [0x416] = {{stat="Keen Edge:DMG:", offset=1,multiplier=5,percent=true}}, + [0x417] = {{stat="Steel Cyclone:DMG:", offset=1,multiplier=5,percent=true}}, + [0x418] = {{stat="Nightmare Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x419] = {{stat="Spinning Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41A] = {{stat="Vorpal Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41B] = {{stat="Spiral Hell:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41C] = {{stat="Leg Sweep:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41D] = {{stat="Skewer:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41E] = {{stat="Vorpal Thrust:DMG:", offset=1,multiplier=5,percent=true}}, + [0x41F] = {{stat="Impulse Drive:DMG:", offset=1,multiplier=5,percent=true}}, + [0x420] = {{stat="Blade: To:DMG:", offset=1,multiplier=5,percent=true}}, + [0x421] = {{stat="Blade: Chi:DMG:", offset=1,multiplier=5,percent=true}}, + [0x422] = {{stat="Blade: Ten:DMG:", offset=1,multiplier=5,percent=true}}, + [0x423] = {{stat="Blade: Ku:DMG:", offset=1,multiplier=5,percent=true}}, + [0x424] = {{stat="Tachi: Goten:DMG:", offset=1,multiplier=5,percent=true}}, + [0x425] = {{stat="Tachi: Jinpu:DMG:", offset=1,multiplier=5,percent=true}}, + [0x426] = {{stat="Tachi: Koki:DMG:", offset=1,multiplier=5,percent=true}}, + [0x427] = {{stat="Tachi: Kasha:DMG:", offset=1,multiplier=5,percent=true}}, + [0x428] = {{stat="Brainshaker:DMG:", offset=1,multiplier=5,percent=true}}, + [0x429] = {{stat="Skullbreaker:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42A] = {{stat="Judgment:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42B] = {{stat="Black Halo:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42C] = {{stat="Rock Crusher:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42D] = {{stat="Shell Crusher:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42E] = {{stat="Full Swing:DMG:", offset=1,multiplier=5,percent=true}}, + [0x42F] = {{stat="Retribution:DMG:", offset=1,multiplier=5,percent=true}}, + [0x430] = {{stat="Dulling Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x431] = {{stat="Blast Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x432] = {{stat="Arching Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x433] = {{stat="Empyreal Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x434] = {{stat="Hot Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x435] = {{stat="Split Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x436] = {{stat="Sniper Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x437] = {{stat="Detonator:DMG:", offset=1,multiplier=5,percent=true}}, + [0x438] = {{stat="Weapon Skill:DMG:", offset=1,multiplier=5,percent=true}}, + + [0x480] = {{stat="DEF", offset=1,multiplier=10}}, + [0x481] = {{stat="Evasion", offset=1,multiplier=3}}, + [0x482] = {{stat="Mag. Evasion", offset=1,multiplier=3}}, + [0x483] = {{stat="Phys. dmg. taken", offset=1,multiplier=-2,percent=true}}, + [0x484] = {{stat="Magic dmg. taken", offset=1,multiplier=-2,percent=true}}, + [0x485] = {{stat="Spell interruption rate down", offset=1,multiplier=-2,percent=true}}, + [0x486] = {{stat="Occ. inc. resist. to stat. ailments", offset=1,multiplier=2}}, + + [0x4E0] = {{stat="Enh. Mag. eff. dur. ", offset=1}}, + [0x4E1] = {{stat="Helix eff. dur. ", offset=1}}, + [0x4E2] = {{stat="Indi. eff. dur. ", offset=1}}, + + [0x4F0] = {{stat="Meditate eff. dur. ", offset=1}}, + + [0x500] = {{stat='Enhances "Mighty Strikes" effect', offset=0,multiplier=0}}, + [0x501] = {{stat='Enhances "Hundred Fists" effect', offset=0,multiplier=0}}, + [0x502] = {{stat='Enhances "Benediction" effect', offset=0,multiplier=0}}, + [0x503] = {{stat='Enhances "Manafont" effect', offset=0,multiplier=0}}, + [0x504] = {{stat='Enhances "Chainspell" effect', offset=0,multiplier=0}}, + [0x505] = {{stat='Enhances "Perfect Dodge" effect', offset=0,multiplier=0}}, + [0x506] = {{stat='Enhances "Invincible" effect', offset=0,multiplier=0}}, + [0x507] = {{stat='Enhances "Blood Weapon" effect', offset=0,multiplier=0}}, + [0x508] = {{stat='Enhances "Familiar" effect', offset=0,multiplier=0}}, + [0x509] = {{stat='Enhances "Soul Voice" effect', offset=0,multiplier=0}}, + [0x50A] = {{stat='Enhances "Eagle Eye Shot" effect', offset=0,multiplier=0}}, + [0x50B] = {{stat='Enhances "Meikyo Shisui" effect', offset=0,multiplier=0}}, + [0x50C] = {{stat='Enhances "Mijin Gakure" effect', offset=0,multiplier=0}}, + [0x50D] = {{stat='Enhances "Spirit Surge" effect', offset=0,multiplier=0}}, + [0x50E] = {{stat='Enhances "Astral Flow" effect', offset=0,multiplier=0}}, + [0x50F] = {{stat='Enhances "Azure Lore" effect', offset=0,multiplier=0}}, + [0x510] = {{stat='Enhances "Wild Card" effect', offset=0,multiplier=0}}, + [0x511] = {{stat='Enhances "Overdrive" effect', offset=0,multiplier=0}}, + [0x512] = {{stat='Enhances "Trance" effect', offset=0,multiplier=0}}, + [0x513] = {{stat='Enhances "Tabula Rasa" effect', offset=0,multiplier=0}}, + [0x514] = {{stat='Enhances "Bolster" effect', offset=0,multiplier=0}}, + [0x515] = {{stat='Enhances "Elemental Sforzo" effect', offset=0,multiplier=0}}, + + [0x530] = {{stat='Enhances "Savagery" effect', offset=0,multiplier=0}}, + [0x531] = {{stat='Enhances "Aggressive Aim" effect', offset=0,multiplier=0}}, + [0x532] = {{stat='Enhances "Warrior\'s Charge" effect', offset=0,multiplier=0}}, + [0x533] = {{stat='Enhances "Tomahawk" effect', offset=0,multiplier=0}}, + + [0x536] = {{stat='Enhances "Penance" effect', offset=0,multiplier=0}}, + [0x537] = {{stat='Enhances "Formless Strikes" effect', offset=0,multiplier=0}}, + [0x538] = {{stat='Enhances "Invigorate" effect', offset=0,multiplier=0}}, + [0x539] = {{stat='Enhances "Mantra" effect', offset=0,multiplier=0}}, + + [0x53C] = {{stat='Enhances "Afflatus Solace" effect', offset=0,multiplier=0}}, + [0x53D] = {{stat='Enhances "Martyr" effect', offset=0,multiplier=0}}, + [0x53E] = {{stat='Enhances "Afflatus Misery" effect', offset=0,multiplier=0}}, + [0x53F] = {{stat='Enhances "Devotion" effect', offset=0,multiplier=0}}, + + [0x542] = {{stat='Increases Ancient Magic damage and magic burst damage', offset=0,multiplier=0}}, + [0x543] = {{stat='Increases Elemental Magic accuracy', offset=0,multiplier=0}}, + [0x544] = {{stat='Increases Elemental Magic debuff time and potency', offset=0,multiplier=0}}, + [0x545] = {{stat='Increases Aspir absorption amount', offset=0,multiplier=0}}, + + [0x548] = {{stat='Enfeebling Magic duration', offset=0,multiplier=0}}, + [0x549] = {{stat='Magic Accuracy', offset=0,multiplier=0}}, + [0x54A] = {{stat='Enhancing Magic duration', offset=0,multiplier=0}}, + [0x54B] = {{stat='Enspell Damage', offset=0,multiplier=0}}, + [0x54C] = {{stat='Accuracy', offset=0,multiplier=0}}, + [0x54D] = {{stat='Immunobreak Chance', offset=0,multiplier=0}}, + + [0x54E] = {{stat='Enhances "Aura Steal" effect', offset=0,multiplier=0}}, + [0x54F] = {{stat='Enhances "Ambush" effect', offset=0,multiplier=0}}, + [0x550] = {{stat='Enhances "Feint" effect', offset=0,multiplier=0}}, + [0x551] = {{stat='Enhances "Assassin\'s Charge" effect', offset=0,multiplier=0}}, + + [0x554] = {{stat='Enhances "Iron Will" effect', offset=0,multiplier=0}}, + [0x555] = {{stat='Enhances "Fealty" effect', offset=0,multiplier=0}}, + [0x556] = {{stat='Enhances "Chivalry" effect', offset=0,multiplier=0}}, + [0x557] = {{stat='Enhances "Guardian" effect', offset=0,multiplier=0}}, + + [0x55A] = {{stat='Enhances "Dark Seal" effect', offset=0,multiplier=0}}, + [0x55B] = {{stat='Enhances "Diabolic Eye" effect', offset=0,multiplier=0}}, + [0x55C] = {{stat='Enhances "Muted Soul" effect', offset=0,multiplier=0}}, + [0x55D] = {{stat='Enhances "Desperate Blows" effect', offset=0,multiplier=0}}, + + [0x560] = {{stat='Enhances "Killer Instinct" effect', offset=0,multiplier=0}}, + [0x561] = {{stat='Enhances "Feral Howl" effect', offset=0,multiplier=0}}, + [0x562] = {{stat='Enhances "Beast Affinity" effect', offset=0,multiplier=0}}, + [0x563] = {{stat='Enhances "Beast Healer" effect', offset=0,multiplier=0}}, + + [0x566] = {{stat='Enhances "Con Anima" effect', offset=0,multiplier=0}}, + [0x567] = {{stat='Enhances "Troubadour" effect', offset=0,multiplier=0}}, + [0x568] = {{stat='Enhances "Con Brio" effect', offset=0,multiplier=0}}, + [0x569] = {{stat='Enhances "Nightingale" effect', offset=0,multiplier=0}}, + + [0x56C] = {{stat='Enhances "Recycle" effect', offset=0,multiplier=0}}, + [0x56D] = {{stat='Enhances "Snapshot" effect', offset=0,multiplier=0}}, + [0x56E] = {{stat='Enhances "Flashy Shot" effect', offset=0,multiplier=0}}, + [0x56F] = {{stat='Enhances "Stealth Shot" effect', offset=0,multiplier=0}}, + + [0x572] = {{stat='Enhances "Shikikoyo" effect', offset=0,multiplier=0}}, + [0x573] = {{stat='Enhances "Overwhelm" effect', offset=0,multiplier=0}}, + [0x574] = {{stat='Enhances "Blade Bash" effect', offset=0,multiplier=0}}, + [0x575] = {{stat='Enhances "Ikishoten" effect', offset=0,multiplier=0}}, + + [0x578] = {{stat='Enhances "Yonin" and "Innin" effect', offset=0,multiplier=0}}, + [0x579] = {{stat='Enhances "Sange" effect', offset=0,multiplier=0}}, + [0x57A] = {{stat='Enh. "Ninja Tool Expertise" effect', offset=0,multiplier=0}}, + [0x57B] = {{stat='Enh. Ninj. Mag. Acc/Cast Time Red.', offset=0,multiplier=0}}, + + [0x57E] = {{stat='Enhances "Deep Breathing" effect', offset=0,multiplier=0}}, + [0x57F] = {{stat='Enhances "Angon" effect', offset=0,multiplier=0}}, + [0x580] = {{stat='Enhances "Strafe" effect', offset=0,multiplier=0}}, + [0x581] = {{stat='Enhances "Empathy" effect', offset=0,multiplier=0}}, + + [0x584] = {{stat='Reduces Sp. "Blood Pact" MP cost', offset=0,multiplier=0}}, + [0x585] = {{stat='Inc. Sp. "Blood Pact" magic burst dmg.', offset=0,multiplier=0}}, + [0x586] = {{stat='Increases Sp. "Blood Pact" accuracy', offset=0,multiplier=0}}, + [0x587] = {{stat='Inc. Sp. "Blood Pact" magic crit. dmg.', offset=0,multiplier=0}}, + + [0x58A] = {{stat='Enhances "Convergence" effect', offset=0,multiplier=0}}, + [0x58B] = {{stat='Enhances "Enchainment" effect', offset=0,multiplier=0}}, + [0x58C] = {{stat='Enhances "Assimilation" effect', offset=0,multiplier=0}}, + [0x58D] = {{stat='Enhances "Diffusion" effect', offset=0,multiplier=0}}, + + [0x590] = {{stat='Enhances "Winning Streak" effect', offset=0,multiplier=0}}, + [0x591] = {{stat='Enhances "Loaded Deck" effect', offset=0,multiplier=0}}, + [0x592] = {{stat='Enhances "Fold" effect', offset=0,multiplier=0}}, + [0x593] = {{stat='Enhances "Snake Eye" effect', offset=0,multiplier=0}}, + + [0x596] = {{stat='Enhances "Optimization" effect', offset=0,multiplier=0}}, + [0x597] = {{stat='Enhances "Fine-Tuning" effect', offset=0,multiplier=0}}, + [0x598] = {{stat='Enhances "Ventriloquy" effect', offset=0,multiplier=0}}, + [0x599] = {{stat='Enhances "Role Reversal" effect', offset=0,multiplier=0}}, + + [0x59C] = {{stat='Enhances "No Foot Rise" effect', offset=0,multiplier=0}}, + [0x59D] = {{stat='Enhances "Fan Dance" effect', offset=0,multiplier=0}}, + [0x59E] = {{stat='Enhances "Saber Dance" effect', offset=0,multiplier=0}}, + [0x59F] = {{stat='Enhances "Closed Position" effect', offset=0,multiplier=0}}, + + [0x5A2] = {{stat='Enh. "Altruism" and "Focalization"', offset=0,multiplier=0}}, + [0x5A3] = {{stat='Enhances "Enlightenment" effect', offset=0,multiplier=0}}, + [0x5A4] = {{stat='Enh. "Tranquility" and "Equanimity"', offset=0,multiplier=0}}, + [0x5A5] = {{stat='Enhances "Stormsurge" effect', offset=0,multiplier=0}}, + + [0x5A8] = {{stat='Enhances "Mending Halation" effect', offset=0,multiplier=0}}, + [0x5A9] = {{stat='Enhances "Radial Arcana" effect', offset=0,multiplier=0}}, + [0x5AA] = {{stat='Enhances "Curative Recantation" effect', offset=0,multiplier=0}}, + [0x5AB] = {{stat='Enhances "Primeval Zeal" effect', offset=0,multiplier=0}}, + + [0x5AE] = {{stat='Enhances "Battuta" effect', offset=0,multiplier=0}}, + [0x5AF] = {{stat='Enhances "Rayke" effect', offset=0,multiplier=0}}, + [0x5B0] = {{stat='Enhances "Inspire" effect', offset=0,multiplier=0}}, + [0x5B1] = {{stat='Enhances "Sleight of Sword" effect', offset=0,multiplier=0}}, + + [0x5C0] = {{stat="Parrying rate", offset=1,percent=true}}, + + [0x600] = {{stat="Backhand Blow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x601] = {{stat="Spinning Attack:DMG:", offset=1,multiplier=5,percent=true}}, + [0x602] = {{stat="Howling Fist:DMG:", offset=1,multiplier=5,percent=true}}, + [0x603] = {{stat="Dragon Kick:DMG:", offset=1,multiplier=5,percent=true}}, + [0x604] = {{stat="Viper Bite:DMG:", offset=1,multiplier=5,percent=true}}, + [0x605] = {{stat="Shadowstitch:DMG:", offset=1,multiplier=5,percent=true}}, + [0x606] = {{stat="Cyclone:DMG:", offset=1,multiplier=5,percent=true}}, + [0x607] = {{stat="Evisceration:DMG:", offset=1,multiplier=5,percent=true}}, + [0x608] = {{stat="Burning Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x609] = {{stat="Shining Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60A] = {{stat="Circle Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60B] = {{stat="Savage Blade:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60C] = {{stat="Freezebite:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60D] = {{stat="Shockwave:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60E] = {{stat="Ground Strike:DMG:", offset=1,multiplier=5,percent=true}}, + [0x60F] = {{stat="Sickle Moon:DMG:", offset=1,multiplier=5,percent=true}}, + [0x610] = {{stat="Gale Axe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x611] = {{stat="Spinning Axe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x612] = {{stat="Calamity:DMG:", offset=1,multiplier=5,percent=true}}, + [0x613] = {{stat="Decimation:DMG:", offset=1,multiplier=5,percent=true}}, + [0x614] = {{stat="Iron Tempest:DMG:", offset=1,multiplier=5,percent=true}}, + [0x615] = {{stat="Sturmwind:DMG:", offset=1,multiplier=5,percent=true}}, + [0x616] = {{stat="Keen Edge:DMG:", offset=1,multiplier=5,percent=true}}, + [0x617] = {{stat="Steel Cyclone:DMG:", offset=1,multiplier=5,percent=true}}, + [0x618] = {{stat="Nightmare Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x619] = {{stat="Spinning Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61A] = {{stat="Vorpal Scythe:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61B] = {{stat="Spiral Hell:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61C] = {{stat="Leg Sweep:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61D] = {{stat="Skewer:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61E] = {{stat="Vorpal Thrust:DMG:", offset=1,multiplier=5,percent=true}}, + [0x61F] = {{stat="Impulse Drive:DMG:", offset=1,multiplier=5,percent=true}}, + [0x620] = {{stat="Blade: To:DMG:", offset=1,multiplier=5,percent=true}}, + [0x621] = {{stat="Blade: Chi:DMG:", offset=1,multiplier=5,percent=true}}, + [0x622] = {{stat="Blade: Ten:DMG:", offset=1,multiplier=5,percent=true}}, + [0x623] = {{stat="Blade: Ku:DMG:", offset=1,multiplier=5,percent=true}}, + [0x624] = {{stat="Tachi: Goten:DMG:", offset=1,multiplier=5,percent=true}}, + [0x625] = {{stat="Tachi: Jinpu:DMG:", offset=1,multiplier=5,percent=true}}, + [0x626] = {{stat="Tachi: Koki:DMG:", offset=1,multiplier=5,percent=true}}, + [0x627] = {{stat="Tachi: Kasha:DMG:", offset=1,multiplier=5,percent=true}}, + [0x628] = {{stat="Brainshaker:DMG:", offset=1,multiplier=5,percent=true}}, + [0x629] = {{stat="Skullbreaker:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62A] = {{stat="Judgment:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62B] = {{stat="Black Halo:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62C] = {{stat="Rock Crusher:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62D] = {{stat="Shell Crusher:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62E] = {{stat="Full Swing:DMG:", offset=1,multiplier=5,percent=true}}, + [0x62F] = {{stat="Retribution:DMG:", offset=1,multiplier=5,percent=true}}, + [0x630] = {{stat="Dulling Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x631] = {{stat="Blast Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x632] = {{stat="Arching Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x633] = {{stat="Empyreal Arrow:DMG:", offset=1,multiplier=5,percent=true}}, + [0x634] = {{stat="Hot Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x635] = {{stat="Split Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x636] = {{stat="Sniper Shot:DMG:", offset=1,multiplier=5,percent=true}}, + [0x637] = {{stat="Detonator:DMG:", offset=1,multiplier=5,percent=true}}, + [0x638] = {{stat="Weapon Skill:DMG:", offset=1,multiplier=5,percent=true}}, + + [0x700] = {{stat="Pet: STR", offset=1}}, + [0x701] = {{stat="Pet: DEX", offset=1}}, + [0x702] = {{stat="Pet: VIT", offset=1}}, + [0x703] = {{stat="Pet: AGI", offset=1}}, + [0x704] = {{stat="Pet: INT", offset=1}}, + [0x705] = {{stat="Pet: MND", offset=1}}, + [0x706] = {{stat="Pet: CHR", offset=1}}, + [0x707] = {{stat="Pet: STR", offset=1,multiplier=-1}}, + [0x708] = {{stat="Pet: DEX", offset=1,multiplier=-1}}, + [0x709] = {{stat="Pet: VIT", offset=1,multiplier=-1}}, + [0x70A] = {{stat="Pet: AGI", offset=1,multiplier=-1}}, + [0x70B] = {{stat="Pet: INT", offset=1,multiplier=-1}}, + [0x70C] = {{stat="Pet: MND", offset=1,multiplier=-1}}, + [0x70D] = {{stat="Pet: CHR", offset=1,multiplier=-1}}, + [0x70E] = {{stat="Pet: STR", offset=1},{stat="Pet: DEX", offset=1},{stat="Pet: VIT", offset=1}}, + [0x70F] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x710] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x711] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x712] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x713] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x714] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x715] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x716] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x717] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x718] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x719] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71A] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71B] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71C] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71D] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71E] = {{stat="Pet:", offset=0,multiplier=0}}, + [0x71F] = {{stat="Pet:", offset=0,multiplier=0}}, + + [0x7FF] = {{stat='???', offset=0}}, + }, + [2] = { + [0x00] = {{stat="none",offset=0}}, + [0x01] = {{stat="----------------",offset=0}}, + [0x02] = {{stat="HP",offset=1}}, + [0x03] = {{stat="HP",offset=256}}, + [0x04] = {{stat="MP",offset=1}}, + [0x05] = {{stat="MP",offset=256}}, + [0x08] = {{stat="Attack",offset=1}}, + [0x09] = {{stat="Attack",offset=256}}, + [0x0A] = {{stat="Rng.Atk.",offset=1}}, + [0x0B] = {{stat="Rng.Atk.",offset=256}}, + [0x0C] = {{stat="Accuracy",offset=1}}, + [0x0D] = {{stat="Accuracy",offset=256}}, + [0x0E] = {{stat="Rng.Acc.",offset=1}}, + [0x0F] = {{stat="Rng.Acc.",offset=256}}, + [0x10] = {{stat="DEF",offset=1}}, + [0x11] = {{stat="DEF",offset=256}}, + [0x12] = {{stat="Evasion",offset=1}}, + [0x13] = {{stat="Evasion",offset=256}}, + [0x14] = {{stat='"Mag.Atk.Bns."',offset=1}}, + [0x15] = {{stat='"Mag.Atk.Bns."',offset=256}}, + [0x16] = {{stat='"Mag.Def.Bns."',offset=1}}, + [0x17] = {{stat='"Mag.Def.Bns."',offset=256}}, + [0x18] = {{stat="Mag. Acc.",offset=1}}, + [0x19] = {{stat="Mag. Acc.",offset=256}}, + [0x1A] = {{stat="Mag. Evasion",offset=1}}, + [0x1B] = {{stat="Mag. Evasion",offset=256}}, + [0x1C] = {{stat="DMG:",offset=1}}, + [0x1D] = {{stat="DMG:",offset=256}}, + + [0x72] = {{stat='Weapon skill damage ',offset=1,percent=true}}, + [0x73] = {{stat='Magic damage',offset=1}}, + [0x74] = {{stat='Blood Pact Dmg.',offset=1}}, + [0x74] = {{stat='Blood Pact Dmg.',offset=1}}, + [0x75] = {{stat='"Avatar perpetuation cost"',offset=1}}, + [0x76] = {{stat='"Blood Pact" ability delay',offset=1}}, + [0x77] = {{stat='Haste',offset=1,percent=true}}, + [0x78] = {{stat='Enmity',offset=1}}, + [0x79] = {{stat='Enmity',offset=1,multiplier=-1}}, + [0x7A] = {{stat='Crit. hit rate',offset=1,percent=true}}, + [0x7B] = {{stat='"Cure" spellcasting time ',offset=1,multiplier=-1,percent=true}}, + [0x7C] = {{stat='"Cure" potency ',offset=1,percent=true}}, + [0x7D] = {{stat='"Refresh"',offset=1}}, + [0x7E] = {{stat='Spell interruption rate down ',offset=1,percent=true}}, + [0x7F] = {{stat='Potency of "Cure" effect received ',offset=1,percent=true}}, + [0x80] = {{stat='Pet: "Mag.Atk.Bns."',offset=1}}, + [0x81] = {{stat="Pet: Mag. Acc.",offset=1}}, + [0x82] = {{stat="Pet: Attack",offset=1}}, + [0x83] = {{stat="Pet: Accuracy",offset=1}}, + [0x84] = {{stat="Pet: Enmity",offset=1}}, + [0x85] = {{stat="Pet: Enmity",offset=1}}, + [0x86] = {{stat="Pet: HP",offset=1}}, + [0x87] = {{stat="Pet: MP",offset=1}}, + [0x88] = {{stat="Pet: STR",offset=1}}, + [0x89] = {{stat="Pet: DEX",offset=1}}, + [0x8A] = {{stat="Pet: VIT",offset=1}}, + [0x8B] = {{stat="Pet: AGI",offset=1}}, + [0x8C] = {{stat="Pet: INT",offset=1}}, + [0x8D] = {{stat="Pet: MND",offset=1}}, + [0x8E] = {{stat="Pet: CHR",offset=1}}, + + [0x98] = {{stat='Pet: "Dbl. Atk."',offset=1}}, + [0x99] = {{stat='Pet: Damage taken ',offset=1,multiplier=-1,percent=true}}, + [0x9A] = {{stat='Pet: "Regen"',offset=1}}, + [0x9B] = {{stat='Pet: Haste',offset=1,percent=true}}, + [0x9C] = {{stat='Automaton: "Cure" potency ',offset=1,percent=true}}, + [0x9D] = {{stat='Automaton: "Fast Cast"',offset=1}}, + + [0xAB] = {{stat='"Dual Wield"',offset=1}}, + [0xAC] = {{stat='Damage Taken ',offset=1,multiplier=-1,percent=true}}, + [0xAD] = {{stat='All songs ',offset=1}}, + + [0xB1] = {{stat='"Conserve MP"',offset=1}}, + [0xB2] = {{stat='"Counter"',offset=1}}, + [0xB3] = {{stat='"Triple Atk."',offset=1}}, + [0xB4] = {{stat='"Fast Cast"',offset=1}}, + [0xB5] = {{stat='"Blood Boon"',offset=1}}, + [0xB6] = {{stat='"Subtle Blow"',offset=1}}, + [0xB7] = {{stat='"Rapid Shot"',offset=1}}, + [0xB8] = {{stat='"Recycle"',offset=1}}, + [0xB9] = {{stat='"Store TP"',offset=1}}, + [0xBA] = {{stat='"Dbl.Atk."',offset=1}}, + [0xBB] = {{stat='"Snapshot"',offset=1}}, + [0xBC] = {{stat="Phys. dmg. taken ",offset=1,multiplier=-1}}, + [0xBD] = {{stat="Magic dmg. taken ",offset=1,multiplier=-1}}, + [0xBE] = {{stat="Breath dmg. taken ",offset=1,multiplier=-1}}, + [0xBF] = {{stat="STR",offset=1}}, + [0xC0] = {{stat="DEX",offset=1}}, + [0xC1] = {{stat="VIT",offset=1}}, + [0xC2] = {{stat="AGI",offset=1}}, + [0xC3] = {{stat="INT",offset=1}}, + [0xC4] = {{stat="MND",offset=1}}, + [0xC5] = {{stat="CHR",offset=1}}, + [0xC6] = {{stat="none",offset=0}}, + [0xC7] = {{stat="none",offset=0}}, + [0xC8] = {{stat="none",offset=0}}, + [0xC9] = {{stat="none",offset=0}}, + [0xCA] = {{stat="none",offset=0}}, + [0xCB] = {{stat="none",offset=0}}, + [0xCC] = {{stat="none",offset=0}}, + [0xCD] = {{stat="none",offset=0}}, + [0xCE] = {{stat="STR",offset=1},{stat="DEX",offset=1},{stat="VIT",offset=1},{stat="AGI",offset=1},{stat="INT",offset=1},{stat="MND",offset=1},{stat="CHR",offset=1}}, + [0xCF] = {{stat="none",offset=0}}, + [0xD0] = {{stat="Hand-to-Hand skill ",offset=1}}, + [0xD1] = {{stat="Dagger skill ",offset=1}}, + [0xD2] = {{stat="Sword skill ", offset=1}}, + [0xD3] = {{stat="Great Sword skill ", offset=1}}, + [0xD4] = {{stat="Axe skill ", offset=1}}, + [0xD5] = {{stat="Great Axe skill ", offset=1}}, + [0xD6] = {{stat="Scythe skill ", offset=1}}, + [0xD7] = {{stat="Polearm skill ", offset=1}}, + [0xD8] = {{stat="Katana skill ", offset=1}}, + [0xD9] = {{stat="Great Katana skill ", offset=1}}, + [0xDA] = {{stat="Club skill ", offset=1}}, + [0xDB] = {{stat="Staff skill ", offset=1}}, + [0xDC] = {{stat="269", offset=0}}, + [0xDD] = {{stat="270", offset=0}}, + [0xDE] = {{stat="271", offset=0}}, + [0xDF] = {{stat="272", offset=0}}, + [0xE0] = {{stat="273", offset=0}}, + [0xE1] = {{stat="274", offset=0}}, + [0xE2] = {{stat="275", offset=0}}, + [0xE3] = {{stat="276", offset=0}}, + [0xE4] = {{stat="277", offset=0}}, + [0xE5] = {{stat="Melee skill ", offset=1}}, -- Automaton + [0xE6] = {{stat="Ranged skill ", offset=1}}, -- Automaton + [0xE7] = {{stat="Magic skill ", offset=1}}, -- Automaton + [0xE8] = {{stat="Archery skill ", offset=1}}, + [0xE9] = {{stat="Marksmanship skill ", offset=1}}, + [0xEA] = {{stat="Throwing skill ", offset=1}}, + [0xEB] = {{stat="284", offset=0}}, + [0xEC] = {{stat="285", offset=0}}, + [0xED] = {{stat="Shield skill ", offset=1}}, + [0xEE] = {{stat="287", offset=0}}, + [0xEF] = {{stat="Divine magic skill ", offset=1}}, + [0xF0] = {{stat="Healing magic skill ", offset=1}}, + [0xF1] = {{stat="Enha.mag. skill ", offset=1}}, + [0xF2] = {{stat="Enfb.mag. skill ", offset=1}}, + [0xF3] = {{stat="Elem. magic skill ", offset=1}}, + [0xF4] = {{stat="Dark magic skill ", offset=1}}, + [0xF5] = {{stat="Summoning magic skill ", offset=1}}, + [0xF6] = {{stat="Ninjutsu skill ", offset=1}}, + [0xF7] = {{stat="Singing skill ", offset=1}}, + [0xF8] = {{stat="String instrument skill ", offset=1}}, + [0xF9] = {{stat="Wind instrument skill ", offset=1}}, + [0xFA] = {{stat="Blue Magic skill ", offset=1}}, + [0xFB] = {{stat="Geomancy Skill ", offset=1}}, + [0xFC] = {{stat="Handbell Skill", offset=1}}, + [0xFD] = {{stat="302", offset=0}}, + [0xFE] = {{stat="303", offset=0}} + }, + [3] = { + [0x000] = {{stat='----------------',potency=potencies.zero}}, + [0x001] = {{stat='Vs. beasts: Attack',potency=potencies.family.attack}}, + [0x002] = {{stat='Vs. beasts: DEF',potency=potencies.family.defense}}, + [0x003] = {{stat='Vs. beasts: Accuracy',potency=potencies.family.accuracy}}, + [0x004] = {{stat='Vs. beasts: Evasion',potency=potencies.family.evasion}}, + [0x005] = {{stat='Vs. beasts: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x006] = {{stat='Vs. beasts: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x007] = {{stat='Vs. beasts: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x008] = {{stat='Vs. beasts: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x009] = {{stat='Vs. beasts: Rng.Atk.',potency=potencies.family.attack}}, + [0x00A] = {{stat='Vs. beasts: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x00B] = {{stat='Vs. plantoids: Attack',potency=potencies.family.attack}}, + [0x00C] = {{stat='Vs. plantoids: DEF',potency=potencies.family.defense}}, + [0x00D] = {{stat='Vs. plantoids: Accuracy',potency=potencies.family.accuracy}}, + [0x00E] = {{stat='Vs. plantoids: Evasion',potency=potencies.family.evasion}}, + [0x00F] = {{stat='Vs. plantoids: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x010] = {{stat='Vs. plantoids: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x011] = {{stat='Vs. plantoids: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x012] = {{stat='Vs. plantoids: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x013] = {{stat='Vs. plantoids: Rng.Atk.',potency=potencies.family.attack}}, + [0x014] = {{stat='Vs. plantoids: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x015] = {{stat='Vs. vermin: Attack',potency=potencies.family.attack}}, + [0x016] = {{stat='Vs. vermin: DEF',potency=potencies.family.defense}}, + [0x017] = {{stat='Vs. vermin: Accuracy',potency=potencies.family.accuracy}}, + [0x018] = {{stat='Vs. vermin: Evasion',potency=potencies.family.evasion}}, + [0x019] = {{stat='Vs. vermin: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x01A] = {{stat='Vs. vermin: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x01B] = {{stat='Vs. vermin: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x01C] = {{stat='Vs. vermin: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x01D] = {{stat='Vs. vermin: Rng.Atk.',potency=potencies.family.attack}}, + [0x01E] = {{stat='Vs. vermin: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x01F] = {{stat='Vs. lizards: Attack',potency=potencies.family.attack}}, + [0x020] = {{stat='Vs. lizards: DEF',potency=potencies.family.defense}}, + [0x021] = {{stat='Vs. lizards: Accuracy',potency=potencies.family.accuracy}}, + [0x022] = {{stat='Vs. lizards: Evasion',potency=potencies.family.evasion}}, + [0x023] = {{stat='Vs. lizards: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x024] = {{stat='Vs. lizards: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x025] = {{stat='Vs. lizards: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x026] = {{stat='Vs. lizards: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x027] = {{stat='Vs. lizards: Rng.Atk.',potency=potencies.family.attack}}, + [0x028] = {{stat='Vs. lizards: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x029] = {{stat='Vs. birds: Attack',potency=potencies.family.attack}}, + [0x02A] = {{stat='Vs. birds: DEF',potency=potencies.family.defense}}, + [0x02B] = {{stat='Vs. birds: Accuracy',potency=potencies.family.accuracy}}, + [0x02C] = {{stat='Vs. birds: Evasion',potency=potencies.family.evasion}}, + [0x02D] = {{stat='Vs. birds: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x02E] = {{stat='Vs. birds: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x02F] = {{stat='Vs. birds: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x030] = {{stat='Vs. birds: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x031] = {{stat='Vs. birds: Rng.Atk.',potency=potencies.family.attack}}, + [0x032] = {{stat='Vs. birds: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x033] = {{stat='Vs. amorphs: Attack',potency=potencies.family.attack}}, + [0x034] = {{stat='Vs. amorphs: DEF',potency=potencies.family.defense}}, + [0x035] = {{stat='Vs. amorphs: Accuracy',potency=potencies.family.accuracy}}, + [0x036] = {{stat='Vs. amorphs: Evasion',potency=potencies.family.evasion}}, + [0x037] = {{stat='Vs. amorphs: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x038] = {{stat='Vs. amorphs: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x039] = {{stat='Vs. amorphs: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x03A] = {{stat='Vs. amorphs: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x03B] = {{stat='Vs. amorphs: Rng.Atk.',potency=potencies.family.attack}}, + [0x03C] = {{stat='Vs. amorphs: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x03D] = {{stat='Vs. aquans: Attack',potency=potencies.family.attack}}, + [0x03E] = {{stat='Vs. aquans: DEF',potency=potencies.family.defense}}, + [0x03F] = {{stat='Vs. aquans: Accuracy',potency=potencies.family.accuracy}}, + [0x040] = {{stat='Vs. aquans: Evasion',potency=potencies.family.evasion}}, + [0x041] = {{stat='Vs. aquans: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x042] = {{stat='Vs. aquans: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x043] = {{stat='Vs. aquans: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x044] = {{stat='Vs. aquans: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x045] = {{stat='Vs. aquans: Rng.Atk.',potency=potencies.family.attack}}, + [0x046] = {{stat='Vs. aquans: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x047] = {{stat='Vs. undead: Attack',potency=potencies.family.attack}}, + [0x048] = {{stat='Vs. undead: DEF',potency=potencies.family.defense}}, + [0x049] = {{stat='Vs. undead: Accuracy',potency=potencies.family.accuracy}}, + [0x04A] = {{stat='Vs. undead: Evasion',potency=potencies.family.evasion}}, + [0x04B] = {{stat='Vs. undead: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x04C] = {{stat='Vs. undead: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x04D] = {{stat='Vs. undead: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x04E] = {{stat='Vs. undead: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x04F] = {{stat='Vs. undead: Rng.Atk.',potency=potencies.family.attack}}, + [0x050] = {{stat='Vs. undead: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x051] = {{stat='Vs. elementals: Attack',potency=potencies.family.attack}}, + [0x052] = {{stat='Vs. elementals: DEF',potency=potencies.family.defense}}, + [0x053] = {{stat='Vs. elementals: Accuracy',potency=potencies.family.accuracy}}, + [0x054] = {{stat='Vs. elementals: Evasion',potency=potencies.family.evasion}}, + [0x055] = {{stat='Vs. elementals: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x056] = {{stat='Vs. elementals: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x057] = {{stat='Vs. elementals: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x058] = {{stat='Vs. elementals: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x059] = {{stat='Vs. elementals: Rng.Atk.',potency=potencies.family.attack}}, + [0x05A] = {{stat='Vs. elementals: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x05B] = {{stat='Vs. arcana: Attack',potency=potencies.family.attack}}, + [0x05C] = {{stat='Vs. arcana: DEF',potency=potencies.family.defense}}, + [0x05D] = {{stat='Vs. arcana: Accuracy',potency=potencies.family.accuracy}}, + [0x05E] = {{stat='Vs. arcana: Evasion',potency=potencies.family.evasion}}, + [0x05F] = {{stat='Vs. arcana: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x060] = {{stat='Vs. arcana: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x061] = {{stat='Vs. arcana: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x062] = {{stat='Vs. arcana: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x063] = {{stat='Vs. arcana: Rng.Atk.',potency=potencies.family.attack}}, + [0x064] = {{stat='Vs. arcana: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x065] = {{stat='Vs. Demons: Attack',potency=potencies.family.attack}}, + [0x066] = {{stat='Vs. Demons: DEF',potency=potencies.family.defense}}, + [0x067] = {{stat='Vs. Demons: Accuracy',potency=potencies.family.accuracy}}, + [0x068] = {{stat='Vs. Demons: Evasion',potency=potencies.family.evasion}}, + [0x069] = {{stat='Vs. Demons: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x06A] = {{stat='Vs. Demons: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x06B] = {{stat='Vs. Demons: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x06C] = {{stat='Vs. Demons: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x06D] = {{stat='Vs. Demons: Rng.Atk.',potency=potencies.family.attack}}, + [0x06E] = {{stat='Vs. Demons: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x06F] = {{stat='Vs. dragons: Attack',potency=potencies.family.attack}}, + [0x070] = {{stat='Vs. dragons: DEF',potency=potencies.family.defense}}, + [0x071] = {{stat='Vs. dragons: Accuracy',potency=potencies.family.accuracy}}, + [0x072] = {{stat='Vs. dragons: Evasion',potency=potencies.family.evasion}}, + [0x073] = {{stat='Vs. dragons: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x074] = {{stat='Vs. dragons: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x075] = {{stat='Vs. dragons: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x076] = {{stat='Vs. dragons: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x077] = {{stat='Vs. dragons: Rng.Atk.',potency=potencies.family.attack}}, + [0x078] = {{stat='Vs. dragons: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x079] = {{stat='Vs. Empty: Attack',potency=potencies.family.attack}}, + [0x07A] = {{stat='Vs. Empty: DEF',potency=potencies.family.defense}}, + [0x07B] = {{stat='Vs. Empty: Accuracy',potency=potencies.family.accuracy}}, + [0x07C] = {{stat='Vs. Empty: Evasion',potency=potencies.family.evasion}}, + [0x07D] = {{stat='Vs. Empty: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x07E] = {{stat='Vs. Empty: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x07F] = {{stat='Vs. Empty: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x080] = {{stat='Vs. Empty: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x081] = {{stat='Vs. Empty: Rng.Atk.',potency=potencies.family.attack}}, + [0x082] = {{stat='Vs. Empty: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x083] = {{stat='Vs. Luminians: Attack',potency=potencies.family.attack}}, + [0x084] = {{stat='Vs. Luminians: DEF',potency=potencies.family.defense}}, + [0x085] = {{stat='Vs. Luminians: Accuracy',potency=potencies.family.accuracy}}, + [0x086] = {{stat='Vs. Luminians: Evasion',potency=potencies.family.evasion}}, + [0x087] = {{stat='Vs. Luminians: "Mag.Atk.Bns."',potency=potencies.family.magic_bonus}}, + [0x088] = {{stat='Vs. Luminians: "Mag.Def.Bns."',potency=potencies.family.magic_bonus}}, + [0x089] = {{stat='Vs. Luminians: Mag. Acc.',potency=potencies.family.magic_accuracy}}, + [0x08A] = {{stat='Vs. Luminians: Mag. Evasion',potency=potencies.family.accuracy}}, + [0x08B] = {{stat='Vs. Luminians: Rng.Atk.',potency=potencies.family.attack}}, + [0x08C] = {{stat='Vs. Luminians: Rng.Acc.',potency=potencies.family.accuracy}}, + [0x08D] = {{stat='Might Strikes: Ability delay ',potency=potencies.sp_recast}}, + [0x08E] = {{stat='Hundred Fists: Ability delay ',potency=potencies.sp_recast}}, + [0x08F] = {{stat='Benediction: Ability delay ',potency=potencies.sp_recast}}, + [0x090] = {{stat='Manafont: Ability delay ',potency=potencies.sp_recast}}, + [0x091] = {{stat='Chainspell: Ability delay ',potency=potencies.sp_recast}}, + [0x092] = {{stat='Perfect Dodge: Ability delay ',potency=potencies.sp_recast}}, + [0x093] = {{stat='Invincible: Ability delay ',potency=potencies.sp_recast}}, + [0x094] = {{stat='Blood Weapon: Ability delay ',potency=potencies.sp_recast}}, + [0x095] = {{stat='Familiar: Ability delay ',potency=potencies.sp_recast}}, + [0x096] = {{stat='Soul Voice: Ability delay ',potency=potencies.sp_recast}}, + [0x097] = {{stat='Eagle Eye Shot: Ability delay ',potency=potencies.sp_recast}}, + [0x098] = {{stat='Meikyo Shisui: Ability delay ',potency=potencies.sp_recast}}, + [0x099] = {{stat='Mijin Gakure: Ability delay ',potency=potencies.sp_recast}}, + [0x09A] = {{stat='Spirit Surge: Ability delay ',potency=potencies.sp_recast}}, + [0x09B] = {{stat='Astral Flow: Ability delay ',potency=potencies.sp_recast}}, + [0x09C] = {{stat='Azure Lore: Ability delay ',potency=potencies.sp_recast}}, + [0x09D] = {{stat='Wild Card: Ability delay ',potency=potencies.sp_recast}}, + [0x09E] = {{stat='Overdrive: Ability delay ',potency=potencies.sp_recast}}, + [0x09F] = {{stat='Trance: Ability delay ',potency=potencies.sp_recast}}, + [0x0A0] = {{stat='Tabula Rasa: Ability delay ',potency=potencies.sp_recast}}, + -- There are 308 augments total, and they stop being even remotely systematic after this point. + }, +} + +server_timestamp_offset = 1009792800 + +soul_plates = { + [0x001] = "Main Job: Warrior", + [0x002] = "Main Job: Monk", + [0x003] = "Main Job: White Mage", + [0x004] = "Main Job: Black Mage", + [0x005] = "Main Job: Red Mage", + [0x006] = "Main Job: Thief", + [0x007] = "Main Job: Paladin", + [0x008] = "Main Job: Dark Knight", + [0x009] = "Main Job: Beastmaster", + [0x00A] = "Main Job: Bard", + [0x00B] = "Main Job: Ranger", + [0x00C] = "Main Job: Samurai", + [0x00D] = "Main Job: Ninja", + [0x00E] = "Main Job: Dragoon", + [0x00F] = "Main Job: Summoner", + [0x010] = "Main Job: Blue Mage", + [0x011] = "Main Job: Corsair", + [0x012] = "Main Job: Puppetmaster", + + [0x01F] = "Support Job: Warrior", + [0x020] = "Support Job: Monk", + [0x021] = "Support Job: White Mage", + [0x022] = "Support Job: Black Mage", + [0x023] = "Support Job: Red Mage", + [0x024] = "Support Job: Thief", + [0x025] = "Support Job: Paladin", + [0x026] = "Support Job: Dark Knight", + [0x027] = "Support Job: Beastmaster", + [0x028] = "Support Job: Bard", + [0x029] = "Support Job: Ranger", + [0x02A] = "Support Job: Samurai", + [0x02B] = "Support Job: Ninja", + [0x02C] = "Support Job: Dragoon", + [0x02D] = "Support Job: Summoner", + [0x02E] = "Support Job: Blue Mage", + [0x02F] = "Support Job: Corsair", + [0x030] = "Support Job: Puppetmaster", + + [0x03D] = "Job Ability: Warrior", + [0x03E] = "Job Ability: Monk", + [0x03F] = "Job Ability: White Mage", + [0x040] = "Job Ability: Black Mage", + [0x041] = "Job Ability: Red Mage", + [0x042] = "Job Ability: Thief", + [0x043] = "Job Ability: Paladin", + [0x044] = "Job Ability: Dark Knight", + [0x045] = "Job Ability: Beastmaster", + [0x046] = "Job Ability: Bard", + [0x047] = "Job Ability: Ranger", + [0x048] = "Job Ability: Samurai", + [0x049] = "Job Ability: Ninja", + [0x04A] = "Job Ability: Dragoon", + [0x04B] = "Job Ability: Summoner", + [0x04C] = "Job Ability: Blue Mage", + [0x04D] = "Job Ability: Corsair", + [0x04E] = "Job Ability: Puppetmaster", + + [0x05B] = "Job Trait: Warrior", + [0x05C] = "Job Trait: Monk", + [0x05D] = "Job Trait: White Mage", + [0x05E] = "Job Trait: Black Mage", + [0x05F] = "Job Trait: Red Mage", + [0x060] = "Job Trait: Thief", + [0x061] = "Job Trait: Paladin", + [0x062] = "Job Trait: Dark Knight", + [0x063] = "Job Trait: Beastmaster", + [0x064] = "Job Trait: Bard", + [0x065] = "Job Trait: Ranger", + [0x066] = "Job Trait: Samurai", + [0x067] = "Job Trait: Ninja", + [0x068] = "Job Trait: Dragoon", + [0x069] = "Job Trait: Summoner", + [0x06A] = "Job Trait: Blue Mage", + [0x06B] = "Job Trait: Corsair", + [0x06C] = "Job Trait: Puppetmaster", + + [0x079] = "White Magic Scrolls", + [0x07A] = "Black Magic Scrolls", + [0x07B] = "Bard Scrolls", + [0x07C] = "Ninjutsu Scrolls", + [0x07D] = "Avatar Scrolls", + [0x07E] = "Blue Magic Scrolls", + [0x07F] = "Corsair Dice", + + [0x08D] = "HP Max Bonus", + [0x08E] = "HP Max Bonus II", + [0x08F] = "HP Max +50", + [0x090] = "HP Max +100", + [0x091] = "MP Max Bonus", + [0x092] = "MP Max Bonus II", + [0x093] = "MP Max +50", + [0x094] = "MP Max +100", + [0x095] = "STR Bonus", + [0x096] = "STR Bonus II", + [0x097] = "STR +25", + [0x098] = "STR +50", + [0x099] = "VIT Bonus", + [0x09A] = "VIT Bonus II", + [0x09B] = "VIT +25", + [0x09C] = "VIT +50", + [0x09D] = "AGI Bonus", + [0x09E] = "AGI Bonus II", + [0x09F] = "AGI +25", + [0x0A0] = "AGI +50", + [0x0A1] = "DEX Bonus", + [0x0A2] = "DEX Bonus II", + [0x0A3] = "DEX +25", + [0x0A4] = "DEX +50", + [0x0A5] = "INT Bonus", + [0x0A6] = "INT Bonus II", + [0x0A7] = "INT +25", + [0x0A8] = "INT +50", + [0x0A9] = "MND Bonus", + [0x0AA] = "MND Bonus II", + [0x0AB] = "MND +25", + [0x0AC] = "MND +50", + [0x0AD] = "CHR Bonus", + [0x0AE] = "CHR Bonus II", + [0x0AF] = "CHR +25", + [0x0B0] = "CHR +50", + + [0x0C9] = "Monster Level Bonus", + [0x0CA] = "Monster Level Bonus II", + [0x0CB] = "Monster Level +2", + [0x0CC] = "Monster Level +4", + [0x0CD] = "Skill Level Bonus", + [0x0CE] = "Skill Level Bonus II", + [0x0CF] = "Skill Level +4", + [0x0D0] = "Skill Level +8", + [0x0D1] = "HP Max Rate Bonus", + [0x0D2] = "HP Max Rate Bonus II", + [0x0D3] = "HP Max +15%", + [0x0D4] = "HP Max +30%", + [0x0D5] = "MP Max Rate Bonus", + [0x0D6] = "MP Max Rate Bonus II", + [0x0D7] = "MP Max +15%", + [0x0D8] = "MP Max +30%", + [0x0D9] = "Attack Bonus", + [0x0DA] = "Attack Bonus II", + [0x0DB] = "Attack +15%", + [0x0DC] = "Attack +30%", + [0x0DD] = "Defense Bonus", + [0x0DE] = "Defense Bonus II", + [0x0DF] = "Defense +15%", + [0x0E0] = "Defense +30%", + [0x0E1] = "Magic Attack Bonus", + [0x0E2] = "Magic Attack Bonus II", + [0x0E3] = "Magic Attack +15%", + [0x0E4] = "Magic Attack +30%", + [0x0E5] = "Magic Defense Bonus", + [0x0E6] = "Magic Defense Bonus II", + [0x0E7] = "Magic Defense +15%", + [0x0E8] = "Magic Defense +30%", + [0x0E9] = "Accuracy Bonus", + [0x0EA] = "Accuracy Bonus II", + [0x0EB] = "Accuracy +15%", + [0x0EC] = "Accuracy +30%", + [0x0ED] = "Magic Accuracy Bonus", + [0x0EE] = "Magic Accuracy Bonus II", + [0x0EF] = "Magic Accuracy +15%", + [0x0F0] = "Magic Accuracy +30%", + [0x0F1] = "Evasion Bonus", + [0x0F2] = "Evasion Bonus II", + [0x0F3] = "Evasion +15%", + [0x0F4] = "Evasion +30%", + [0x0F5] = "Critical Hit Bonus", + [0x0F6] = "Critical Hit Bonus II", + [0x0F7] = "Critical Hit Rate +10%", + [0x0F8] = "Critical Hit Rate +20%", + [0x0F9] = "Interruption Rate Bonus", + [0x0FA] = "Interruption Rate Bonus II", + [0x0FB] = "Interruption Rate -25%", + [0x0FC] = "Interruption Rate -50%", + [0x0FD] = "Auto Regen", + [0x0FE] = "Auto Regen II", + [0x0FF] = "Auto Regen +5", + [0x100] = "Auto Regen +10", + [0x101] = "Auto Refresh", + [0x102] = "Auto Refresh II", + [0x103] = "Auto Refresh +5", + [0x104] = "Auto Refresh +10", + [0x105] = "Auto Regain", + [0x106] = "Auto Regain II", + [0x107] = "Auto Regain +3", + [0x108] = "Auto Regain +6", + [0x109] = "Store TP", + [0x10A] = "Store TP II", + [0x10B] = "Store TP +10%", + [0x10C] = "Store TP +20%", + [0x10D] = "Healing Magic Bonus", + [0x10E] = "Healing Magic Bonus II", + [0x10F] = "Healing Magic Skill +10%", + [0x110] = "Healing Magic Skill +20%", + [0x111] = "Divine Magic Bonus", + [0x112] = "Divine Magic Bonus II", + [0x113] = "Divine Magic Skill +10%", + [0x114] = "Divine Magic Skill +20%", + [0x115] = "Enhancing Magic Bonus", + [0x116] = "Enhancing Magic Bonus II", + [0x117] = "Enhancing Magic Skill +10%", + [0x118] = "Enhancing Magic Skill +20%", + [0x119] = "Enfeebling Magic Bonus", + [0x11A] = "Enfeebling Magic Bonus II", + [0x11B] = "Enfeebling Magic Skill +10%", + [0x11C] = "Enfeebling Magic Skill +20%", + [0x11D] = "Elemental Magic Bonus", + [0x11E] = "Elemental Magic Bonus II", + [0x11F] = "Elemental Magic Skill +10%", + [0x120] = "Elemental Magic Skill +20%", + [0x121] = "Dark Magic Bonus", + [0x122] = "Dark Magic Bonus II", + [0x123] = "Dark Magic Skill +10%", + [0x124] = "Dark Magic Skill +20%", + [0x125] = "Singing Bonus", + [0x126] = "Singing Bonus II", + [0x127] = "Singing Skill +10%", + [0x128] = "Singing Skill +20%", + [0x129] = "Ninjutsu Bonus", + [0x12A] = "Ninjutsu Bonus II", + [0x12B] = "Ninjutsu Skill +10%", + [0x12C] = "Ninjutsu Skill +20%", + [0x12D] = "Summoning Magic Bonus", + [0x12E] = "Summoning Magic Bonus II", + [0x12F] = "Summoning Magic Skill +10%", + [0x130] = "Summoning Magic Skill +20%", + [0x131] = "Blue Magic Bonus", + [0x132] = "Blue Magic Bonus II", + [0x133] = "Blue Magic Skill +10%", + [0x134] = "Blue Magic Skill +20%", + [0x135] = "Movement Speed Bonus", + [0x136] = "Movement Speed Bonus II", + [0x137] = "Movement Speed +5", + [0x138] = "Movement Speed +10", + [0x139] = "Attack Speed Bonus", + [0x13A] = "Attack Speed Bonus II", + [0x13B] = "Attack Speed +50", + [0x13C] = "Attack Speed +100", + [0x13D] = "Magic Frequency Bonus", + [0x13E] = "Magic Frequency Bonus II", + [0x13F] = "Magic Frequency +3", + [0x140] = "Magic Frequency +6", + [0x141] = "Ability Speed Bonus", + [0x142] = "Ability Speed Bonus II", + [0x143] = "Ability Speed +15%", + [0x144] = "Ability Speed +30%", + [0x145] = "Magic Casting Speed Bonus", + [0x146] = "Magic Casting Speed Bonus II", + [0x147] = "Magic Casting Speed +15%", + [0x148] = "Magic Casting Speed +30%", + [0x149] = "Ability Recast Speed Bonus", + [0x14A] = "Ability Recast Speed Bonus II", + [0x14B] = "Ability Recast Speed +15%", + [0x14C] = "Ability Recast Speed +30%", + [0x14D] = "Magic Recast Bonus", + [0x14E] = "Magic Recast Bonus II", + [0x14F] = "Magic Recast Speed +25%", + [0x150] = "Magic Recast Speed +50%", + [0x151] = "Ability Range Bonus", + [0x152] = "Ability Range Bonus II", + [0x153] = "Ability Range +2", + [0x154] = "Ability Range +4", + [0x155] = "Magic Range Bonus", + [0x156] = "Magic Range Bonus II", + [0x157] = "Magic Range +2", + [0x158] = "Magic Range +4", + [0x159] = "Ability Acquisition Bonus", + [0x15A] = "Ability Acquisition Bonus II", + [0x15B] = "Ability Acquisition Level -5", + [0x15C] = "Ability Acquisition Level -10", + [0x15D] = "Magic Acquisition Bonus", + [0x15E] = "Magic Acquisition Bonus II", + [0x15F] = "Magic Acquisition Level -5", + [0x160] = "Magic Acquisition Level -10", -- 00 B0 F8 03 + [0x161] = "Healing Potency Bonus", + [0x162] = "Healing Potency Bonus II", + [0x163] = "Healing Potency +40", + [0x164] = "Healing Potency +80", + [0x165] = "MP Recovery Bonus", + [0x166] = "MP Recovery Bonus II", + [0x167] = "MP Recovery +25", + [0x168] = "MP Recovery +50", + [0x169] = "TP Recovery Bonus", + [0x16A] = "TP Recovery Bonus II", + [0x16B] = "TP Recovery +25", + [0x16C] = "TP Recovery +50", + [0x16D] = "50% MP Conserve Rate Bonus", + [0x16E] = "50% MP Conserve Rate Bonus II", + [0x16F] = "50% MP Conserve Rate +15%", + [0x170] = "50% MP Conserve Rate +30%", + [0x171] = "100% MP Conserve Rate Bonus", + [0x172] = "100% MP Conserve Rate Bonus II", + [0x173] = "100% MP Conserve Rate +15%", + [0x174] = "100% MP Conserve Rate +30%", + [0x175] = "50% TP Conserve Rate Bonus", + [0x176] = "50% TP Conserve Rate Bonus II", + [0x177] = "50% TP Conserve Rate +15%", + [0x178] = "50% TP Conserve Rate +30%", + [0x179] = "100% TP Conserve Rate Bonus", + [0x17A] = "100% TP Conserve Rate Bonus II", + [0x17B] = "100% TP Conserve Rate +15%", + [0x17C] = "100% TP Conserve Rate +30%", + [0x17D] = "EXP Bonus", + [0x17E] = "EXP Bonus II", + [0x17F] = "EXP +15%", + [0x180] = "EXP +30%", + [0x181] = "Skill Improvement Rate Bonus", + [0x182] = "Skill Improvement Rate Bonus II", + [0x183] = "Skill Improvement Rate +15%", + [0x184] = "Skill Improvement Rate +30%", + [0x185] = "Trust Bonus", + [0x186] = "Trust Bonus II", + [0x187] = "Trust +15", + [0x188] = "Trust +30", + + [0x195] = "Embiggen", + [0x196] = "Embiggen II", + [0x197] = "Minimize", + [0x198] = "Minimize II", + + [0x19D] = "Gradual HP Max Bonus", + [0x19E] = "Diminishing HP Max Bonus", + [0x19F] = "Gradual MP Max Bonus", + [0x1A0] = "Diminishing MP Max Bonus", + [0x1A1] = "Gradual Attack Bonus", + [0x1A2] = "Diminishing Attack Bonus", + [0x1A3] = "Gradual Defense Bonus", + [0x1A4] = "Diminishing Defense Bonus", + [0x1A5] = "Gradual Magic Attack Bonus", + [0x1A6] = "Diminishing Magic Attack Bonus", + [0x1A7] = "Gradual Magic Defense Bonus", + [0x1A8] = "Diminishing Magic Defense Bonus", + [0x1A9] = "Gradual Accuracy Bonus", + [0x1B0] = "Diminishing Accuracy Bonus", + [0x1B1] = "Gradual Magic Accuracy Bonus", + [0x1B2] = "Diminishing Magic Accuracy Bonus", + [0x1B3] = "Gradual Evasion Bonus", + [0x1B4] = "Diminishing Evasion Bonus", + [0x1B5] = "Gradual Critial Hit Rate Bonus", + [0x1B6] = "Diminishing Critial Hit Rate Bonus", + [0x1B7] = "Gradual Interruption Rate Bonus", + [0x1B8] = "Diminishing Interruption Rate Bonus", + [0x1B9] = "Gradual EXP Bonus", + [0x1BA] = "Diminishing EXP Bonus", + [0x1BB] = "Resist Poison", + [0x1BC] = "Resist Sleep", + [0x1BD] = "Resist Blind", + [0x1BE] = "Resist Slow", + [0x1BF] = "Resist Paralysis", + [0x1C0] = "Resist Bind", + [0x1C1] = "Resist Silence", + [0x1C2] = "Bird Killer", + [0x1C3] = "Amorph Killer", + [0x1C4] = "Lizard Killer", + [0x1C5] = "Aquan Killer", + [0x1C6] = "Plantoid Killer", + [0x1C7] = "Beast Killer", + [0x1C8] = "Demon Killer", + [0x1C9] = "Dragon Killer", + [0x1CA] = "Double Attack", + [0x1CB] = "Double Attack II", + [0x1CC] = "Double Attack Rate +15%", + [0x1CD] = "Double Attack Rate +30%", + [0x1CE] = "Triple Attack", + [0x1CF] = "Triple Attack II", + [0x1D0] = "Triple Attack Rate +15%", + [0x1D1] = "Triple Attack Rate +30%", + [0x1D2] = "Counter", + [0x1D3] = "Counter II", + [0x1D4] = "Counter Rate +15%", + [0x1D5] = "Counter Rate +30%", + [0x1D6] = "Subtle Blow", + [0x1D7] = "Subtle Blow II", + [0x1D8] = "Subtle Blow Rate +15%", + [0x1D9] = "Subtle Blow Rate +30%", + [0x1DA] = "Savagery", + [0x1DB] = "Aggressive Aim", + [0x1DC] = "Martial Arts", + [0x1DD] = "Kick Attacks", + [0x1DE] = "Invigorate", + [0x1DF] = "Penance", + [0x1E0] = "Clear Mind", + [0x1E1] = "Divine Veil", + [0x1E2] = "Assassin", + [0x1E3] = "Aura Steal", + [0x1E4] = "Ambush", + [0x1E5] = "Shield Mastery", + [0x1E6] = "Iron Will", + [0x1E7] = "Guardian", + [0x1E8] = "Muted Soul", + [0x1E9] = "Desperate Blows", + [0x1EA] = "Carrot Broth", + [0x1EB] = "Herbal Broth", + [0x1EC] = "Fish Broth", + [0x1ED] = "Alchemist's Water", + [0x1EE] = "Meat Broth", + [0x1EF] = "Bug Broth", + [0x1F0] = "Carrion Broth", + [0x1F1] = "Seedbed Soil", + [0x1F2] = "Tree Sap", + [0x1F3] = "Beast Affinity", + [0x1F4] = "Beast Healer", + [0x1F5] = "Alertness", + [0x1F6] = "Recycle", + [0x1F7] = "Rapid Shot", + [0x1F8] = "Snapshot", + [0x1F9] = "Zanshin", + [0x1FA] = "Overwhelm", + [0x1FB] = "Ikishoten", + [0x1FC] = "Stealth", + [0x1FD] = "Dual Wield", + [0x1FE] = "Ninja Tool Expertise", + [0x1FF] = "Ninja Tool Supply", +} + +-- TOOLS FOR HANDLING EXTDATA + +tools = {} +tools.aug = {} + +tools.bit = {} +----------------------------------------------------------------------------------- +--Name: tools.bit.l_to_r_bit_packed(dat_string,start,stop) +--Args: +---- dat_string - string that is being bit-unpacked to a number +---- start - first bit +---- stop - last bit +----------------------------------------------------------------------------------- +--Returns: +---- number from the indicated range of bits +----------------------------------------------------------------------------------- +function tools.bit.l_to_r_bit_packed(dat_string,start,stop) + local newval = 0 + + local c_count = math.ceil(stop/8) + while c_count >= math.ceil((start+1)/8) do + -- Grabs the most significant byte first and works down towards the least significant. + local cur_val = dat_string:byte(c_count) or 0 + local scal = 1 + + if c_count == math.ceil(stop/8) then -- Take the least significant bits of the most significant byte + -- Moduluses by 2^number of bits into the current byte. So 8 bits in would %256, 1 bit in would %2, etc. + -- Cuts off the bottom. + cur_val = math.floor(cur_val/(2^(8-((stop-1)%8+1)))) -- -1 and +1 set the modulus result range from 1 to 8 instead of 0 to 7. + end + + if c_count == math.ceil((start+1)/8) then -- Take the most significant bits of the least significant byte + -- Divides by the significance of the final bit in the current byte. So 8 bits in would /128, 1 bit in would /1, etc. + -- Cuts off the top. + cur_val = cur_val%(2^(8-start%8)) + end + + if c_count == math.ceil(stop/8)-1 then + scal = 2^(((stop-1)%8+1)) + end + + newval = newval + cur_val*scal -- Need to multiply by 2^number of bits in the next byte + c_count = c_count - 1 + end + return newval +end + + +function tools.bit.bit_string(bits,str,map) + local i,sig = 0,'' + while map[tools.bit.l_to_r_bit_packed(str,i,i+bits)] do + sig = sig..map[tools.bit.l_to_r_bit_packed(str,i,i+bits)] + i = i+bits + end + return sig +end + +tools.sig = {} +function tools.sig.decode(str) + local sig_map = {[1]='0',[2]='1',[3]='2',[4]='3',[5]='4',[6]='5',[7]='6',[8]='7',[9]='8',[10]='9', + [11]='A',[12]='B',[13]='C',[14]='D',[15]='E',[16]='F',[17]='G',[18]='H',[19]='I',[20]='J',[21]='K',[22]='L',[23]='M', + [24]='N',[25]='O',[26]='P',[27]='Q',[28]='R',[29]='S',[30]='T',[31]='U',[32]='V',[33]='W',[34]='X',[35]='Y',[36]='Z', + [37]='a',[38]='b',[39]='c',[40]='d',[41]='e',[42]='f',[43]='g',[44]='h',[45]='i',[46]='j',[47]='k',[48]='l',[49]='m', + [50]='n',[51]='o',[52]='p',[53]='q',[54]='r',[55]='s',[56]='t',[57]='u',[58]='v',[59]='w',[60]='x',[61]='y',[62]='z', + [63]='{' + } + return tools.bit.bit_string(6,str,sig_map) +end + + +function tools.aug.unpack_augment(sys,short) + if sys == 1 then + return short:byte(1) + short:byte(2)%8*256, math.floor(short:byte(2)/8) + elseif sys == 2 then + return short:byte(1), short:byte(2) + elseif sys == 3 then + return short:byte(1) + short:byte(2)%8*256, math.floor(short:byte(2)%128/8) + elseif sys == 4 then + return short:byte(1), short:byte(2) + end +end + +function tools.aug.string_augment(sys,id,val) + local augment + local augment_table = augment_values[sys][id] + if not augment_table then --print('Augments Lib: ',sys,id) + elseif augment_table.Secondary_Handling then + -- This is handling for system 1's indices 0x390~0x392, which have their own static augment lookup table + augment_table = sp_390_augments[ (id-0x390)*16 + 545 + val] + end + if augment_table then + if sys == 3 then + augment = augment_table[1].stat + local pot = augment_table[1].potency[val] + if pot > 0 then + augment = augment..'+' + end + augment = augment..pot + else + for i,v in pairs(augment_table) do + if i > 1 then augment = augment..' ' end + augment = (augment or '')..v.stat + local potency = ((val+v.offset)*(v.multiplier or 1)) + if potency > 0 then augment = augment..'+'..potency + elseif potency < 0 then augment = augment..potency end + if v.percent then + augment = augment..'%' + end + end + end + else + augment = 'System: '..tostring(sys)..' ID: '..tostring(id)..' Val: '..tostring(val) + end + return augment +end + +function tools.aug.augments_to_table(sys,str) + local augments,ids,vals = {},{},{} + for i=1,#str,2 do + local id,val = tools.aug.unpack_augment(sys,str:sub(i,i+1)) + augments[#augments+1] = tools.aug.string_augment(sys,id,val) + end + return augments +end + +function decode.Enchanted(str) + local rettab = {type = 'Enchanted Equipment', + charges_remaining = str:byte(2), + usable = str:byte(4)%128/64>=1, + next_use_time = str:unpack('I',5) + server_timestamp_offset, + activation_time = str:unpack('I',9) + server_timestamp_offset, + } + return rettab +end + +function decode.Augmented(str) + local flag_2 = str:byte(2) + local rettab = {type = 'Augmented Equipment'} + if flag_2%128/64>= 1 then + rettab.trial_number = (str:byte(12)%128)*256+str:byte(11) + rettab.trial_complete = str:byte(12)/128>=1 + end + + if flag_2%16/8 >= 1 then -- Crafting shields + rettab.objective = str:byte(6) + local units = {30,50,100,100} + rettab.stage = math.max(1,math.min(4,str:byte(0x9))) + rettab.completion = str:unpack('H',7)/units[rettab.stage] + elseif flag_2%64/32 >=1 then + rettab.augment_system = 2 + local path_map = {[0] = 'A',[1] = 'B', [2] = 'C', [3] = 'D'} + local points_map = {[1] = 50, [2] = 80, [3] = 120, [4] = 170, [5] = 220, [6] = 280, [7] = 340, [8] = 410, [9] = 480, [10]=560, [11]=650, [12] = 750, [13] = 960, [14] = 980} + rettab.path = path_map[str:byte(3)%4] + rettab.rank = math.floor(str:byte(3)%128/4) + rettab.RP = math.max(points_map[rettab.rank] or 0 - str:byte(6)*256 - str:byte(5),0) + rettab.augments = tools.aug.augments_to_table(rettab.augment_system,str:sub(7,12)) + elseif flag_2 == 131 then + rettab.augment_system = 4 + local path_map = {[0] = 'A',[1] = 'B', [2] = 'C', [3] = 'D'} + rettab.path = path_map[math.floor(str:byte(5)%4)] + rettab.augments = {'Path: ' ..rettab.path} + elseif flag_2/128 >= 1 then -- Evolith + rettab.augment_system = 3 + local slot_type_map = {[0] = 'None', [1] = 'Filled Upside-down Triangle', [2] = 'Filled Diamond', [3] = 'Filled Star', [4] = 'Empty Triangle', [5] = 'Empty Square', [6] = 'Empty Circle', [7] = 'Empty Upside-down Triangle', [8] = 'Empty Diamond', [9] = 'Empty Star', [10] = 'Filled Triangle', [11] = 'Filled Square', [12] = 'Filled Circle', [13] = 'Empty Circle', [14] = 'Fire', [15] = 'Ice'} + rettab.slots = {[1] = {type = slot_type_map[str:byte(9)%16], size = math.floor(str:byte(10)/16)+1, element = str:byte(12)%8}, + [2] = {type = slot_type_map[math.floor(str:byte(9)/16)], size = str:byte(11)%16+1, element = math.floor(str:byte(12)/8)%8}, + [3] = {type = slot_type_map[str:byte(10)%16], size = math.floor(str:byte(11)/16)+1, element = math.floor(str:byte(12)/64) + math.floor(str:byte(8)/128)}, + } + rettab.augments = tools.aug.augments_to_table(rettab.augment_system,str:sub(3,8)) + else + rettab.augment_system = 1 + if rettab.trial_number then + rettab.augments = tools.aug.augments_to_table(rettab.augment_system,str:sub(3,10)) + else + rettab.augments = tools.aug.augments_to_table(rettab.augment_system,str:sub(3,12)) + end + end + return rettab +end + + + +-- EXTDATA subgroups +-- Which subgroup an item falls into depends on its type, which is pulled from the resources based on ites item ID. + +function decode.General(str) + decoded = {type = 'General'} + if str:byte(13) ~= 0 then + decoded.signature = tools.sig.decode(str:sub(13)) + end + return decoded +end + +function decode.Fish(str) + local rettab = { + size = str:unpack('H'), + weight = str:unpack('H',3), + is_ranked = str:byte(5)%2 == 1 + } + return rettab +end + +function decode.Equipment(str) + local rettab + local flag_1 = str:byte(1) + local flag_1_mapping = { + [1] = decode.Enchanted, + [2] = decode.Augmented, + [3] = decode.Augmented, + } + if flag_1_mapping[flag_1] then + rettab = flag_1_mapping[flag_1](str:sub(1,12)) + else + rettab= decode.Unknown(str:sub(1,12)) + rettab.type = 'Equipment' + end + if str:byte(13) ~= 0 then + rettab.signature = tools.sig.decode(str:sub(13)) + end + return rettab +end + +function decode.Linkshell(str) + local status_map = {[0]='Unopened',[1]='Linkshell',[2]='Pearlsack',[3]='Linkpearl',[4]='Broken'} + local name_end = #str + while str:byte(name_end) == 0 and name_end > 10 do + name_end = name_end - 1 + end + local name_map = {[0]="'",[1]="a",[2]='b',[3]='c',[4]='d',[5]='e',[6]='f',[7]='g',[8]='h',[9]='i',[10]='j', + [11]='k',[12]='l',[13]='m',[14]='n',[15]='o',[16]='p',[17]='q',[18]='r',[19]='s',[20]='t',[21]='u',[22]='v',[23]='w', + [24]='x',[25]='y',[26]='z',[27]='A',[28]='B',[29]='C',[30]='D',[31]='E',[32]='F',[33]='G',[34]='H',[35]='I',[36]='J', + [37]='K',[38]='L',[39]='M',[40]='N',[41]='O',[42]='P',[43]='Q',[44]='R',[45]='S',[46]='T',[47]='U',[48]='V',[49]='W', + [50]='X',[51]='Y',[52]='Z' + } + local rettab = {type = 'Linkshell', + linkshell_id = str:unpack('I'), + r = 17*(str:byte(7)%16), + g = 17*math.floor(str:byte(7)/16), + b = 17*(str:byte(8)%16), + status_id = str:byte(9), + status = status_map[str:byte(9)]} + + if rettab.status_id ~= 0 then + rettab.name = tools.bit.bit_string(6,str:sub(10,name_end),name_map) + end + + return rettab +end + +function decode.Furniture(str) + local rettab = {type = 'Furniture', + is_displayed = (str:byte(2)%128/64 >= 1)} + if rettab.is_displayed then + rettab.grid_x = str:byte(7) + rettab.grid_z = str:byte(8) + rettab.grid_y = str:byte(9) + rettab.rotation = str:byte(10) + end + return rettab +end + +function decode.Flowerpot(str) + --[[ 0 = Empty pot, Plant seed menu + (1) 2-11 = Herb Seeds + (14?)15-24 = Grain Seeds + (27?)28-37 = Vegetable Seeds + (40?)41-50 = Cactus Stems + (53?)54-63 = Tree Cutting + (65?)66-76 = Tree Sapling + (79?)80-89 = Wildgrass Seed]] + local rettab = {type = 'Flowerpot', + is_displayed = (str:byte(2)%128/64 >= 1)} + if rettab.is_displayed then + rettab.grid_x = str:byte(7) + rettab.grid_z = str:byte(8) + rettab.grid_y = str:byte(9) + rettab.rotation = str:byte(10) + rettab.is_planted = str:byte(1)%13 > 0 + end + if rettab.is_planted then + local plants = {[0] = 'Herb Seeds', [1] = 'Grain Seeds',[2] = 'Vegetable Seeds', [3] = 'Cactus Stems', + [4] = 'Tree Cuttings', [5] = 'Tree Saplings', [6] = 'Wildgrass Seeds'} + local stages = {[1] = 'Initial', [2] = 'First Sprouts', [3] = 'First Sprouts 2', [4] = 'First Sprouts - Crystal', + [5] = 'Second Sprouts', [6] = 'Second Sprouts 2', [7] = 'Second Sprouts - Crystal', [8] = 'Second Sprouts 3', + [9] = 'Third Sprouts', [10] = 'Mature Plant', [11] = 'Wilted'} + rettab.plant_id = math.floor((str:byte(1)-1)/13) + rettab.plant = plants[plant_id] + rettab.stage_id = str:byte(1)%13 -- Stages from 1 to 10 are valid + rettab.ts_1 = str:unpack('I',13) + server_timestamp_offset + rettab.ts_2 = str:unpack('I',17) + server_timestamp_offset + rettab.unknown = str:byte(5)+str:byte(6)*256 + end + return rettab +end + +function decode.Mannequin(str) + local rettab = {type = 'Mannequin', + is_displayed = (str:byte(2)%128/64 >= 1) + } + if rettab.is_displayed then + local facing_map = { + [0] = 'West', + [1] = 'South', + [2] = 'East', + [3] = 'North', + } + rettab.grid_x = str:byte(7) + rettab.grid_z = str:byte(8) + rettab.grid_y = str:byte(9) + rettab.facing = facing_map[str:byte(10)] + local storage = windower.ffxi.get_items(2) + local empty = {} + rettab.equipment = {main = storage[str:byte(11)] or empty, sub = storage[str:byte(12)] or empty, + ranged = storage[str:byte(13)] or empty, head = storage[str:byte(14)] or empty, body = storage[str:byte(15)] or empty, + hands = storage[str:byte(16)] or empty, legs = storage[str:byte(17)] or empty, feet = storage[str:byte(18)] or empty} + rettab.race_id = str:byte(19) + rettab.race = res.races[rettab.race_id] + rettab.pose_id = str:byte(20) + end + return rettab +end + +function decode.PvPReservation(str) + local rettab = {type='PvP Reservation', + time = str:unpack('I') + server_timestamp_offset, + level = math.floor(str:byte(4)/32)*10 + } + if rettab.level == 0 then rettab.level = 99 end + return rettab +end + +function decode.SoulPlate(str) + local name_end = string.find(str,string.char(0),1) + local name_map = {} + for i = 1,127 do + name_map[i] = string.char(i) + end + local rettab = {type = 'Soul Plate', + skill_id = math.floor(str:byte(21)/128) + str:byte(22)*2 + str:byte(23)%8*(2^9), -- Index for whatever table I end up making, so table[skill_id] would be {name = "Breath Damage", multiplier = 1, percent=true} + skill = soul_plates[math.floor(str:byte(21)/128) + str:byte(22)*2 + str:byte(23)%8*(2^9)] or 'Unknown', -- "Breath damage +5%, etc." + FP = math.floor(str:byte(23)/8) + str:byte(24)%4*16, -- Cost in FP + name = tools.bit.bit_string(7,str:sub(1,name_end),name_map), -- Name of the monster +-- 9D 87 AE C0 = 'Naul' + } + return rettab +end + +function decode.Reflector(str) + local firstnames = {"Bloody", "Brutal", "Celestial", "Combat", "Cyclopean", "Dark", "Deadly", + "Drachen", "Giant", "Hostile", "Howling", "Hyper", "Invincible", "Merciless", "Mighty", + "Necro", "Nimble", "Poison", "Putrid", "Rabid", "Radiant", "Raging", "Relentless", + "Savage", "Silent", "Tenebrous", "The", "Triple", "Undead", "Writhing", "Serpentine", + "Aile", "Bete", "Croc", "Babine", "Carapace", "Colosse", "Corne", "Fauve", + "Flamme", "Griffe", "Machoire", "Mandibule", "Patte", "Rapace", "Tentacule", "Voyou", + "Zaubernder", "Brutaler", "Explosives", "Funkelnder", "Kraftvoller", "Moderndes", "Tosender", "Schwerer", + "Sprintender", "Starker", "Stinkender", "Taumelnder", "Tolles", "Verlornes", "Wendiger", "Wuchtiger"} + firstnames[0] = "Pit" + local lastnames = {"Beast", "Butcher", "Carnifex", "Critter", "Cyclone", "Dancer", "Darkness", + "Erudite", "Fang", "Fist", "Flayer", "Gladiator", "Heart", "Howl", "Hunter", + "Jack", "Machine", "Mountain", "Nemesis", "Raven", "Reaver", "Rock", "Stalker", + "T", "Tiger", "Tornado", "Vermin", "Vortex", "Whispers", "X", "Prime", + "Agile", "Brave", "Coriace", "Diabolique", "Espiegle", "Feroce", "Fidele", "Fourbe", + "Impitoyable", "Nuisible", "Robuste", "Sanguinaire", "Sauvage", "Stupide", "Tenace", "Tendre", + "Boesewicht", "Engel", "Flitzpiepe", "Gottkaiser", "Klotz", "Muelleimer", "Pechvogel", "Postbote", + "Prinzessin", "Raecherin", "Riesenbaby", "Hexer", "Teufel", "Vieh", "Vielfrass", "Fleischer"} + lastnames[0] = "Monster" + local rettab = {type='Reflector', + first_name = firstnames[str:byte(1)%64], + last_name = lastnames[math.floor(str:byte(1)/64) + (str:byte(2)%16)*4], + level = (math.floor(str:byte(7)/16) + (str:byte(8)%16)*16)%128 + } + return rettab +end + +function decode.BonanzaMarble(str) + local event_list = { + [0x00] = 'CS Event Race', + [0x01] = 'Race Type 1', + [0x02] = 'Race Type 2', + [0x03] = 'Race Type 3', + [0x04] = 'Race Type 4', + [0x05] = 'Race Type 5', + [0x06] = 'Race Type 6', + [0x07] = 'Race Type 7', + [0x08] = 'Race Type 8', + [0x09] = 'Race Type 9', + [0x0A] = 'Race Type 10', + [0x0B] = 'Altana Cup II', + [0x0C] = 'C1 Crystal Stakes', + [0x0D] = 'C2 Chocobo Race', + [0x0E] = 'C3 Chocobo Race', + [0x0F] = 'C4 Chocobo Race', + [0x3D] = 'Ohohohohoho!', + [0x3E] = 'Item Level:%d', + [0x3F] = 'Type:%c/Rank:%d/RP:%d', + [0x40] = '6th Anniversary Mog Bonanza', + [0x41] = '7th Anniversary Mog Bonanza', + [0x42] = 'Moggy New Year Bonanza', + [0x43] = '8th Anniversary Mog Bonanza', + [0x44] = 'Moggy New Year Bonanza', + [0x45] = '9th Anniversary Mog Bonanza', + [0x46] = 'Mog Bonanza Home Coming', + [0x47] = "11th Vana'versary Mog Bonanza", + [0x48] = 'I Dream of Mog Bonanza 2014', + [0x49] = '12th Anniversary Mog Bonanza', + [0x4A] = 'I Dream of Mog Bonanza 2015', + } + local rettab = {type = 'Bonanza Marble', + number = str:byte(3)*256*256 + str:unpack('H',1), -- Who uses 3 bytes? SE does! + event = event_list[str:byte(4)] or (str:byte(4) < 0x4B and '.'), + } + return rettab +end + +function decode.LegionPass(str) + local chamber_list = { + [0x2EF] = "Hall of An: 18 combatants", + [0x2F0] = "Hall of An: 36 combatants", + [0x2F1] = "Hall of Ki: 18 combatants", + [0x2F2] = "Hall of Ki: 36 combatants", + [0x2F3] = "Hall of Im: 18 combatants", + [0x2F4] = "Hall of Im: 36 combatants", + [0x2F5] = "Hall of Muru: 18 combatants", + [0x2F6] = "Hall of Muru: 36 combatants", + [0x2F7] = "Hall of Mul: 18 combatants", + [0x2F8] = "Hall of Mul: 36 combatants",} + local rettab = {type = 'Legion Pass', + entry_time = str:unpack('I',1) + server_timestamp_offset, + leader = tools.sig.decode(str:sub(13,24)), + chamber = chamber_list[str:unpack('H',5)] or 'Unknown', + } + return rettab +end + +function decode.Unknown(str) + return {type = 'Unknown', + value = str:hex() + } +end + +function decode.Lamp(str) + local chambers = {[0x1E] = 'Rossweisse', [0x1F] = 'Grimgerde', [0x20] = 'Siegrune', [0x21] = 'Helmwige', + [0x22] = 'Schwertleite', [0x23] = 'Waltraute', [0x24] = 'Ortlinde', [0x25] = 'Gerhilde', [0x26] = 'Brunhilde', + [0x27] = 'Odin'} + local statuses = {[0] = 'Uninitialized', [1] = 'Active', [2] = 'Active', [3] = 'Spent'} + local rettab = {type='Lamp', + chamber = chambers[str:unpack('H')] or 'unknown', + exit_time = str:unpack('I',9), + entry_time = str:unpack('I',13), + zone_id = str:unpack('H',17), + status_id = str:byte(3)%4, + status = statuses[str:byte(3)%4], + _unknown1 = str:unpack('i',5) + } + + if res.zones[rettab.zone_id] then + rettab.zone = res.zones[rettab.zone_id].english + else + rettab.zone = 'unknown' + end + + return rettab +end + +function decode.Hourglass(str) + local statuses = {[0] = 'Uninitialized', [1] = 'Active', [2] = 'Active', [3] = 'Spent'} + local rettab = {type='Hourglass', + exit_time = str:unpack('I',9), +-- entry_time = str:unpack('I',13), + zone_id = str:unpack('H',17), + status_id = str:byte(3)%4, + status = statuses[rettab.status_id], + _unknown1 = str:unpack('i',5) + } + + if res.zones[rettab.zone_id] then + rettab.zone = res.zones[rettab.zone_id].english + else + rettab.zone = 'unknown' + end + + return rettab +end + +function decode.EmptySlot(str) + local rettab = {type='Empty Slot', + ws_points = str:unpack('I') + } + + return rettab +end + + +-- In general, the function used to decode an item's extdata is determined by its type, which can be looked up using its item ID in the resources. +typ_mapping = { + [1] = decode.General, -- General + [2] = decode.General, -- Fight Entry Items + [3] = decode.Fish, -- Fish + [4] = decode.Equipment, -- Weapons + [5] = decode.Equipment, -- Armor + [6] = decode.Linkshell, -- Linkshells (but not broken linkshells) + [7] = decode.General, -- Usable Items + [8] = decode.General, -- Crystals + [10] = decode.Furniture, -- Furniture + [11] = decode.General, -- Seeds, reason for extdata unclear + [12] = decode.Flowerpot, -- Flowerpots + [14] = decode.Mannequin, -- Mannequins + [15] = decode.PvPReservation, -- Ballista Books + --[16] = decode.Chocobo, -- Chocobo paraphenelia (eggs, cards, slips, etc.) + --[17] = decode.ChocoboTicket, -- Chocobo Ticket and Completion Certificate + [18] = decode.SoulPlate, -- Soul Plates + [19] = decode.Reflector, -- Soul Reflectors + --[20] = decode.SalvageLog, -- Salvage Logs for the Mythic quest + [21] = decode.BonanzaMarble, -- Mog Bonanza Marbles + --[22] = decode.MazeTabulaM, -- MMM Maze Tabula M + --[23] = decode.MazeTabulaR, -- MMM Maze Tabula R + --[24] = decode.MazeVoucher, -- MMM Maze Vouchers + --[25] = decode.MazeRunes, -- MMM Maze Runes + --[26] = decode.Evoliths, -- Evoliths + --[27] = decode.StorageSlip, -- Storage Slips, already handled by slips.lua + [28] = decode.LegionPass, -- Legion Pass + --[29] = decode.MeeblesGrimore, -- Meebles Burrow Grimoires + } + +-- However, some items appear to have the function they use hardcoded based purely on their ID. +id_mapping = { + [0] = decode.EmptySlot, + [4237] = decode.Hourglass, + [5414] = decode.Lamp, + } + + + +-- ACTUAL EXTDATA LIB FUNCTIONS + +local extdata = {} + +_libs.extdata = extdata + +function extdata.decode(tab) + if not tab then error('extdata.decode was passed a nil value') end + if not tab.id or not tonumber(tab.id) then + error('extdata.decode was passed an invalid id ('..tostring(tab.id)..')',2) + elseif tab.id ~= 0 and not res.items[tab.id] then + error('extdata.decode was passed an id that is not yet in the resources ('..tostring(tab.id)..')',2) + end + if not tab.extdata or type(tab.extdata)~= 'string' or #tab.extdata ~= 24 then + error('extdata.decode was passed an invalid extdata string ('..tostring(tab.extdata)..') ID = '..tostring(tab.id),2) + end + + local typ, func + + if tab.id ~= 0 then + typ = res.items[tab.id].type + end + + local func = id_mapping[tab.id] or typ_mapping[typ] or decode.Unknown + + local decoded = func(tab.extdata) + decoded.__raw = tab.extdata + return decoded +end + + +----------------------------------------------------------------------------------- +--Name: compare_augments(goal,current) +--Args: +---- goal - First set of augments +---- current - Second set of augments +----------------------------------------------------------------------------------- +--Returns: +---- boolean indicating whether the goal augments are contained within the +---- current augments. Will return false if there are excess goal augments +---- or the goal augments do not match the current augments. +----------------------------------------------------------------------------------- +function extdata.compare_augments(goal_augs,current) + if not current then return false end + local cur = T{} + + local fn = function (str) + return type(str) == 'string' and str ~= 'none' + end + + for i,v in pairs(table.filter(current,fn)) do + cur:append(v) + end + + local goal = T{} + for i,v in pairs(table.filter(goal_augs,fn)) do + goal:append(v) + end + + local num_augments = 0 + local aug_strip = function(str) + return str:lower():gsub('[^%-%w,]','') + end + for aug_ind,augment in pairs(cur) do + if augment == 'none' then + cur[aug_ind] = nil + else + num_augments = num_augments + 1 + end + end + if num_augments < #goal then + return false + else + local function recheck_lib(str) + local sys, id, val = string.match(str,'System: (%d+) ID: (%d+) Val: (%d+)') + if tonumber(sys) and tonumber(id) and tonumber(val) then + str = tools.aug.string_augment(tonumber(sys),tonumber(id),tonumber(val)) + end + return str + end + local count = 0 + for goal_ind,goal_aug in pairs(goal) do + local bool + for cur_ind,cur_aug in pairs(cur) do + goal_aug = recheck_lib(goal_aug) + cur_aug = recheck_lib(cur_aug) + if aug_strip(goal_aug) == aug_strip(cur_aug) then + bool = true + count = count +1 + cur[cur_ind] = nil + break + end + end + if not bool then + return false + end + end + if count == #goal then + return true + else + return false + end + end +end + +-- Encode currently does nothing +--[[local encode = {} + +function extdata.encode(tab) + if tab and type(tab) == 'table' and tab.type and encode[tab.type] then + encode[tab.type](tab) + else + error('extdata.encode was passed an invalid extdata table',2) + end +end]] + + +return extdata diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/files.lua b/Data/DefaultContent/Libraries/addons/addons/libs/files.lua new file mode 100644 index 0000000..52a9909 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/files.lua @@ -0,0 +1,250 @@ +--[[ +File handler. +]] + +_libs = _libs or {} + +require('strings') +require('tables') + +local string, table = _libs.strings, _libs.tables + +local files = {} + +_libs.files = files + +-- Create a new file object. +function files.new(path, create) + return setmetatable({_create = create == true or nil, path = path}, {__index = files}) +end + +-- Creates a new file. Creates path, if necessary. +function files.create(f) + f:create_path() + local fh = io.open(windower.addon_path .. f.path, 'w') + fh:write('') + fh:close() + + f._create = nil + + return f +end + +-- Check if file exists. There's no better way, it would seem. +function files.exists(f) + local path + + if type(f) == 'string' then + path = f + else + if not f.path then + return nil, 'No file path set, cannot check file.' + end + + path = f.path + end + + return windower.file_exists(windower.addon_path .. path) +end + +-- Checks existance of a number of paths, returns the first that exists. +function files.check(...) + return table.find[2]({...}, files.exists) +end + +-- Read from file and return string of the contents. +function files.read(f) + local path + if type(f) == 'string' then + if not files.exists(f) then + return nil, 'File \'' .. f .. '\' not found, cannot read.' + end + + path = f + else + if not f.path then + return nil, 'No file path set, cannot write.' + end + + if not f:exists() then + if f._create then + return '' + else + return nil, 'File \'' .. f.path .. '\' not found, cannot read.' + end + end + + path = f.path + end + + local fh = io.open(windower.addon_path .. path, 'r') + local content = fh:read('*all*') + fh:close() + + -- Remove byte order mark for UTF-8, if present + if content:sub(1, 3) == string.char(0xEF, 0xBB, 0xBF) then + content = content:sub(4) + end + + return content +end + +-- Creates a directory. +function files.create_path(f) + local path + if type(f) == 'string' then + path = f + else + if not f.path then + return nil, 'No file path set, cannot create directories.' + end + + path = f.path:match('(.*)[/\\].-') + + if not path then + return nil, 'File path already in addon directory: ' .. windower.addon_path .. f.path + end + end + + newpath = windower.addon_path + for dir in path:psplit('[/\\]'):filter(-''):it() do + newpath = newpath .. dir .. '/' + + if not windower.dir_exists(newpath) then + local res, err = windower.create_dir(newpath) + if not res then + if err then + return nil, err .. ': ' .. newpath + end + + return nil, 'Unknown error trying to create path ' .. newpath + end + end + end + + return newpath +end + +-- Read from file and return lines of the contents in a table. +function files.readlines(f) + return files.read(f):split('\n') +end + +-- Return an iterator over the lines of a file. +function files.it(f) + local path + if type(f) == 'string' then + if not files.exists(f) then + return nil, 'File \'' .. f .. '\' not found, cannot read.' + end + + path = f + else + if not f.path then + return nil, 'No file path set, cannot write.' + end + + if not f:exists() then + if f._create then + return '' + else + return nil, 'File \'' .. f.path .. '\' not found, cannot read.' + end + end + + path = f.path + end + + return io.lines(windower.addon_path .. path) +end + +-- Write to file. Overwrites everything within the file, if present. +function files.write(f, content, flush) + local path + if type(f) == 'string' then + if not files.exists(f) then + return nil, 'File \'' .. f .. '\' not found, cannot write.' + end + + path = f + else + if not f.path then + return nil, 'No file path set, cannot write.' + end + + if not f:exists() then + if f.path then + (_libs.logger and notice or print)('New file: ' .. f.path) + f:create() + else + return nil, 'File \'' .. f.path .. '\' not found, cannot write.' + end + end + + path = f.path + end + + if type(content) == 'table' then + content = table.concat(content) + end + + local fh = io.open(windower.addon_path .. path, 'w') + fh:write(content) + if flush then + fh:flush() + end + fh:close() + + return f +end + +-- Append to file. Sets a newline per default, unless newline is set to false. +function files.append(f, content, flush) + local path + if type(f) == 'string' then + if not files.exists(f) then + return nil, 'File \'' .. f .. '\' not found, cannot write.' + end + + path = f + else + if not f.path then + return nil, 'No file path set, cannot write.' + end + + if not f:exists() then + if f._create then + (_libs.logger and notice or print)('New file: ' .. f.path) + f:create() + else + return nil, 'File \'' .. f.path .. '\' not found, cannot write.' + end + end + + path = f.path + end + + local fh = io.open(windower.addon_path .. path, 'a') + fh:write(content) + if flush then + fh:flush() + end + fh:close() + + return f +end + +return files + +--[[ +Copyright © 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/functions.lua b/Data/DefaultContent/Libraries/addons/addons/libs/functions.lua new file mode 100644 index 0000000..5dc15f3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/functions.lua @@ -0,0 +1,508 @@ +--[[ + Adds some tools for functional programming. Amends various other namespaces by functions used in a functional context, when they don't make sense on their own. +]] + +_libs = _libs or {} + +local string, math, table, coroutine = require('string'), require('math'), require('table'), require('coroutine') + +functions = {} +boolean = {} + +_libs.functions = functions + +local functions, boolean = functions, boolean + +-- The empty function. +functions.empty = function() end + +debug.setmetatable(false, {__index = function(_, k) + return boolean[k] or (_raw and _raw.error or error)('"%s" is not defined for booleans':format(tostring(k)), 2) +end}) + +for _, t in pairs({functions, boolean, math, string, table}) do + t.fn = function(val) + return function() + return val + end + end +end + +-- The identity function. +function functions.identity(...) + return ... +end + +-- Returns a function that returns a constant value. +function functions.const(val) + return function() + return val + end +end + +-- A function calling function. +function functions.call(fn, ...) + return fn(...) +end + +-- A function that executes the provided function if the provided condition is met. +function functions.cond(fn, check) + return function(...) + return check(...) and fn(...) or nil + end +end + +-- +function functions.args(fn, ...) + local args = {...} + return function(...) + local res = {} + for key, arg in ipairs(args) do + if type(arg) == 'number' then + rawset(res, key, select(arg, ...)) + else + rawset(res, key, arg(...)) + end + end + return fn(unpack(res)) + end +end + +-- Returns a function fully applied to the provided arguments. +function functions.prepare(fn, ...) + local args = {...} + return function() + return fn(unpack(args)) + end +end + +-- Returns a partially applied function, depending on the number of arguments provided. +function functions.apply(fn, ...) + local args = {...} + return function(...) + local res = {} + for key, arg in ipairs(args) do + res[key] = arg + end + local key = #args + for _, arg in ipairs({...}) do + key = key + 1 + res[key] = arg + end + return fn(unpack(res)) + end +end + +-- Returns a partially applied function, with the argument provided at the end. +function functions.endapply(fn, ...) + local args = {...} + return function(...) + local res = {...} + local key = #res + for _, arg in ipairs(args) do + key = key + 1 + res[key] = arg + end + return fn(unpack(res)) + end +end + +-- Returns a function that calls a provided chain of functions in right-to-left order. +function functions.pipe(fn1, fn2) + return function(...) + return fn1(fn2(...)) + end +end + +-- Returns a closure over the argument el that returns true, if its argument equals el. +function functions.equals(el) + return function(cmp) + return el == cmp + end +end + +-- Returns a negation function of a boolean function. +function functions.negate(fn) + return function(...) + return not (true == fn(...)) + end +end + +-- Returns the ith element of a function. +function functions.select(fn, i) + return function(...) + return select(i, fn(...)) + end +end + +-- Returns an iterator over the results of a function. +function functions.it(fn, ...) + local res = {fn(...)} + local key = 0 + return function() + key = key + 1 + return res[key] + end +end + +-- Schedules the current function to run delayed by the provided time in seconds and returns the coroutine +function functions.schedule(fn, time, ...) + return coroutine.schedule(fn:prepare(...), time) +end + +-- Returns a function that, when called, will execute the underlying function delayed by the provided number of seconds +function functions.delay(fn, time, ...) + local args = {...} + + return function() + fn:schedule(time, unpack(args)) + end +end + +-- Returns a wrapper table representing the provided function with additional functions: +function functions.loop(fn, interval, cond) + if interval <= 0 then + return + end + + if type(cond) == 'number' then + cond = function() + local i = 0 + local lim = cond + return function() + i = i + 1 + return i <= lim + end + end() + end + cond = cond or true:fn() + + return coroutine.schedule(function() + while cond() do + fn() + coroutine.sleep(interval) + end + end, 0) +end + +--[[ + Various built-in wrappers +]] + +-- tostring wrapper +function functions.string(fn) + return tostring(fn) +end + +-- type wrapper +function functions.type(fn) + return type(fn) +end + +-- class wrapper +function functions.class(fn) + return class(fn) +end + +local function index(fn, key) + if type(key) == 'number' then + return fn:select(key) + elseif rawget(functions, key) then + return function(...) + return functions[key](...) + end + end + + (_raw and _raw.error or error)('"%s" is not defined for functions':format(tostring(key)), 2) +end + +local function add(fn, args) + return fn:apply(unpack(args)) +end + +local function sub(fn, args) + return fn:endapply(unpack(args)) +end + +-- Assigns a metatable on functions to introduce certain function operators. +-- * fn+{...} partially applies a function to arguments. +-- * fn-{...} partially applies a function to arguments from the end. +-- * fn1..fn2 pipes input from fn2 to fn1. + +debug.setmetatable(functions.empty, { + __index = index, + __add = add, + __sub = sub, + __concat = functions.pipe, + __unm = functions.negate, + __class = 'Function' +}) + +--[[ + Logic functions +Mainly used to pass as arguments. +]] + +-- Returns true if element is true. +function boolean._true(val) + return val == true +end + +-- Returns false if element is false. +function boolean._false(val) + return val == false +end + +-- Returns the negation of a value. +function boolean._not(val) + return not val +end + +-- Returns true if both values are true. +function boolean._and(val1, val2) + return val1 and val2 +end + +-- Returns true if either value is true. +function boolean._or(val1, val2) + return val1 or val2 +end + +-- Returns true if element exists. +function boolean._exists(val) + return val ~= nil +end + +-- Returns true if two values are the same. +function boolean._is(val1, val2) + return val1 == val2 +end + +--[[ + Math functions +]] + +-- Returns true, if num is even, false otherwise. +function math.even(num) + return num % 2 == 0 +end + +-- Returns true, if num is odd, false otherwise. +function math.odd(num) + return num % 2 == 1 +end + +-- Adds two numbers. +function math.add(val1, val2) + return val1 + val2 +end + +-- Multiplies two numbers. +function math.mult(val1, val2) + return val1 * val2 +end + +-- Subtracts one number from another. +function math.sub(val1, val2) + return val1 - val2 +end + +-- Divides one number by another. +function math.div(val1, val2) + return val1 / val2 +end + +--[[ + Table functions +]] + +-- Returns an attribute of a table. +function table.get(t, ...) + local res = {} + for i = 1, select('#', ...) do + rawset(res, i, t[select(i, ...)]) + end + return unpack(res) +end + +-- Returns an attribute of a table without invoking metamethods. +function table.rawget(t, ...) + local res = {} + for i = 1, select('#', ...) do + rawset(res, i, rawget(t, select(i, ...))) + end + return unpack(res) +end + +-- Sets an attribute of a table to a specified value. +function table.set(t, ...) + for i = 1, select('#', ...), 2 do + t[select(i, ...)] = select(i + 1, ...) + end + return t +end + +-- Sets an attribute of a table to a specified value, without invoking metamethods. +function table.rawset(t, ...) + for i = 1, select('#', ...), 2 do + rawset(t, select(i, ...), select(i + 1, ...)) + end + return t +end + +-- Looks up the value of a table element in another table +function table.lookup(t, ref, key) + return ref[t[key]] +end + +table.it = function() + local it = function(t) + local key + + return function() + key = next(t, key) + return t[key], key + end + end + + return function(t) + local meta = getmetatable(t) + if not meta then + return it(t) + end + + local index = meta.__index + if index == table then + return it(t) + end + + local fn = type(index) == 'table' and index.it or index(t, 'it') or it + return (fn == table.it and it or fn)(t) + end +end() + +-- Applies function fn to all values of the table and returns the resulting table. +function table.map(t, fn) + local res = {} + for value, key in table.it(t) do + -- Evaluate fn with the element and store it. + res[key] = fn(value) + end + + return setmetatable(res, getmetatable(t)) +end + +-- Applies function fn to all keys of the table, and returns the resulting table. +function table.key_map(t, fn) + local res = {} + for value, key in table.it(t) do + res[fn(key)] = value + end + + return setmetatable(res, getmetatable(t)) +end + +-- Returns a table with all elements from t that satisfy the condition fn, or don't satisfy condition fn, if reverse is set to true. Defaults to false. +function table.filter(t, fn) + if type(fn) ~= 'function' then + fn = functions.equals(fn) + end + + local res = {} + for value, key in table.it(t) do + -- Only copy if fn(val) evaluates to true + if fn(value) then + res[key] = value + end + end + + return setmetatable(res, getmetatable(t)) +end + +-- Returns a table with all elements from t whose keys satisfy the condition fn, or don't satisfy condition fn, if reverse is set to true. Defaults to false. +function table.key_filter(t, fn) + if type(fn) ~= 'function' then + fn = functions.equals(fn) + end + + local res = {} + for value, key in table.it(t) do + -- Only copy if fn(key) evaluates to true + if fn(key) then + res[key] = value + end + end + + return setmetatable(res, getmetatable(t)) +end + +-- Returns the result of applying the function fn to the first two elements of t, then again on the result and the next element from t, until all elements are accumulated. +-- init is an optional initial value to be used. If provided, init and t[1] will be compared first, otherwise t[1] and t[2]. +function table.reduce(t, fn, init) + -- Set the accumulator variable to the init value (which can be nil as well) + local acc = init + for value in table.it(t) do + if init then + acc = fn(acc, value) + else + acc = value + init = true + end + end + + return acc +end + +-- Return true if any element of t satisfies the condition fn. +function table.any(t, fn) + for value in table.it(t) do + if fn(value) then + return true + end + end + + return false +end + +-- Return true if all elements of t satisfy the condition fn. +function table.all(t, fn) + for value in table.it(t) do + if not fn(value) then + return false + end + end + + return true +end + +--[[ + String functions. +]] + +-- Checks for exact string equality. +function string.eq(str, strcmp) + return str == strcmp +end + +-- Checks for case-insensitive string equality. +function string.ieq(str, strcmp) + return str:lower() == strcmp:lower() +end + +-- Applies a function to every character of str, concatenates the result. +function string.map(str, fn) + return (str:gsub('.', fn)) +end + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/images.lua b/Data/DefaultContent/Libraries/addons/addons/libs/images.lua new file mode 100644 index 0000000..b540210 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/images.lua @@ -0,0 +1,461 @@ +--[[ + A library to facilitate image primitive creation and manipulation. +]] + +local table = require('table') +local math = require('math') + +local images = {} +local meta = {} + +saved_images = {} +local dragged + +local events = { + reload = true, + left_click = true, + double_left_click = true, + right_click = true, + double_right_click = true, + middle_click = true, + scroll_up = true, + scroll_down = true, + hover = true, + drag = true, + right_drag = true +} + +_libs = _libs or {} +_libs.images = images + +_meta = _meta or {} +_meta.Image = _meta.Image or {} +_meta.Image.__class = 'Image' +_meta.Image.__index = images + +local set_value = function(t, key, value) + local m = meta[t] + m.values[key] = value + m.images[key] = value ~= nil and (m.formats[key] and m.formats[key]:format(value) or tostring(value)) or m.defaults[key] +end + +_meta.Image.__newindex = function(t, k, v) + set_value(t, k, v) + t:update() +end + +--[[ + Local variables +]] + +local default_settings = {} +default_settings.pos = {} +default_settings.pos.x = 0 +default_settings.pos.y = 0 +default_settings.visible = true +default_settings.color = {} +default_settings.color.alpha = 255 +default_settings.color.red = 255 +default_settings.color.green = 255 +default_settings.color.blue = 255 +default_settings.size = {} +default_settings.size.width = 0 +default_settings.size.height = 0 +default_settings.texture = {} +default_settings.texture.path = '' +default_settings.texture.fit = true +default_settings.repeatable = {} +default_settings.repeatable.x = 1 +default_settings.repeatable.y = 1 +default_settings.draggable = true + +math.randomseed(os.clock()) + +local amend +amend = function(settings, defaults) + for key, val in pairs(defaults) do + if type(val) == 'table' then + settings[key] = amend(settings[key] or {}, val) + elseif settings[key] == nil then + settings[key] = val + end + end + + return settings +end + +local call_events = function(t, event, ...) + if not meta[t].events[event] then + return + end + + -- Trigger registered post-reload events + for _, event in ipairs(meta[t].events[event]) do + event(t, meta[t].root_settings) + end +end + +local apply_settings = function(_, t, settings) + settings = settings or meta[t].settings + images.pos(t, settings.pos.x, settings.pos.y) + images.visible(t, meta[t].status.visible) + images.alpha(t, settings.color.alpha) + images.color(t, settings.color.red, settings.color.green, settings.color.blue) + images.size(t, settings.size.width, settings.size.height) + images.fit(t, settings.texture.fit) + images.path(t, settings.texture.path) + images.repeat_xy(t, settings.repeatable.x, settings.repeatable.y) + images.draggable(t, settings.draggable) + + call_events(t, 'reload') +end + +function images.new(str, settings, root_settings) + if type(str) ~= 'string' then + str, settings, root_settings = '', str, settings + end + + -- Sets the settings table to the provided settings, if not separately provided and the settings are a valid settings table + if not _libs.config then + root_settings = nil + else + root_settings = + root_settings and class(root_settings) == 'settings' and + root_settings + or settings and class(settings) == 'settings' and + settings + or + nil + end + + t = {} + local m = {} + meta[t] = m + m.name = (_addon and _addon.name or 'image') .. '_gensym_' .. tostring(t):sub(8) .. '_%.8x':format(16^8 * math.random()):sub(3) + m.settings = settings or {} + m.status = m.status or {visible = false, image = {}} + m.root_settings = root_settings + m.base_str = str + + m.events = {} + + m.keys = {} + m.values = {} + m.imageorder = {} + m.defaults = {} + m.formats = {} + m.images = {} + + windower.prim.create(m.name) + + amend(m.settings, default_settings) + if m.root_settings then + config.save(m.root_settings) + end + + if _libs.config and m.root_settings and settings then + _libs.config.register(m.root_settings, apply_settings, t, settings) + else + apply_settings(_, t, settings) + end + + -- Cache for deletion + table.insert(saved_images, 1, t) + + return setmetatable(t, _meta.Image) +end + +function images.update(t, attr) + attr = attr or {} + local m = meta[t] + + -- Add possibly new keys + for key, value in pairs(attr) do + m.keys[key] = true + end + + -- Update all image segments + for key in pairs(m.keys) do + set_value(t, key, attr[key] == nil and m.values[key] or attr[key]) + end +end + +function images.clear(t) + local m = meta[t] + m.keys = {} + m.values = {} + m.imageorder = {} + m.images = {} + m.defaults = {} + m.formats = {} +end + +-- Makes the primitive visible +function images.show(t) + windower.prim.set_visibility(meta[t].name, true) + meta[t].status.visible = true +end + +-- Makes the primitive invisible +function images.hide(t) + windower.prim.set_visibility(meta[t].name, false) + meta[t].status.visible = false +end + +-- Returns whether or not the image object is visible +function images.visible(t, visible) + local m = meta[t] + if visible == nil then + return m.status.visible + end + + windower.prim.set_visibility(m.name, visible) + m.status.visible = visible +end + +--[[ + The following methods all either set the respective values or return them, if no arguments to set them are provided. +]] + +function images.pos(t, x, y) + local m = meta[t] + if x == nil then + return m.settings.pos.x, m.settings.pos.y + end + + windower.prim.set_position(m.name, x, y) + m.settings.pos.x = x + m.settings.pos.y = y +end + +function images.pos_x(t, x) + if x == nil then + return meta[t].settings.pos.x + end + + t:pos(x, meta[t].settings.pos.y) +end + +function images.pos_y(t, y) + if y == nil then + return meta[t].settings.pos.y + end + + t:pos(meta[t].settings.pos.x, y) +end + +function images.size(t, width, height) + local m = meta[t] + if width == nil then + return m.settings.size.width, m.settings.size.height + end + + windower.prim.set_size(m.name, width, height) + m.settings.size.width = width + m.settings.size.height = height +end + +function images.width(t, width) + if width == nil then + return meta[t].settings.size.width + end + + t:size(width, meta[t].settings.size.height) +end + +function images.height(t, height) + if height == nil then + return meta[t].settings.size.height + end + + t:size(meta[t].settings.size.width, height) +end + +function images.path(t, path) + if path == nil then + return meta[t].settings.texture.path + end + + windower.prim.set_texture(meta[t].name, path) + meta[t].settings.texture.path = path +end + +function images.fit(t, fit) + if fit == nil then + return meta[t].settings.texture.fit + end + + windower.prim.set_fit_to_texture(meta[t].name, fit) + meta[t].settings.texture.fit = fit +end + +function images.repeat_xy(t, x, y) + local m = meta[t] + if x == nil then + return m.settings.repeatable.x, m.settings.repeatable.y + end + + windower.prim.set_repeat(m.name, x, y) + m.settings.repeatable.x = x + m.settings.repeatable.y = y +end + +function images.draggable(t, drag) + if drag == nil then + return meta[t].settings.draggable + end + + meta[t].settings.draggable = drag +end + +function images.color(t, red, green, blue) + local m = meta[t] + if red == nil then + return m.settings.color.red, m.settings.color.green, m.settings.color.blue + end + + windower.prim.set_color(m.name, m.settings.color.alpha, red, green, blue) + m.settings.color.red = red + m.settings.color.green = green + m.settings.color.blue = blue +end + +function images.alpha(t, alpha) + local m = meta[t] + if alpha == nil then + return m.settings.color.alpha + end + + windower.prim.set_color(m.name, alpha, m.settings.color.red, m.settings.color.green, m.settings.color.blue) + m.settings.color.alpha = alpha +end + +-- Sets/returns image transparency. Based on percentage values, with 1 being fully transparent, while 0 is fully opaque. +function images.transparency(t, alpha) + local m = meta[t] + if alpha == nil then + return 1 - m.settings.color.alpha/255 + end + + alpha = math.floor(255*(1-alpha)) + windower.prim.set_color(m.name, alpha, m.settings.color.red, m.settings.color.green, m.settings.color.blue) + m.settings.color.alpha = alpha +end + +-- Returns true if the coordinates are currently over the image object +function images.hover(t, x, y) + if not t:visible() then + return false + end + + local start_pos_x, start_pos_y = t:pos() + local end_pos_x, end_pos_y = t:get_extents() + + return (start_pos_x <= x and x <= end_pos_x + or start_pos_x >= x and x >= end_pos_x) + and (start_pos_y <= y and y <= end_pos_y + or start_pos_y >= y and y >= end_pos_y) +end + +function images.destroy(t) + for i, t_needle in ipairs(saved_images) do + if t == t_needle then + table.remove(saved_images, i) + break + end + end + windower.prim.delete(meta[t].name) + meta[t] = nil +end + +function images.get_extents(t) + local m = meta[t] + + local ext_x = m.settings.pos.x + m.settings.size.width + local ext_y = m.settings.pos.y + m.settings.size.height + + return ext_x, ext_y +end + +-- Handle drag and drop +windower.register_event('mouse', function(type, x, y, delta, blocked) + if blocked then + return + end + + -- Mouse drag + if type == 0 then + if dragged then + dragged.image:pos(x - dragged.x, y - dragged.y) + return true + end + + -- Mouse left click + elseif type == 1 then + for _, t in pairs(saved_images) do + local m = meta[t] + if m.settings.draggable and t:hover(x, y) then + local pos_x, pos_y = t:pos() + dragged = {image = t, x = x - pos_x, y = y - pos_y} + return true + end + end + + -- Mouse left release + elseif type == 2 then + if dragged then + if meta[dragged.image].root_settings then + config.save(meta[dragged.image].root_settings) + end + dragged = nil + return true + end + end + + return false +end) + +-- Can define functions to execute every time the settings are reloaded +function images.register_event(t, key, fn) + if not events[key] then + error('Event %s not available for text objects.':format(key)) + return + end + + local m = meta[t] + m.events[key] = m.events[key] or {} + m.events[key][#m.events[key] + 1] = fn + return #m.events[key] +end + +function images.unregister_event(t, key, fn) + if not (events[key] and meta[t].events[key]) then + return + end + + if type(fn) == 'number' then + table.remove(meta[t].events[key], fn) + else + for index, event in ipairs(meta[t].events[key]) do + if event == fn then + table.remove(meta[t].events[key], index) + return + end + end + end +end + +return images + +--[[ +Copyright © 2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/json.lua b/Data/DefaultContent/Libraries/addons/addons/libs/json.lua new file mode 100644 index 0000000..ff8e314 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/json.lua @@ -0,0 +1,297 @@ +--[[ +Small implementation of a JSON file reader. +]] + +_libs = _libs or {} + +require('tables') +require('lists') +require('sets') +require('strings') + +local table, list, set, string = _libs.tables, _libs.lists, _libs.sets, _libs.strings +local files = require('files') + +local json = {} + +_libs.json = json + +-- Define singleton JSON characters that can delimit strings. +local singletons = '{}[],:' +local key_types = S{'string', 'number'} +local value_types = S{'boolean', 'number', 'string', 'nil'} + +-- Takes a filename and tries to parse the JSON in it, after a validity check. +function json.read(file) + if type(file) == 'string' then + file = files.new(file) + end + + if not file:exists() then + return json.error('File not found: \''..file.path..'\'') + end + + return json.parse(file:read()) +end + +-- Returns nil as the parsed table and an additional error message with an optional line number. +function json.error(message, line) + if line == nil then + return nil, 'JSON error: '..message + end + return nil, 'JSON error, line '..line..': '..message +end + +-- Returns a Lua value based on a string. +-- Recognizes all valid atomic JSON values: booleans, numbers, strings and null. +-- Object and error groupings will be eliminated during the classifying process. +-- If stripquotes is set to true, quote characters delimiting strings will be stripped. +function json.make_val(str, stripquotes) + stripquotes = true and (stripquotes ~= false) + + str = str:trim() + if str == '' then + return nil + elseif str == 'true' then + return true + elseif str == 'false' then + return false + elseif str == 'null' then + return nil + elseif stripquotes and (str:enclosed('\'') or str:enclosed('"')) then + return str:slice(2, -2) + end + + str = str:gsub('\\x([%w%d][%w%d])', string.char..tonumber-{16}) + + return tonumber(str) or str +end + +-- Parsing function. Gets a string representation of a JSON object and outputs a Lua table or an error message. +function json.parse(content) + return json.classify(json.tokenize(content)) +end + +-- Tokenizer. Reads a string and returns an array of lines, each line with a number of valid JSON tokens. Valid tokens include: +-- * \w+ Keys or values +-- * : Key indexer +-- * , Value separator +-- * \{\} Dictionary start/end +-- * \[\] List start/end +function json.tokenize(content) + -- Tokenizer. Reads the string by characters and finds word boundaries, returning an array of tokens to be interpreted. + local current = nil + local tokens = L{L{}} + local quote = nil + local comment = false + local line = 1 + + content = content:trim() + local length = #content + if content:sub(length, length) == ',' then + content = content:sub(1, length - 1) + length = length - 1 + end + + local first = content:sub(1, 1) + local last = content:sub(length, length) + if first ~= '[' and first ~= '{' then + return json.error('Invalid JSON format. Document needs to start with \'{\' (object) or \'[\' (array).') + end + + if not (first == '[' and last == ']' or first == '{' and last == '}') then + return json.error('Invalid JSON format. Document starts with \''..first..'\' but ends with \''..last..'\'.') + end + + local root + if first == '[' then + root = 'array' + else + root = 'object' + end + + content = content:sub(2, length - 1) + + for c in content:it() do + -- Only useful for a line count, to produce more accurate debug messages. + if c == '\n' then + line = line + 1 + comment = false + tokens:append(L{}) + end + + -- If the quote character is set, don't parse but syntax, but instead just append to the string until the same quote character is encountered. + if quote ~= nil then + current = current..c + -- If the quote character is found, append the parsed string and reset the parsing values. + if quote == c then + tokens[line]:append(json.make_val(current)) + current = nil + quote = nil + end + elseif not comment then + -- If the character is a singleton character, append the previous token and this one, reset the parsing values. + if singletons:contains(c) then + if current ~= nil then + tokens[line]:append(json.make_val(current)) + current = nil + end + tokens[line]:append(c) + -- If a quote character is found, start a quoting session, see alternative condition. + elseif c == '"' or c == '\'' and current == nil then + quote = c + current = c + -- Otherwise, just append + elseif not c:match('%s') or current ~= nil then + -- Ignore comments. Not JSON conformant. + if c == '/' and current ~= nil and current:last() == '/' then + current = current:slice(1, -2) + if current == '' then + current = nil + end + comment = true + else + current = current or '' + current = current..c + end + end + end + end + + return tokens, root +end + +-- Takes a list of tokens and analyzes it to construct a valid Lua object from it. +function json.classify(tokens, root) + if tokens == nil then + return tokens, root + end + + local scopes = L{root} + + -- Scopes and their domains: + -- * 'object': Object scope, delimited by '{' and '}' as well as global scope + -- * 'array': Array scope, delimited by '[' and ']' + -- Possible modes and triggers: + -- * 'new': After an opening brace, bracket, comma or at the start, expecting a new element + -- * 'key': After reading a key + -- * 'colon': After reading a colon + -- * 'value': After reading or having scoped a value (either an object, or an array for the latter) + local modes = L{'new'} + + local parsed + if root == 'object' then + parsed = L{T{}} + else + parsed = L{L{}} + end + + local keys = L{} + -- Classifier. Iterates through the tokens and assigns meaning to them. Determines scoping and creates objects and arrays. + for array, line in tokens:it() do + for token, pos in array:it() do + if token == '{' then + if modes:last() == 'colon' or modes:last() == 'new' and scopes:last() == 'array' then + parsed:append(T{}) + scopes:append('object') + modes:append('new') + else + return json.error('Unexpected token \'{\'.', line) + end + elseif token == '}' then + if modes:last() == 'value' or modes:last() == 'new' then + modes:remove() + scopes:remove() + if modes:last() == 'colon' then + parsed:last(2)[keys:remove()] = parsed:remove() + elseif modes:last() == 'new' and scopes:last() == 'array' then + parsed:last():append(parsed:remove()) + else + return json.error('Unexpected token \'}\'.', line) + end + modes[#modes] = 'value' + else + return json.error('Unexpected token \'}\'.', line) + end + elseif token == '[' then + if modes:last() == 'colon' or modes:last() == 'new' and scopes:last() == 'array' then + parsed:append(T{}) + scopes:append('array') + modes:append('new') + else + return json.error('Unexpected token \'{\'.', line) + end + elseif token == ']' then + if modes:last() == 'value' or modes:last() == 'new' then + modes:remove() + scopes:remove() + if modes:last() == 'colon' then + parsed[#parsed-1][keys:remove()] = parsed:remove() + elseif modes:last() == 'new' and scopes:last() == 'array' then + parsed:last():append(parsed:remove()) + else + return json.error('Unexpected token \'}\'.', line) + end + modes[#modes] = 'value' + else + return json.error('Unexpected token \'}\'.', line) + end + elseif token == ':' then + if modes:last() == 'key' then + modes[#modes] = 'colon' + else + return json.error('Unexpected token \':\'.', line) + end + elseif token == ',' then + if modes:last() == 'value' then + modes[#modes] = 'new' + else + return json.error('Unexpected token \',\'.', line) + end + elseif key_types:contains(type(token)) and modes:last() == 'new' and scopes:last() == 'object' then + keys:append(token) + modes[#modes] = 'key' + elseif value_types:contains(type(token)) then + if modes:last() == 'colon' then + parsed:last()[keys:remove()] = token + modes[#modes] = 'value' + elseif modes:last() == 'new' then + if scopes:last() == 'array' then + parsed:last():append(token) + modes[#modes] = 'value' + else + return json.error('Unexpected token \''..token..'\'.', line) + end + else + return json.error('Unexpected token \''..token..'\'.', line) + end + else + return json.error('Unkown token parsed. You should never see this. Token type: '..type(token), line) + end + end + end + + if parsed:empty() then + return json.error('No JSON found.') + end + if #parsed > 1 then + return json.error('Invalid nesting, missing closing tags.') + end + + return parsed:remove() +end + +return json + +--[[ +Copyright (c) 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/lists.lua b/Data/DefaultContent/Libraries/addons/addons/libs/lists.lua new file mode 100644 index 0000000..2ea86e1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/lists.lua @@ -0,0 +1,455 @@ +--[[ + A library providing advanced list support and better optimizations for list-based operations. +]] + +_libs = _libs or {} + +require('tables') + +local table = _libs.tables + +list = {} + +local list = list + +_libs.lists = list + +_raw = _raw or {} +_raw.table = _raw.table or {} + +_meta = _meta or {} +_meta.L = {} + +_meta.L.__index = function(l, k) + if type(k) == 'number' then + k = k < 0 and l.n + k + 1 or k + + return rawget(l, k) + end + + return list[k] or table[k] +end + +_meta.L.__newindex = function(l, k, v) + if type(k) == 'number' then + k = k < 0 and l.n + k + 1 or k + + if k >= 1 and k <= l.n then + rawset(l, k, v) + else + (warning or print)('Trying to assign outside of list range (%u/%u): %s':format(k, l.n, tostring(v))) + end + + else + (warning or print)('Trying to assign to non-numerical list index:', k) + + end +end +_meta.L.__class = 'List' + +function L(t) + local l + if class(t) == 'Set' then + l = L{} + + for el in pairs(t) do + l:append(el) + end + else + l = t or {} + end + + l.n = #l + return setmetatable(l, _meta.L) +end + +function list.empty(l) + return l.n == 0 +end + +function list.length(l) + return l.n +end + +function list.flat(l) + for key = 1, l.n do + if type(rawget(l, key)) == 'table' then + return false + end + end + + return true +end + +function list.equals(l1, l2) + if l1.n ~= l2.n then + return false + end + + for key = 1, l.n do + if rawget(l1, key) ~= rawget(l2, key) then + return false + end + end + + return true +end + +function list.append(l, el) + l.n = l.n + 1 + return rawset(l, l.n, el) +end + +function list.last(l, i) + return rawget(l, l.n - ((i or 1) - 1)) +end + +function list.insert(l, i, el) + l.n = l.n + 1 + table.insert(l, i, el) +end + +function list.remove(l, i) + i = i or l.n + local res = rawget(l, i) + + for key = i, l.n do + rawset(l, key, rawget(l, key + 1)) + end + + l.n = l.n - 1 + return res +end + +function list.extend(l1, l2) + local n1 = l1.n + local n2 = l2.n + for k = 1, n2 do + rawset(l1, n1 + k, rawget(l2, k)) + end + + l1.n = n1 + n2 + return l1 +end + +_meta.L.__add = function(l1, l2) + return L{}:extend(l1):extend(l2) +end + +function list.contains(l, el) + for key = 1, l.n do + if rawget(l, key) == el then + return true + end + end + + return false +end + +function list.count(l, fn) + local count = 0 + if type(fn) ~= 'function' then + for i = 1, l.n do + if rawget(l, i) == fn then + count = count + 1 + end + end + else + for i = 1, l.n do + if fn(rawget(l, i)) then + count = count + 1 + end + end + end + + return count +end + +function list.concat(l, str, from, to) + str = str or '' + from = from or 1 + to = to or l.n + local res = '' + + for key = from, to do + local val = rawget(l, key) + if val then + res = res..tostring(val) + if key < l.n then + res = res..str + end + end + end + + return res +end + +function list.with(l, attr, val) + for i = 1, l.n do + local el = rawget(l, i) + if type(el) == 'table' and rawget(el, attr) == val then + return el + end + end +end + +function list.map(l, fn) + local res = {} + + for key = 1, l.n do + res[key] = fn(rawget(l, key)) + end + + res.n = l.n + return setmetatable(res, _meta.L) +end + +function list.filter(l, fn) + local res = {} + + local key = 0 + local val + for okey = 1, l.n do + val = rawget(l, okey) + if fn(val) == true then + key = key + 1 + rawset(res, key, val) + end + end + + res.n = key + return setmetatable(res, _meta.L) +end + +function list.flatten(l, rec) + rec = true and (rec ~= false) + + local res = {} + local key = 0 + local val + local flat + local n2 + for k1 = 1, l.n do + val = rawget(l, k1) + if type(val) == 'table' then + if rec then + flat = list.flatten(val, rec) + n2 = flat.n + for k2 = 1, n2 do + rawset(res, key + k2, rawget(flat, k2)) + end + else + if class(val) == 'List' then + n2 = val.n + else + n2 = #val + end + for k2 = 1, n2 do + rawset(res, key + k2, rawget(val, k2)) + end + end + key = key + n2 + else + key = key + 1 + rawset(res, key, val) + end + end + + res.n = key + return setmetatable(res, _meta.L) +end + +function list.it(l) + local key = 0 + return function() + key = key + 1 + return rawget(l, key), key + end +end + +function list.equals(l1, l2) + if l1.n ~= l2.n then + return false + end + + for key = 1, l1.n do + if rawget(l1, key) ~= rawget(l2, key) then + return false + end + end + + return true +end + +function list.slice(l, from, to) + local n = l.n + + from = from or 1 + if from < 0 then + from = (from % n) + 1 + end + + to = to or n + if to < 0 then + to = (to % n) + 1 + end + + local res = {} + local key = 0 + for i = from, to do + key = key + 1 + rawset(res, key, rawget(l, i)) + end + + res.n = key + return setmetatable(res, _meta.L) +end + +function list.splice(l1, from, to, l2) + -- TODO + (_raw.error or error)('list.splice is not yet implemented.') +end + +function list.clear(l) + for key = 1, l.n do + rawset(l, key, nil) + end + + l.n = 0 + return l +end + +function list.copy(l, deep) + deep = deep ~= false and true + local res = {} + + for key = 1, l.n do + local value = rawget(l, key) + if deep and type(value) == 'table' then + res[key] = (not rawget(value, copy) and value.copy or table.copy)(value) + else + res[key] = value + end + end + + res.n = l.n + return setmetatable(res, _meta.L) +end + +function list.reassign(l, ln) + l:clear() + + for key = 1, ln.n do + rawset(l, key, rawget(ln, key)) + end + + l.n = ln.n + return l +end + +_raw.table.sort = _raw.table.sort or table.sort + +function list.sort(l, ...) + _raw.table.sort(l, ...) + return l +end + +function list.reverse(l) + local res = {} + + local n = l.n + local rkey = n + for key = 1, n do + rawset(res, key, rawget(l, rkey)) + rkey = rkey - 1 + end + + res.n = n + return setmetatable(res, _meta.L) +end + +function list.range(n, init) + local res = {} + + for key = 1, n do + rawset(res, key, init or key) + end + + res.n = n + return setmetatable(res, _meta.L) +end + +function list.tostring(l) + local str = '[' + + for key = 1, l.n do + if key > 1 then + str = str..', ' + end + str = str..tostring(rawget(l, key)) + end + + return str..']' +end + +_meta.L.__tostring = list.tostring + +function list.format(l, trail, subs) + if l.n == 0 then + return subs or '' + end + + trail = trail or 'and' + + local last + if trail == 'and' then + last = ' and ' + elseif trail == 'or' then + last = ' or ' + elseif trail == 'list' then + last = ', ' + elseif trail == 'csv' then + last = ',' + elseif trail == 'oxford' then + last = ', and ' + elseif trail == 'oxford or' then + last = ', or ' + else + warning('Invalid format for table.format: \''..trail..'\'.') + end + + local res = '' + for i = 1, l.n do + local add = tostring(l[i]) + if trail == 'csv' and add:match('[,"]') then + res = res .. add:gsub('"', '""'):enclose('"') + else + res = res .. add + end + + if i < l.n - 1 then + if trail == 'csv' then + res = res .. ',' + else + res = res .. ', ' + end + elseif i == l.n - 1 then + res = res .. last + end + end + + return res +end + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/logger.lua b/Data/DefaultContent/Libraries/addons/addons/libs/logger.lua new file mode 100644 index 0000000..b1fb360 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/logger.lua @@ -0,0 +1,281 @@ +--[[ +This library provides a set of functions to aid in debugging. +]] + +_libs = _libs or {} + +require('strings') +require('chat') + +local string, chat = _libs.strings, _libs.chat +local table = require('table') + +local logger = {} + +_libs.logger = logger + +_raw = _raw or {} + +-- Set up, based on addon. +logger.defaults = {} +logger.defaults.logtofile = false +logger.defaults.defaultfile = 'lua.log' +logger.defaults.logcolor = 207 +logger.defaults.errorcolor = 167 +logger.defaults.warningcolor = 200 +logger.defaults.noticecolor = 160 + +--[[ + Local functions +]] + +local arrstring +local captionlog + +-- Returns a concatenated string list, separated by whitespaces, for the chat output function. +-- Converts any kind of object type to a string, so it's type-safe. +-- Concatenates all provided arguments with whitespaces. +function arrstring(...) + local str = '' + local args = {...} + + for i = 1, select('#', ...) do + if i > 1 then + str = str..' ' + end + str = str .. tostring(args[i]) + end + + return str +end + +-- Prints the arguments provided to the FFXI chatlog, in the same color used for Campaign/Bastion alerts and Kupower messages. Can be changed below. +function captionlog(msg, msgcolor, ...) + local caption = table.concat({_addon and _addon.name, msg}, ' ') + + if #caption > 0 then + if logger.settings.logtofile then + flog(nil, caption .. ':', ...) + return + end + caption = (caption .. ':'):color(msgcolor) .. ' ' + end + + local str = '' + if select('#', ...) == 0 or ... == '' then + str = ' ' + else + str = arrstring(...):gsub('\t', (' '):rep(4)) + end + + for _, line in ipairs(str:split('\n')) do + windower.add_to_chat(logger.settings.logcolor, caption .. windower.to_shift_jis(line) .. _libs.chat.controls.reset) + end +end + +function log(...) + captionlog(nil, logger.settings.logcolor, ...) +end + +_raw.error = error +function error(...) + captionlog('Error', logger.settings.errorcolor, ...) +end + +function warning(...) + captionlog('Warning', logger.settings.warningcolor, ...) +end + +function notice(...) + captionlog('Notice', logger.settings.noticecolor, ...) +end + +-- Prints the arguments provided to a file, analogous to log(...) in functionality. +-- If the first argument ends with '.log', it will print to that output file, otherwise to 'lua.log' in the addon directory. +function flog(filename, ...) + filename = filename or logger.settings.defaultfile + + local fh, err = io.open(windower.addon_path..filename, 'a') + if fh == nil then + if err ~= nil then + error('File error:', err) + else + error('File error:', 'Unknown error.') + end + else + fh:write(os.date('%Y-%m-%d %H:%M:%S') .. '| ' .. arrstring(...) .. '\n') + fh:close() + end +end + +-- Returns a string representation of a table in explicit Lua syntax: {...} +function table.tostring(t) + if next(t) == nil then + return '{}' + end + + keys = keys or false + + -- Iterate over table. + local tstr = '' + local kt = {} + k = 0 + for key in pairs(t) do + k = k + 1 + kt[k] = key + end + table.sort(kt, function(x, y) + if type(x) == 'number' and type(y) == 'string' then + return true + elseif type(x) == 'string' and type(y) == 'number' then + return false + end + + return x<y + end) + + for i, key in ipairs(kt) do + val = t[key] + -- Check for nested tables + if type(val) == 'table' then + if val.tostring then + valstr = val:tostring() + else + valstr = table.tostring(val) + end + else + if type(val) == 'string' then + valstr = '"' .. val .. '"' + else + valstr = tostring(val) + end + end + + -- Append to the string. + if tonumber(key) then + tstr = tstr .. valstr + else + tstr = tstr .. tostring(key) .. '=' .. valstr + end + + -- Add comma, unless it's the last value. + if next(kt, i) ~= nil then + tstr = tstr .. ', ' + end + end + + -- Output the result, enclosed in braces. + return '{' .. tstr .. '}' +end + +_meta = _meta or {} +_meta.T = _meta.T or {} +_meta.T.__tostring = table.tostring + +-- Prints a string representation of a table in explicit Lua syntax: {...} +function table.print(t, keys) + if t.tostring then + log(t:tostring(keys)) + else + log(table.tostring(t, keys)) + end +end + +-- Returns a vertical string representation of a table in explicit Lua syntax, with every element in its own line: +--- { +--- ... +--- } +function table.tovstring(t, keys, indentlevel) + if next(t) == nil then + return '{}' + end + + indentlevel = indentlevel or 0 + keys = keys or false + + local indent = (' '):rep(indentlevel*4) + local tstr = '{\n' + local kt = {} + k = 0 + for key in pairs(t) do + k = k + 1 + kt[k] = key + end + table.sort(kt, function(x, y) + return type(x) ~= type(y) and type(x) == 'number' or type(x) == 'number' and type(y) == 'number' and x < y + end) + + for i, key in pairs(kt) do + val = t[key] + + local function sanitize(val) + local ret + if type(val) == 'string' then + ret = '"' .. val:gsub('"','\\"') .. '"' + else + ret = tostring(val) + end + return ret + end + + -- Check for nested tables + if type(val) == 'table' then + if val.tovstring then + valstr = val:tovstring(keys, indentlevel + 1) + else + valstr = table.tovstring(val, keys, indentlevel + 1) + end + else + valstr = sanitize(val) + end + + -- Append one line with indent. + if not keys and tonumber(key) then + tstr = tstr .. indent .. ' ' .. '[' .. sanitize(key) .. ']=' .. valstr + else + tstr = tstr .. indent .. ' ' .. '[' .. sanitize(key) .. ']=' .. valstr + end + + -- Add comma, unless it's the last value. + if next(kt, i) ~= nil then + tstr = tstr .. ', ' + end + + tstr = tstr .. '\n' + end + tstr = tstr .. indent .. '}' + + return tstr +end + +-- Prints a vertical string representation of a table in explicit Lua syntax, with every element in its own line: +--- { +--- ... +--- } +function table.vprint(t, keys) + if t.tovstring then + log(t:tovstring(keys)) + else + log(table.tovstring(t, keys)) + end +end + +-- Load logger settings (has to be after the logging functions have been defined, so those work in the config and related files). +local config = require('config') + +logger.settings = config.load('../libs/logger.xml', logger.defaults) + +return logger + +--[[ +Copyright 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/logger.xml b/Data/DefaultContent/Libraries/addons/addons/libs/logger.xml new file mode 100644 index 0000000..b9c3c9b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/logger.xml @@ -0,0 +1,11 @@ +<?xml version="1.1" ?> +<settings> + <global> + <logtofile>false</logtofile> <!-- Set to true to output logging to a file. --> + <defaultfile>lua.log</defaultfile> <!-- Default file to log to, if logging to file. --> + <logcolor>207</logcolor> <!-- Regular logging color. --> + <errorcolor>167</errorcolor> <!-- Error logging color. --> + <warningcolor>200</warningcolor> <!-- Warning logging color. --> + <noticecolor>160</noticecolor> <!-- Notice logging color. --> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/ltn12.lua b/Data/DefaultContent/Libraries/addons/addons/libs/ltn12.lua new file mode 100644 index 0000000..575c5a7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/ltn12.lua @@ -0,0 +1,309 @@ +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local unpack = unpack or table.unpack +local base = _G +local _M = {} +if module then -- heuristic for exporting a global package table + ltn12 = _M +end +local filter,source,sink,pump = {},{},{},{} + +_M.filter = filter +_M.source = source +_M.sink = sink +_M.pump = pump + +local unpack = unpack or table.unpack +local select = base.select + +-- 2048 seems to be better in windows... +_M.BLOCKSIZE = 2048 +_M._VERSION = "LTN12 1.0.3" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local arg = {...} + local n = base.select('#',...) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(_M.BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) + i = i + _M.BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +-- chains a source with one or several filter(s) +function source.chain(src, f, ...) + if ... then f=filter.chain(f, ...) end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local arg = {...} + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with one or several filter(s) +function sink.chain(f, snk, ...) + if ... then + local args = { f, snk, ... } + snk = table.remove(args, #args) + f = filter.chain(unpack(args)) + end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/luau.lua b/Data/DefaultContent/Libraries/addons/addons/libs/luau.lua new file mode 100644 index 0000000..4b8e68b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/luau.lua @@ -0,0 +1,28 @@ +--[[ +LuaU - A utility tool for making Lua usable within FFXI. Loads several libraries and makes them available within the global namespace. +]] + +require('logger') +require('strings') +require('tables') +require('lists') +require('sets') +require('maths') +require('functions') +config = require('config') +res = require('resources') + +collectgarbage() + +--[[ +Copyright (c) 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/maths.lua b/Data/DefaultContent/Libraries/addons/addons/libs/maths.lua new file mode 100644 index 0000000..732f8bd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/maths.lua @@ -0,0 +1,129 @@ +--[[ + A few math helper functions. +]] + +_libs = _libs or {} + +require('functions') + +local functions = _libs.functions +local string = require('string') + +local math = require('math') + +_libs.maths = math + +_raw = _raw or {} +_raw.math = setmetatable(_raw.math or {}, {__index = math}) + +debug.setmetatable(0, { + __index = function(_, k) + return math[k] or (_raw and _raw.error or error)('"%s" is not defined for numbers':format(tostring(k)), 2) + end +}) + +-- Order of digits for higher base math +local digitorder = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} + +-- Constants +math.e = 1:exp() +math.tau = 2 * math.pi +math.phi = (1 + 5:sqrt())/2 + +-- Rounds to prec decimal digits. Accepts negative numbers for precision. +function math.round(num, prec) + local mult = 10^(prec or 0) + return (num * mult + 0.5):floor() / mult +end + +-- Returns the sign of num, -1 for a negative number, +1 for a positive number and 0 for 0. +function math.sgn(num) + return num > 0 and 1 or num < 0 and -1 or 0 +end + +-- Backs up the old log function. +_raw.math.log = math.log + +-- Returns an arbitrary-base logarithm. Defaults to e. +function math.log(val, base) + if not base then + return _raw.math.log(val) + end + + return _raw.math.log(val)/_raw.math.log(base) +end + +-- Returns a binary string representation of val. +function math.binary(val) + return val:base(2) +end + +-- Returns a octal string representation of val. +function math.octal(val) + return val:base(8) +end + +-- Returns a hex string representation of val. +function math.hex(val) + return val:base(16) +end + +-- Converts a number val to a string in base base. +function math.base(val, base) + if base == nil or base == 10 or val == 0 then + return val:string() + elseif base == 1 then + return '1':rep(val) + end + + local num = val:abs() + + local res = {} + local key = 1 + local pos + while num > 0 do + pos = num % base + 1 + res[key] = digitorder[pos] + num = (num / base):floor() + key = key + 1 + end + + local str = '' + local n = key - 1 + for key = 1, n do + str = str..res[n - key + 1] + end + + if val < 0 then + str = '-'..str + end + + return str +end + +-- tostring wrapper. +math.string = tostring + +-- string.char wrapper, to allow method-like calling on numbers. +math.char = string.char + +function math.degree(v) + return 360 * v / math.tau +end + +function math.radian(v) + return math.tau * v / 360 +end + +--[[ +Copyright 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/matrices.lua b/Data/DefaultContent/Libraries/addons/addons/libs/matrices.lua new file mode 100644 index 0000000..5931aad --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/matrices.lua @@ -0,0 +1,266 @@ +--[[ +Library for a matrix data structure and operations defined on it. +]] + +_libs = _libs or {} + +require('tables') +require('maths') +require('vectors') + +local table, math, vector = _libs.tables, _libs.maths, _libs.vectors + +matrix = {} + +_libs.matrices = matrix + +_meta = _meta or {} +_meta.M = _meta.M or {} +_meta.M.__index = matrix +_meta.M.__class = 'Matrix' + +-- Constructor for vectors. +-- matrix.m is the row-dimension +-- matrix.n is the column-dimension +function M(t) + t.rows, t.cols = #t, #t[1] + return setmetatable(t, _meta.M) +end + +-- Returns a transposed matrix. +function matrix.transpose(m) + local res = {} + for i, row in ipairs(m) do + for j, val in ipairs(row) do + res[j][i] = val + end + end + + res.rows, res.cols = m.cols, m.rows + return res +end + +-- Returns the identity matrix of dimension n. +function matrix.identity(n) + local res = {} + for i = 1, n do + res[i] = {} + for j = 1, n do + res[i][j] = i == j and 1 or 0 + end + end + + res.rows, res.cols = n, n + return setmetatable(res, _meta.M) +end + +-- Returns the row and column number of the matrix. +function matrix.dimensions(m) + return m.rows, m.cols +end + +-- Returns the vector of the diagonal of m. +function matrix.diag(m) + local res = {} + for i, row in ipairs(m) do + res[i] = row[i] + end + + res.n = m.rows + return setmetatable(res, _meta.V) +end + +-- Return a matrix scaled by a constant. +function matrix.scale(m, k) + local res = {} + for i, row in ipairs(m) do + res[i] = {} + for j, val in ipairs(row) do + res[i][j] = val*k + end + end + + res.rows, res.cols = m.rows, m.cols + return setmetatable(res, _meta.M) +end + +-- Returns m scaled by -1 (every value negated. +function matrix.negate(m) + return m:scale(-1) +end + +_meta.M.__unm = matrix.negate + +-- Returns the nth row of a matrix as a vector. +function matrix.row(m, n) + return V(m[n], n) +end + +-- Returns the nth column of a matrix as a vector. +function matrix.column(m, n) + local res = {} + for i, col in ipairs(m) do + res[i] = col[n] + end + + res.n = m.m + return setmetatable(res, _meta.V) +end + +-- Returns the determinant of a matrix. +function matrix.det(m) + if m.rows == 2 then + return m[1][1]*m[2][2] - m[1][2]*m[2][1] + end + + local acc = 0 + for i, val in ipairs(m[1]) do + acc = acc + (-1)^i * m:exclude(1, i):det() + end + + return acc +end + +-- Returns a matrix with one row and column excluded. +function matrix.exclude(m, exrow, excol) + local res = {} + local ik = 1 + local jk = 1 + for i, row in ipairs(m) do + if i ~= exrow then + res[ik] = {} + for j, val in ipairs(row) do + if j ~= excol then + res[ik][jk] = val + jk = jk + 1 + end + end + ik = ik + 1 + end + end +end + +-- Returns two matrices added. +function matrix.add(m1, m2) + local res = {} + for i, row in ipairs(m1) do + res[i] = {} + for j, val in ipairs(row) do + res[i][j] = val + m2[i][j] + end + end + + res.rows, res.cols = m1.rows, m1.cols + return setmetatable(res, _meta.M) +end + +_meta.M.__add = matrix.add + +-- Returns m1 subtracted by m2. +function matrix.subtract(m1, m2) + local res = {} + for i, row in ipairs(m1) do + res[i] = {} + for j, val in ipairs(row) do + res[i][j] = val - m2[i][j] + end + end + + res.rows, res.cols = m1.rows, m1.cols + return setmetatable(res, _meta.M) +end + +_meta.M.__sub = matrix.subtract + +-- Return a matrix multiplied by another matrix or vector. +function matrix.multiply(m1, m2) + local res = {} + + local cols = {} + for i, col in ipairs(m2[1]) do + cols[i] = {} + end + for i, row in ipairs(m2) do + for j, val in ipairs(row) do + cols[j][i] = val + end + end + + local acc + for i, row in ipairs(m1) do + res[i] = {} + for c, col in ipairs(cols) do + acc = 0 + for j, val in ipairs(col) do + acc = acc + m1[i][c]*m2[j][c] + end + + res[i][c] = acc + end + end + + res.rows, res.cols = m1.rows, m2.cols + return setmetatable(res, _meta.M) +end + +_meta.M.__mul = matrix.multiply + +-- Returns an inline string representation of the matrix. +function matrix.tostring(m) + local str = '[' + for i, row in ipairs(m) do + if i > 1 then + str = str..', ' + end + str = str..'[' + + for j, val in ipairs(row) do + if j > 1 then + str = str..', ' + end + str = str..tostring(val) + end + str = str..']' + end + + return str..']' +end + +_meta.M.__tostring = matrix.tostring + +-- Returns a multiline string representation of the matrix. +function matrix.tovstring(m) + local str = '' + for i, row in ipairs(m) do + if i > 1 then + str = str..'\n' + end + for j, val in ipairs(row) do + if j > 1 then + str = str..' ' + end + str = str..tostring(val) + end + end + + return str +end + +function matrix.vprint(m) + if log then + log(m:tovstring()) + end +end + +--[[ +Copyright © 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/mime.lua b/Data/DefaultContent/Libraries/addons/addons/libs/mime.lua new file mode 100644 index 0000000..642cd9c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/mime.lua @@ -0,0 +1,90 @@ +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local io = require("io") +local string = require("string") +local _M = mime + +-- encode, decode and wrap algorithm tables +local encodet, decodet, wrapt = {},{},{} + +_M.encodet = encodet +_M.decodet = decodet +_M.wrapt = wrapt + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(_M.b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(_M.qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(_M.unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(_M.unqp, "") +end + +local function format(chunk) + if chunk then + if chunk == "" then return "''" + else return string.len(chunk) end + else return "nil" end +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(_M.wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(_M.qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +_M.encode = choose(encodet) +_M.decode = choose(decodet) +_M.wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function _M.normalize(marker) + return ltn12.filter.cycle(_M.eol, 0, marker) +end + +-- high level stuffing filter +function _M.stuff() + return ltn12.filter.cycle(_M.dot, 2) +end + +return _M
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/packets.lua b/Data/DefaultContent/Libraries/addons/addons/libs/packets.lua new file mode 100644 index 0000000..f3ab5b0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/packets.lua @@ -0,0 +1,456 @@ +--[[ +A library to facilitate packet usage +]] + +_libs = _libs or {} + +require('lists') +require('maths') +require('strings') +require('functions') +require('pack') + +local table = require('table') + +local packets = {} + +_libs.packets = packets + +if not warning then + warning = print+{_addon.name and '%s warning:':format(_addon.name) or 'Warning:'} +end + +__meta = __meta or {} +__meta.Packet = {__tostring = function(packet) + local res = '%s packet 0x%.3X (%s):':format(packet._dir:capitalize(), packet._id, packet._name or 'Unrecognized packet') + + local raw = packets.build(packet) + for field in packets.fields(packet._dir, packet._id, raw):it() do + res = '%s\n%s: %s':format(res, field.label, tostring(packet[field.label])) + if field.fn then + res = '%s (%s)':format(res, tostring(field.fn(packet[field.label], raw))) + end + end + + return res +end} + +--[[ + Packet database. Feel free to correct/amend it wherever it's lacking. +]] + +packets.data = require('packets/data') +packets.raw_fields = require('packets/fields') + +--[[ + Lengths for C data types. +]] + +local sizes = { + ['unsigned char'] = 8, + ['unsigned short'] = 16, + ['unsigned int'] = 32, + ['unsigned long'] = 64, + ['signed char'] = 8, + ['signed short'] = 16, + ['signed int'] = 32, + ['signed long'] = 64, + ['char'] = 8, + ['short'] = 16, + ['int'] = 32, + ['long'] = 64, + ['bool'] = 8, + ['float'] = 32, + ['double'] = 64, + ['data'] = 8, + ['bit'] = 1, + ['boolbit'] = 1, +} + +-- This defines whether to treat a type with brackets at the end as an array or something special +local non_array_types = S{'bit', 'data', 'char'} + +-- Pattern to match variable size array +local pointer_pattern = '(.+)%*' +-- Pattern to match fixed size array +local array_pattern = '(.+)%[(.+)%]' + +-- Function returns number of bytes, bits, items and type name +local parse_type = function(field) + local ctype = field.ctype + + if ctype:endswith('*') then + return nil, 1, ctype:match(pointer_pattern):trim() + end + + local type, count_str = ctype:match(array_pattern) + type = (type or ctype):trim() + + local array = not non_array_types:contains(type) + local count_num = count_str and count_str:number() or 1 + local type_count = count_str and array and count_num or 1 + + local bits = (array and type_count or count_num) * sizes[type]; + + return bits, type_count, type +end + +local size +size = function(fields, count) + -- A single field + if fields.ctype then + local bits, _, type = parse_type(fields) + return bits or type == 'char' and 8 or count and count * sizes[type] or 0 + end + + -- A reference field + if fields.ref then + return size(fields.ref, count) * (fields.count == '*' and count or fields.count) + end + + return fields:reduce(function(acc, field) + return acc + size(field, count) + end, 0) +end + +local parse +parse = function(fields, data, index, max, lookup, depth) + depth = depth or 0 + max = max == '*' and 0 or max or 1 + index = index or 32 + + local res = L{} + local count = 0 + local length = 8 * #data + while index < length do + count = count + 1 + + local parsed = L{} + local parsed_index = index + for field in fields:it() do + if field.ctype then + -- A regular type field + field = table.copy(field) + local bits, type_count, type = parse_type(field) + + if not non_array_types:contains(type) and (not bits or type_count > 1) then + -- An array field with more than one entry, reparse recursively + field.ctype = type + local ext, new_index = parse(L{field}, data, parsed_index, not bits and '*' or type_count, nil, depth + 1) + parsed = parsed + ext + parsed_index = new_index + else + -- A non-array field or an array field with one entry + if max ~= 1 then + -- Append indices to labels + if lookup then + -- Look up index name in provided table + local resource = lookup[1][count + lookup[2] - 1] + field.label = '%s %s':format(resource and resource.name or 'Unknown %d':format(count + lookup[2] - 1), field.label) + else + -- Just increment numerically + field.label = '%s %d':format(field.label, count) + end + end + + if parsed_index % 8 ~= 0 and type ~= 'bit' and type ~= 'boolbit' then + -- Adjust to byte boundary, if non-bit type + parsed_index = 8 * (parsed_index / 8):ceil() + end + + if not bits then + -- Determine length for pointer types (*) + type_count = ((length - parsed_index) / sizes[type]):floor() + bits = sizes[type] * type_count + + field.ctype = '%s[%u]':format(type, type_count) + + count = max + end + + field.type = type + field.index = parsed_index + field.length = bits + field.count = type_count + + parsed:append(field) + parsed_index = parsed_index + bits + end + else + -- A reference field, call the parser recursively + local type_count = field.count + if not type_count then + -- If reference count not explicitly given it must be contained in the packet data + type_count = data:byte(field.count_ref + 1) + end + + local ext, new_index = parse(field.ref, data, parsed_index, type_count, field.lookup, depth + 1) + parsed = parsed + ext + parsed_index = new_index + end + end + + if parsed_index <= length then + -- Only add parsed chunk, if within length boundary + res = res + parsed + index = parsed_index + else + count = max + end + + if count == max then + break + end + end + + return res, index +end + +-- Arguments are: +-- dir 'incoming' or 'outgoing' +-- id Packet ID +-- data Binary packet data, nil if creating a blank packet +-- ... Any parameters taken by a packet constructor function +-- If a packet has a variable length field (e.g. char* or ref with count='*') the last value in here must be the count of that field +function packets.fields(dir, id, data, ...) + local fields = packets.raw_fields[dir][id] + + if type(fields) == 'function' then + fields = fields(data, ...) + end + + if not fields then + return nil + end + + if not data then + local argcount = select('#', ...) + local bits = size(fields, argcount > 0 and select(argcount, ...) or nil) + data = 0:char():rep(4 + 4 * ((bits or 0) / 32):ceil()) + end + + return parse(fields, data) +end + +local dummy = {name='Unknown', description='No data available.'} + +-- Type identifiers as declared in lpack.c +-- Windower uses an adjusted set of identifiers +-- This is marked where applicable +local pack_ids = {} +pack_ids['bit'] = 'b' -- Windower exclusive +pack_ids['boolbit'] = 'q' -- Windower exclusive +pack_ids['bool'] = 'B' -- Windower exclusive +pack_ids['unsigned char'] = 'C' -- Originally 'b', replaced by 'bit' for Windower +pack_ids['unsigned short'] = 'H' +pack_ids['unsigned int'] = 'I' +pack_ids['unsigned long'] = 'L' +pack_ids['signed char'] = 'c' +pack_ids['signed short'] = 'h' +pack_ids['signed int'] = 'i' +pack_ids['signed long'] = 'L' +pack_ids['char'] = 'c' +pack_ids['short'] = 'h' +pack_ids['int'] = 'i' +pack_ids['long'] = 'l' +pack_ids['float'] = 'f' +pack_ids['double'] = 'd' +pack_ids['data'] = 'A' + +local make_pack_string = function(field) + local ctype = field.ctype + + if pack_ids[ctype] then + return pack_ids[ctype] + end + + local type_name, number = ctype:match(array_pattern) + if type_name then + number = tonumber(number) + local pack_id = pack_ids[type_name] + if pack_id then + if type_name == 'char' then + return 'S' .. number -- Windower exclusive + else + return pack_id .. number + end + end + end + + type_name = ctype:match(pointer_pattern) + if type_name then + local pack_id = pack_ids[type_name] + if pack_id then + if type_name == 'char' then + return 'z' + else + return pack_id .. '*' + end + end + end + + return nil +end + +-- Constructor for packets (both injected and parsed). +-- If data is a string it parses an existing packet, otherwise it will create +-- a new packet table for injection. In that case, data can ba an optional +-- table containing values to initialize the packet to. +-- +-- Example usage +-- Injection: +-- local packet = packets.new('outgoing', 0x050, { +-- ['Inventory Index'] = 27, -- 27th item in the inventory +-- ['Equipment Slot'] = 15 -- 15th slot, left ring +-- }) +-- packets.inject(packet) +-- +-- Injection (Alternative): +-- local packet = packets.new('outgoing', 0x050) +-- packet['Inventory Index'] = 27 -- 27th item in the inventory +-- packet['Equipment Slot'] = 15 -- 15th slot, left ring +-- packets.inject(packet) +-- +-- Parsing: +-- windower.register_event('outgoing chunk', function(id, data) +-- if id == 0x0B6 then -- outgoing /tell +-- local packet = packets.parse('outgoing', data) +-- print(packet['Target Name'], packet['Message']) +-- end +-- end) +function packets.parse(dir, data) + local rem = #data % 4 + if rem ~= 0 then + data = data .. 0:char():rep(4 - rem) + end + + local res = setmetatable({}, __meta.Packet) + res._id, res._size, res._sequence = data:unpack('b9b7H') + res._size = res._size * 4 + res._raw = data + res._dir = dir + res._name = packets.data[dir][res._id].name + res._description = packets.data[dir][res._id].description + res._data = data:sub(5) + + local fields = packets.fields(dir, res._id, data) + if not fields or #fields == 0 then + return res + end + + local pack_str = fields:map(make_pack_string):concat() + + for key, val in ipairs({res._data:unpack(pack_str)}) do + local field = fields[key] + if field then + res[field.label] = field.enc and val:decode(field.enc) or val + end + end + + return res +end + +function packets.new(dir, id, values, ...) + values = values or {} + + local packet = setmetatable({}, __meta.Packet) + packet._id = id + packet._dir = dir + packet._sequence = 0 + packet._args = {...} + + local fields = packets.fields(packet._dir, packet._id, nil, ...) + if not fields then + warning('Packet 0x%.3X not recognized.':format(id)) + return packet + end + + for field in fields:it() do + packet[field.label] = values[field.label] + + -- Data not set + if not packet[field.label] then + if field.const then + packet[field.label] = field.const + + elseif field.ctype == 'bool' or field.ctype == 'boolbit' then + packet[field.label] = false + + elseif sizes[field.ctype] or field.ctype:startswith('bit') then + packet[field.label] = 0 + + elseif field.ctype:startswith('char') or field.ctype:startswith('data') then + packet[field.label] = '' + + else + warning('Bad packet! Unknown packet C type:', field.ctype) + packet._error = true + + end + end + end + + return packet +end + +local lookup = function(packet, field) + local val = packet[field.label] + return field.enc and val:encode(field.enc) or val +end + +-- Returns binary data from a packet +function packets.build(packet) + local fields = packets.fields(packet._dir, packet._id, packet._raw, unpack(packet._args or {})) + if not fields then + error('Packet 0x%.3X not recognized, unable to build.':format(packet._id)) + return nil + end + + local pack_string = fields:map(make_pack_string):concat() + local data = pack_string:pack(fields:map(lookup+{packet}):unpack()) + local rem = #data % 4 + if rem ~= 0 then + data = data .. 0:char():rep(4 - rem) + end + + return 'b9b7H':pack(packet._id, 1 + #data / 4, packet._sequence) .. data +end + +-- Injects a packet built with packets.new +function packets.inject(packet) + if packet._error then + error('Bad packet, cannot inject') + return nil + end + + local fields = packets.fields(packet._dir, packet._id, packet._raw) + if not fields then + error('Packet 0x%.3X not recognized, unable to send.':format(packet._id)) + return nil + end + + packet._raw = packets.build(packet) + + if packet._dir == 'incoming' then + windower.packets.inject_incoming(packet._id, packet._raw) + elseif packet._dir == 'outgoing' then + windower.packets.inject_outgoing(packet._id, packet._raw) + else + error('Error sending packet, no direction specified. Please specify \'incoming\' or \'outgoing\'.') + end +end + +return packets + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/packets/data.lua b/Data/DefaultContent/Libraries/addons/addons/libs/packets/data.lua new file mode 100644 index 0000000..31df307 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/packets/data.lua @@ -0,0 +1,253 @@ +--[[ + This file returns a table of known packet data. +]] + +local data = {} +local dummy = {name='Unknown', description='No data available.'} + +data.incoming = setmetatable({}, {__index = function() return dummy end}) +data.outgoing = setmetatable({}, {__index = function() return dummy end}) + +-- Client packets (outgoing) +data.outgoing[0x00A] = {name='Client Connect', description='(unencrypted/uncompressed) First packet sent when connecting to new zone.'} +data.outgoing[0x00C] = {name='Zone In 1', description='Likely triggers certain packets to be sent from the server.'} +data.outgoing[0x00D] = {name='Client Leave', description='Last packet sent from client before it leaves the zone.'} +data.outgoing[0x00F] = {name='Zone In 2', description='Likely triggers certain packets to be sent from the server.'} +data.outgoing[0x011] = {name='Zone In 3', description='Likely triggers certain packets to be sent from the server.'} +data.outgoing[0x015] = {name='Standard Client', description='Packet contains data that is sent almost every time (i.e your character\'s position).'} +data.outgoing[0x016] = {name='Update Request', description='Packet that requests a PC/NPC update packet.'} +data.outgoing[0x017] = {name='NPC Race Error', description='Packet sent in response to impossible incoming NPC packets (like trying to put equipment on a race 0 monster).'} +data.outgoing[0x01A] = {name='Action', description='An action being done on a target (i.e. an attack or spell).'} +data.outgoing[0x01E] = {name='Volunteer', description='Sent in response to a /volunteer command.'} +data.outgoing[0x028] = {name='Drop Item', description='Drops an item.'} +data.outgoing[0x029] = {name='Move Item', description='Move item from one inventory to another.'} +data.outgoing[0x02B] = {name='Translate Request', description='Request that a phrase be translated.'} +data.outgoing[0x032] = {name='Offer Trade', description='This is sent when you offer to trade somebody.'} +data.outgoing[0x033] = {name='Trade Tell', description='This packet allows you to accept or cancel a trade request.'} +data.outgoing[0x034] = {name='Trade Item', description='Sends the item you want to trade to the server.'} +data.outgoing[0x036] = {name='Menu Item', description='Use an item from the item menu.'} +data.outgoing[0x037] = {name='Use Item', description='Use an item.'} +data.outgoing[0x03A] = {name='Sort Item', description='Stacks the items in your inventory. Sent when hitting "Sort" in the menu.'} +data.outgoing[0x03D] = {name='Blacklist Command', description='Sent in response to /blacklist add or /blacklist delete.'} +data.outgoing[0x041] = {name='Lot Item', description='Lotting an item in the treasure pool.'} +data.outgoing[0x042] = {name='Pass Item', description='Passing an item in the treasure pool.'} +data.outgoing[0x04B] = {name='Servmes', description='Requests the server message (/servmes).'} +data.outgoing[0x04D] = {name='Delivery Box', description='Used to manipulate the delivery box.'} +data.outgoing[0x04E] = {name='Auction', description='Used to bid on an Auction House item.'} +data.outgoing[0x050] = {name='Equip', description='This command is used to equip your character.'} +data.outgoing[0x051] = {name='Equipset', description='This packet is sent when using /equipset.'} +data.outgoing[0x052] = {name='Equipset Build', description='This packet is sent when building an equipset.'} +data.outgoing[0x053] = {name='Lockstyleset', description='This packet is sent when locking to an equipset.'} +data.outgoing[0x059] = {name='End Synth', description='This packet is sent to end a synth.'} +data.outgoing[0x05A] = {name='Conquest', description='This command asks the server for data pertaining to conquest/besieged status.'} +data.outgoing[0x05B] = {name='Dialog choice', description='Chooses a dialog option.'} +data.outgoing[0x05C] = {name='Warp Request', description='Request a warp. Used by teleporters and the like.'} +data.outgoing[0x05D] = {name='Emote', description='This command is used in emotes.'} +data.outgoing[0x05E] = {name='Request Zone', description='Request from the client to zone.'} +data.outgoing[0x061] = {name='Equipment Screen', description='This command is used when you open your equipment screen.'} +data.outgoing[0x063] = {name='Digging Finished', description='This packet is sent when the chocobo digging animation is finished.'} +data.outgoing[0x064] = {name='New KI examination', description='Sent when you examine a key item with a "new" flag on it.'} +data.outgoing[0x06E] = {name='Party invite', description='Sent when inviting another player to either party or alliance.'} +data.outgoing[0x06F] = {name='Party leave', description='Sent when leaving the party or alliance.'} +data.outgoing[0x070] = {name='Party breakup', description='Sent when disbanding the entire party or alliance.'} +data.outgoing[0x071] = {name='Kick', description='Sent when you kick someone from linkshell or party.'} +data.outgoing[0x074] = {name='Party response', description='Sent when responding to a party or alliance invite.'} +data.outgoing[0x077] = {name='Change permissions', description='Sent when giving party or alliance leader to another player or elevating/decreasing linkshell permissions.'} +data.outgoing[0x078] = {name='Party list request', description='Sent when checking the party list.'} +data.outgoing[0x083] = {name='NPC Buy Item', description='Buy an item from a generic NPC.'} +data.outgoing[0x084] = {name='Appraise', description='Ask server for selling price.'} +data.outgoing[0x085] = {name='Sell Item', description='Sell an item from your inventory.'} +data.outgoing[0x096] = {name='Synth', description='Packet sent containing all data of an attempted synth.'} +data.outgoing[0x0A0] = {name='Nominate', description='Sent in response to a /nominate command.'} +data.outgoing[0x0A1] = {name='Vote', description='Sent in response to a /vote command.'} +data.outgoing[0x0A2] = {name='Random', description='Sent in response to a /random command.'} +data.outgoing[0x0AA] = {name='Guild Buy Item', description='Buy an item from a guild.'} +data.outgoing[0x0AB] = {name='Get Guild Inv List', description='Gets the offerings of the guild.'} +data.outgoing[0x0AC] = {name='Guild Sell Item', description='Sell an item to the guild.'} +data.outgoing[0x0AD] = {name='Get Guild Sale List', description='Gets the list of things the guild will buy.'} +data.outgoing[0x0B5] = {name='Speech', description='Packet contains normal speech.'} +data.outgoing[0x0B6] = {name='Tell', description='/tell\'s sent from client.'} +data.outgoing[0x0BE] = {name='Merit Point Increase',description='Sent when you increase a merit point ability.'} +data.outgoing[0x0BF] = {name='Job Point Increase', description='Sent when you increase a job point ability.'} +data.outgoing[0x0C0] = {name='Job Point Menu', description='Sent when you open the Job Point menu and triggers Job Point Information packets.'} +data.outgoing[0x0C3] = {name='Make Linkshell', description='Sent in response to the /makelinkshell command.'} +data.outgoing[0x0C4] = {name='Equip Linkshell', description='Sent to equip a linkshell.'} +data.outgoing[0x0CB] = {name='Open Mog', description='Sent when opening or closing your mog house.'} +data.outgoing[0x0D2] = {name='Party Marker Request',description='Requests map markers for your party.'} +data.outgoing[0x0D3] = {name='GM Call', description='Places a call to the GM queue.'} +data.outgoing[0x0D4] = {name='Help Desk Menu', description='Opens the Help Desk submenu.'} +data.outgoing[0x0DC] = {name='Type Bitmask', description='This command is sent when change your party-seek or /anon status.'} +data.outgoing[0x0DD] = {name='Check', description='Used to check other players.'} +data.outgoing[0x0DE] = {name='Set Bazaar Message', description='Sets your bazaar message.'} +data.outgoing[0x0E0] = {name='Search Comment', description='Sets your search comment.'} +data.outgoing[0x0E1] = {name='Get LS Message', description='Requests the current linkshell message.'} +data.outgoing[0x0E2] = {name='Set LS Message', description='Sets the current linkshell message.'} +data.outgoing[0x0EA] = {name='Sit', description='A request to sit or stand is sent to the server.'} +data.outgoing[0x0E7] = {name='Logout', description='A request to logout of the server.'} +data.outgoing[0x0E8] = {name='Toggle Heal', description='This command is used to both heal and cancel healing.'} +data.outgoing[0x0F1] = {name='Cancel', description='Sent when canceling a buff.'} +data.outgoing[0x0F2] = {name='Declare Subregion', description='Sent when moving to a new subregion of a zone (for instance, a different combination of open doors).'} +data.outgoing[0x0F4] = {name='Widescan', description='This command asks the server for a widescan.'} +data.outgoing[0x0F5] = {name='Widescan Track', description='Sent when you choose to track something on widescan.'} +data.outgoing[0x0F6] = {name='Widescan Cancel', description='Sent when you choose to stop track something on widescan.'} +data.outgoing[0x0FA] = {name='Place/Move Furniture',description='Sends new position for your furniture.'} +data.outgoing[0x0FB] = {name='Remove Furniture', description='Informs the server you have removed some furniture.'} +data.outgoing[0x0FC] = {name='Plant Flowerpot', description='Plants a seed in a flowerpot.'} +data.outgoing[0x0FD] = {name='Examine Flowerpot', description='Sent when you examine a flowerpot.'} +data.outgoing[0x0FE] = {name='Uproot Flowerpot', description='Uproots a flowerpot.'} +data.outgoing[0x100] = {name='Job Change', description='Sent when initiating a job change.'} +data.outgoing[0x102] = {name='Untraditional Equip', description='Sent when equipping a pseudo-item like an Automaton Attachment, Instinct, or Blue Magic Spell.'} +data.outgoing[0x104] = {name='Leave Bazaar', description='Sent when client leaves a bazaar.'} +data.outgoing[0x105] = {name='View Bazaar', description='Sent when viewing somebody\'s bazaar.'} +data.outgoing[0x106] = {name='Buy Bazaar Item', description='Buy an item from somebody\'s bazaar.'} +data.outgoing[0x109] = {name='Close Bazaar', description='Sent after closing your bazaar window.'} +data.outgoing[0x10A] = {name='Set Price', description='Set the price on a bazaar item.'} +data.outgoing[0x10B] = {name='Open Bazaar', description='Sent when opening your bazaar window to set prices.'} +data.outgoing[0x10C] = {name='Start RoE Quest', description='Sent to undertake a Records of Eminence Quest.'} +data.outgoing[0x10D] = {name='Cancel RoE Quest', description='Sent to cancel a Records of Eminence Quest.'} +data.outgoing[0x10E] = {name='Accept RoE Reward', description='Accept an RoE qust reward that was not given automatically due to inventory restrictions.'} +data.outgoing[0x10F] = {name='Currency Menu', description='Requests currency information for the menu.'} +data.outgoing[0x110] = {name='Fishing Action', description='Sent when casting, releasing a fish, catching a fish, and putting away your fishing rod.'} +data.outgoing[0x111] = {name='Lockstyle', description='Sent when using the lockstyle command to lock or unlock.'} +data.outgoing[0x112] = {name='RoE Log Request', description='Sent when zoning. Requests the ROE quest log.'} +data.outgoing[0x114] = {name='HP Map Trigger', description='Sent when entering a homepoint list for a zone to trigger maps to appear.'} +data.outgoing[0x115] = {name='Currency Menu 2', description='Requests currency 2 information for the menu.'} +data.outgoing[0x116] = {name='Unity Menu', description='Sent when opening the Status/Unity menu.'} +data.outgoing[0x117] = {name='Unity Ranking Menu', description='Sent when opening the Status/Unity/Unity Ranking menu.'} +data.outgoing[0x118] = {name='Unity Chat Status', description='Sent when changing unity chat status.'} + +-- Server packets (incoming) +data.incoming[0x009] = {name='Standard Message', description='A standardized message send from FFXI.'} +data.incoming[0x00A] = {name='Zone In', description='Info about character and zone around it.'} +data.incoming[0x00B] = {name='Zone Out', description='Packet contains IP and port of next zone to connect to.'} +data.incoming[0x00D] = {name='PC Update', description='Packet contains info about another PC (i.e. coordinates).'} +data.incoming[0x00E] = {name='NPC Update', description='Packet contains data about nearby targets (i.e. target\'s position, name).'} +data.incoming[0x017] = {name='Incoming Chat', description='Packet contains data about incoming chat messages.'} +data.incoming[0x01B] = {name='Job Info', description='Job Levels and levels unlocked.'} +data.incoming[0x01C] = {name='Inventory Count', description='Describes number of slots in inventory.'} +data.incoming[0x01D] = {name='Finish Inventory', description='Finish listing the items in inventory.'} +data.incoming[0x01E] = {name='Modify Inventory', description='Modifies items in your inventory.'} +data.incoming[0x01F] = {name='Item Assign', description='Assigns an ID to equipped items in your inventory.'} +data.incoming[0x020] = {name='Item Update', description='Info about item in your inventory.'} +data.incoming[0x021] = {name='Trade Requested', description='Sent when somebody offers to trade with you.'} +data.incoming[0x022] = {name='Trade Action', description='Sent whenever something happens with the trade window.'} +data.incoming[0x023] = {name='Trade Item', description='Sent when an item appears in the trade window.'} +data.incoming[0x025] = {name='Item Accepted', description='Sent when the server will allow you to trade an item.'} +data.incoming[0x026] = {name='Count to 80', description='It counts to 80 and does not have any obvious function. May have something to do with populating inventory.'} +data.incoming[0x027] = {name='String Message', description='Message that includes a string as a parameter.'} +data.incoming[0x028] = {name='Action', description='Packet sent when an NPC is attacking.'} +data.incoming[0x029] = {name='Action Message', description='Packet sent for simple battle-related messages.'} +data.incoming[0x02A] = {name='Resting Message', description='Packet sent when you rest in Abyssea.'} +data.incoming[0x02D] = {name='Kill Message', description='Packet sent when you gain XP/LP/CP/JP/MP, advance RoE objectives, etc. by defeating a mob.'} +data.incoming[0x02E] = {name='Mog House Menu', description='Sent when talking to moogle inside mog house.'} +data.incoming[0x02F] = {name='Digging Animation', description='Generates the chocobo digging animation'} +data.incoming[0x030] = {name='Synth Animation', description='Generates the synthesis animation'} +data.incoming[0x031] = {name='Synth List', description='List of recipes or materials needed for a recipe'} +data.incoming[0x032] = {name='NPC Interaction 1', description='Occurs before menus and some cutscenes'} +data.incoming[0x033] = {name='String NPC Interaction',description='Triggers a menu or cutscene to appear. Contains 4 strings.'} +data.incoming[0x034] = {name='NPC Interaction 2', description='Occurs before menus and some cutscenes'} +data.incoming[0x036] = {name='NPC Chat', description='Dialog from NPC\'s.'} +data.incoming[0x037] = {name='Update Char', description='Updates a characters stats and animation.'} +data.incoming[0x038] = {name='Entity Animation', description='Sent when a model should play a specific animation.'} +data.incoming[0x039] = {name='Env. Animation', description='Sent to force animations to specific objects.'} +data.incoming[0x03A] = {name='Independ. Animation', description='Used for arbitrary battle animations that are unaccompanied by an action packet.'} +data.incoming[0x03C] = {name='Shop', description='Displays items in a vendors shop.'} +data.incoming[0x03D] = {name='Shop Value/Sale', description='Returns the value of an item or notice it has been sold.'} +data.incoming[0x03E] = {name='Open Buy/Sell', description='Opens the buy/sell menu for vendors.'} +data.incoming[0x03F] = {name='Shop Buy Response', description='Sent when you buy something from normal vendors.'} +data.incoming[0x041] = {name='Blacklist', description='Contains player ID and name for blacklist.'} +data.incoming[0x042] = {name='Blacklist Command', description='Sent in response to /blacklist add or /blacklist delete.'} +data.incoming[0x044] = {name='Job Info Extra', description='Contains information about Automaton stats and set Blue Magic spells.'} +data.incoming[0x047] = {name='Translate Response', description='Response to a translate request.'} +data.incoming[0x04B] = {name='Logout Acknowledge', description='Acknowledges a logout attempt.'} +data.incoming[0x04B] = {name='Delivery Item', description='Item in delivery box.'} +data.incoming[0x04C] = {name='Auction House Menu', description='Sent when visiting auction counter.'} +data.incoming[0x04D] = {name='Servmes Resp', description='Server response when someone requests it.'} +data.incoming[0x04F] = {name='Data Download 2', description='The data that is sent to the client when it is "Downloading data...".'} +data.incoming[0x050] = {name='Equip', description='Updates the characters equipment slots.'} +data.incoming[0x051] = {name='Model Change', description='Info about equipment and appearance.'} +data.incoming[0x052] = {name='NPC Release', description='Allows your PC to move after interacting with an NPC.'} +data.incoming[0x053] = {name='Logout Time', description='The annoying message that tells how much time till you logout.'} +data.incoming[0x055] = {name='Key Item Log', description='Updates your key item log on zone and when appropriate.'} +data.incoming[0x056] = {name='Quest/Mission Log', description='Updates your quest and mission log on zone and when appropriate.'} +data.incoming[0x057] = {name='Weather Change', description='Updates the weather effect when the weather changes.'} +data.incoming[0x058] = {name='Lock Target', description='Locks your target.'} +data.incoming[0x05A] = {name='Server Emote', description='This packet is the server\'s response to a client /emote p.'} +data.incoming[0x05B] = {name='Spawn', description='Server packet sent when a new mob spawns in area.'} +data.incoming[0x05C] = {name='Dialogue Information',description='Used when all the information required for a menu cannot be fit in an NPC Interaction packet.'} +data.incoming[0x05E] = {name='Camp./Besieged Map', description='Contains information about Campaign and Besieged status.'} +data.incoming[0x05F] = {name='Music Change', description='Changes the current music.'} +data.incoming[0x061] = {name='Char Stats', description='Packet contains a lot of data about your character\'s stats.'} +data.incoming[0x062] = {name='Skills Update', description='Packet that shows your weapon and magic skill stats.'} +data.incoming[0x063] = {name='Set Update', description='Frequently sent packet during battle that updates specific types of job information, like currently available/set automaton equipment and currently set BLU spells.'} +data.incoming[0x065] = {name='Repositioning', description='Moves your character. Seems to be functionally idential to the Spawn packet'} +data.incoming[0x067] = {name='Pet Info', description='Updates information about whether or not you have a pet and the TP, HP, etc. of the pet if appropriate.'} +data.incoming[0x068] = {name='Pet Status', description='Updates information about whether or not you have a pet and the TP, HP, etc. of the pet if appropriate.'} +data.incoming[0x06F] = {name='Self Synth Result', description='Results of an attempted synthesis process by yourself.'} +data.incoming[0x070] = {name='Others Synth Result', description='Results of an attempted synthesis process by others.'} +data.incoming[0x071] = {name='Campaign Map Info', description='Populates the Campaign map.'} +data.incoming[0x075] = {name='Unity Start', description='Creates the timer and glowing fence that accompanies Unity fights.'} +data.incoming[0x076] = {name='Party Buffs', description='Packet updated every time a party member\'s buffs change.'} +data.incoming[0x078] = {name='Proposal', description='Carries proposal information from a /propose or /nominate command.'} +data.incoming[0x079] = {name='Proposal Update', description='Proposal update following a /vote command.'} +data.incoming[0x082] = {name='Guild Buy Response', description='Buy an item from a guild.'} +data.incoming[0x083] = {name='Guild Inv List', description='Provides the items, prices, and counts for guild inventories.'} +data.incoming[0x084] = {name='Guild Sell Response', description='Sell an item to a guild.'} +data.incoming[0x085] = {name='Guild Sale List', description='Provides the items, prices, and counts for guild inventories.'} +data.incoming[0x086] = {name='Guild Open', description='Sent to update the current guild status or open the guild buy/sell menu.'} +data.incoming[0x08C] = {name='Merits', description='Contains all merit information. 3 packets are sent.'} +data.incoming[0x08D] = {name='Job Points', description='Contains all job point information. 12 packets are sent.'} +data.incoming[0x0A0] = {name='Party Map Marker', description='Marks where players are on your map.'} +data.incoming[0x0AA] = {name='Spell List', description='Packet that shows the spells that you know.'} +data.incoming[0x0AC] = {name='Ability List', description='Packet that shows your current abilities and traits.'} +data.incoming[0x0AE] = {name='Mount List', description='Packet that shows your current mounts.'} +data.incoming[0x0B4] = {name='Seek AnonResp', description='Server response sent after you put up party or anon flag.'} +data.incoming[0x0B5] = {name='Help Desk Open', description='Sent when you open the Help Desk submenu.'} +data.incoming[0x0BF] = {name='Reservation Response',description='Sent to inform the client about the status of entry to an instanced area.'} +data.incoming[0x0C8] = {name='Party Struct Update', description='Updates all party member info in one struct. No player vital data (HP/MP/TP) or names are sent here.'} +data.incoming[0x0C9] = {name='Show Equip', description='Shows another player your equipment after using the Check command.'} +data.incoming[0x0CA] = {name='Bazaar Message', description='Shows another players bazaar message after using the Check command or sets your own on zoning.'} +data.incoming[0x0CC] = {name='Linkshell Message', description='/lsmes text and headers.'} +data.incoming[0x0D2] = {name='Found Item', description='This command shows an item found on defeated mob or from a Treasure Chest.'} +data.incoming[0x0D3] = {name='Lot/drop item', description='Sent when someone casts a lot on an item or when the item drops to someone.'} +data.incoming[0x0DC] = {name='Party Invite', description='Party Invite packet.'} +data.incoming[0x0DD] = {name='Party Member Update', description='Alliance/party member info - zone, HP%, HP% etc.'} +data.incoming[0x0DF] = {name='Char Update', description='A packet sent from server which updates character HP, MP and TP.'} +data.incoming[0x0E0] = {name='Linkshell Equip', description='Updates your linkshell menu with the current linkshell.'} +data.incoming[0x0E1] = {name='Party Member List', description='Sent when you look at the party member list.'} +data.incoming[0x0E2] = {name='Char Info', description='Sends name, HP, HP%, etc.'} +data.incoming[0x0F4] = {name='Widescan Mob', description='Displays one monster.'} +data.incoming[0x0F5] = {name='Widescan Track', description='Updates information when tracking a monster.'} +data.incoming[0x0F6] = {name='Widescan Mark', description='Marks the start and ending of a widescan list.'} +data.incoming[0x0F9] = {name='Reraise Activation', description='Reassigns targetable status on reraise activation?'} +data.incoming[0x0FA] = {name='Furniture Interact', description='Confirms furniture manipulation.'} +data.incoming[0x105] = {name='Data Download 4', description='The data that is sent to the client when it is "Downloading data...".'} +data.incoming[0x106] = {name='Bazaar Seller Info', description='Information on the purchase sent to the buyer when they attempt to buy something.'} +data.incoming[0x107] = {name='Bazaar closed', description='Tells you when a bazaar you are currently in has closed.'} +data.incoming[0x108] = {name='Data Download 5', description='The data that is sent to the client when it is "Downloading data...".'} +data.incoming[0x109] = {name='Bazaar Purch. Info', description='Information on the purchase sent to the buyer when the purchase is successful.'} +data.incoming[0x10A] = {name='Bazaar Buyer Info', description='Information on the purchase sent to the seller when a sale is successful.'} +data.incoming[0x110] = {name='Sparks Update', description='Occurs when you sparks increase and generates the related message.'} +data.incoming[0x111] = {name='Eminence Update', description='Causes Records of Eminence messages.'} +data.incoming[0x112] = {name='RoE Quest Log', description='Updates your RoE quest log on zone and when appropriate.'} +data.incoming[0x113] = {name='Currency Info', description='Contains all currencies to be displayed in the currency menu.'} +data.incoming[0x115] = {name='Fish Bite Info', description='Contains information about the fish that you hooked.'} +data.incoming[0x116] = {name='Equipset Build Response', description='Returned from the server when building a set.'} +data.incoming[0x117] = {name='Equipset Response', description='Returned from the server after the /equipset command.'} +data.incoming[0x118] = {name='Currency 2 Info', description='Contains all currencies to be displayed in the currency menu.'} +data.incoming[0x119] = {name='Ability Recasts', description='Contains the currently available job abilities and their remaining recast times.'} + +return data + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/packets/fields.lua b/Data/DefaultContent/Libraries/addons/addons/libs/packets/fields.lua new file mode 100644 index 0000000..bdc9b78 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/packets/fields.lua @@ -0,0 +1,3959 @@ +--[[ + A collection of detailed packet field information. +]] + +require('pack') +require('functions') +require('strings') +require('maths') +require('lists') +require('sets') +local bit = require('bit') + +local fields = {} +fields.outgoing = {} +fields.incoming = {} + +local func = { + incoming = {}, + outgoing = {}, +} + +-- String decoding definitions +local ls_enc = { + charset = T('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':split()):update({ + [0] = '`', + [60] = 0:char(), + [63] = 0:char(), + }), + bits = 6, + terminator = function(str) + return (#str % 4 == 2 and 60 or 63):binary() + end +} +local sign_enc = { + charset = T('0123456798ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{':split()):update({ + [0] = 0:char(), + }), + bits = 6, +} + +-- Function definitions. Used to display packet field information. +local res = require('resources') + +local function s(val, from, to) + from = from - 1 + to = to + return bit.band(bit.rshift(val, from), 2^(to - from) - 1) +end + +local function id(val) + local mob = windower.ffxi.get_mob_by_id(val) + return mob and mob.name or '-' +end + +local function index(val) + local mob = windower.ffxi.get_mob_by_index(val) + return mob and mob.name or '-' +end + +local function ip(val) + return '%d.%d.%d.%d':format('I':pack(val):unpack('CCCC')) +end + +local function gil(val) + return tostring(val):reverse():chunks(3):concat(','):reverse() .. ' G' +end + +local function bool(val) + return val ~= 0 +end + +local function invbool(val) + return val == 0 +end + +local function div(denom, val) + return val/denom +end + +local function add(amount, val) + return val + amount +end + +local function sub(amount, val) + return val - amount +end + +local time +local utime +do + local now = os.time() + local h, m = (os.difftime(now, os.time(os.date('!*t', now))) / 3600):modf() + + local timezone = '%+.2d:%.2d':format(h, 60 * m) + + local fn = function(ts) + return os.date('%Y-%m-%dT%H:%M:%S' .. timezone, ts) + end + + time = function(ts) + return fn(os.time() - ts) + end + + utime = function(ts) + return fn(ts) + end + + bufftime = function(ts) + return fn((ts / 60) + 572662306 + 1009810800) + end +end + +local time_ms = time .. function(val) return val/1000 end + +local dir = function() + local dir_sets = L{'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNE', 'NE', 'ENE', 'E'} + return function(val) + return dir_sets[((val + 8)/16):floor() + 1] + end +end() + +local function cap(max, val) + return '%.1f':format(100*val/max)..'%' +end + +local function zone(val) + return res.zones[val] and res.zones[val].name or '- (Unknown zone ID: %d)':format(val) +end + +local function item(val) + return val ~= 0 and res.items[val] and res.items[val].name or '-' +end + +local function server(val) + return res.servers[val].name +end + +local function weather(val) + return res.weather[val].name +end + +local function buff(val) + return val ~= 0xFF and res.buffs[val].name or '-' +end + +local function chat(val) + return res.chat[val].name +end + +local function skill(val) + return res.skills[val].name +end + +local function title(val) + return res.titles[val].name +end + +local function job(val) + return res.jobs[val].name +end + +local function emote(val) + return '/' .. res.emotes[val].command +end + +local function bag(val) + return res.bags[val].name +end + +local function race(val) + return res.races[val].name +end + +local function slot(val) + return res.slots[val].name +end + +local function statuses(val) + return res.statuses[val] and res.statuses[val].name or 'Unknown' +end + +local function srank(val) + return res.synth_ranks[val].name +end + +local function arecast(val) + return res.ability_recasts[val].name +end + +local function inv(bag, val) + if val == 0 or not res.bags[bag] then + return '-' + end + + return item(windower.ffxi.get_items(bag, val).id) +end + +local function invp(index, val, data) + return inv(data[index + 1]:byte(), val) +end + +local function hex(fill, val) + return val:hex():zfill(2*fill):chunks(2):reverse():concat(' ') +end + +local function bin(fill, val) + return type(val) == 'string' and val:binary(' ') or val:binary():zfill(8*fill):chunks(8):reverse():concat(' ') +end + +--[[ + Custom types +]] +local types = {} + +local enums = { + ['synth'] = { + [0] = 'Success', + [1] = 'Fail', + [2] = 'Fail, interrupted', + [3] = 'Cancel, invalid recipe', + [4] = 'Cancel', + [5] = 'Fail, crystal lost', + [6] = 'Cancel, skill too low', + [7] = 'Cancel, rare', + }, + ['logout'] = { + [1] = '/loguot', + [2] = '/pol', + [3] = '/shutdown', + }, + ['zone'] = { + [1] = 'Logout', + [2] = 'Teleport', + [3] = 'Zone line', + }, + [0x038] = { + deru = 'Appear', + kesu = 'Disappear', + }, + ['itemstat'] = { + [0x00] = 'None', + [0x05] = 'Equipped', + [0x0F] = 'Synthing', + [0x13] = 'Active linkshell', + [0x19] = 'Bazaaring', + }, + ['ws track'] = { + [1] = 'Update', + [2] = 'Reset (zone)', + [3] = 'Reset (new scan)', + }, + ['ws mob'] = { + [0] = 'Other', + [1] = 'Friendly', + [2] = 'Enemy', + }, + ['ws mark'] = { + [1] = 'Start', + [2] = 'End', + }, + ['bazaar'] = { + [0] = 'Open', + [1] = 'Close', + }, + ['try'] = { + [0] = 'Succeeded', + [1] = 'Failed', + }, +} + +local e = function(t, val) + return enums[t][val] or 'Unknown value for \'%s\': %s':format(t, tostring(val)) +end + +--[[ + Outgoing packets +]] + +-- Zone In 1 +-- Likely triggers specific incoming packets. +-- Does not trigger any packets when randomly injected. +fields.outgoing[0x00C] = L{ + {ctype='int', label='_unknown1'}, -- 04 Always 00s? + {ctype='int', label='_unknown2'}, -- 04 Always 00s? +} + +-- Client Leave +-- Last packet sent when zoning. Disconnects from the zone server. +fields.outgoing[0x00D] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 Always 00? + {ctype='unsigned char', label='_unknown2'}, -- 05 Always 00? + {ctype='unsigned char', label='_unknown3'}, -- 06 Always 00? + {ctype='unsigned char', label='_unknown4'}, -- 07 Always 00? +} + +-- Zone In 2 +-- Likely triggers specific incoming packets. +-- Does not trigger any packets when randomly injected. +fields.outgoing[0x00F] = L{ + {ctype='data[32]', label='_unknown1'}, -- 04 Always 00s? +} + +-- Zone In 3 +-- Likely triggers specific incoming packets. +-- Does not trigger any packets when randomly injected. +fields.outgoing[0x011] = L{ + {ctype='int', label='_unknown1'}, -- 04 Always 02 00 00 00? +} + + +-- Standard Client +fields.outgoing[0x015] = L{ + {ctype='float', label='X'}, -- 04 + {ctype='float', label='Z'}, -- 08 + {ctype='float', label='Y'}, -- 0C + {ctype='unsigned short', label='_junk1'}, -- 10 + {ctype='unsigned short', label='Run Count'}, -- 12 Counter that indicates how long you've been running? + {ctype='unsigned char', label='Rotation', fn=dir}, -- 14 + {ctype='unsigned char', label='_flags1'}, -- 15 Bit 0x04 indicates that maintenance mode is activated + {ctype='unsigned short', label='Target Index', fn=index}, -- 16 + {ctype='unsigned int', label='Timestamp', fn=time_ms}, -- 18 Milliseconds + {ctype='unsigned int', label='_unknown3'}, -- 1C +} + +-- Update Request +fields.outgoing[0x016] = L{ + {ctype='unsigned short', label='Target Index', fn=index}, -- 04 + {ctype='unsigned short', label='_junk1'}, -- 06 +} + +-- NPC Race Error +fields.outgoing[0x017] = L{ + {ctype='unsigned short', label='NPC Index', fn=index}, -- 04 + {ctype='unsigned short', label='_unknown1'}, -- 06 + {ctype='unsigned int', label='NPC ID', fn=id}, -- 08 + {ctype='data[6]', label='_unknown2'}, -- 0C + {ctype='unsigned char', label='Reported NPC type'}, -- 12 + {ctype='unsigned char', label='_unknown3'}, -- 13 +} + +enums['action'] = { + [0x00] = 'NPC Interaction', + [0x02] = 'Engage monster', + [0x03] = 'Magic cast', + [0x04] = 'Disengage', + [0x05] = 'Call for Help', + [0x07] = 'Weaponskill usage', + [0x09] = 'Job ability usage', + [0x0C] = 'Assist', + [0x0D] = 'Reraise dialogue', + [0x0E] = 'Cast Fishing Rod', + [0x0F] = 'Switch target', + [0x10] = 'Ranged attack', + [0x12] = 'Dismount Chocobo', + [0x13] = 'Tractor Dialogue', + [0x14] = 'Zoning/Appear', -- I think, the resource for this is ambiguous. + [0x19] = 'Monsterskill', + [0x1A] = 'Mount', +} + +-- Action +fields.outgoing[0x01A] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 + {ctype='unsigned short', label='Category', fn=e+{'action'}}, -- 0A + {ctype='unsigned short', label='Param'}, -- 0C + {ctype='unsigned short', label='_unknown1', const=0}, -- 0E + {ctype='float', label='X Offset'}, -- 10 -- non-zero values only observed for geo spells cast using a repositioned subtarget + {ctype='float', label='Z Offset'}, -- 14 + {ctype='float', label='Y Offset'}, -- 18 +} + +-- /volunteer +fields.outgoing[0x01E] = L{ + {ctype='char*', label='Target Name'}, -- 04 null terminated string. Length of name to the nearest 4 bytes. +} + +-- Drop Item +fields.outgoing[0x028] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned char', label='Bag', fn=bag}, -- 08 + {ctype='unsigned char', label='Inventory Index', fn=invp+{0x08}}, -- 09 + {ctype='unsigned short', label='_junk1'}, -- 0A +} + +-- Move Item +fields.outgoing[0x029] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned char', label='Bag', fn=bag}, -- 08 + {ctype='unsigned char', label='Target Bag', fn=bag}, -- 09 + {ctype='unsigned char', label='Current Index', fn=invp+{0x08}}, -- 0A + {ctype='unsigned char', label='Target Index'}, -- 0B This byte is 0x52 when moving items between bags. It takes other values when manually sorting. +} + +-- Translate +-- German and French translations appear to no longer be supported. +fields.outgoing[0x02B] = L{ + {ctype='unsigned char', label='Starting Language'}, -- 04 0 == JP, 1 == EN + {ctype='unsigned char', label='Ending Language'}, -- 05 0 == JP, 1 == EN + {ctype='unsigned short', label='_unknown1', const=0x0000}, -- 06 + {ctype='char[64]', label='Phrase'}, -- 08 Quotation marks are removed. Phrase is truncated at 64 characters. +} + +-- Trade request +fields.outgoing[0x032] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 + {ctype='data[2]', label='_junk1'} -- 0A +} + +enums[0x033] = { + [0] = 'Accept trade', + [1] = 'Cancel trade', + [2] = 'Confirm trade', +} + +-- Trade confirm +-- Sent when accepting, confirming or canceling a trade +fields.outgoing[0x033] = L{ + {ctype='unsigned int', label='Type', fn=e+{0x033}}, -- 04 + {ctype='unsigned int', label='Trade Count'} -- 08 Necessary to set if you are receiving items, comes from incoming packet 0x023 +} + +-- Trade offer +fields.outgoing[0x034] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='Inventory Index', fn=inv+{0}}, -- 0A + {ctype='unsigned char', label='Slot'}, -- 0F +} + +-- Menu Item +fields.outgoing[0x036] = L{ +-- Item order is Gil -> top row left-to-right -> bottom row left-to-right, but +-- they slide up and fill empty slots + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned int[9]', label='Item Count'}, -- 08 + {ctype='unsigned int', label='_unknown1'}, -- 2C + {ctype='unsigned char[9]', label='Item Index', fn=inv+{0}}, -- 30 Gil has an Inventory Index of 0 + {ctype='unsigned char', label='_unknown2'}, -- 39 + {ctype='unsigned short', label='Target Index', fn=index}, -- 3A + {ctype='unsigned char', label='Number of Items'}, -- 3C +} + +-- Use Item +fields.outgoing[0x037] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='_unknown1'}, -- 08 00 00 00 00 observed + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C + {ctype='unsigned char', label='Slot', fn=inv+{0}}, -- 0E + {ctype='unsigned char', label='_unknown2'}, -- 0F Takes values + {ctype='unsigned char', label='Bag', fn=bag}, -- 10 + {ctype='data[3]', label='_unknown3'} -- 11 +} + +-- Sort Item +fields.outgoing[0x03A] = L{ + {ctype='unsigned char', label='Bag', fn=bag}, -- 04 + {ctype='unsigned char', label='_unknown1'}, -- 05 + {ctype='unsigned short', label='_unknown2'}, -- 06 +} + +-- Blacklist (add/delete) +fields.outgoing[0x03D] = L{ + {ctype='int', label='_unknown1'}, -- 04 Looks like a player ID, but does not match the sender or the receiver. + {ctype='char[16]', label='Name'}, -- 08 Character name + {ctype='bool', label='Add/Remove'}, -- 18 0 = add, 1 = remove + {ctype='data[3]', label='_unknown2'}, -- 19 Values observed on adding but not deleting. +} + +-- Lot item +fields.outgoing[0x041] = L{ + {ctype='unsigned char', label='Slot'}, -- 04 +} + +-- Pass item +fields.outgoing[0x042] = L{ + {ctype='unsigned char', label='Slot'}, -- 04 +} + +-- Servmes +-- First 4 bytes resemble the first 4 bytes of the incoming servmessage packet +fields.outgoing[0x04B] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 Always 1? + {ctype='unsigned char', label='_unknown2'}, -- 05 Can be 1 or 0 + {ctype='unsigned char', label='_unknown3'}, -- 06 Always 1? + {ctype='unsigned char', label='_unknown4'}, -- 07 Always 2? + {ctype='data[12]', label='_unknown5'}, -- 08 All 00s + {ctype='unsigned int', label='_unknown5'}, -- 14 EC 00 00 00 observed. May be junk. +} + +-- Delivery Box +fields.outgoing[0x04D] = L{ + {ctype='unsigned char', label='Type'}, -- 04 + -- + + -- Removing an item from the d-box sends type 0x08 + -- It then responds to the server's 0x4B (id=0x08) with a 0x0A type packet. + -- Their assignment is the same, as far as I can see. + {ctype='unsigned char', label='_unknown1'}, -- 05 01 observed + {ctype='unsigned char', label='Slot'}, -- 06 + {ctype='data[5]', label='_unknown2'}, -- 07 FF FF FF FF FF observed + {ctype='data[20]', label='_unknown3'}, -- 0C All 00 observed +} + +enums['ah otype'] = { + [0x04] = 'Sell item request', + [0x05] = 'Check sales', + [0x0A] = 'Open AH menu', + [0x0B] = 'Sell item confirmation', + [0x0C] = 'Stop sale', + [0x0D] = 'Sale status confirmation', + [0x0E] = 'Place bid', + [0x10] = 'Item sold', +} + +func.outgoing[0x04E] = {} +func.outgoing[0x04E].base = L{ + {ctype='unsigned char', label='Type', fn=e+{'ah otype'}}, -- 04 +} + +-- Sent when putting an item up for auction (request) +func.outgoing[0x04E][0x04] = L{ + {ctype='data[3]', label='_unknown1'}, -- 05 + {ctype='unsigned int', label='Price', fn=gil}, -- 08 + {ctype='unsigned short', label='Inventory Index', fn=inv+{0}}, -- 0C + {ctype='unsigned short', label='Item', fn=item}, -- 0E + {ctype='unsigned char', label='Stack', fn=invbool}, -- 10 + {ctype='char*', label='_junk'}, -- 11 +} + +-- Sent when checking your sale status +func.outgoing[0x04E][0x05] = L{ + {ctype='char*', label='_junk'}, -- 05 +} + +-- Sent when initially opening the AH menu +func.outgoing[0x04E][0x0A] = L{ + {ctype='unsigned char', label='_unknown1', const=0xFF}, -- 05 + {ctype='char*', label='_junk'}, -- 06 +} + +-- Sent when putting an item up for auction (confirmation) +func.outgoing[0x04E][0x0B] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='data[2]', label='_unknown1'}, -- 06 + {ctype='unsigned int', label='Price', fn=gil}, -- 08 + {ctype='unsigned short', label='Inventory Index', fn=inv+{0}}, -- 0C + {ctype='unsigned short', label='_unknown2'}, -- 0E + {ctype='unsigned char', label='Stack', fn=invbool}, -- 10 + {ctype='char*', label='_junk'}, -- 11 +} + +-- Sent when stopping an item from sale +func.outgoing[0x04E][0x0C] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='char*', label='_junk'}, -- 06 +} + +-- Sent after receiving the sale status list for each item +func.outgoing[0x04E][0x0D] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='char*', label='_junk'}, -- 06 +} + +-- Sent when bidding on an item +func.outgoing[0x04E][0x0E] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='unsigned short', label='_unknown3'}, -- 06 + {ctype='unsigned int', label='Price', fn=gil}, -- 08 + {ctype='unsigned short', label='Item', fn=item}, -- 0C + {ctype='unsigned short', label='_unknown4'}, -- 0E + {ctype='bool', label='Stack', fn=invbool}, -- 10 + {ctype='char*', label='_junk'}, -- 11 +} + +-- Sent when taking a sold item from the list +func.outgoing[0x04E][0x10] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='char*', label='_junk'}, -- 06 +} + +-- Auction Interaction +fields.outgoing[0x04E] = function(data, type) + type = type or data and data:byte(5) + return func.outgoing[0x04E].base + (func.outgoing[0x04E][type] or L{}) +end + +-- Equip +fields.outgoing[0x050] = L{ + {ctype='unsigned char', label='Item Index', fn=invp+{0x06}}, -- 04 + {ctype='unsigned char', label='Equip Slot', fn=slot}, -- 05 + {ctype='unsigned char', label='Bag', fn=bag}, -- 06 + {ctype='data[1]', label='_junk1'} -- 07 +} + +types.equipset = L{ + {ctype='unsigned char', label='Inventory Index', fn=invp+{0x0A}}, -- 00 + {ctype='unsigned char', label='Equipment Slot', fn=slot}, -- 01 + {ctype='unsigned char', label='Bag', fn=bag}, -- 02 + {ctype='unsigned char', label='_padding1'}, -- 03 +} + +func.outgoing[0x051] = {} +func.outgoing[0x051].base = L{ + {ctype='unsigned char', label='Count'}, -- 04 + {ctype='unsigned char[3]', label='_unknown1'}, -- 05 Same as _unknown1 in outgoing 0x052 +} + +-- Equipset +fields.outgoing[0x051] = function(data, count) + count = count or data:byte(5) + + return func.outgoing[0x051].base + L{ + -- Only the number given in Count will be properly populated, the rest is junk + {ref=types.equipset, count=count}, -- 08 + {ctype='data[%u]':format((16 - count) * 4), label='_junk1'}, -- 08 + 4 * count + } +end + +types.equipset_build = L{ + {ctype='boolbit', label='Active'}, -- 00 + {ctype='bit', label='_unknown1'}, -- 00 + {ctype='bit[6]', label='Bag', fn=bag}, -- 00 + {ctype='unsigned char', label='Inventory Index'}, -- 01 + {ctype='unsigned short', label='Item', fn=item}, -- 02 +} + +-- Equipset Build +fields.outgoing[0x052] = L{ + -- First 8 bytes are for the newly changed item + {ctype='unsigned char', label='New Equipment Slot', fn=slot}, -- 04 + {ctype='unsigned char[3]', label='_unknown1'}, -- 05 + {ref=types.equipset_build, count=1}, -- 08 + -- The next 16 are the entire current equipset, excluding the newly changed item + {ref=types.equipset_build, lookup={res.slots, 0x00}, count=0x10}, -- 0C +} + +types.lockstyleset = L{ + {ctype='unsigned char', label='Inventory Index'}, -- 00 + {ctype='unsigned char', label='Equipment Slot', fn=slot}, -- 01 + {ctype='unsigned char', label='Bag', fn=bag}, -- 02 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 03 + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='unsigned short', label='_unknown3', const=0x0000}, -- 06 +} + +-- lockstyleset +fields.outgoing[0x053] = L{ + -- First 4 bytes are a header for the set + {ctype='unsigned char', label='Count'}, -- 04 + {ctype='unsigned char', label='Type'}, -- 05 0 = "Stop locking style", 1 = "Continue locking style", 3 = "Lock style in this way". Might be flags? + {ctype='unsigned short', label='_unknown1', const=0x0000}, -- 06 + {ref=types.lockstyleset, count=16}, -- 08 + } + + +-- End Synth +-- This packet is sent after receiving a result when synthesizing. +fields.outgoing[0x059] = L{ + {ctype='unsigned int', label='_unknown1'}, -- 04 Often 00 00 00 00, but 01 00 00 00 observed. + {ctype='data[8]', label='_junk1'} -- 08 Often 00 00 00 00, likely junk from a non-zero'd buffer. +} + +-- Conquest +fields.outgoing[0x05A] = L{ +} + +-- Dialogue options +fields.outgoing[0x05B] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned short', label='Option Index'}, -- 08 + {ctype='unsigned short', label='_unknown1'}, -- 0A + {ctype='unsigned short', label='Target Index', fn=index}, -- 0C + {ctype='bool', label='Automated Message'}, -- 0E 1 if the response packet is automatically generated, 0 if it was selected by you + {ctype='unsigned char', label='_unknown2'}, -- 0F + {ctype='unsigned short', label='Zone', fn=zone}, -- 10 + {ctype='unsigned short', label='Menu ID'}, -- 12 +} + +-- Warp Request +fields.outgoing[0x05C] = L{ + {ctype='float', label='X'}, -- 04 + {ctype='float', label='Z'}, -- 08 + {ctype='float', label='Y'}, -- 0C + {ctype='unsigned int', label='Target ID', fn=id}, -- 10 NPC that you are requesting a warp from + {ctype='unsigned int', label='_unknown1'}, -- 14 01 00 00 00 observed + {ctype='unsigned short', label='Zone'}, -- 18 + {ctype='unsigned short', label='Menu ID'}, -- 1A + {ctype='unsigned short', label='Target Index', fn=index}, -- 1C + {ctype='unsigned char', label='_unknown2', const=1}, -- 1E + {ctype='unsigned char', label='Rotation'}, -- 1F +} + +-- Outgoing emote +fields.outgoing[0x05D] = L{ + {ctype='unsigned int', label='Target ID', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 + {ctype='unsigned char', label='Emote', fn=emote}, -- 0A + {ctype='unsigned char', label='Type'}, -- 0B 2 for motion, 0 otherwise + {ctype='unsigned int', label='_unknown1', const=0}, -- 0C +} + +-- Zone request +-- Sent when crossing a zone line. +fields.outgoing[0x05E] = L{ + {ctype='unsigned int', label='Zone Line'}, -- 04 This seems to be a fourCC consisting of the following chars: + -- 'z' (apparently constant) + -- Region-specific char ('6' for Jeuno, '3' for Qufim, etc.) + -- Zone-specific char ('u' for Port Jeuno, 't' for Lower Jeuno, 's' for Upper Jeuno, etc.) + -- Zone line identifier ('4' for Port Jeuno > Qufim Island, '2' for Port Jeuno > Lower Jeuno, etc.) + {ctype='data[12]', label='_unknown1', const=''}, -- 08 + {ctype='unsigned short', label='_unknown2', const=0}, -- 14 + {ctype='unsigned char', label='_unknown3', const=0x04}, -- 16 Seemed to never vary for me + {ctype='unsigned char', label='Type'}, -- 17 03 for leaving the MH, 00 otherwise +} + +-- Equipment Screen (0x02 length) -- Also observed when zoning +fields.outgoing[0x061] = L{ +} + +-- Digging Finished +-- This packet alone is responsible for generating the digging result, meaning that anyone that can inject +-- this packet is capable of digging with 0 delay. +fields.outgoing[0x063] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='_unknown1'}, -- 08 + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C + {ctype='unsigned char', label='Action?'}, -- 0E Changing it to anything other than 0x11 causes the packet to fail + {ctype='unsigned char', label='_junk1'}, -- 0F Likely junk. Has no effect on anything notable. +} + +--"New" Key Item examination packet +fields.outgoing[0x064] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='data[0x40]', label='flags'}, -- 08 These correspond to a particular section of the 0x55 incoming packet + {ctype='unsigned int', label='_unknown1'}, -- 48 This field somehow denotes which half-0x55-packet the flags corresponds to +} + +-- Party invite +fields.outgoing[0x06E] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 This is so weird. The client only knows IDs from searching for people or running into them. So if neither has happened, the manual invite will fail, as the ID cannot be retrieved. + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 00 if target not in zone + {ctype='unsigned char', label='Alliance'}, -- 0A 05 for alliance, 00 for party or if invalid alliance target (the client somehow knows..) + {ctype='unsigned char', label='_const1', const=0x041}, -- 0B +} + +-- Party leaving +fields.outgoing[0x06F] = L{ + {ctype='unsigned char', label='Alliance'}, -- 04 05 for alliance, 00 for party + {ctype='data[3]', label='_junk1'} -- 05 +} + +-- Party breakup +fields.outgoing[0x070] = L{ + {ctype='unsigned char', label='Alliance'}, -- 04 02 for alliance, 00 for party + {ctype='data[3]', label='_junk1'} -- 05 +} + +-- Kick +fields.outgoing[0x071] = L{ + {ctype='data[6]', label='_unknown1'}, -- 04 + {ctype='unsigned char', label='Kick Type'}, -- 0A 0 for party, 1 for linkshell, 2 for alliance (maybe) + {ctype='unsigned char', label='_unknown2'}, -- 0B + {ctype='data[16]', label='Member Name'} -- 0C Null terminated string +} + +-- Party invite response +fields.outgoing[0x074] = L{ + {ctype='bool', label='Join', fn=bool}, -- 04 + {ctype='data[3]', label='_junk1'} -- 05 +} + +--[[ -- Unnamed 0x76 +-- Observed when zoning (sometimes). Probably triggers some information to be sent (perhaps about linkshells?) +fields.outgoing[0x076] = L{ + {ctype='unsigned char', label='flag'}, -- 04 Only 01 observed + {ctype='data[3]', label='_junk1'}, -- 05 Only 00 00 00 observed. +}]] + +-- Change Permissions +fields.outgoing[0x077] = L{ + {ctype='char[16]', label='Target Name'}, -- 04 Name of the person to give leader to + {ctype='unsigned char', label='Party Type'}, -- 14 00 = party, 01 = linkshell, 02 = alliance + {ctype='unsigned short', label='Permissions'}, -- 15 01 for alliance leader, 00 for party leader, 03 for linkshell "to sack", 02 for linkshell "to pearl" + {ctype='unsigned short', label='_unknown1'}, -- 16 +} + +-- Party list request (4 byte packet) +fields.outgoing[0x078] = L{ +} + +-- Guild NPC Buy +-- Sent when buying an item from a guild NPC +fields.outgoing[0x082] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 0A + {ctype='unsigned char', label='Count'}, -- 0B Number you are buying +} + +-- NPC Buy Item +-- Sent when buying an item from a generic NPC vendor +fields.outgoing[0x083] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='_unknown2'}, -- 08 Redirection Index? When buying from a guild helper, this was the index of the real guild NPC. + {ctype='unsigned char', label='Shop Slot'}, -- 0A The same index sent in incoming packet 0x03C + {ctype='unsigned char', label='_unknown3'}, -- 0B Always 0? Possibly padding + {ctype='unsigned int', label='_unknown4'}, -- 0C Always 0? +} + +-- NPC Sell price query +-- Sent when trying to sell an item to an NPC +-- Clicking on the item the first time will determine the price +-- Also sent automatically when finalizing a sale, immediately preceeding packet 0x085 +fields.outgoing[0x084] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='Inventory Index', fn=inv+{0}}, -- 09 Inventory index of the same item + {ctype='unsigned char', label='_unknown3'}, -- 0A Always 0? Likely padding +} + +-- NPC Sell confirm +-- Sent when confirming a sell of an item to an NPC +fields.outgoing[0x085] = L{ + {ctype='unsigned int', label='_unknown1', const=1}, -- 04 Always 1? Possibly a type +} + +-- Synth +fields.outgoing[0x096] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 Crystal ID? Earth = 0x02, Wind-break = 0x19?, Wind no-break = 0x2D? + {ctype='unsigned char', label='_unknown2'}, -- 05 + {ctype='unsigned short', label='Crystal', fn=item}, -- 06 + {ctype='unsigned char', label='Crystal Index', fn=inv+{0}}, -- 08 + {ctype='unsigned char', label='Ingredient count'}, -- 09 + {ctype='unsigned short[8]', label='Ingredient', fn=item}, -- 0A + {ctype='unsigned char[8]', label='Ingredient Index', fn=inv+{0}}, -- 1A + {ctype='unsigned short', label='_junk1'}, -- 22 +} + +-- /nominate or /proposal +fields.outgoing[0x0A0] = L{ + {ctype='unsigned char', label='Packet Type'}, -- 04 Not typical mapping. 0=Open poll (say), 1 = Open poll (party), 3 = conclude poll + -- Just padding if the poll is being concluded. + {ctype='char*', label='Proposal'}, -- 05 Proposal exactly as written. Space delimited with quotes and all. Null terminated. +} + +-- /vote +fields.outgoing[0x0A1] = L{ + {ctype='unsigned char', label='Option'}, -- 04 Voting option + {ctype='char*', label='Character Name'}, -- 05 Character name. Null terminated. +} + +-- /random +fields.outgoing[0x0A2] = L{ + {ctype='int', label='_unknown1'}, -- 04 No clear purpose +} + +-- Guild Buy Item +-- Sent when buying an item from a guild NPC +fields.outgoing[0x0AA] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 06 + {ctype='unsigned char', label='Count'}, -- 07 Number you are buying +} + +-- Get Guild Inv List +-- It's unclear how the server figures out which guild you're asking about, but this triggers 0x83 Incoming. +fields.outgoing[0x0AB] = L{ +} + +-- Guild Sell Item +-- Sent when selling an item to a guild NPC +fields.outgoing[0x0AC] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='unsigned char', label='_unknown1'}, -- 06 + {ctype='unsigned char', label='Count'}, -- 07 Number you are selling +} + +-- Get Guild Sale List +-- It's unclear how the server figures out which guild you're asking about, but this triggers 0x85 Incoming. +fields.outgoing[0x0AD] = L{ +} + +-- Speech +fields.outgoing[0x0B5] = L{ + {ctype='unsigned char', label='Mode', fn=chat}, -- 04 + {ctype='unsigned char', label='GM', fn=bool}, -- 05 + {ctype='char*', label='Message'}, -- 06 +} + +-- Tell +fields.outgoing[0x0B6] = L{ + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 04 00 for a normal tell -- Varying this does nothing. + {ctype='char[15]', label='Target Name'}, -- 05 + {ctype='char*', label='Message'}, -- 14 +} + +-- Merit Point Increase +fields.outgoing[0x0BE] = L{ + {ctype='unsigned char', label='_unknown1', const=0x03}, -- 04 No idea what it is, but it's always 0x03 for me + {ctype='unsigned char', label='Flag'}, -- 05 1 when you're increasing a merit point. 0 when you're decreasing it. + {ctype='unsigned short', label='Merit Point'}, -- 06 No known mapping, but unique to each merit point. Could be an int. + {ctype='unsigned int', label='_unknown2', const=0x00000000}, -- 08 +} + +-- Job Point Increase +fields.outgoing[0x0BF] = L{ + {ctype='bit[5]', label='Type'}, -- 04 + {ctype='bit[11]', label='Job', fn=job}, -- 04 + {ctype='unsigned short', label='_junk1', const=0x0000}, -- 06 No values seen so far +} + +-- Job Point Menu +-- This packet has no content bytes +fields.outgoing[0x0C0] = L{ +} + +-- /makelinkshell +fields.outgoing[0x0C3] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 + {ctype='unsigned char', label='Linkshell Number'}, -- 05 + {ctype='data[2]', label='_junk1'} -- 05 +} + +-- Equip Linkshell +fields.outgoing[0x0C4] = L{ + {ctype='unsigned short', label='_unknown1'}, -- 04 0x00 0x0F for me + {ctype='unsigned char', label='Inventory Slot ID'}, -- 06 Inventory Slot that holds the linkshell + {ctype='unsigned char', label='Linkshell Number'}, -- 07 Inventory Slot that holds the linkshell + {ctype='data[16]', label='String of unclear purpose'} -- 08 Probably going to be used in the future system somehow. Currently "dummy"..string.char(0,0,0).."%s %s "..string.char(0,1) +} + +-- Open Mog +fields.outgoing[0x0CB] = L{ + {ctype='unsigned char', label='type'}, -- 04 1 = open mog, 2 = close mog + {ctype='data[3]', label='_junk1'} -- 05 +} + +-- Party Marker Request +fields.outgoing[0x0D2] = L{ + {ctype='unsigned short', label='Zone', fn=zone}, -- 04 + {ctype='unsigned short', label='_junk1'} -- 06 +} + +-- Open Help Submenu +fields.outgoing[0x0D4] = L{ + {ctype='unsigned int', label='Number of Opens'}, -- 04 Number of times you've opened the submenu. +} + +-- Check +fields.outgoing[0x0DD] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 + {ctype='unsigned short', label='_unknown1'}, -- 0A + {ctype='unsigned char', label='Check Type'}, -- 0C 00 = Normal /check, 01 = /checkname, 02 = /checkparam + {ctype='data[3]', label='_junk1'} -- 0D +} + +-- Search Comment +fields.outgoing[0x0E0] = L{ + {ctype='char[40]', label='Line 1'}, -- 04 Spaces (0x20) fill out any empty characters. + {ctype='char[40]', label='Line 2'}, -- 2C Spaces (0x20) fill out any empty characters. + {ctype='char[40]', label='Line 3'}, -- 54 Spaces (0x20) fill out any empty characters. + {ctype='data[4]', label='_unknown1'}, -- 7C 20 20 20 00 observed. + {ctype='data[24]', label='_unknown2'}, -- 80 Likely contains information about the flags. +} + +-- Get LS Message +fields.outgoing[0x0E1] = L{ + {ctype='data[136]', label='_unknown1', const=0x0}, -- 04 +} + +-- Set LS Message +fields.outgoing[0x0E2] = L{ + {ctype='unsigned int', label='_unknown1', const=0x00000040}, -- 04 + {ctype='unsigned int', label='_unknown2'}, -- 08 Usually 0, but sometimes contains some junk + {ctype='char[128]', label='Message'} -- 0C +} + +-- Logout +fields.outgoing[0x0E7] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 Observed to be 00 + {ctype='unsigned char', label='_unknown2'}, -- 05 Observed to be 00 + {ctype='unsigned char', label='Logout Type', fn=e+{'logout'}}, -- 06 /logout = 01, /pol == 02 (removed), /shutdown = 03 + {ctype='unsigned char', label='_unknown3'}, -- 07 Observed to be 00 +} + +-- Sit +fields.outgoing[0x0EA] = L{ + {ctype='unsigned char', label='Movement'}, -- 04 + {ctype='unsigned char', label='_unknown1'}, -- 05 + {ctype='unsigned char', label='_unknown2'}, -- 06 + {ctype='unsigned char', label='_unknown3'}, -- 07 +} + +-- Cancel +fields.outgoing[0x0F1] = L{ + {ctype='unsigned char', label='Buff'}, -- 04 + {ctype='unsigned char', label='_unknown1'}, -- 05 + {ctype='unsigned char', label='_unknown2'}, -- 06 + {ctype='unsigned char', label='_unknown3'}, -- 07 +} + +-- Declare Subregion +fields.outgoing[0x0F2] = L{ + {ctype='unsigned char', label='_unknown1', const=0x01}, -- 04 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 05 + {ctype='unsigned short', label='Subregion Index'}, -- 06 +} + +-- Unknown packet 0xF2 +--[[fields.outgoing[0x0F2] = L{ + {ctype='unsigned char', label='type'}, -- 04 Was always 01 for me + {ctype='unsigned char', label='_unknown1'}, -- 05 Was always 00 for me + {ctype='unsigned short', label='Index', fn=index}, -- 07 Has always been the index of a synergy enthusiast or furnace for me +}]] + +-- Widescan +fields.outgoing[0x0F4] = L{ + {ctype='unsigned char', label='Flags'}, -- 04 1 when requesting widescan information. No other values observed. + {ctype='unsigned char', label='_unknown1'}, -- 05 + {ctype='unsigned short', label='_unknown2'}, -- 06 +} + +-- Widescan Track +fields.outgoing[0x0F5] = L{ + {ctype='unsigned short', label='Index', fn=index}, -- 04 Setting an index of 0 stops tracking + {ctype='unsigned short', label='_junk1'}, -- 06 +} + +-- Widescan Cancel +fields.outgoing[0x0F6] = L{ + {ctype='unsigned int', label='_junk1'}, -- 04 Always observed as 00 00 00 00 +} + +-- Place/Move Furniture +fields.outgoing[0x0FA] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 04 00 00 just gives the general update + {ctype='unsigned char', label='Safe Index', fn=inv+{1}}, -- 06 + {ctype='unsigned char', label='X'}, -- 07 0 to 0x12 + {ctype='unsigned char', label='Z'}, -- 08 0 to ? + {ctype='unsigned char', label='Y'}, -- 09 0 to 0x17 + {ctype='unsigned short', label='_junk1'}, -- 0A 00 00 observed +} + +-- Remove Furniture +fields.outgoing[0x0FB] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='unsigned char', label='Safe Index', fn=inv+{1}}, -- 06 + {ctype='unsigned char', label='_junk1'}, -- 07 +} + +-- Plant Flowerpot +fields.outgoing[0x0FC] = L{ + {ctype='unsigned short', label='Flowerpot Item', fn=item}, -- 04 + {ctype='unsigned short', label='Seed Item', fn=item}, -- 06 + {ctype='unsigned char', label='Flowerpot Safe Index', fn=inv+{1}}, -- 08 + {ctype='unsigned char', label='Seed Safe Index', fn=inv+{1}}, -- 09 + {ctype='unsigned short', label='_junk1'}, -- 0A 00 00 observed +} + +-- Examine Flowerpot +fields.outgoing[0x0FD] = L{ + {ctype='unsigned short', label='Flowerpot Item ID'}, -- 04 + {ctype='unsigned char', label='Flowerpot Safe Slot'}, -- 06 + {ctype='unsigned char', label='_junk1'}, -- 07 +} + +-- Uproot Flowerpot +fields.outgoing[0x0FE] = L{ + {ctype='unsigned short', label='Flowerpot Item', fn=item}, -- 04 + {ctype='unsigned char', label='Flowerpot Safe Index', fn=inv+{1}}, -- 06 + {ctype='unsigned char', label='_unknown1'}, -- 07 Value of 1 observed. +} + +-- Job Change +fields.outgoing[0x100] = L{ + {ctype='unsigned char', label='Main Job'}, -- 04 + {ctype='unsigned char', label='Sub Job'}, -- 05 + {ctype='unsigned char', label='_unknown1'}, -- 06 + {ctype='unsigned char', label='_unknown2'}, -- 07 +} + +-- Untraditional Equip +-- Currently only commented for changing instincts in Monstrosity. Refer to the doku wiki for information on Autos/BLUs. +-- https://gist.github.com/nitrous24/baf9980df69b3dc7d3cf +fields.outgoing[0x102] = L{ + {ctype='unsigned short', label='_unknown1'}, -- 04 -- 00 00 for Monsters + {ctype='unsigned short', label='_unknown1'}, -- 06 -- Varies by Monster family for the species change packet. Monsters that share the same tnl seem to have the same value. 00 00 for instinct changing. + {ctype='unsigned char', label='Main Job', fn=job}, -- 08 -- 00x17 for Monsters + {ctype='unsigned char', label='Sub Job', fn=job}, -- 09 -- 00x00 for Monsters + {ctype='unsigned short', label='Flag'}, -- 0A -- 04 00 for Monsters changing instincts. 01 00 for changing Monsters + {ctype='unsigned short', label='Species'}, -- 0C -- True both for species change and instinct change packets + {ctype='unsigned short', label='_unknown2'}, -- 0E -- 00 00 for Monsters + {ctype='unsigned short[12]',label='Instinct'}, -- 10 + {ctype='unsigned char', label='Name 1'}, -- 28 + {ctype='unsigned char', label='Name 2'}, -- 29 + {ctype='char*', label='_unknown'}, -- 2A -- All 00s for Monsters +} + +-- Open Bazaar +-- Sent when you open someone's bazaar from the /check window +fields.outgoing[0x105] = L{ + {ctype='unsigned int', label='Target', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 +} + +-- Bid Bazaar +-- Sent when you bid on an item in someone's bazaar +fields.outgoing[0x106] = L{ + {ctype='unsigned char', label='Inventory Index'}, -- 04 The seller's inventory index of the wanted item + {ctype='data[3]', label='_junk1'}, -- 05 + {ctype='unsigned int', label='Count'}, -- 08 +} + +-- Close own Bazaar +-- Sent when you close your bazaar window +fields.outgoing[0x109] = L{ +} + +-- Bazaar price set +-- Sent when you set the price of an item in your bazaar +fields.outgoing[0x10A] = L{ + {ctype='unsigned char', label='Inventory Index', fn=inv+{0}}, -- 04 + {ctype='data[3]', label='_junk1'}, -- 05 + {ctype='unsigned int', label='Price', fn=gil}, -- 08 +} + +-- Open own Bazaar +-- Sent when you attempt to open your bazaar to set prices +fields.outgoing[0x10B] = L{ + {ctype='unsigned int', label='_unknown1', const=0x00000000}, -- 04 00 00 00 00 for me +} + +-- Start RoE Quest +fields.outgoing[0x10C] = L{ + {ctype='unsigned short', label='RoE Quest'}, -- 04 This field is likely actually 12 bits +} + +-- Cancel RoE Quest +fields.outgoing[0x10D] = L{ + {ctype='unsigned short', label='RoE Quest'}, -- 04 This field is likely actually 12 bits +} + +-- Accept RoE Quest reward that was denied due to a full inventory +fields.outgoing[0x10E] = L{ + {ctype='unsigned short', label='RoE Quest'}, -- 04 This field is likely actually 12 bits +} + +-- Currency Menu +fields.outgoing[0x10F] = L{ +} + +enums['fishing'] = { + [2] = 'Cast rod', + [3] = 'Release/catch', + [4] = 'Put away rod', +} + +-- Fishing Action +fields.outgoing[0x110] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='Fish HP'}, -- 08 Always 200 when releasing, zero when casting and putting away rod + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C + {ctype='unsigned char', label='Action', fn=e+{'fishing'}}, -- 0E + {ctype='unsigned char', label='_unknown1'}, -- 0F Always zero (pre-March fishing update this value would increase over time, probably zone fatigue) + {ctype='unsigned int', label='Catch Key'}, -- 10 When catching this matches the catch key from the 0x115 packet, otherwise zero +} + +-- Lockstyle +fields.outgoing[0x111] = L{ + {ctype='bool', label='Lock'}, -- 04 0 = unlock, 1 = lock + {ctype='data[3]', label='_junk1'}, -- 05 +} + +-- ROE quest log request +fields.outgoing[0x112] = L{ + {ctype='int', label='_unknown1'}, -- 04 +} + +-- Homepoint Map Trigger :: 4 bytes, sent when entering a specific zone's homepoint list to cause maps to appear. +fields.outgoing[0x114] = L{ +} + +-- Currency 2 Menu +fields.outgoing[0x115] = L{ +} + +-- Open Unity Menu :: Two of these are sent whenever I open my unity menu. The first one has a bool of 0 and the second of 1. +fields.outgoing[0x116] = L{ + {ctype='bool', label='_unknown1'}, -- 04 + {ctype='char[3]', label='_unknown2'}, -- 05 +} + +-- Unity Ranking Results :: Sent when I open my Unity Ranking Results menu. Triggers a Sparks Update packet and may trigger ranking packets that I could not record. +fields.outgoing[0x117] = L{ + {ctype='int', label='_unknown2'}, -- 04 +} + +-- Open Chat status +fields.outgoing[0x118] = L{ + {ctype='bool', label='Chat Status'}, -- 04 0 for Inactive and 1 for Active + {ctype='char[3]', label='_unknown2'}, -- 05 +} + +types.job_level = L{ + {ctype='unsigned char', label='Level'}, -- 00 +} + +-- Zone update +fields.incoming[0x00A] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned short', label='Player Index', fn=index}, -- 08 + {ctype='unsigned char', label='_padding'}, -- 0A + {ctype='unsigned char', label='Heading', fn=dir}, -- 0B -- 0B to + {ctype='float', label='X'}, -- 0C + {ctype='float', label='Z'}, -- 10 + {ctype='float', label='Y'}, -- 14 + {ctype='unsigned short', label='Run Count'}, -- 18 + {ctype='unsigned short', label='Target Index', fn=index}, -- 1A + {ctype='unsigned char', label='Movement Speed'}, -- 1C 32 represents 100% + {ctype='unsigned char', label='Animation Speed'}, -- 1D 32 represents 100% + {ctype='unsigned char', label='HP %', fn=percent}, -- 1E + {ctype='unsigned char', label='Status', fn=statuses}, -- 1F + {ctype='data[16]', label='_unknown1'}, -- 20 + {ctype='unsigned short', label='Zone', fn=zone}, -- 30 + {ctype='data[6]', label='_unknown2'}, -- 32 + {ctype='unsigned int', label='Timestamp 1', fn=time}, -- 38 + {ctype='unsigned int', label='Timestamp 2', fn=time}, -- 3C + {ctype='unsigned short', label='_unknown3'}, -- 40 + {ctype='unsigned short', label='_dupeZone', fn=zone}, -- 42 + {ctype='unsigned char', label='Face'}, -- 44 + {ctype='unsigned char', label='Race'}, -- 45 + {ctype='unsigned short', label='Head'}, -- 46 + {ctype='unsigned short', label='Body'}, -- 48 + {ctype='unsigned short', label='Hands'}, -- 4A + {ctype='unsigned short', label='Legs'}, -- 4C + {ctype='unsigned short', label='Feet'}, -- 4E + {ctype='unsigned short', label='Main'}, -- 50 + {ctype='unsigned short', label='Sub'}, -- 52 + {ctype='unsigned short', label='Ranged'}, -- 54 + {ctype='unsigned short', label='Day Music'}, -- 56 + {ctype='unsigned short', label='Night Music'}, -- 58 + {ctype='unsigned short', label='Solo Combat Music'}, -- 5A + {ctype='unsigned short', label='Party Combat Music'}, -- 5C + {ctype='unsigned short', label='Mount Music'}, -- 5E + {ctype='data[2]', label='_unknown4'}, -- 60 + {ctype='unsigned short', label='Menu Zone'}, -- 62 Only set if the menu ID is sent, used as the zone for menu responses (0x5b, 0x5c) + {ctype='unsigned short', label='Menu ID'}, -- 64 + {ctype='unsigned short', label='_unknown5'}, -- 66 + {ctype='unsigned short', label='Weather', fn=weather}, -- 68 + {ctype='unsigned short', label='_unknown6'}, -- 6A + {ctype='data[24]', label='_unknown7'}, -- 6C + {ctype='char[16]', label='Player Name'}, -- 84 + {ctype='data[12]', label='_unknown8'}, -- 94 + {ctype='unsigned int', label='Abyssea Timestamp', fn=time}, -- A0 + {ctype='unsigned int', label='_unknown9', const=0x0003A020}, -- A4 + {ctype='data[2]', label='_unknown10'}, -- A8 + {ctype='unsigned short', label='Zone model'}, -- AA + {ctype='data[8]', label='_unknown11'}, -- AC 0xAC is 2 for some zones, 0 for others + {ctype='unsigned char', label='Main Job', fn=job}, -- B4 + {ctype='unsigned char', label='_unknown12'}, -- B5 + {ctype='unsigned char', label='_unknown13'}, -- B6 + {ctype='unsigned char', label='Sub Job', fn=job}, -- B7 + {ctype='unsigned int', label='_unknown14'}, -- B8 + {ref=types.job_level, lookup={res.jobs, 0x00}, count=0x10}, -- BC + {ctype='signed short', label='STR'}, -- CC + {ctype='signed short', label='DEX'}, -- CE + {ctype='signed short', label='VIT'}, -- D0 + {ctype='signed short', label='AGI'}, -- D2 + {ctype='signed short', label='IND'}, -- F4 + {ctype='signed short', label='MND'}, -- D6 + {ctype='signed short', label='CHR'}, -- D8 + {ctype='signed short', label='STR Bonus'}, -- DA + {ctype='signed short', label='DEX Bonus'}, -- DC + {ctype='signed short', label='VIT Bonus'}, -- DE + {ctype='signed short', label='AGI Bonus'}, -- E0 + {ctype='signed short', label='INT Bonus'}, -- E2 + {ctype='signed short', label='MND Bonus'}, -- E4 + {ctype='signed short', label='CHR Bonus'}, -- E6 + {ctype='unsigned int', label='Max HP'}, -- E8 + {ctype='unsigned int', label='Max MP'}, -- EC + {ctype='data[20]', label='_unknown15'}, -- F0 +} + +-- Zone Response +fields.incoming[0x00B] = L{ + {ctype='unsigned int', label='Type', fn=e+{'zone'}}, -- 04 + {ctype='unsigned int', label='IP', fn=ip}, -- 08 + {ctype='unsigned short', label='Port'}, -- 0C + {ctype='unsigned short', label='_unknown1'}, -- 10 + {ctype='unsigned short', label='_unknown2'}, -- 12 + {ctype='unsigned short', label='_unknown3'}, -- 14 + {ctype='unsigned short', label='_unknown4'}, -- 16 + {ctype='unsigned short', label='_unknown5'}, -- 18 + {ctype='unsigned short', label='_unknown6'}, -- 1A + {ctype='unsigned short', label='_unknown7'}, -- 1C +} + +-- PC Update +fields.incoming[0x00D] = L{ + -- The flags in this byte are complicated and may not strictly be flags. + -- Byte 0x20: -- Mentor is somewhere in this byte + -- 01 = None + -- 02 = Deletes everyone + -- 04 = Deletes everyone + -- 08 = None + -- 16 = None + -- 32 = None + -- 64 = None + -- 128 = None + + + -- Byte 0x21: + -- 01 = None + -- 02 = None + -- 04 = None + -- 08 = LFG + -- 16 = Anon + -- 32 = Turns your name orange + -- 64 = Away + -- 128 = None + + -- Byte 0x22: + -- 01 = POL Icon, can target? + -- 02 = no notable effect + -- 04 = DCing + -- 08 = Untargettable + -- 16 = No linkshell + -- 32 = No Linkshell again + -- 64 = No linkshell again + -- 128 = No linkshell again + + -- Byte 0x23: + -- 01 = Trial Account + -- 02 = Trial Account + -- 04 = GM Mode + -- 08 = None + -- 16 = None + -- 32 = Invisible models + -- 64 = None + -- 128 = Bazaar + + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned short', label='Index', fn=index}, -- 08 + {ctype='boolbit', label='Update Position'}, -- 0A:0 Position, Rotation, Target, Speed + {ctype='boolbit', label='Update Status'}, -- 1A:1 Not used for 0x00D + {ctype='boolbit', label='Update Vitals'}, -- 0A:2 HP%, Status, Flags, LS color, "Face Flags" + {ctype='boolbit', label='Update Name'}, -- 0A:3 Name + {ctype='boolbit', label='Update Model'}, -- 0A:4 Race, Face, Gear models + {ctype='boolbit', label='Despawn'}, -- 0A:5 Only set if player runs out of range or zones + {ctype='boolbit', label='_unknown1'}, -- 0A:6 + {ctype='boolbit', label='_unknown2'}, -- 0A:6 + {ctype='unsigned char', label='Heading', fn=dir}, -- 0B + {ctype='float', label='X'}, -- 0C + {ctype='float', label='Z'}, -- 10 + {ctype='float', label='Y'}, -- 14 + {ctype='bit[13]', label='Run Count'}, -- 18:0 Analogue to Run Count from outgoing 0x015 + {ctype='bit[3]', label='_unknown3'}, -- 19:5 Analogue to Run Count from outgoing 0x015 + {ctype='boolbit', label='_unknown4'}, -- 1A:0 + {ctype='bit[15]', label='Target Index', fn=index}, -- 1A:1 + {ctype='unsigned char', label='Movement Speed'}, -- 1C 32 represents 100% + {ctype='unsigned char', label='Animation Speed'}, -- 1D 32 represents 100% + {ctype='unsigned char', label='HP %', fn=percent}, -- 1E + {ctype='unsigned char', label='Status', fn=statuses}, -- 1F + {ctype='unsigned int', label='Flags', fn=bin+{4}}, -- 20 + {ctype='unsigned char', label='Linkshell Red'}, -- 24 + {ctype='unsigned char', label='Linkshell Green'}, -- 25 + {ctype='unsigned char', label='Linkshell Blue'}, -- 26 + {ctype='unsigned char', label='_flags1'}, -- 27 0x80 Autogroup flag + {ctype='unsigned char', label='_flags2'}, -- 28 0x01 puts your weapon on hand, 0x02 Request flag, + {ctype='unsigned char', label='PvP Stuff'}, -- 29 Same pattern than incoming 0x037 packet + {ctype='unsigned char', label='_flags3'}, -- 2A 0x20 Sneak Effect flag, 0x80 New Adventurer flag + {ctype='unsigned char', label='_flags4'}, -- 2B 0x01 Mentor flag + {ctype='data[4]', label='_unknown6'}, -- 2C + {ctype='unsigned short', label='Costume'}, -- 30 ID of the Model + {ctype='data[1]', label='_unknown7'}, -- 32 + {ctype='unsigned char', label='_flags5'}, -- 33 0x02 Trial Account flag, 0x40 Job Master Stars flag + {ctype='unsigned int', label='_unknown8'}, -- 34 Related to mounts + {ctype='data[4]', label='_unknown9'}, -- 38 + {ctype='unsigned short', label='Pet Index', fn=index}, -- 3C + {ctype='unsigned short', label='Monstrosity Species'}, -- 3E High bit is always set while in monstrosity and determines the display of the third name + {ctype='unsigned char', label='Monstrosity Name 1'}, -- 40 + {ctype='unsigned char', label='Monstrosity Name 2'}, -- 41 + {ctype='unsigned char', label='Indi Bubble'}, -- 42 Geomancer (GEO) Indi spell effect on players. 0 is no effect. + {ctype='unsigned char', label='Face Flags'}, -- 43 0, 3, 4, or 8 + {ctype='bit[4]', label='_unknown10'}, -- 44 + {ctype='bit[8]', label='Mount'}, -- 44 Related to Mounts, seems to be mount id + 1, except for chocobo. The value doesn't get zeroed after dismount + {ctype='bit[20]', label='_unknown11'}, -- 45 + {ctype='unsigned char', label='Face'}, -- 48 + {ctype='unsigned char', label='Race'}, -- 49 + {ctype='unsigned short', label='Head'}, -- 4A + {ctype='unsigned short', label='Body'}, -- 4C + {ctype='unsigned short', label='Hands'}, -- 4E + {ctype='unsigned short', label='Legs'}, -- 50 + {ctype='unsigned short', label='Feet'}, -- 52 + {ctype='unsigned short', label='Main'}, -- 54 + {ctype='unsigned short', label='Sub'}, -- 56 + {ctype='unsigned short', label='Ranged'}, -- 58 + {ctype='char*', label='Character Name'}, -- 5A - * +} + +-- NPC Update +-- There are two different types of these packets. One is for regular NPCs, the other occurs for certain NPCs (often nameless) and differs greatly in structure. +-- The common fields seem to be the ID, Index, mask and _unknown3. +-- The second one seems to have an int counter at 0x38 that increases by varying amounts every time byte 0x1F changes. +-- Currently I don't know how to algorithmically distinguish when the packets are different. + +-- Mask values (from antiquity): +-- 0x01: "Basic" +-- 0x02: Status +-- 0x04: HP +-- 0x08: Name +-- 0x10: "Bit 4" +-- 0x20: "Bit 5" +-- 0x40: "Bit 6" +-- 0x80: "Bit 7" + + +-- Status flags (from antiquity): +-- 0b00100000 = CFH Bit +-- 0b10000101 = "Normal_Status?" +fields.incoming[0x00E] = L{ + {ctype='unsigned int', label='NPC', fn=id}, -- 04 + {ctype='unsigned short', label='Index', fn=index}, -- 08 + {ctype='unsigned char', label='Mask', fn=bin+{1}}, -- 0A Bits that control which parts of the packet are actual updates (rest is zeroed). Model is always sent + -- 0A Bit 0: Position, Rotation, Walk Count + -- 0A Bit 1: Claimer ID + -- 0A Bit 2: HP, Status + -- 0A Bit 3: Name + -- 0A Bit 4: + -- 0A Bit 5: The client stops displaying the mob when this bit is set (dead, out of range, etc.) + -- 0A Bit 6: + -- 0A Bit 7: + {ctype='unsigned char', label='Rotation', fn=dir}, -- 0B + {ctype='float', label='X'}, -- 0C + {ctype='float', label='Z'}, -- 10 + {ctype='float', label='Y'}, -- 14 + {ctype='unsigned int', label='Walk Count'}, -- 18 Steadily increases until rotation changes. Does not reset while the mob isn't walking. Only goes until 0xFF1F. + {ctype='unsigned short', label='_unknown1', fn=bin+{2}}, -- 1A + {ctype='unsigned char', label='HP %', fn=percent}, -- 1E + {ctype='unsigned char', label='Status', fn=statuses}, -- 1F Status used to be 0x20 + {ctype='unsigned int', label='_unknown2', fn=bin+{4}}, -- 20 + {ctype='unsigned int', label='_unknown3', fn=bin+{4}}, -- 24 + {ctype='unsigned int', label='_unknown4', fn=bin+{4}}, -- 28 In Dynamis - Divergence statue's eye colors + {ctype='unsigned int', label='Claimer', fn=id}, -- 2C + {ctype='unsigned short', label='_unknown5'}, -- 30 + {ctype='unsigned short', label='Model'}, -- 32 + {ctype='char*', label='Name'}, -- 34 - * +} + +enums['mentor icon'] = { + [0] = 'None', + [1] = 'Bronze', + [2] = 'Silver', + [3] = 'Gold' +} + +func.incoming[0x017] = {} +func.incoming[0x017].base = L{ + {ctype='unsigned char', label='Mode', fn=chat}, -- 04 +} +func.incoming[0x017].default = L{ + {ctype='bool', label='GM'}, -- 05 + {ctype='unsigned short', label='_padding1',}, -- 06 Reserved for Yell and Assist Modes + {ctype='char[0xF]', label='Sender Name'}, -- 08 + {ctype='char*', label='Message'}, -- 17 Max of 150 characters +} +func.incoming[0x017][0x1A] = L{ -- Yell + {ctype='bool', label='GM'}, -- 05 + {ctype='unsigned short', label='Zone', fn=zone}, -- 06 Zone ID of sender + {ctype='char[0xF]', label='Sender Name'}, -- 08 + {ctype='char*', label='Message'}, -- 17 Max of 150 characters +} +func.incoming[0x017][0x22] = L{ -- AssistJ + {ctype='bool', label='GM'}, -- 05 + {ctype='unsigned char', label='Mastery Rank'}, -- 06 Sender Mastery Rank + {ctype='unsigned char', label='Mentor Icon', fn=e+{'mentor icon'}},-- 07 Color of Mentor Flag + {ctype='char[0xF]', label='Sender Name'}, -- 08 + {ctype='char*', label='Message'}, -- 17 Max of 150 characters +} +func.incoming[0x017][0x23] = func.incoming[0x017][0x22] -- AssistE + +-- Incoming Chat +fields.incoming[0x017] = function() + local fields = func.incoming[0x017] + + return function(data, type) + return fields.base + (fields[type or data:byte(5)] or fields.default) + end +end() + +types.job_master= L{ + {ctype='boolbit', label='Master'} +} +types.job_master_level= L{ + {ctype='unsigned char', label='Master Level'} +} + +-- Job Info +fields.incoming[0x01B] = L{ + {ctype='unsigned int', label='_unknown1'}, -- 04 Observed value of 05 + {ctype='unsigned char', label='Main Job', fn=job}, -- 08 + {ctype='unsigned char', label='Flag or Main Job Level?'}, -- 09 + {ctype='unsigned char', label='Flag or Sub Job Level?'}, -- 0A + {ctype='unsigned char', label='Sub Job', fn=job}, -- 0B + {ctype='bit[32]', label='Sub/Job Unlock Flags'}, -- 0C Indicate whether subjob is unlocked and which jobs are unlocked. lsb of 0x0C indicates subjob unlock. + {ctype='unsigned char', label='_unknown3'}, -- 10 Flag or List Start + {ref=types.job_level, lookup={res.jobs, 0x01}, count=0x0F}, -- 11 + {ctype='unsigned short', label='Base STR'}, -- 20 -- Altering these stat values has no impact on your equipment menu. + {ctype='unsigned short', label='Base DEX'}, -- 22 + {ctype='unsigned short', label='Base VIT'}, -- 24 + {ctype='unsigned short', label='Base AGI'}, -- 26 + {ctype='unsigned short', label='Base INT'}, -- 28 + {ctype='unsigned short', label='Base MND'}, -- 2A + {ctype='unsigned short', label='Base CHR'}, -- 2C + {ctype='data[14]', label='_unknown4'}, -- 2E Flags and junk? Hard to say. All 0s observed. + {ctype='unsigned int', label='Maximum HP'}, -- 3C + {ctype='unsigned int', label='Maximum MP'}, -- 40 + {ctype='unsigned int', label='Flags'}, -- 44 Looks like a bunch of flags. Observed value if 01 00 00 00 + {ctype='unsigned char', label='_unknown5'}, -- 48 Potential flag to signal the list start. Observed value of 01 + {ref=types.job_level, lookup={res.jobs, 0x01}, count=0x16}, -- 49 + {ctype='unsigned char', label='Current Monster Level'}, -- 5F + {ctype='unsigned int', label='Encumbrance Flags'}, -- 60 [legs, hands, body, head, ammo, range, sub, main,] [back, right_ring, left_ring, right_ear, left_ear, waist, neck, feet] [HP, CHR, MND, INT, AGI, VIT, DEX, STR,] [X X X X X X X MP] + {ctype='unsigned char', label='_unknown7'}, -- 64 + {ctype='unsigned char', label='Mentor Icon', fn=e+{'mentor icon'}},-- 65 + {ctype='unsigned char', label='Mastery Rank'}, -- 66 + {ctype='unsigned char', label='_unknown8'}, -- 67 + {ctype='bit[1]', label='_junk1'}, -- 68 + {ref=types.job_master, lookup={res.jobs, 0x01}, count=0x16}, -- 68 Indicates if the job is mastered, but only after receiving "Master Breaker" KI. Used to populate "Master Levels" Menu + {ctype='bit[1]', label='_junk2'}, -- 6A + {ctype='unsigned short', label='_junk3'}, -- 6B + {ref=types.job_master_level,lookup={res.jobs, 0x01}, count=0x16}, -- 6D +} + +-- Inventory Count +-- It is unclear why there are two representations of the size for this. +-- I have manipulated my inventory size on a mule after the item update packets have +-- all arrived and still did not see any change in the second set of sizes, so they +-- may not be max size/used size chars as I initially assumed. Adding them as shorts +-- for now. +-- There appears to be space for another 8 bags. +fields.incoming[0x01C] = L{ + {ctype='unsigned char', label='Inventory Size'}, -- 04 + {ctype='unsigned char', label='Safe Size'}, -- 05 + {ctype='unsigned char', label='Storage Size'}, -- 06 + {ctype='unsigned char', label='Temporary Size'}, -- 07 + {ctype='unsigned char', label='Locker Size'}, -- 08 + {ctype='unsigned char', label='Satchel Size'}, -- 09 + {ctype='unsigned char', label='Sack Size'}, -- 0A + {ctype='unsigned char', label='Case Size'}, -- 0B + {ctype='unsigned char', label='Wardrobe Size'}, -- 0C + {ctype='unsigned char', label='Safe 2 Size'}, -- 0D + {ctype='unsigned char', label='Wardrobe 2 Size'}, -- 0E + {ctype='unsigned char', label='Wardrobe 3 Size'}, -- 0F + {ctype='unsigned char', label='Wardrobe 4 Size'}, -- 10 + {ctype='data[19]', label='_padding1', const=''}, -- 11 + {ctype='unsigned short', label='_dupeInventory Size'}, -- 24 These "dupe" sizes are set to 0 if the inventory disabled. + {ctype='unsigned short', label='_dupeSafe Size'}, -- 26 + {ctype='unsigned short', label='_dupeStorage Size'}, -- 28 The accumulated storage from all items (uncapped) -1 + {ctype='unsigned short', label='_dupeTemporary Size'}, -- 2A + {ctype='unsigned short', label='_dupeLocker Size'}, -- 2C + {ctype='unsigned short', label='_dupeSatchel Size'}, -- 2E + {ctype='unsigned short', label='_dupeSack Size'}, -- 30 + {ctype='unsigned short', label='_dupeCase Size'}, -- 32 + {ctype='unsigned short', label='_dupeWardrobe Size'}, -- 34 + {ctype='unsigned short', label='_dupeSafe 2 Size'}, -- 36 + {ctype='unsigned short', label='_dupeWardrobe 2 Size'}, -- 38 + {ctype='unsigned short', label='_dupeWardrobe 3 Size'}, -- 3A This is not set to 0 despite being disabled for whatever reason + {ctype='unsigned short', label='_dupeWardrobe 4 Size'}, -- 3C This is not set to 0 despite being disabled for whatever reason + {ctype='data[22]', label='_padding2', const=''}, -- 3E +} + +-- Finish Inventory +fields.incoming[0x01D] = L{ + {ctype='unsigned char', label='Flag', const=0x01}, -- 04 + {ctype='data[3]', label='_junk1'}, -- 06 +} + +-- Modify Inventory +fields.incoming[0x01E] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned char', label='Bag', fn=bag}, -- 08 + {ctype='unsigned char', label='Index', fn=inv+{0}}, -- 09 + {ctype='unsigned char', label='Status', fn=e+{'itemstat'}}, -- 0A + {ctype='unsigned char', label='_junk1'}, -- 0B +} + +-- Item Assign +fields.incoming[0x01F] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='Bag', fn=bag}, -- 0A + {ctype='unsigned char', label='Index', fn=invp+{0x0A}}, -- 0B + {ctype='unsigned char', label='Status', fn=e+{'itemstat'}}, -- 0C +} + +-- Item Updates +fields.incoming[0x020] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned int', label='Bazaar', fn=gil}, -- 08 + {ctype='unsigned short', label='Item', fn=item}, -- 0C + {ctype='unsigned char', label='Bag', fn=bag}, -- 0E + {ctype='unsigned char', label='Index', fn=invp+{0x0E}}, -- 0F + {ctype='unsigned char', label='Status', fn=e+{'itemstat'}}, -- 10 + {ctype='data[24]', label='ExtData', fn='...':fn()}, -- 11 + {ctype='data[3]', label='_junk1'}, -- 29 +} + +-- Trade request received +fields.incoming[0x021] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned short', label='Index', fn=index}, -- 08 + {ctype='unsigned short', label='_junk1'}, -- 0A +} + +-- Trade request sent +enums['trade'] = { + [0] = 'Trade started', + [1] = 'Trade canceled', + [2] = 'Trade accepted by other party', + [9] = 'Trade successful', +} +fields.incoming[0x022] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='Type', fn=e+{'trade'}}, -- 08 + {ctype='unsigned short', label='Index', fn=index}, -- 0C + {ctype='unsigned short', label='_junk1'}, -- 0E +} + +-- Trade item, other party +fields.incoming[0x023] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='Trade Count'}, -- 08 Seems to increment every time packet 0x023 comes in, i.e. every trade action performed by the other party + {ctype='unsigned short', label='Item', fn=item}, -- 0A If the item is removed, gil is used with a count of zero + {ctype='unsigned char', label='_unknown1', const=0x05}, -- 0C Possibly junk? + {ctype='unsigned char', label='Slot'}, -- 0D Gil itself is in slot 0, whereas the other slots start at 1 and count up horizontally + {ctype='data[24]', label='ExtData'}, -- 0E + {ctype='data[2]', label='_junk1'}, -- 26 +} + +-- Trade item, self +fields.incoming[0x025] = L{ + {ctype='unsigned int', label='Count'}, -- 04 + {ctype='unsigned short', label='Item', fn=item}, -- 08 If the item is removed, gil is used with a count of zero + {ctype='unsigned char', label='Slot'}, -- 0A Gil itself is in slot 0, whereas the other slots start at 1 and count up horizontally + {ctype='unsigned char', label='Inventory Index', fn=inv+{0}}, -- 0B +} + +-- Count to 80 +-- Sent after Item Update chunks for active inventory (sometimes) when zoning. +fields.incoming[0x026] = L{ + {ctype='data[1]', label='_unknown1', const=0x00}, -- 04 + {ctype='unsigned char', label='Slot'}, -- 05 Corresponds to the slot IDs of the previous incoming packet's Item Update chunks for active Inventory. + {ctype='data[22]', label='_unknown2', const=0}, -- 06 +} + +-- String Message +fields.incoming[0x027] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 0x0112413A in Omen, 0x010B7083 in Legion, Layer Reserve ID for Ambuscade queue, 0x01046062 for Chocobo circuit + {ctype='unsigned short', label='Player Index', fn=index}, -- 08 0x013A in Omen, 0x0083 in Legion , Layer Reserve Index for Ambuscade queue, 0x0062 for Chocobo circuit + {ctype='unsigned short', label='Message ID', fn=sub+{0x8000}}, -- 0A -0x8000 + {ctype='unsigned int', label='Type'}, -- 0C 0x04 for Fishing/Salvage, 0x05 for Omen/Legion/Ambuscade queue/Chocobo Circuit + {ctype='unsigned int', label='Param 1'}, -- 10 Parameter 0 on the display messages dat files + {ctype='unsigned int', label='Param 2'}, -- 14 Parameter 1 on the display messages dat files + {ctype='unsigned int', label='Param 3'}, -- 18 Parameter 2 on the display messages dat files + {ctype='unsigned int', label='Param 4'}, -- 1C Parameter 3 on the display messages dat files + {ctype='char[16]', label='Player Name'}, -- 20 + {ctype='data[16]', label='_unknown6'}, -- 30 + {ctype='char[16]', label='_dupePlayer Name'}, -- 40 + {ctype='data[32]', label='_unknown7'}, -- 50 +} + +-- Action +func.incoming[0x028] = {} +fields.incoming[0x028] = function() + local self = func.incoming[0x028] + + -- start and length are both in bits + local extract = function(data, start, length) + return data:unpack('b' .. length, (start / 8):floor() + 1, start % 8 + 1) + end + + -- All values here are in bits + local add_effect_offset = 85 + local add_effect_size = 37 + local spike_effect_size = 34 + local add_action = function(data, pos) + action = L{} + action:extend(self.action_base) + + action:extend(self.add_effect_base) + pos = pos + add_effect_offset + local has_add_effect = extract(data, pos, 1) == 1 + pos = pos + 1 + if has_add_effect then + action:extend(self.add_effect_body) + pos = pos + add_effect_size + end + + action:extend(self.spike_effect_base) + local has_spike_effect = extract(data, pos, 1) == 1 + pos = pos + 1 + if has_spike_effect then + action:extend(self.spike_effect_body) + pos = pos + spike_effect_size + end + + return action, pos + end + + local action_count_offset = 32; + local add_target = function(data, pos) + local target = L{} + target:extend(self.target_base:copy()) + + pos = pos + action_count_offset + local action_count = extract(data, pos, 4) + pos = pos + 4 + for i = 1, action_count do + local action + action, pos = add_action(data, pos) + + action = action:copy():map(function(field) + field.label = 'Action %u %s':format(i, field.label) + return field + end) + target:extend(action) + end + + return target, pos + end + + local target_count_offset = 72 + local first_target_offset = 150 + return function(data) + local fields = self.base:copy() + local target_count = extract(data, target_count_offset, 10) + pos = first_target_offset + for i = 1, target_count do + local target + target, pos = add_target(data, pos) + + target = target:copy():map(function(field) + field.label = 'Target %u %s':format(i, field.label) + return field + end) + fields:extend(target) + end + + return fields + end +end() + +enums.action_in = { + [1] = 'Melee attack', + [2] = 'Ranged attack finish', + [3] = 'Weapon Skill finish', + [4] = 'Casting finish', + [5] = 'Item finish', + [6] = 'Job Ability', + [7] = 'Weapon Skill start', + [8] = 'Casting start', + [9] = 'Item start', + [11] = 'NPC TP finish', + [12] = 'Ranged attack start', + [13] = 'Avatar TP finish', + [14] = 'Job Ability DNC', + [15] = 'Job Ability RUN', +} + +func.incoming[0x028].base = L{ + {ctype='unsigned char', label='Size'}, -- 04 + {ctype='unsigned int', label='Actor', fn=id}, -- 05 + {ctype='bit[10]', label='Target Count'}, -- 09:0 + {ctype='bit[4]', label='Category', fn=e+{'action_in'}},-- 0A:2 + {ctype='bit[16]', label='Param'}, -- 0C:6 + {ctype='bit[16]', label='_unknown1'}, -- 0E:6 + {ctype='bit[32]', label='Recast'}, -- 10:6 +} + +func.incoming[0x028].target_base = L{ + {ctype='bit[32]', label='ID', fn=id}, -- 00:0 + {ctype='bit[4]', label='Action Count'}, -- 04:0 +} + +func.incoming[0x028].action_base = L{ + {ctype='bit[5]', label='Reaction'}, -- 00:0 + {ctype='bit[12]', label='Animation'}, -- 00:5 + {ctype='bit[4]', label='Effect'}, -- 02:1 Particle effects: bit 1 finishing blow, bit 2 critical hit, bit 3 hit not connected, bit 4 effect follow up (I have only seen in jishnu's radiance) + {ctype='bit[3]', label='Stagger'}, -- 02:5 head moving animation when getting hit, the value seems to be set based on dmg done, more dmg more bits sets (not sure if raw or percentage) + {ctype='bit[3]', label='Knockback'}, -- 03:0 Knockback effect, the more value the more distance + {ctype='bit[17]', label='Param'}, -- 03:3 + {ctype='bit[10]', label='Message'}, -- 06:2 + {ctype='bit[31]', label='_unknown'}, -- 07:4 --Message Modifier? If you get a complete (Resist!) this is set to 2 otherwise a regular Resist is 0. +} + +func.incoming[0x028].add_effect_base = L{ + {ctype='boolbit', label='Has Added Effect'}, -- 00:0 +} + +func.incoming[0x028].add_effect_body = L{ + {ctype='bit[6]', label='Added Effect Animation'}, -- 00:0 + {ctype='bit[4]', label='Added Effect Effect'}, -- 00:6 + {ctype='bit[17]', label='Added Effect Param'}, -- 01:2 + {ctype='bit[10]', label='Added Effect Message'}, -- 04:1 +} + +func.incoming[0x028].spike_effect_base = L{ + {ctype='boolbit', label='Has Spike Effect'}, -- 00:0 +} + +func.incoming[0x028].spike_effect_body = L{ + {ctype='bit[6]', label='Spike Effect Animation'}, -- 00:0 + {ctype='bit[4]', label='Spike Effect Effect'}, -- 00:6 + {ctype='bit[14]', label='Spike Effect Param'}, -- 01:2 + {ctype='bit[10]', label='Spike Effect Message'}, -- 03:0 +} + +-- Action Message +fields.incoming[0x029] = L{ + {ctype='unsigned int', label='Actor', fn=id}, -- 04 + {ctype='unsigned int', label='Target', fn=id}, -- 08 + {ctype='unsigned int', label='Param 1'}, -- 0C + {ctype='unsigned int', label='Param 2'}, -- 10 + {ctype='unsigned short', label='Actor Index', fn=index}, -- 14 + {ctype='unsigned short', label='Target Index', fn=index}, -- 16 + {ctype='unsigned short', label='Message'}, -- 18 + {ctype='unsigned short', label='_unknown1'}, -- 1A +} + + +--[[ 0x2A can be triggered by knealing in the right areas while in the possession of a VWNM KI: + Field1 will be lights level: + 0 = 'Tier 1', -- faintly/feebly depending on whether it's outside of inside Abyssea + 1 = 'Tier 2', -- softly + 2 = 'Tier 3', -- solidly. Highest Tier in Abyssea + 3 = 'Tier 4', --- strongly + 4 = 'Tier 5', -- very strongly. Unused currently + 5 = 'Tier 6', --- furiously. Unused currently + - But if there are no mobs left in area, or no mobs nearby, field1 will be the KeyItem# + 1253 = 'Clear Abyssite' + 1254 = 'Colorful Abyssite' + 1564 = 'Clear Demilune Abyssite' + etc. + + Field2 will be direction: + 0 = 'East' + 1 = 'Southeast' + 2 = 'South' + 3 = 'Southwest' + 4 = 'West' + 5 = 'Northwest' + 6 = 'North' + 7 = 'Northeast' + + Field3 will be distance. When there are no mobs, this value is set to 300000 + + Field4 will be KI# of the abyssite used. Ex: + 1253 = 'Clear Abyssite' + 1254 = 'Colorful Abyssite' + 1564 = 'Clear Demilune Abyssite' + etc. +]] + +--[[ 0x2A can also be triggered by buying/disposing of a VWNM KI from an NPC: + Index/ID field will be those of the NPC + Field1 will be 1000 (gil) when acquiring in Jueno, 300 (cruor) when acquiring in Abyssea + Field2 will be the KI# acquired + Fields are used slighly different when dropping the KI using the NPC. +]] + +--[[ 0x2A can also be triggered by spending cruor by buying non-vwnm related items, or even activating/using Flux + Field1 will be the amount of cruor spent +]] + + +--[[ 0x2A can also be triggered by zoning into Abyssea: + Field1 will be set to your remaining time. 5 at first, then whatever new value when acquiring visiting status. + 0x2A will likely be triggered as well when extending your time limit. Needs verification. +]] + + +--[[ 0x2A can be triggered sometimes when zoning into non-Abyssea: + Not sure what it means. +]] + +-- Resting Message +fields.incoming[0x02A] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='Param 1'}, -- 08 + {ctype='unsigned int', label='Param 2'}, -- 0C + {ctype='unsigned int', label='Param 3'}, -- 10 + {ctype='unsigned int', label='Param 4'}, -- 14 + {ctype='unsigned short', label='Player Index', fn=index}, -- 18 + {ctype='unsigned short', label='Message ID'}, -- 1A The high bit is occasionally set, though the reason for it is unclear. + {ctype='unsigned int', label='_unknown1'}, -- 1C Possibly flags, 0x06000000 and 0x02000000 observed +} + +-- Kill Message +-- Updates EXP gained, RoE messages, Limit Points, and Capacity Points +fields.incoming[0x02D] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='Target', fn=id}, -- 08 Player ID in the case of RoE log updates + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C + {ctype='unsigned short', label='Target Index', fn=index}, -- 0E Player Index in the case of RoE log updates + {ctype='unsigned int', label='Param 1'}, -- 10 EXP gained, etc. Numerator for RoE objectives + {ctype='unsigned int', label='Param 2'}, -- 14 Denominator for RoE objectives + {ctype='unsigned short', label='Message'}, -- 18 + {ctype='unsigned short', label='_flags1'}, -- 1A This could also be a third parameter, but I suspect it is flags because I have only ever seen one bit set. +} + +-- Mog House Menu +fields.incoming[0x02E] = L{} -- Seems to contain no fields. Just needs to be sent to client to open. + +-- Digging Animation +fields.incoming[0x02F] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned short', label='Player Index', fn=index}, -- 08 + {ctype='unsigned char', label='Animation'}, -- 0A Changing it to anything other than 1 eliminates the animation + {ctype='unsigned char', label='_junk1'}, -- 0B Likely junk. Has no effect on anything notable. +} + +-- Synth Animation +fields.incoming[0x030] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned short', label='Player Index', fn=index}, -- 08 + {ctype='unsigned short', label='Effect'}, -- 0A -- 10 00 is water, 11 00 is wind, 12 00 is fire, 13 00 is earth, 14 00 is lightning, 15 00 is ice, 16 00 is light, 17 00 is dark + {ctype='unsigned char', label='Param'}, -- 0C -- 00 is NQ, 01 is break, 02 is HQ + {ctype='unsigned char', label='Animation'}, -- 0D -- Always C2 for me. + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 0E -- Appears to just be trash. +} + +-- Synth List / Synth Recipe +--[[ This packet is used for list of recipes, but also for details of a specific recipe. + + If you ask the guild NPC that provides regular Image Suppor for recipes, + s/he will give you a list of recipes, fields are as follows: + Field1-2: NPC ID + Field3: NPC Index + Field4-6: Unknown + Field7-22: Item ID of recipe + Field23: Unknown + Field24: Usually Item ID of the recipe on next page + + + If you ask a guild NPC for a specific recipe, fields are as follows: + field1: item to make (item id) + field2,3,4: sub-crafts needed. Note that main craft will not be listed. + 1 = woodworking + 2 = smithing + 3 = goldsmithing + 4 = clothcraft + 5 = leatherworking + 6 = bonecraft + 7 = Alchemy + 8 = Cooking + field5: crystal (item id) + field6: KeyItem needed, if any (in Big Endian) + field7-14: material required (item id) + field15-22: qty for each material above. + field23-24: Unknown + ]] +fields.incoming[0x031] = L{ + {ctype='unsigned short[24]', label='Field'}, -- 04 +} + +-- NPC Interaction Type 1 +fields.incoming[0x032] = L{ + {ctype='unsigned int', label='NPC', fn=id}, -- 04 + {ctype='unsigned short', label='NPC Index', fn=index}, -- 08 + {ctype='unsigned short', label='Zone', fn=zone}, -- 0A + {ctype='unsigned short', label='Menu ID'}, -- 0C Seems to select between menus within a zone + {ctype='unsigned short', label='_unknown1'}, -- 0E 00 for me + {ctype='unsigned char', label='_dupeZone', fn=zone}, -- 10 + {ctype='data[3]', label='_junk1'}, -- 11 Always 00s for me +} + +-- String NPC Interaction +fields.incoming[0x033] = L{ + {ctype='unsigned int', label='NPC', fn=id}, -- 04 + {ctype='unsigned short', label='NPC Index', fn=index}, -- 08 + {ctype='unsigned short', label='Zone', fn=zone}, -- 0A + {ctype='unsigned short', label='Menu ID'}, -- 0C Seems to select between menus within a zone + {ctype='unsigned short', label='_unknown1'}, -- 0E 00 00 or 08 00 for me + {ctype='char[16]', label='NPC Name'}, -- 10 + {ctype='char[16]', label='_dupeNPC Name1'}, -- 20 + {ctype='char[16]', label='_dupeNPC Name2'}, -- 30 + {ctype='char[16]', label='_dupeNPC Name3'}, -- 40 + {ctype='char[32]', label='Menu Parameters'}, -- 50 The way this information is interpreted varies by menu. +} + +-- NPC Interaction Type 2 +fields.incoming[0x034] = L{ + {ctype='unsigned int', label='NPC', fn=id}, -- 04 + {ctype='data[32]', label='Menu Parameters'}, -- 08 + {ctype='unsigned short', label='NPC Index', fn=index}, -- 28 + {ctype='unsigned short', label='Zone', fn=zone}, -- 2A + {ctype='unsigned short', label='Menu ID'}, -- 2C Seems to select between menus within a zone + {ctype='unsigned short', label='_unknown1'}, -- 2E Ususually 8, but often not for newer menus + {ctype='unsigned short', label='_dupeZone', fn=zone}, -- 30 + {ctype='data[2]', label='_junk1'}, -- 31 Always 00s for me +} + +--- When messages are fishing related, the player is the Actor. +--- For some areas, the most significant bit of the message ID is set sometimes. +-- NPC Chat +fields.incoming[0x036] = L{ + {ctype='unsigned int', label='Actor', fn=id}, -- 04 + {ctype='unsigned short', label='Actor Index', fn=index}, -- 08 + {ctype='bit[15]', label='Message ID'}, -- 0A + {ctype='bit', label='_unknown1'}, -- 0B + {ctype='unsigned int', label='_unknown2'}, -- 0C Probably junk +} + +enums.indi = { + [0x5F] = 'Enemy Dark', + [0x5E] = 'Enemy Light', + [0x5D] = 'Enemy Water', + [0x5C] = 'Enemy Lightning', + [0x5B] = 'Enemy Earth', + [0x5A] = 'Enemy Wind', + [0x59] = 'Enemy Ice', + [0x58] = 'Enemy Fire', + [0x57] = 'Party Dark', + [0x56] = 'Party Light', + [0x55] = 'Party Water', + [0x54] = 'Party Lightning', + [0x53] = 'Party Earth', + [0x52] = 'Party Wind', + [0x51] = 'Party Ice', + [0x50] = 'Party Fire', + [0x00] = 'None', +} + +-- Player update +-- Buff IDs go can over 0xFF, but in the packet each buff only takes up one byte. +-- To address that there's a 8 byte bitmask starting at 0x4C where each 2 bits +-- represent how much to add to the value in the respective byte. + +--[[ _flags1: The structure here looks similar to byte 0x33 of 0x00D, but left shifted by 1 bit + -- 0x0001 -- Despawns your character + -- 0x0002 -- Also despawns your character, and may trigger an outgoing packet to the server (which triggers an incoming 0x037 packet) + -- 0x0004 -- No obvious effect + -- 0x0008 -- No obvious effect + -- 0x0010 -- LFG flag + -- 0x0020 -- /anon flag - blue name + -- 0x0040 -- orange name? + -- 0x0080 -- Away flag + -- 0x0100 -- No obvious effect + -- 0x0200 -- No obvious effect + -- 0x0400 -- No obvious effect + -- 0x0800 -- No obvious effect + -- 0x1000 -- No obvious effect + -- 0x2000 -- No obvious effect + -- 0x4000 -- No obvious effect + -- 0x8000 -- No obvious effect + + _flags2: + -- 0x01 -- POL Icon :: Actually a flag, overrides everything else but does not affect name color + -- 0x02 -- No obvious effect + -- 0x04 -- Disconnection icon :: Actually a flag, overrides everything but POL Icon + -- 0x08 -- No linkshell + -- 0x0A -- No obvious effect + + -- 0x10 -- No linkshell + -- 0x20 -- Trial account icon + -- 0x40 -- Trial account icon + -- 0x60 -- POL Icon (lets you walk through NPCs/PCs) + -- 0x80 -- GM mode + -- 0xA0 -- GM mode + -- 0xC0 -- GM mode + -- 0xE0 -- SGM mode + -- No statuses differentiate based on 0x10 + -- Bit 0x20 + 0x40 makes 0x60, which is different. + -- Bit 0x80 overpowers those bits + -- Bit 0x80 combines with 0x04 and 0x02 to make SGM. + -- These are basically flags, but they can be combined to mean different things sometimes. + + _flags3: + -- 0x10 -- No obvious effect + -- 0x20 -- Event mode? Can't activate the targeting cursor but can still spin the camera + -- 0x40 -- No obvious effect + -- 0x80 -- Invisible model + + _flags4: + -- 0x02 -- No obvious effect + -- 0x04 -- No obvious effect + -- 0x08 -- No obvious effect + -- 0x10 -- No obvious effect + -- 0x20 -- Bazaar icon + -- 0x40 -- Event status again? Can't activate the targeting cursor but can move the camera. + -- 0x80 -- No obvious effects + + _flags5: + -- 0x01 -- No obvious effect + -- 0x02 -- No obvious effect + -- 0x04 -- Autoinvite icon + + _flags6: + -- 0x08 -- Terror flag + -- 0x10 -- No obvious effect + + PvP stuff: + -- 0x0020 -- No obvious effect + -- 0x0040 -- San d'Oria ballista flag + -- 0x0060 -- Bastok ballista flag + -- 0x0080 -- Windurst Ballista flag + -- 0x00A0 -- Wyverns team icon + -- 0x00C0 -- Gryphons team icon + -- 0x0100 -- Belligerency icon (used in monstrosity) + -- 0x0200 -- Has some effect + -- 0x0400 -- Pankration red icon + -- 0x0420 -- Pankration blue icon + -- 0x0800 -- and I still don't D:< + -- 0x1000 -- and I still don't D:< + + _flags7: + -- 0x0020 -- No obvious effect + -- 0x0040 -- Individually, this bit has no effect. When combined with 0x20, it prevents you from returning to a walking animation after you stop (sliding along the ground while bound) + -- 0x0080 -- No obvious effect + -- 0x0100 -- Request icon + -- 0x0200 -- Trial Account emblem + -- 0x0400 -- Sneak Effect + -- 0x0800 -- New Adventurer icon + -- 0x1000 -- Mentor icon +]] +fields.incoming[0x037] = L{ + {ctype='unsigned char[32]', label='Buff', fn=buff}, -- 04 + {ctype='unsigned int', label='Player', fn=id}, -- 24 + {ctype='unsigned short', label='_flags1'}, -- 28 Called "Flags" on the old dev wiki. Second byte might not be part of the flags, actually. + {ctype='unsigned char', label='HP %', fn=percent}, -- 2A + {ctype='bit[8]', label='_flags2'}, -- 2B + {ctype='bit[12]', label='Movement Speed/2'}, -- 2C Player movement speed + {ctype='bit[4]', label='_flags3'}, -- 2D + {ctype='bit[9]', label='Yalms per step'}, -- 2E Determines how quickly your animation walks + {ctype='bit[7]', label='_flags4'}, -- 2F + {ctype='unsigned char', label='Status', fn=statuses}, -- 30 + {ctype='unsigned char', label='LS Color Red'}, -- 31 + {ctype='unsigned char', label='LS Color Green'}, -- 32 + {ctype='unsigned char', label='LS Color Blue'}, -- 33 + {ctype='bit[3]', label='_flags5'}, -- 34 + {ctype='bit[16]', label='Pet Index', fn=index}, -- 34 From 0x08 of byte 0x34 to 0x04 of byte 0x36 + {ctype='bit[2]', label='_flags6'}, -- 36 + {ctype='bit[9]', label='PvP Stuff'}, -- 36 Ballista flags here also makes appear the score, but it is probably modified in a ballista specific packet. + {ctype='bit[8]', label='_flags7'}, -- 37 + {ctype='bit[26]', label='_unknown1'}, -- 38 No obvious effect from any of these + {ctype='unsigned int', label='Time offset?', fn=time}, -- 3C For me, this is the number of seconds in 66 hours + {ctype='unsigned int', label='Timestamp', fn=time}, -- 40 This is 32 years off of JST at the time the packet is sent. + {ctype='unsigned short', label='Costume'}, -- 44 ID of the Model + {ctype='data[2]', label='_unknown3'}, -- 46 + {ctype='unsigned short', label='Fellow Index', fn=index}, -- 48 + {ctype='unsigned char', label='Fishing Start Animation'}, -- 4A mostly 0x0D value and sometimes 0x0C observed + {ctype='data[1]', label='_unknown4'}, -- 4B + {ctype='data[8]', label='Bit Mask'}, -- 4C + {ctype='unsigned short', label='Monstrosity Species'}, -- 54 High bit is always set while in monstrosity and determines the display of the third name + {ctype='unsigned char', label='Monstrosity Name 1'}, -- 56 + {ctype='unsigned char', label='Monstrosity Name 2'}, -- 57 + {ctype='bit[7]', label='Indi Buff', fn=e+{'indi'}}, -- 58 + {ctype='boolbit', label='Job Master Flag'}, -- 58 + {ctype='unsigned char', label='Face Flags'}, -- 59 + {ctype='unsigned char', label='_unknown5'}, -- 5A + {ctype='unsigned char', label='Mount'}, -- 5B Related to Mounts, seems to be mount id + 1, except for chocobo. The value doesn't get zeroed after dismount + {ctype='boolbit', label='Wardrobe 3'}, -- 5C + {ctype='boolbit', label='Wardrobe 4'}, -- 5C + {ctype='bit[6]', label='_flags8'}, -- 5C + {ctype='data[3]', label='_junk1'}, -- 5D +} + +-- Entity Animation +-- Most frequently used for spawning ("deru") and despawning ("kesu") +-- Another example: "sp00" for Selh'teus making his spear of light appear +fields.incoming[0x038] = L{ + {ctype='unsigned int', label='Mob', fn=id}, -- 04 + {ctype='unsigned int', label='_dupeMob', fn=id}, -- 08 + {ctype='char[4]', label='Type', fn=e+{0x038}}, -- 0C Four character animation name + {ctype='unsigned short', label='Mob Index', fn=index}, -- 10 + {ctype='unsigned short', label='_dupeMob Index', fn=index}, -- 12 +} + +-- Env. Animation +-- Animations without entities will have zeroes for ID and Index +-- Example without IDs: Runic Gate/Runic Portal +-- Example with IDs: Diabolos floor tiles +fields.incoming[0x039] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned int', label='_dupeID', fn=id}, -- 08 + {ctype='char[4]', label='Type', fn=e+{0x038}}, -- 0C Four character animation name + {ctype='unsigned short', label='Index', fn=index}, -- 10 + {ctype='unsigned short', label='_dupeIndex', fn=index}, -- 10 +} + +-- Independent Animation +-- This is sometimes sent along with an Action Message packet, to provide an animation for an action message. +fields.incoming[0x03A] = L{ + {ctype='unsigned int', label='Actor ID', fn=id}, -- 04 + {ctype='unsigned int', label='Target ID', fn=id}, -- 08 + {ctype='unsigned short', label='Actor Index', fn=index}, -- 0C + {ctype='unsigned short', label='Target Index', fn=index}, -- 0E + {ctype='unsigned short', label='Animation ID'}, -- 10 + {ctype='unsigned char', label='Animation type'}, -- 12 0 = magic, 1 = item, 2 = JA, 3 = environmental animations, etc. + {ctype='unsigned char', label='_junk1'}, -- 13 Deleting this has no effect +} + +types.shop_item = L{ + {ctype='unsigned int', label='Price', fn=gil}, -- 00 + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='unsigned short', label='Shop Slot'}, -- 08 + {ctype='unsigned short', label='Craft Skill'}, -- 0A Zero on normal shops, has values that correlate to res\skills. + {ctype='unsigned short', label='Craft Rank'}, -- 0C Correlates to Rank able to purchase product from GuildNPC +} + +-- Shop +fields.incoming[0x03C] = L{ + {ctype='unsigned short', label='_zero1', const=0x0000}, -- 04 + {ctype='unsigned short', label='_padding1'}, -- 06 + {ref=types.shop_item, label='Item', count='*'}, -- 08 - * +} + +-- Price/sale response +-- Sent in response to an outgoing price request for an NPC vendor (0x085), and in response to player finalizing a sale. +fields.incoming[0x03D] = L{ + {ctype='unsigned int', label='Price', fn=gil}, -- 04 + {ctype='unsigned char', label='Inventory Index', fn=inv+{0}}, -- 08 + {ctype='unsigned char', label='Type'}, -- 09 0 = on price check, 1 = when sale is finalized + {ctype='unsigned short', label='_junk1'}, -- 0A + {ctype='unsigned int', label='Count'}, -- 0C Will be 1 on price check +} + +-- Open Buy/Sell +fields.incoming[0x03E] = L{ + {ctype='unsigned char', label='type'}, -- 04 Only 0x04 observed so far + {ctype='data[3]', label='_junk1'}, -- 05 +} + +types.blacklist_entry = L{ + {ctype='unsigned int', label='ID'}, -- 00 + {ctype='char[16]', label='Name'}, -- 04 +} + +-- Shop Buy Response +fields.incoming[0x03F] = L{ + {ctype='unsigned short', label='Shop Slot'}, -- 04 + {ctype='unsigned short', label='_unknown1'}, -- 06 First byte always seems to be 1, second byte varies between 0 and 1? Unclear correlation to anything. + {ctype='unsigned int', label='Count'}, -- 08 +} + +-- Blacklist +fields.incoming[0x041] = L{ + {ref=types.blacklist_entry, count=12}, -- 08 + {ctype='unsigned char', label='_unknown3', const=3}, -- F4 Always 3 + {ctype='unsigned char', label='Size'}, -- F5 Blacklist entries +} + +-- Blacklist (add/delete) +fields.incoming[0x042] = L{ + {ctype='int', label='_unknown1'}, -- 04 Looks like a player ID, but does not match the sender or the receiver. + {ctype='char[16]', label='Name'}, -- 08 Character name + {ctype='bool', label='Add/Remove'}, -- 18 0 = add, 1 = remove + {ctype='data[3]', label='_unknown2'}, -- 19 Values observed on adding but not deleting. +} + +-- Pet Stat +-- This packet varies and is indexed by job ID (byte 4) +-- Packet 0x044 is sent twice in sequence when stats could change. This can be caused by anything from +-- using a Maneuver on PUP to changing job. The two packets are the same length. The first +-- contains information about your main job. The second contains information about your +-- subjob and has the Subjob flag flipped. +func.incoming[0x044] = {} +fields.incoming[0x044] = function(data, type) + return func.incoming[0x044].base + (func.incoming[0x044][type or data:byte(5)] or L{}) +end + +-- Base, shared by all jobs +func.incoming[0x044].base = L{ + {ctype='unsigned char', label='Job', fn=job}, -- 04 + {ctype='bool', label='Subjob'}, -- 05 + {ctype='unsigned short', label='_unknown1'}, -- 06 +} + +-- PUP +func.incoming[0x044][0x12] = L{ + {ctype='unsigned char', label='Automaton Head'}, -- 08 Harlequinn 1, Valoredge 2, Sharpshot 3, Stormwaker 4, Soulsoother 5, Spiritreaver 6 + {ctype='unsigned char', label='Automaton Frame'}, -- 09 Harlequinn 20, Valoredge 21, Sharpshot 22, Stormwaker 23 + {ctype='unsigned char', label='Slot 1'}, -- 0A Attachment assignments are based off their position in the equipment list. + {ctype='unsigned char', label='Slot 2'}, -- 0B Strobe is 01, etc. + {ctype='unsigned char', label='Slot 3'}, -- 0C + {ctype='unsigned char', label='Slot 4'}, -- 0D + {ctype='unsigned char', label='Slot 5'}, -- 0E + {ctype='unsigned char', label='Slot 6'}, -- 0F + {ctype='unsigned char', label='Slot 7'}, -- 10 + {ctype='unsigned char', label='Slot 8'}, -- 11 + {ctype='unsigned char', label='Slot 9'}, -- 12 + {ctype='unsigned char', label='Slot 10'}, -- 13 + {ctype='unsigned char', label='Slot 11'}, -- 14 + {ctype='unsigned char', label='Slot 12'}, -- 15 + {ctype='unsigned short', label='_unknown2'}, -- 16 + {ctype='unsigned int', label='Available Heads'}, -- 18 Flags for the available heads (Position corresponds to Item ID shifted down by 8192) + {ctype='unsigned int', label='Available Bodies'}, -- 1C Flags for the available bodies (Position corresponds to Item ID) + {ctype='unsigned int', label='_unknown3'}, -- 20 + {ctype='unsigned int', label='_unknown4'}, -- 24 + {ctype='unsigned int', label='_unknown5'}, -- 28 + {ctype='unsigned int', label='_unknown6'}, -- 2C + {ctype='unsigned int', label='_unknown7'}, -- 30 + {ctype='unsigned int', label='_unknown8'}, -- 34 + {ctype='unsigned int', label='Fire Attachments'}, -- 38 Flags for the available Fire Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Ice Attachments'}, -- 3C Flags for the available Ice Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Wind Attachments'}, -- 40 Flags for the available Wind Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Earth Attachments'}, -- 44 Flags for the available Earth Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Thunder Attachments'}, -- 48 Flags for the available Thunder Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Water Attachments'}, -- 4C Flags for the available Water Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Light Attachments'}, -- 50 Flags for the available Light Attachments (Position corresponds to Item ID) + {ctype='unsigned int', label='Dark Attachments'}, -- 54 Flags for the available Dark Attachments (Position corresponds to Item ID) + {ctype='char[16]', label='Pet Name'}, -- 58 + {ctype='unsigned short', label='Current HP'}, -- 68 + {ctype='unsigned short', label='Max HP'}, -- 6A + {ctype='unsigned short', label='Current MP'}, -- 6C + {ctype='unsigned short', label='Max MP'}, -- 6E + {ctype='unsigned short', label='Current Melee Skill'}, -- 70 + {ctype='unsigned short', label='Max Melee Skill'}, -- 72 + {ctype='unsigned short', label='Current Ranged Skill'}, -- 74 + {ctype='unsigned short', label='Max Ranged Skill'}, -- 76 + {ctype='unsigned short', label='Current Magic Skill'}, -- 78 + {ctype='unsigned short', label='Max Magic Skill'}, -- 7A + {ctype='unsigned int', label='_unknown9'}, -- 7C + {ctype='unsigned short', label='Base STR'}, -- 80 + {ctype='unsigned short', label='Additional STR'}, -- 82 + {ctype='unsigned short', label='Base DEX'}, -- 84 + {ctype='unsigned short', label='Additional DEX'}, -- 86 + {ctype='unsigned short', label='Base VIT'}, -- 88 + {ctype='unsigned short', label='Additional VIT'}, -- 8A + {ctype='unsigned short', label='Base AGI'}, -- 8C + {ctype='unsigned short', label='Additional AGI'}, -- 8E + {ctype='unsigned short', label='Base INT'}, -- 90 + {ctype='unsigned short', label='Additional INT'}, -- 92 + {ctype='unsigned short', label='Base MND'}, -- 94 + {ctype='unsigned short', label='Additional MND'}, -- 96 + {ctype='unsigned short', label='Base CHR'}, -- 98 + {ctype='unsigned short', label='Additional CHR'}, -- 9A +} + +-- For BLM, 0x29 to 0x43 appear to represent the black magic that you know + +-- MON +func.incoming[0x044][0x17] = L{ + {ctype='unsigned short', label='Species'}, -- 08 + {ctype='unsigned short', label='_unknown2'}, -- 0A + {ctype='unsigned short[12]',label='Instinct'}, -- 0C Instinct assignments are based off their position in the equipment list. + {ctype='unsigned char', label='Monstrosity Name 1'}, -- 24 + {ctype='unsigned char', label='Monstrosity Name 2'}, -- 25 + {ctype='data[118]', label='_unknown3'}, -- 26 Zeroing everything beyond this point has no notable effect. +} + +-- Translate Response +fields.incoming[0x047] = L{ + {ctype='unsigned short', label='Autotranslate Code'}, -- 04 In a 6 byte autotranslate code, these are the 5th and 4 bytes respectively. + {ctype='unsigned char', label='Starting Language'}, -- 06 0 == JP, 1 == EN + {ctype='unsigned char', label='Ending Language'}, -- 07 0 == JP, 1 == EN + {ctype='char[64]', label='Initial Phrase'}, -- 08 + {ctype='char[64]', label='Translated Phrase'}, -- 48 Will be 00'd if no match was found. +} + + +-- Unknown 0x048 incoming :: Sent when loading linkshell information from the Linkshell Concierge +-- One per entry, 128 bytes long, mostly empty, does not contain name as far as I can see. +-- Likely contributes to that information. + +-- Delivery Item +func.incoming[0x04B] = {} +fields.incoming[0x04B] = function() + local full = S{0x01, 0x04, 0x06, 0x08, 0x0A} -- This might not catch all packets with 'slot-info' (extra 68 bytes) + return function(data, type) + return full:contains(type or data:byte(5)) and func.incoming[0x04B].slot or func.incoming[0x04B].base + end +end() + +enums.delivery = { + -- Seems to occur when refreshing the d-box after any change (or before changes). + [0x01] = 'Slot info', + -- Seems to occur when placing items into the d-box. + [0x02] = 'Place item', + -- Two occur per item that is actually sent (hitting "OK" to send). + [0x03] = 'Send confirm', + -- Two occur per sent item that is Canceled. + [0x04] = 'Send cancel', + -- Seems to occur quasi-randomly. Can be seen following spells. + [0x05] = 'Unknown 0x05', + -- Occurs for new items. + -- Two of these are sent sequentially. The first one doesn't seem to contain much/any + -- information and the second one is very similar to a type 0x01 packet + -- First packet's frst line: 4B 58 xx xx 06 01 00 01 FF FF FF FF 02 02 FF FF + -- Second packet's first line: 4B 58 xx xx 06 01 00 FF FF FF FF FF 01 02 FF FF + [0x06] = 'New item', + -- Occurs as the first packet when removing something from the send box. + [0x07] = 'Remove item (send)', + -- Occurs as the first packet when removing or dropping something from the delivery box. + [0x08] = 'Remove/drop item (delivery)', + -- Occurs when someone returns something from the delivery box. + [0x09] = 'Return item', + -- Occurs as the second packet when removing something from the delivery box or send box. + [0x0A] = 'Remove item confirm', + -- Occurs as the second packet when dropping something from the delivery box. + [0x0B] = 'Drop item (delivery)', + -- Sent after entering a name and hitting "OK" in the outbox. + [0x0C] = 'Send request', + -- Sent after requesting the send box, causes the client to open the send box dialogue. + [0x0D] = 'Send dialogue start', + -- Sent after requesting the delivery box, causes the client to open the delivery box dialogue. + [0x0E] = 'Delivery dialogue start', + -- Sent after closing the delivery box or send box. + [0x0F] = 'Delivery/send dialogue finish', +} + +-- This is always sent for every packet of this ID +func.incoming[0x04B].base = L{ + {ctype='unsigned char', label='Type', fn=e+{'delivery'}}, -- 04 + {ctype='unsigned char', label='_unknown1'}, -- 05 FF if Type is 05, otherwise 01 + {ctype='signed char', label='Delivery Slot'}, -- 06 This goes left to right and then drops down a row and left to right again. Value is 00 through 07 + -- 01 if Type is 06, otherwise FF + -- 06 Type always seems to come in a pair, this field is only 01 for the first packet + {ctype='signed char', label='_unknown2'}, -- 07 Always FF FF FF FF? + {ctype='signed int', label='_unknown3', const=-1}, -- 0C When in a 0x0D/0x0E type, 01 grants request to open inbox/outbox. With FA you get "Please try again later" + {ctype='signed char', label='_unknown4'}, -- 0D 02 and 03 observed + {ctype='signed char', label='Packet Number'}, -- 0E FF FF observed + {ctype='signed char', label='_unknown5'}, -- 0F FF FF observed + {ctype='signed char', label='_unknown6'}, -- 10 06 00 00 00 and 07 00 00 00 observed - (06 was for the first packet and 07 was for the second) + {ctype='unsigned int', label='_unknown7'}, -- 10 00 00 00 00 also observed +} + +-- If the type is 0x01, 0x04, 0x06, 0x08 or 0x0A, these fields appear in the packet in addition to the base. Maybe more +func.incoming[0x04B].slot = L{ + {ref=func.incoming[0x04B].base, count=1}, -- 04 + {ctype='char[16]', label='Player Name'}, -- 14 This is used for sender (in inbox) and recipient (in outbox) + {ctype='unsigned int', label='_unknown8'}, -- 24 46 32 00 00 and 42 32 00 00 observed - Possibly flags. Rare vs. Rare/Ex.? + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 28 + {ctype='unsigned int', label='_unknown9'}, -- 2C 00 00 00 00 observed + {ctype='unsigned short', label='Item', fn=item}, -- 30 + {ctype='unsigned short', label='_unknown10'}, -- 32 Fiendish Tome: Chapter 11 had it, but Oneiros Pebble was just 00 00 + -- 32 May well be junked, 38 38 observed + {ctype='unsigned int', label='Flags?'}, -- 34 01/04 00 00 00 observed + {ctype='unsigned short', label='Count'}, -- 38 + {ctype='unsigned short', label='_unknown11'}, -- 3A + {ctype='data[28]', label='_unknown12'}, -- 3C All 00 observed, ext data? Doesn't seem to be the case, but same size +} + +enums['ah itype'] = { + [0x02] = 'Open menu response', + [0x03] = 'Unknown Logout', + [0x04] = 'Sell item confirmation', + [0x05] = 'Open sales status menu', + [0x0A] = 'Open menu confirmation', + [0x0B] = 'Sell item confirmation', + [0x0D] = 'Sales item status', + [0x0E] = 'Purchase item result', +} + +func.incoming[0x04C] = {} +func.incoming[0x04C].base = L{ + {ctype='unsigned char', label='Type', fn=e+{'ah itype'}}, -- 04 +} + +func.incoming[0x04C][0x02] = L{ + {ctype='unsigned char', label='_unknown1', const=0xFF}, -- 05 + {ctype='unsigned char', label='Success', fn=bool}, -- 06 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='char*', label='_junk'}, -- 08 +} + +-- Sent when initating logout +func.incoming[0x04C][0x03] = L{ + {ctype='unsigned char', label='_unknown1', const=0xFF}, -- 05 + {ctype='unsigned char', label='Success', fn=bool}, -- 06 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='char*', label='_junk'}, -- 08 +} + +func.incoming[0x04C][0x04] = L{ + {ctype='unsigned char', label='_unknown1', const=0xFF}, -- 05 + {ctype='unsigned char', label='Success', fn=bool}, -- 06 + {ctype='unsigned char', label='_unknown2'}, -- 07 + {ctype='unsigned int', label='Fee', fn=gil}, -- 08 + {ctype='unsigned short', label='Inventory Index', fn=inv+{0}}, -- 0C + {ctype='unsigned short', label='Item', fn=item}, -- 0E + {ctype='unsigned char', label='Stack', fn=invbool}, -- 10 + {ctype='char*', label='_junk'}, -- 11 +} + +func.incoming[0x04C][0x05] = L{ + {ctype='unsigned char', label='_unknown1', const=0xFF}, -- 05 + {ctype='unsigned char', label='Success', fn=bool}, -- 06 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='char*', label='_junk'}, -- 08 +} + +enums['sale stat'] = { + [0x00] = '-', + [0x02] = 'Placing', + [0x03] = 'On auction', + [0x0A] = 'Sold', + [0x0B] = 'Not sold', + [0x10] = 'Checking', +} +enums['buy stat'] = { + [0x01] = 'Success', + [0x02] = 'Placing', + [0xC5] = 'Failed', + [0xE5] = 'Cannot Bid' +} + +-- 0x0A, 0x0B and 0x0D could probably be combined, the fields seem the same. +-- However, they're populated a bit differently. Both 0x0B and 0x0D are sent twice +-- on action completion, the second seems to contain updated information. +func.incoming[0x04C][0x0A] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='unsigned char', label='_unknown1', const=0x01}, -- 06 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='data[12]', label='_junk1'}, -- 08 + {ctype='unsigned char', label='Sale status', fn=e+{'sale stat'}},-- 14 + {ctype='unsigned char', label='_unknown3'}, -- 15 + {ctype='unsigned char', label='Inventory Index'}, -- 16 From when the item was put on auction + {ctype='unsigned char', label='_unknown4', const=0x00}, -- 17 Possibly padding + {ctype='char[16]', label='Name'}, -- 18 Seems to always be the player's name + {ctype='unsigned short', label='Item', fn=item}, -- 28 + {ctype='unsigned char', label='Count'}, -- 2A + {ctype='unsigned char', label='AH Category'}, -- 2B + {ctype='unsigned int', label='Price', fn=gil}, -- 2C + {ctype='unsigned int', label='_unknown6'}, -- 30 + {ctype='unsigned int', label='_unknown7'}, -- 34 + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 38 +} + +func.incoming[0x04C][0x0B] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='unsigned char', label='_unknown1'}, -- 06 This packet, like 0x0D, is sent twice, the first one always has 0x02 here, the second one 0x01 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='data[12]', label='_junk1'}, -- 08 + {ctype='unsigned char', label='Sale status', fn=e+{'sale stat'}},-- 14 + {ctype='unsigned char', label='_unknown3'}, -- 15 + {ctype='unsigned char', label='Inventory Index'}, -- 16 From when the item was put on auction + {ctype='unsigned char', label='_unknown4', const=0x00}, -- 17 Possibly padding + {ctype='char[16]', label='Name'}, -- 18 Seems to always be the player's name + {ctype='unsigned short', label='Item', fn=item}, -- 28 + {ctype='unsigned char', label='Count'}, -- 2A + {ctype='unsigned char', label='AH Category'}, -- 2B + {ctype='unsigned int', label='Price', fn=gil}, -- 2C + {ctype='unsigned int', label='_unknown6'}, -- 30 Only populated in the second packet + {ctype='unsigned int', label='_unknown7'}, -- 34 Only populated in the second packet + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 38 +} + +func.incoming[0x04C][0x0D] = L{ + {ctype='unsigned char', label='Slot'}, -- 05 + {ctype='unsigned char', label='_unknown1'}, -- 06 Some sort of type... the packet seems to always be sent twice, once with this value as 0x02, followed by 0x01 + {ctype='unsigned char', label='_unknown2'}, -- 07 If 0x06 is 0x01 this seems to be 0x01 as well, otherwise 0x00 + {ctype='data[12]', label='_junk1'}, -- 08 + {ctype='unsigned char', label='Sale status', fn=e+{'sale stat'}},-- 14 + {ctype='unsigned char', label='_unknown3'}, -- 15 + {ctype='unsigned char', label='Inventory Index'}, -- 16 From when the item was put on auction + {ctype='unsigned char', label='_unknown4', const=0x00}, -- 17 Possibly padding + {ctype='char[16]', label='Name'}, -- 18 Seems to always be the player's name + {ctype='unsigned short', label='Item', fn=item}, -- 28 + {ctype='unsigned char', label='Count'}, -- 2A + {ctype='unsigned char', label='AH Category'}, -- 2B + {ctype='unsigned int', label='Price', fn=gil}, -- 2C + {ctype='unsigned int', label='_unknown6'}, -- 30 + {ctype='unsigned int', label='_unknown7'}, -- 34 + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 38 +} + +func.incoming[0x04C][0x0E] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 05 + {ctype='unsigned char', label='Buy Status', fn=e+{'buy stat'}}, -- 06 + {ctype='unsigned char', label='_unknown2'}, -- 07 + {ctype='unsigned int', label='Price', fn=gil}, -- 08 + {ctype='unsigned short', label='Item ID', fn=item}, -- 0C + {ctype='unsigned short', label='_unknown3'}, -- 0E + {ctype='unsigned short', label='Count'}, -- 10 + {ctype='unsigned int', label='_unknown4'}, -- 12 + {ctype='unsigned short', label='_unknown5'}, -- 16 + {ctype='char[16]', label='Name'}, -- 18 Character name (pending buy only) + {ctype='unsigned short', label='Pending Item ID', fn=item}, -- 28 Only filled out during pending packets + {ctype='unsigned short', label='Pending Count'}, -- 2A Only filled out during pending packets + {ctype='unsigned int', label='Pending Price', fn=gil}, -- 2C Only filled out during pending packets + {ctype='unsigned int', label='_unknown6'}, -- 30 + {ctype='unsigned int', label='_unknown7'}, -- 34 + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 38 Only filled out during pending packets +} + +func.incoming[0x04C][0x10] = L{ + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 05 + {ctype='unsigned char', label='Success', fn=bool}, -- 06 + {ctype='unsigned char', label='_unknown2', const=0x00}, -- 07 + {ctype='char*', label='_junk'}, -- 08 +} + +-- Auction Interaction +-- All types in here are server responses to the equivalent type in 0x04E +-- The only exception is type 0x02, which is sent to initiate the AH menu +fields.incoming[0x04C] = function() + local fields = func.incoming[0x04C] + + return function(data, type) + return fields.base + (fields[type or data:byte(5)] or L{}) + end +end() + +-- Servmes Resp +-- Length of the packet may vary based on message length? Kind of hard to test. +-- The server message appears to generate some kind of feedback to the server based on the flags? +-- If you set the first byte to 0 in incoming chunk with eval and do /smes, the message will not display until you unload eval. +fields.incoming[0x4D] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 01 Message does not appear without this + {ctype='unsigned char', label='_unknown2'}, -- 05 01 Nonessential to message appearance + {ctype='unsigned char', label='_unknown3'}, -- 06 01 Message does not appear without this + {ctype='unsigned char', label='_unknown4'}, -- 07 02 Message does not appear without this + {ctype='unsigned int', label='Timestamp', fn=time}, -- 08 UTC Timestamp + {ctype='unsigned int', label='Message Length 1'}, -- 0A Number of characters in the message + {ctype='unsigned int', label='_unknown2'}, -- 10 00 00 00 00 observed + {ctype='unsigned int', label='Message Length 2'}, -- 14 Same as Message Length 1. Not sure why this needs to be an int or in here twice. + {ctype='char*', label='Message'}, -- 18 Currently prefixed with 0x81, 0xA1 - A custom shift-jis character that translates to a square. +} + +-- Data Download 2 +fields.incoming[0x04F] = L{ +-- This packet's contents are nonessential. They are often leftovers from other outgoing +-- packets. It is common to see things like inventory size, equipment information, and +-- character ID in this packet. They do not appear to be meaningful and the client functions +-- normally even if they are blocked. +-- Tends to bookend model change packets (0x51), though blocking it, zeroing it, etc. affects nothing. + {ctype='unsigned int', label='_unknown1'}, -- 04 +} + +-- Equip +fields.incoming[0x050] = L{ + {ctype='unsigned char', label='Inventory Index', fn=invp+{0x06}}, -- 04 + {ctype='unsigned char', label='Equipment Slot', fn=slot}, -- 05 + {ctype='unsigned char', label='Inventory Bag', fn=bag}, -- 06 + {ctype='data[1]', label='_junk1'} -- 07 +} + +-- Model Change +fields.incoming[0x051] = L{ + {ctype='unsigned char', label='Face'}, -- 04 + {ctype='unsigned char', label='Race'}, -- 05 + {ctype='unsigned short', label='Head'}, -- 06 + {ctype='unsigned short', label='Body'}, -- 08 + {ctype='unsigned short', label='Hands'}, -- 0A + {ctype='unsigned short', label='Legs'}, -- 0C + {ctype='unsigned short', label='Feet'}, -- 0E + {ctype='unsigned short', label='Main'}, -- 10 + {ctype='unsigned short', label='Sub'}, -- 12 + {ctype='unsigned short', label='Ranged'}, -- 14 + {ctype='unsigned short', label='_unknown1'}, -- 16 May varying meaningfully, but it's unclear +} + +enums[0x052] = { + [0x00] = 'Standard', + [0x01] = 'Event', + [0x02] = 'Event Skipped', + [0x03] = 'String Event', + [0x04] = 'Fishing', +} + +func.incoming[0x052] = {} +func.incoming[0x052].base = L{ + {ctype='unsigned char', label='Type', fn=e+{0x052}}, -- 04 +} + +func.incoming[0x052][0x02] = L{ + {ctype='unsigned short', label='Menu ID'}, -- 05 +} + +-- NPC Release +fields.incoming[0x052] = function(data, type) + return func.incoming[0x052].base + (func.incoming[0x052][type or data:byte(5)] or L{}) +end + +-- Logout Time +-- This packet is likely used for an entire class of system messages, +-- but the only one commonly encountered is the logout counter. +fields.incoming[0x053] = L{ + {ctype='unsigned int', label='param'}, -- 04 Parameter + {ctype='unsigned int', label='_unknown1'}, -- 08 00 00 00 00 observed + {ctype='unsigned short', label='Message ID'}, -- 0C It is unclear which dialogue table this corresponds to + {ctype='unsigned short', label='_unknown2'}, -- 0E Probably junk. +} + +-- Key Item Log +fields.incoming[0x055] = L{ + -- There are 6 of these packets sent on zone, which likely corresponds to the 6 categories of key items. + -- FFing these packets between bytes 0x14 and 0x82 gives you access to all (or almost all) key items. + {ctype='data[0x40]', label='Key item available', fn=hex+{0x40}}, -- 04 + {ctype='data[0x40]', label='Key item examined', fn=hex+{0x40}}, -- 44 Bit field correlating to the previous, 1 if KI has been examined, 0 otherwise + {ctype='unsigned int', label='Type'}, -- 84 Goes from 0 to 5, determines which KI are being sent +} + +enums.quest_mission_log = { + [0x0030] = 'Completed Campaign Missions', + [0x0038] = 'Completed Campaign Missions (2)', -- Starts at index 256 + [0x0050] = 'Current San d\'Oria Quests', + [0x0058] = 'Current Bastok Quests', + [0x0060] = 'Current Windurst Quests', + [0x0068] = 'Current Jeuno Quests', + [0x0070] = 'Current Other Quests', + [0x0078] = 'Current Outlands Quests', + [0x0080] = 'Current TOAU Quests and Missions (TOAU, WOTG, Assault, Campaign)', + [0x0088] = 'Current WOTG Quests', + [0x0090] = 'Completed San d\'Oria Quests', + [0x0098] = 'Completed Bastok Quests', + [0x00A0] = 'Completed Windurst Quests', + [0x00A8] = 'Completed Jeuno Quests', + [0x00B0] = 'Completed Other Quests', + [0x00B8] = 'Completed Outlands Quests', + [0x00C0] = 'Completed TOAU Quests and Assaults', + [0x00C8] = 'Completed WOTG Quests', + [0x00D0] = 'Completed Missions (Nations, Zilart)', + [0x00D8] = 'Completed Missions (TOAU, WOTG)', + [0x00E0] = 'Current Abyssea Quests', + [0x00E8] = 'Completed Abyssea Quests', + [0x00F0] = 'Current Adoulin Quests', + [0x00F8] = 'Completed Adoulin Quests', + [0x0100] = 'Current Coalition Quests', + [0x0108] = 'Completed Coalition Quests', + [0xFFFF] = 'Current Missions', +} + +-- There are 27 variations of this packet to populate different quest information. +-- Current quests, completed quests, and completed missions (where applicable) are represented by bit flags where the position +-- corresponds to the quest index in the respective DAT. +-- "Current Mission" fields refer to the mission ID, except COP, SOA, and ROV, which represent a mapping of some sort(?) +-- Additionally, COP, SOA, and ROV do not have a "completed" missions packet, they are instead updated with the current mission. +-- Quests will remain in your 'current' list after they are completed unless they are repeatable. + +func.incoming[0x056] = {} +fields.incoming[0x056] = function (data, type) + return (func.incoming[0x056][type or data and data:unpack('H',0x25)] or L{{ctype='data[32]', label='Quest Flags'}}) + func.incoming[0x056].type +end + +func.incoming[0x056].type = L{ + {ctype='unsigned short',label='Type', fn=e+{'quest_mission_log'}} -- 24 +} + +func.incoming[0x056][0x0080] = L{ + {ctype='data[16]', label='Current TOAU Quests'}, -- 04 + {ctype='int', label='Current Assault Mission'}, -- 14 + {ctype='int', label='Current TOAU Mission'}, -- 18 + {ctype='int', label='Current WOTG Mission'}, -- 1C + {ctype='int', label='Current Campaign Mission'}, -- 20 +} + +func.incoming[0x056][0x00C0] = L{ + {ctype='data[16]', label='Completed TOAU Quests'}, -- 04 + {ctype='data[16]', label='Completed Assaults'}, -- 14 +} + +func.incoming[0x056][0x00D0] = L{ + {ctype='data[8]', label='Completed San d\'Oria Missions'}, -- 04 + {ctype='data[8]', label='Completed Bastok Missions'}, -- 0C + {ctype='data[8]', label='Completed Windurst Missions'}, -- 14 + {ctype='data[8]', label='Completed Zilart Missions'}, -- 1C +} + +func.incoming[0x056][0x00D8] = L{ + {ctype='data[8]', label='Completed TOAU Missions'}, -- 04 + {ctype='data[8]', label='Completed WOTG Missions'}, -- 0C + {ctype='data[16]', label='_junk'}, -- 14 +} + +func.incoming[0x056][0xFFFF] = L{ + {ctype='int', label='Nation'}, -- 04 + {ctype='int', label='Current Nation Mission'}, -- 08 + {ctype='int', label='Current ROZ Mission'}, -- 0C + {ctype='int', label='Current COP Mission'}, -- 10 Doesn't correspond directly to DAT + {ctype='int', label='_unknown1'}, -- 14 + {ctype='bit[4]', label='Current ACP Mission'}, -- 18 lower 4 + {ctype='bit[4]', label='Current MKD Mission'}, -- 18 upper 4 + {ctype='bit[4]', label='Current ASA Mission'}, -- 19 lower 4 + {ctype='bit[4]', label='_junk1'}, -- 19 upper 4 + {ctype='short', label='_junk2'}, -- 1A + {ctype='int', label='Current SOA Mission'}, -- 1C Doesn't correspond directly to DAT + {ctype='int', label='Current ROV Mission'}, -- 20 Doesn't correspond directly to DAT +} + + +-- Weather Change +fields.incoming[0x057] = L{ + {ctype='unsigned int', label='Vanadiel Time', fn=vtime}, -- 04 Units of minutes. + {ctype='unsigned char', label='Weather', fn=weather}, -- 08 + {ctype='unsigned char', label='_unknown1'}, -- 09 + {ctype='unsigned short', label='_unknown2'}, -- 0A +} + +enums.spawntype = { + [0x03] = 'Monster', + [0x00] = 'Casket or NPC', + [0x0A] = 'Self', +} + +-- Assist Response +fields.incoming[0x058] = L{ + {ctype='unsigned int', label='Player', fn=id}, -- 04 + {ctype='unsigned int', label='Target', fn=id}, -- 08 + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C +} + +-- Emote +fields.incoming[0x05A] = L{ + {ctype='unsigned int', label='Player ID', fn=id}, -- 04 + {ctype='unsigned int', label='Target ID', fn=id}, -- 08 + {ctype='unsigned short', label='Player Index', fn=index}, -- 0C + {ctype='unsigned short', label='Target Index', fn=index}, -- 0E + {ctype='unsigned short', label='Emote', fn=emote}, -- 10 + {ctype='unsigned short', label='_unknown1'}, -- 12 + {ctype='unsigned short', label='_unknown2'}, -- 14 + {ctype='unsigned char', label='Type'}, -- 16 2 for motion, 0 otherwise + {ctype='unsigned char', label='_unknown3'}, -- 17 + {ctype='data[32]', label='_unknown4'}, -- 18 +} + +-- Spawn +fields.incoming[0x05B] = L{ + {ctype='float', label='X'}, -- 04 + {ctype='float', label='Z'}, -- 08 + {ctype='float', label='Y'}, -- 0C + {ctype='unsigned int', label='ID', fn=id}, -- 10 + {ctype='unsigned short', label='Index', fn=index}, -- 14 + {ctype='unsigned char', label='Type', fn=e+{'spawntype'}},-- 16 3 for regular Monsters, 0 for Treasure Caskets and NPCs + {ctype='unsigned char', label='_unknown1'}, -- 17 Always 0 if Type is 3, otherwise a seemingly random non-zero number + {ctype='unsigned int', label='_unknown2'}, -- 18 +} + +-- Dialogue Information +fields.incoming[0x05C] = L{ + {ctype='data[32]', label='Menu Parameters'}, -- 04 How information is packed in this region depends on the particular dialogue exchange. +} + +-- Campaign/Besieged Map information + +-- Bitpacked Campaign Info: +-- First Byte: Influence ranking including Beastmen +-- Second Byte: Influence ranking excluding Beastmen + +-- Third Byte (bitpacked xxww bbss -- First two bits are for beastmen) + -- 0 = Minimal + -- 1 = Minor + -- 2 = Major + -- 3 = Dominant + +-- Fourth Byte: Ownership (value) + -- 0 = Neutral + -- 1 = Sandy + -- 2 = Bastok + -- 3 = Windurst + -- 4 = Beastmen + -- 0xFF = Jeuno + + + + +-- Bitpacked Besieged Info: + +-- Candescence Owners: + -- 0 = Whitegate + -- 1 = MMJ + -- 2 = Halvung + -- 3 = Arrapago + +-- Orders: + -- 0 = Defend Al Zahbi + -- 1 = Intercept Enemy + -- 2 = Invade Enemy Base + -- 3 = Recover the Orb + +-- Beastman Status + -- 0 = Training + -- 1 = Advancing + -- 2 = Attacking + -- 3 = Retreating + -- 4 = Defending + -- 5 = Preparing + +-- Bitpacked region int (for the actual locations on the map, not the overview) + -- 3 Least Significant Bits -- Beastman Status for that region + -- 8 following bits -- Number of Forces + -- 4 following bits -- Level + -- 4 following bits -- Number of Archaic Mirrors + -- 4 following bits -- Number of Prisoners + -- 9 following bits -- No clear purpose + +fields.incoming[0x05E] = L{ + {ctype='unsigned char', label='Balance of Power'}, -- 04 Bitpacked: xxww bbss -- Unclear what the first two bits are for. Number stored is ranking (0-3) + {ctype='unsigned char', label='Alliance Indicator'}, -- 05 Indicates whether two nations are allied (always the bottom two). + {ctype='data[20]', label='_unknown1'}, -- 06 All Zeros, and changed nothing when 0xFF'd. + {ctype='unsigned int', label='Bitpacked Ronfaure Info'}, -- 1A + {ctype='unsigned int', label='Bitpacked Zulkheim Info'}, -- 1E + {ctype='unsigned int', label='Bitpacked Norvallen Info'}, -- 22 + {ctype='unsigned int', label='Bitpacked Gustaberg Info'}, -- 26 + {ctype='unsigned int', label='Bitpacked Derfland Info'}, -- 2A + {ctype='unsigned int', label='Bitpacked Sarutabaruta Info'}, -- 2E + {ctype='unsigned int', label='Bitpacked Kolshushu Info'}, -- 32 + {ctype='unsigned int', label='Bitpacked Aragoneu Info'}, -- 36 + {ctype='unsigned int', label='Bitpacked Fauregandi Info'}, -- 3A + {ctype='unsigned int', label='Bitpacked Valdeaunia Info'}, -- 3E + {ctype='unsigned int', label='Bitpacked Qufim Info'}, -- 42 + {ctype='unsigned int', label="Bitpacked Li'Telor Info"}, -- 46 + {ctype='unsigned int', label='Bitpacked Kuzotz Info'}, -- 4A + {ctype='unsigned int', label='Bitpacked Vollbow Info'}, -- 4E + {ctype='unsigned int', label='Bitpacked Elshimo Lowlands Info'}, -- 52 + {ctype='unsigned int', label="Bitpacked Elshimo Uplands Info"}, -- 56 + {ctype='unsigned int', label="Bitpacked Tu'Lia Info"}, -- 5A + {ctype='unsigned int', label='Bitpacked Movapolos Info'}, -- 5E + {ctype='unsigned int', label='Bitpacked Tavnazian Archipelago Info'}, -- 62 + {ctype='data[32]', label='_unknown2'}, -- 66 All Zeros, and changed nothing when 0xFF'd. + {ctype='unsigned char', label="San d'Oria region bar"}, -- 86 These indicate how full the current region's bar is (in percent). + {ctype='unsigned char', label="Bastok region bar"}, -- 87 + {ctype='unsigned char', label="Windurst region bar"}, -- 88 + {ctype='unsigned char', label="San d'Oria region bar without beastmen"},-- 86 Unsure of the purpose of the without beastman indicators + {ctype='unsigned char', label="Bastok region bar without beastmen"}, -- 87 + {ctype='unsigned char', label="Windurst region bar without beastmen"}, -- 88 + {ctype='unsigned char', label="Days to tally"}, -- 8C Number of days to the next conquest tally + {ctype='data[3]', label="_unknown4"}, -- 8D All Zeros, and changed nothing when 0xFF'd. + {ctype='int', label='Conquest Points'}, -- 90 + {ctype='unsigned char', label="Beastmen region bar"}, -- 94 + {ctype='data[12]', label="_unknown5"}, -- 95 Mostly zeros and noticed no change when 0xFF'd. + +-- These bytes are for the overview summary on the map. + -- The two least significant bits code for the owner of the Astral Candescence. + -- The next two bits indicate the current orders. + -- The four most significant bits indicate the MMJ level. + {ctype='unsigned char', label="MMJ Level, Orders, and AC"}, -- A0 + + -- Halvung is the 4 least significant bits. + -- Arrapago is the 4 most significant bits. + {ctype='unsigned char', label="Halvung and Arrapago Level"}, -- A1 + {ctype='unsigned char', label="Beastman Status (1) "}, -- A2 The 3 LS bits are the MMJ Orders, next 3 bits are the Halvung Orders, top 2 bits are part of the Arrapago Orders + {ctype='unsigned char', label="Beastman Status (2) "}, -- A3 The Least Significant bit is the top bit of the Arrapago orders. Rest of the byte doesn't seem to do anything? + +-- These bytes are for the individual stronghold displays. See above! + {ctype='unsigned int', label='Bitpacked MMJ Info'}, -- A4 + {ctype='unsigned int', label='Bitpacked Halvung Info'}, -- A8 + {ctype='unsigned int', label='Bitpacked Arrapago Info'}, -- AC + + {ctype='int', label='Imperial Standing'}, -- B0 +} + +-- Music Change +fields.incoming[0x05F] = L{ + {ctype='unsigned short', label='BGM Type'}, -- 04 01 = idle music, 06 = mog house music. 00, 02, and 03 are fight musics and some other stuff. + {ctype='unsigned short', label='Song ID'}, -- 06 See the setBGM addon for more information +} + +-- Char Stats +fields.incoming[0x061] = L{ + {ctype='unsigned int', label='Maximum HP'}, -- 04 + {ctype='unsigned int', label='Maximum MP'}, -- 08 + {ctype='unsigned char', label='Main Job', fn=job}, -- 0C + {ctype='unsigned char', label='Main Job Level'}, -- 0D + {ctype='unsigned char', label='Sub Job', fn=job}, -- 0E + {ctype='unsigned char', label='Sub Job Level'}, -- 0F + {ctype='unsigned short', label='Current EXP'}, -- 10 + {ctype='unsigned short', label='Required EXP'}, -- 12 + {ctype='unsigned short', label='Base STR'}, -- 14 + {ctype='unsigned short', label='Base DEX'}, -- 16 + {ctype='unsigned short', label='Base VIT'}, -- 18 + {ctype='unsigned short', label='Base AGI'}, -- 1A + {ctype='unsigned short', label='Base INT'}, -- 1C + {ctype='unsigned short', label='Base MND'}, -- 1E + {ctype='unsigned short', label='Base CHR'}, -- 20 + {ctype='signed short', label='Added STR'}, -- 22 + {ctype='signed short', label='Added DEX'}, -- 24 + {ctype='signed short', label='Added VIT'}, -- 26 + {ctype='signed short', label='Added AGI'}, -- 28 + {ctype='signed short', label='Added INT'}, -- 2A + {ctype='signed short', label='Added MND'}, -- 2C + {ctype='signed short', label='Added CHR'}, -- 2E + {ctype='unsigned short', label='Attack'}, -- 30 + {ctype='unsigned short', label='Defense'}, -- 32 + {ctype='signed short', label='Fire Resistance'}, -- 34 + {ctype='signed short', label='Wind Resistance'}, -- 36 + {ctype='signed short', label='Lightning Resistance'}, -- 38 + {ctype='signed short', label='Light Resistance'}, -- 3A + {ctype='signed short', label='Ice Resistance'}, -- 3C + {ctype='signed short', label='Earth Resistance'}, -- 3E + {ctype='signed short', label='Water Resistance'}, -- 40 + {ctype='signed short', label='Dark Resistance'}, -- 42 + {ctype='unsigned short', label='Title', fn=title}, -- 44 + {ctype='unsigned short', label='Nation rank'}, -- 46 + {ctype='unsigned short', label='Rank points', fn=cap+{0xFFF}}, -- 48 + {ctype='unsigned short', label='Home point', fn=zone}, -- 4A + {ctype='unsigned short', label='_unknown1'}, -- 4C 0xFF-ing this last region has no notable effect. + {ctype='unsigned short', label='_unknown2'}, -- 4E + {ctype='unsigned char', label='Nation'}, -- 50 0 = sandy, 1 = bastok, 2 = windy + {ctype='unsigned char', label='_unknown3'}, -- 51 Possibly Unity ID (always 7 for me, I'm in Aldo's unity) + {ctype='unsigned char', label='Su Level'}, -- 52 + {ctype='unsigned char', label='_unknown4'}, -- 53 Always 00 for me + {ctype='unsigned char', label='Maximum iLevel'}, -- 54 + {ctype='unsigned char', label='iLevel over 99'}, -- 55 0x10 would be an iLevel of 115 + {ctype='unsigned char', label='Main Hand iLevel'}, -- 56 + {ctype='unsigned char', label='_unknown5'}, -- 57 Always 00 for me + {ctype='bit[5]', label='Unity ID'}, -- 58 0=None, 1=Pieuje, 2=Ayame, 3=Invincible Shield, 4=Apururu, 5=Maat, 6=Aldo, 7=Jakoh Wahcondalo, 8=Naja Salaheem, 9=Flavira + {ctype='bit[5]', label='Unity Rank'}, -- 58 Danger, 00ing caused my client to crash + {ctype='bit[16]', label='Unity Points'}, -- 59 + {ctype='bit[6]', label='_unknown6'}, -- 5A No obvious function + {ctype='unsigned int', label='_junk1'}, -- 5C + {ctype='unsigned int', label='_junk2'}, -- 60 + {ctype='unsigned char', label='_unknown7'}, -- 64 + {ctype='unsigned char', label='Master Level'}, -- 65 + {ctype='boolbit', label='Master Breaker'}, -- 66 + {ctype='bit[15]', label='_junk3'}, -- 66 + {ctype='unsigned int', label='Current Exemplar Points'}, -- 68 + {ctype='unsigned int', label='Required Exemplar Points'}, -- 6C +} + +types.combat_skill = L{ + {ctype='bit[15]', label='Level'}, -- 00 + {ctype='boolbit', label='Capped'}, -- 01 +} + +types.craft_skill = L{ + {ctype='bit[5]', label='Rank', fn=srank}, -- 00 + {ctype='bit[10]', label='Level'}, -- 00 + {ctype='boolbit', label='Capped'}, -- 01 +} + +-- Skills Update +fields.incoming[0x062] = L{ + {ctype='char[124]', label='_junk1'}, + {ref=types.combat_skill, lookup={res.skills,0x00}, count=0x30}, -- 80 + {ref=types.craft_skill, lookup={res.skills,0x30}, count=0x0A}, -- E0 + {ctype='unsigned short[6]', label='_junk2'}, -- F4 +} + +-- Set Update +-- This packet likely varies based on jobs, but currently I only have it worked out for Monstrosity. +-- It also appears in three chunks, so it's double-varying. +-- Packet was expanded in the March 2014 update and now includes a fourth packet, which contains CP values. + +func.incoming[0x063] = {} +fields.incoming[0x063] = function(data, type) + return func.incoming[0x063].base + (func.incoming[0x063][type or data:byte(5)] or L{}) +end + +func.incoming[0x063].base = L{ + {ctype='unsigned short', label='Order'}, -- 04 +} + +func.incoming[0x063][0x02] = L{ + {ctype='data[7]', label='_flags1', fn=bin+{7}}, -- 06 The 3rd bit of the last byte is the flag that indicates whether or not you are xp capped (blue levels) +} + +func.incoming[0x063][0x03] = L{ + {ctype='unsigned short', label='_flags1'}, -- 06 Consistently D8 for me + {ctype='unsigned short', label='_flags2'}, -- 08 Vary when I change species + {ctype='unsigned short', label='_flags3'}, -- 0A Consistent across species + {ctype='unsigned char', label='Mon. Rank'}, -- 0C 00 = Mon, 01 = NM, 02 = HNM + {ctype='unsigned char', label='_unknown1'}, -- 0D 00 + {ctype='unsigned short', label='_unknown2'}, -- 0E 00 00 + {ctype='unsigned short', label='_unknown3'}, -- 10 76 00 + {ctype='unsigned short', label='Infamy'}, -- 12 + {ctype='unsigned int', label='_unknown4'}, -- 14 00s + {ctype='unsigned int', label='_unknown5'}, -- 18 00s + {ctype='data[64]', label='Instinct Bitfield 1'}, -- 1C See below + -- Bitpacked 2-bit values. 0 = no instincts from that species, 1 == first instinct, 2 == first and second instinct, 3 == first, second, and third instinct. + {ctype='data[128]', label='Monster Level Char field'}, -- 5C Mapped onto the item ID for these creatures. (00 doesn't exist, 01 is rabbit, 02 is behemoth, etc.) +} + +func.incoming[0x063][0x04] = L{ + {ctype='unsigned short', label='_unknown1'}, -- 06 B0 00 + {ctype='data[126]', label='_unknown2'}, -- 08 FF-ing has no effect. + {ctype='unsigned char', label='Slime Level'}, -- 86 + {ctype='unsigned char', label='Spriggan Level'}, -- 87 + {ctype='data[12]', label='Instinct Bitfield 3'}, -- 88 Contains job/race instincts from the 0x03 set. Has 8 unused bytes. This is a 1:1 mapping. + {ctype='data[32]', label='Variants Bitfield'}, -- 94 Does not show normal monsters, only variants. Bit is 1 if the variant is owned. Length is an estimation including the possible padding. +} + +types.job_point_info = L{ + {ctype='unsigned short', label='Capacity Points'}, -- 00 + {ctype='unsigned short', label='Job Points'}, -- 02 + {ctype='unsigned short', label='Spent Job Points'}, -- 04 +} + +func.incoming[0x063][0x05] = L{ + {ctype='unsigned short', label='_unknown1', const=0x0098}, -- 06 + {ctype='unsigned short', label='_unknown2'}, -- 08 Lowest bit of this might indicate JP availability + {ctype='unsigned short', label='_unknown3'}, -- 0A + {ref=types.job_point_info, lookup={res.jobs, 0x00}, count=24}, -- 0C +} + +func.incoming[0x063][0x09] = L{ + {ctype='unsigned short', label='_unknown1', const=0x00C4}, -- 06 + {ctype='unsigned short[32]',label='Buffs', fn=buff}, -- 08 + {ctype='unsigned int[32]', label='Time', fn=bufftime}, -- 48 +} + +-- Repositioning +fields.incoming[0x065] = L{ +-- This is identical to the spawn packet, but has 4 more unused bytes. + {ctype='float', label='X'}, -- 04 + {ctype='float', label='Z'}, -- 08 + {ctype='float', label='Y'}, -- 0C + {ctype='unsigned int', label='ID', fn=id}, -- 10 + {ctype='unsigned short', label='Index', fn=index}, -- 14 + {ctype='unsigned char', label='Animation'}, -- 16 + {ctype='unsigned char', label='Rotation'}, -- 17 + {ctype='data[6]', label='_unknown3'}, -- 18 All zeros observed. +} + +-- Pet Info +fields.incoming[0x067] = L{ +-- The length of this packet is 24, 28, 36 or 40 bytes, featuring a 0, 4, 8, 12, or 16 byte name field. + +-- The Mask is a bitpacked combination of a number indicating the type of information in the packet and +-- a field indicating the length of the packet. + +-- The lower 6 bits of the Mask is the type of packet: +-- 2 occurs often even with no pet, contains player index, id and main job level +-- 3 identifies (potential) pets and who owns them +-- 4 gives status information about your pet + +-- The upper 10 bits of the Mask is the length in bytes of the data excluding the header and any padding +-- after the pet name. + + {ctype='bit[6]', label='Message Type'}, -- 04 + {ctype='bit[10]', label='Message Length'}, -- 05 + {ctype='unsigned short', label='Pet Index', fn=index}, -- 06 + {ctype='unsigned int', label='Pet ID', fn=id}, -- 08 + {ctype='unsigned short', label='Owner Index', fn=index}, -- 0C + {ctype='unsigned char', label='Current HP%', fn=percent}, -- 0E + {ctype='unsigned char', label='Current MP%', fn=percent}, -- 0F + {ctype='unsigned int', label='Pet TP'}, -- 10 + {ctype='unsigned int', label='_unknown1'}, -- 14 + {ctype='char*', label='Pet Name'}, -- 18 +} + +-- Pet Status +-- It is sent every time a pet performs an action, every time anything about its vitals changes (HP, MP, TP) and every time its target changes +fields.incoming[0x068] = L{ + {ctype='bit[6]', label='Message Type', const=0x04}, -- 04 Seems to always be 4 + {ctype='bit[10]', label='Message Length'}, -- 05 Number of bytes from the start of the packet (including header) until the last non-null character in the name + {ctype='unsigned short', label='Owner Index', fn=index}, -- 06 + {ctype='unsigned int', label='Owner ID', fn=id}, -- 08 + {ctype='unsigned short', label='Pet Index', fn=index}, -- 0C + {ctype='unsigned char', label='Current HP%', fn=percent}, -- 0E + {ctype='unsigned char', label='Current MP%', fn=percent}, -- 0F + {ctype='unsigned int', label='Pet TP'}, -- 10 + {ctype='unsigned int', label='Target ID', fn=id}, -- 14 + {ctype='char*', label='Pet Name'}, -- 18 +} + +types.synth_skills = L{ + {ctype='bit[6]', label='Skill'}, -- 1A - 1D:0 + {ctype='boolbit', label='Skillup Allowed'}, -- 1A - 1D:6 + {ctype='boolbit', label='Desynth'}, -- 1A - 1D:7 +} + +-- Self Synth Result +fields.incoming[0x06F] = L{ + {ctype='unsigned char', label='Result', fn=e+{'synth'}}, -- 04 + {ctype='signed char', label='Quality'}, -- 05 + {ctype='unsigned char', label='Count'}, -- 06 Even set for fail (set as the NQ amount in that case) + {ctype='unsigned char', label='_junk1'}, -- 07 + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned short[8]', label='Lost Item', fn=item}, -- 0A + {ref=types.synth_skills, count=4}, + {ctype='unsigned char[4]', label='Skillup', fn=div+{10}}, -- 1E + {ctype='unsigned short', label='Crystal', fn=item}, -- 22 +} + +-- Others Synth Result +fields.incoming[0x070] = L{ + {ctype='unsigned char', label='Result', fn=e+{'synth'}}, -- 04 + {ctype='signed char', label='Quality'}, -- 05 + {ctype='unsigned char', label='Count'}, -- 06 + {ctype='unsigned char', label='_junk1'}, -- 07 + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned short[8]', label='Lost Item', fn=item}, -- 0A + {ref=types.synth_skills, count=4}, + {ctype='char*', label='Player Name'}, -- 1E Name of the player +} + +-- Unity Start +-- Only observed being used for Unity fights. +fields.incoming[0x075] = L{ + {ctype='unsigned int', label='Fight Designation'}, -- 04 Anything other than 0 makes a timer. 0 deletes the timer. + {ctype='unsigned int', label='Timestamp Offset', fn=time}, -- 08 Number of seconds since 15:00:00 GMT 31/12/2002 (0x3C307D70) + {ctype='unsigned int', label='Fight Duration', fn=time}, -- 0C + {ctype='data[12]', label='_unknown1'}, -- 10 This packet clearly needs position information, but it's unclear how these bytes carry it + {ctype='unsigned int', label='Battlefield Radius'}, -- 1C Yalms*1000, so a 50 yalm battlefield would have 50,000 for this field + {ctype='unsigned int', label='Render Radius'}, -- 20 Yalms*1000, so a fence that renders when you're 25 yalms away would have 25,000 for this field +} + +-- Party status icon update +-- Buff IDs go can over 0xFF, but in the packet each buff only takes up one byte. +-- To address that there's a 8 byte bitmask starting at 0x4C where each 2 bits +-- represent how much to add to the value in the respective byte. +types.party_buff_entry = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 00 + {ctype='unsigned short', label='Index', fn=index}, -- 04 + {ctype='unsigned short', label='_unknown1'}, -- 06 + {ctype='data[8]', label='Bit Mask'}, -- 08 + {ctype='data[32]', label='Buffs'}, -- 10 +} + +fields.incoming[0x076] = L{ + {ref=types.party_buff_entry,label='Party Buffs', count=5}, -- 04 This is 00'd out for absent party members. +} + +-- Proposal +fields.incoming[0x078] = L{ + {ctype='unsigned int', label='Proposer ID', fn=id}, -- 04 + {ctype='unsigned int', label='_unknown1'}, -- 08 Proposal ID? + {ctype='unsigned short', label='Proposer Index'}, -- 0C + {ctype='char[15]', label='Proposer Name'}, -- 0E + {ctype='unsigned char', label='Chat mode'}, -- 1D Not typical chat mode mapping. 1 = Party + {ctype='char*', label='Proposal'}, -- 1E Proposal text, complete with special characters +} + +-- Proposal Update +fields.incoming[0x079] = L{ + {ctype='unsigned int', label='_unknown1'}, -- 04 + {ctype='data[21]', label='_unknown2'}, -- 08 Likely contains information about the current chat mode and vote count + {ctype='char[16]', label='Proposer Name'}, -- 1D + {ctype='data[3]', label='_junk1'}, -- 1E All 00s +} + +-- Guild Buy Response +-- Sent when buying an item from a guild NPC +fields.incoming[0x082] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='_junk1'}, -- 0A No obvious purpose + {ctype='unsigned char', label='Count'}, -- 0B Number you bought +} + +types.guild_entry = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 00 + {ctype='unsigned char', label='Current Stock'}, -- 02 Number in stock + {ctype='unsigned char', label='Max Stock'}, -- 03 Max stock can hold + {ctype='unsigned int', label='Price'}, -- 04 +} +-- Guild Inv List +fields.incoming[0x083] = L{ + {ref=types.guild_entry, label='Item', count='30'}, -- 04 + {ctype='unsigned char', label='Item Count'}, -- F4 + {ctype='bit[4]', label='Order'}, -- F5 + {ctype='bit[4]', label='_unknown'}, + {ctype='unsigned short', label='_padding'} -- F6 +} + +-- Guild Sell Response +-- Sent when selling an item to a guild NPC +fields.incoming[0x084] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 08 + {ctype='unsigned char', label='_junk1'}, -- 0A No obvious purpose + {ctype='unsigned char', label='Count'}, -- 0B Number you bought. If 0, the transaction failed. +} + +-- Guild Sale List +fields.incoming[0x085] = L{ + {ref=types.guild_entry, label='Item', count='30'}, -- 04 + {ctype='unsigned char', label='Item Count'}, -- F4 + {ctype='bit[4]', label='Order'}, -- F5 + {ctype='bit[4]', label='_unknown'}, + {ctype='unsigned short', label='_padding'} -- F6 +} +-- Guild Open +-- Sent to update guild status or open the guild menu. +fields.incoming[0x086] = L{ + {ctype='unsigned char', label='Open Menu'}, -- 04 0x00 = Open guild menu, 0x01 = Guild is closed, 0x03 = nothing, so this is treated as an unsigned char + {ctype='data[3]', label='_junk1'}, -- 05 Does not seem to matter in any permutation of this packet + {ctype='data[3]', label='Guild Hours'}, -- 08 First 1 indicates the opening hour. First 0 after that indicates the closing hour. In the event that there are no 0s, 91022244 is used. + {ctype='unsigned char', label='_flags1'}, -- 0B Most significant bit (0x80) indicates whether the "close guild" message should be displayed. +} + + +types.merit_entry = L{ + {ctype='unsigned short', label='Merit'}, -- 00 + {ctype='unsigned char', label='Next Cost'}, -- 02 + {ctype='unsigned char', label='Value'}, -- 03 +} + +-- Merits +fields.incoming[0x08C] = function(data, merits) + return L{ + {ctype='unsigned char', label='Count'}, -- 04 Number of merits entries in this packet (possibly a short, although it wouldn't make sense) + {ctype='data[3]', label='_unknown1'}, -- 05 Always 00 0F 01? + {ref=types.merit_entry, count=merits or data:byte(5)}, -- 08 + {ctype='unsigned int', label='_unknown2', const=0x00000000}, ---04 + } +end + +types.job_point = L{ + {ctype='unsigned short', label='Job Point ID'}, -- 00 32 potential values for every job, which means you could decompose this into a value bitpacked with job ID if you wanted + {ctype='bit[10]', label='_unknown1'}, -- 02 Always 1 in cases where the ID is set at the moment. Zeroing this has no effect. + {ctype='bit[6]', label='Current Level'}, -- 03 Current enhancement for this job point ID +} + +-- Job Points +-- These packets are currently not used by the client in any detectable way. +-- The below pattern repeats itself for the entirety of the packet. There are 2 jobs per packet, +-- and 11 of these packets are sent at the moment in response to the first 0x0C0 outgoing packet since zoning. +-- This is how it works as of 3-19-14, and it is safe to assume that it will change in the future. +fields.incoming[0x08D] = L{ + {ref=types.job_point, count='*'}, -- 04 +} + +-- Campaign Map Info +-- fields.incoming[0x071] +-- Perhaps it's my lack of interest, but this (triple-ish) packet is nearly incomprehensible to me. +-- Does not appear to contain zone IDs. It's probably bitpacked or something. +-- Has a byte that seems to be either 02 or 03, but the packet is sent three times. There are two 02s. +-- The second 02 packet contains different information after the ~48th content byte. + +types.alliance_member = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 00 + {ctype='unsigned short', label='Index', fn=index}, -- 04 + {ctype='unsigned short', label='Flags', fn=bin+{2}}, -- 06 + {ctype='unsigned short', label='Zone', fn=zone}, -- 08 + {ctype='unsigned short', label='_unknown2'}, -- 0A Always 0? +} + +-- Party Map Marker +-- This packet is ignored if your party member is within 50' of you. +fields.incoming[0x0A0] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned short', label='Zone', fn=zone}, -- 08 + {ctype='unsigned short', label='_unknown1'}, -- 0A Look like junk + {ctype='float', label='X'}, -- 0C + {ctype='float', label='Z'}, -- 10 + {ctype='float', label='Y'}, -- 14 +} + +--0x0AA, 0x0AC, and 0x0AE are all bitfields where the lsb indicates whether you have index 0 of the related resource. + +-- Help Desk submenu open +fields.incoming[0x0B5] = L{ + {ctype='data[0x14]', label='_unknown1'}, -- 04 + {ctype='unsigned int', label='Number of Opens'}, -- 18 + {ctype='unsigned int', label='_unknown2'}, -- 1C +} + +-- Alliance status update +fields.incoming[0x0C8] = L{ + {ctype='unsigned char', label='_unknown1'}, -- 04 + {ctype='data[3]', label='_junk1'}, -- 05 + {ref=types.alliance_member, count=18}, -- 08 + {ctype='data[0x18]', label='_unknown3', const=''}, -- E0 Always 0? +} + +types.check_item = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 00 + {ctype='unsigned char', label='Slot', fn=slot}, -- 02 + {ctype='unsigned char', label='_unknown1'}, -- 03 + {ctype='data[0x18]', label='ExtData', fn=hex+{0x18}}, -- 04 +} + +-- Check data +func.incoming[0x0C9] = {} +fields.incoming[0x0C9] = function(data, type) + return func.incoming[0x0C9].base + func.incoming[0x0C9][type or data:byte(0x0B)] +end + +enums[0x0C9] = { + [0x01] = 'Metadata', + [0x03] = 'Equipment', +} + +-- Common to all messages +func.incoming[0x0C9].base = L{ + {ctype='unsigned int', label='Target ID', fn=id}, -- 04 + {ctype='unsigned short', label='Target Index', fn=index}, -- 08 + {ctype='unsigned char', label='Type', fn=e+{0x0C9}}, -- 0A +} + +-- Equipment listing +func.incoming[0x0C9][0x03] = L{ + {ctype='unsigned char', label='Count'}, -- 0B + {ref=types.check_item, count_ref=0x0B}, -- 0C +} + +-- Metadata +-- The title needs to be somewhere in here, but not sure where, maybe bit packed? +func.incoming[0x0C9][0x01] = L{ + {ctype='data[3]', label='_junk1'}, -- 0B + {ctype='unsigned char', label='Icon Set Subtype'}, -- 0E 0 = Unopened Linkshell?, 1 = Linkshell, 2 = Pearlsack, 3 = Linkpearl, 4 = Ripped Pearlsack (I think), 5 = Broken Linkpearl? + {ctype='unsigned char', label='Icon Set ID'}, -- 0F This identifies the icon set, always 2 for linkshells. + {ctype='data[16]', label='Linkshell', enc=ls_enc}, -- 10 6-bit packed + {ctype='bit[4]', label='_junk1'}, -- 20 + {ctype='bit[4]', label='Linkshell Red'}, -- 20 0xGR, 0x-B + {ctype='bit[4]', label='Linkshell Green'}, -- 21 + {ctype='bit[4]', label='Linkshell Blue'}, -- 21 + {ctype='unsigned char', label='_unknown1'}, -- 22 + {ctype='unsigned char', label='Sub Job', fn=job}, -- 23 + {ctype='unsigned char', label='Main Job Level'}, -- 24 + {ctype='unsigned char', label='Sub Job Level'}, -- 25 + {ctype='unsigned char', label='Main Job', fn=job}, -- 26 + {ctype='unsigned char', label='Master Level'}, -- 27 + {ctype='boolbit', label='Master Breaker'}, -- 28 + {ctype='bit[7]', label='_junk2'}, -- 28 + {ctype='data[43]', label='_unknown5'}, -- 29 At least the first two bytes and the last twelve bytes are junk, possibly more +} + +-- Bazaar Message +fields.incoming[0x0CA] = L{ + {ctype='char[124]', label='Bazaar Message'}, -- 04 Terminated with a vertical tab + {ctype='char[16]', label='Player Name'}, -- 80 + {ctype='unsigned short', label='Player Title ID'}, -- 90 + {ctype='unsigned short', label='_unknown4'}, -- 92 00 00 observed. +} + +-- LS Message +fields.incoming[0x0CC] = L{ + {ctype='int', label='_unknown1'}, -- 04 + {ctype='char[128]', label='Message'}, -- 08 + {ctype='unsigned int', label='Timestamp', fn=time}, -- 88 + {ctype='char[16]', label='Player Name'}, -- 8C + {ctype='unsigned int', label='Permissions'}, -- 98 + {ctype='data[15]', label='Linkshell', enc=ls_enc}, -- 9C 6-bit packed +} + +-- Found Item +fields.incoming[0x0D2] = L{ + {ctype='unsigned int', label='_unknown1'}, -- 04 Could be characters starting the line - FD 02 02 18 observed + -- 04 Arcon: Only ever observed 0x00000001 for this + {ctype='unsigned int', label='Dropper', fn=id}, -- 08 + {ctype='unsigned int', label='Count'}, -- 0C Takes values greater than 1 in the case of gil + {ctype='unsigned short', label='Item', fn=item}, -- 10 + {ctype='unsigned short', label='Dropper Index', fn=index}, -- 12 + {ctype='unsigned char', label='Index'}, -- 14 This is the internal index in memory, not the one it appears in in the menu + {ctype='bool', label='Old'}, -- 15 This is true if it's not a new drop, but appeared in the pool before you joined a party + {ctype='unsigned char', label='_unknown4', const=0x00}, -- 16 Seems to always be 00 + {ctype='unsigned char', label='_unknown5'}, -- 17 Seemingly random, both 00 and FF observed, as well as many values in between + {ctype='unsigned int', label='Timestamp', fn=utime}, -- 18 + {ctype='data[28]', label='_unknown6'}, -- AC Always 0 it seems? + {ctype='unsigned int', label='_junk1'}, -- 38 +} + +-- Item lot/drop +fields.incoming[0x0D3] = L{ + {ctype='unsigned int', label='Highest Lotter', fn=id}, -- 04 + {ctype='unsigned int', label='Current Lotter', fn=id}, -- 08 + {ctype='unsigned short', label='Highest Lotter Index',fn=index}, -- 0C + {ctype='unsigned short', label='Highest Lot'}, -- 0E + {ctype='bit[15]', label='Current Lotter Index',fn=index}, -- 10 + {ctype='bit[1]', label='_unknown1'}, -- 11 Always seems set + {ctype='unsigned short', label='Current Lot'}, -- 12 0xFF FF if passing + {ctype='unsigned char', label='Index'}, -- 14 + {ctype='unsigned char', label='Drop'}, -- 15 0 if no drop, 1 if dropped to player, 3 if floored + {ctype='char[16]', label='Highest Lotter Name'}, -- 16 + {ctype='char[16]', label='Current Lotter Name'}, -- 26 + {ctype='data[6]', label='_junk1'}, -- 36 +} + +-- Party Invite +fields.incoming[0x0DC] = L{ + {ctype='unsigned int', label='Inviter ID', fn=id}, -- 04 + {ctype='unsigned int', label='Flags'}, -- 08 This may also contain the type of invite (alliance vs. party) + {ctype='char[16]', label='Inviter Name'}, -- 0C + {ctype='unsigned short', label='_unknown1'}, -- 1C + {ctype='unsigned short', label='_junk1'}, -- 1E +} + +-- Party member update +fields.incoming[0x0DD] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned int', label='HP'}, -- 08 + {ctype='unsigned int', label='MP'}, -- 0C + {ctype='unsigned int', label='TP', fn=percent}, -- 10 + {ctype='unsigned short', label='Flags', fn=bin+{2}}, -- 14 + {ctype='unsigned short', label='_unknown1'}, -- 16 + {ctype='unsigned short', label='Index', fn=index}, -- 18 + {ctype='unsigned short', label='_unknown2'}, -- 1A + {ctype='unsigned char', label='_unknown3'}, -- 1C + {ctype='unsigned char', label='HP%', fn=percent}, -- 1D + {ctype='unsigned char', label='MP%', fn=percent}, -- 1E + {ctype='unsigned char', label='_unknown4'}, -- 1F + {ctype='unsigned short', label='Zone', fn=zone}, -- 20 + {ctype='unsigned char', label='Main job', fn=job}, -- 22 + {ctype='unsigned char', label='Main job level'}, -- 23 + {ctype='unsigned char', label='Sub job', fn=job}, -- 24 + {ctype='unsigned char', label='Sub job level'}, -- 25 + {ctype='unsigned char', label='Master Level'}, -- 26 + {ctype='boolbit', label='Master Breaker'}, -- 27 + {ctype='bit[7]', label='_junk2'}, -- 27 + {ctype='char*', label='Name'}, -- 28 +} + +-- Unnamed 0xDE packet +-- 8 bytes long, sent in response to opening/closing mog house. Occasionally sent when zoning. +-- Injecting it with different values has no obvious effect. +--[[fields.incoming[0x0DE] = L{ + {ctype='unsigned char', label='type'}, -- 04 Was always 0x4 for opening/closing mog house + {ctype='data[3]', label='_junk1'}, -- 05 Looked like junk +}]] + +-- Char Update +fields.incoming[0x0DF] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned int', label='HP'}, -- 08 + {ctype='unsigned int', label='MP'}, -- 0C + {ctype='unsigned int', label='TP', fn=percent}, -- 10 + {ctype='unsigned short', label='Index', fn=index}, -- 14 + {ctype='unsigned char', label='HPP', fn=percent}, -- 16 + {ctype='unsigned char', label='MPP', fn=percent}, -- 17 + {ctype='unsigned short', label='_unknown1'}, -- 18 + {ctype='unsigned short', label='_unknown2'}, -- 1A + {ctype='unsigned short', label='Monstrosity Species'}, -- 1C High bit is always set while in monstrosity and determines the display of the third name + {ctype='unsigned char', label='Monstrosity Name 1'}, -- 1E + {ctype='unsigned char', label='Monstrosity Name 2'}, -- 1F + {ctype='unsigned char', label='Main job', fn=job}, -- 20 + {ctype='unsigned char', label='Main job level'}, -- 21 + {ctype='unsigned char', label='Sub job', fn=job}, -- 22 + {ctype='unsigned char', label='Sub job level'}, -- 23 + {ctype='unsigned char', label='Master Level'}, -- 24 + {ctype='boolbit', label='Master Breaker'}, -- 25 + {ctype='bit[7]', label='_junk2'}, -- 25 +} + +-- Unknown packet 0x0E0: I still can't make heads or tails of the content. The packet is always 8 bytes long. + + +-- Linkshell Equip +fields.incoming[0x0E0] = L{ + {ctype='unsigned char', label='Linkshell Number'}, -- 04 + {ctype='unsigned char', label='Inventory Slot'}, -- 05 + {ctype='unsigned short', label='_junk1'}, -- 06 +} + +-- Party Member List +fields.incoming[0x0E1] = L{ + {ctype='unsigned short', label='Party ID'}, -- 04 For whatever reason, this is always valid ASCII in my captured packets. + {ctype='unsigned short', label='_unknown1', const=0x8000}, -- 06 Likely contains information about the current chat mode and vote count +} + +-- Char Info +fields.incoming[0x0E2] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned int', label='HP'}, -- 08 + {ctype='unsigned int', label='MP'}, -- 0A + {ctype='unsigned int', label='TP', fn=percent}, -- 10 + {ctype='unsigned int', label='_unknown1'}, -- 14 Looks like it could be flags for something. + {ctype='unsigned short', label='Index', fn=index}, -- 18 + {ctype='unsigned char', label='_unknown2'}, -- 1A + {ctype='unsigned char', label='_unknown3'}, -- 1B + {ctype='unsigned char', label='_unknown4'}, -- 1C + {ctype='unsigned char', label='HPP', fn=percent}, -- 1D + {ctype='unsigned char', label='MPP', fn=percent}, -- 1E + {ctype='unsigned char', label='_unknown5'}, -- 1F + {ctype='unsigned char', label='_unknown6'}, -- 20 + {ctype='unsigned char', label='_unknown7'}, -- 21 Could be an initialization for the name. 0x01 observed. + {ctype='char*', label='Name'}, -- 22 * Maybe a base stat +} + +-- Toggle Heal +fields.incoming[0x0E8] = L{ + {ctype='unsigned char', label='Movement'}, -- 04 02 if caused by movement + {ctype='unsigned char', label='_unknown2'}, -- 05 00 observed + {ctype='unsigned char', label='_unknown3'}, -- 06 00 observed + {ctype='unsigned char', label='_unknown4'}, -- 07 00 observed +} + +-- Widescan Mob +fields.incoming[0x0F4] = L{ + {ctype='unsigned short', label='Index', fn=index}, -- 04 + {ctype='unsigned char', label='Level'}, -- 06 + {ctype='unsigned char', label='Type', fn=e+{'ws mob'}}, -- 07 + {ctype='short', label='X Offset', fn=pixel}, -- 08 Offset on the map + {ctype='short', label='Y Offset', fn=pixel}, -- 0A + {ctype='char[16]', label='Name'}, -- 0C Slugged, may not extend all the way to 27. Up to 25 has been observed. This will be used if Type == 0 +} + +-- Widescan Track +fields.incoming[0x0F5] = L{ + {ctype='float', label='X'}, -- 04 + {ctype='float', label='Z'}, -- 08 + {ctype='float', label='Y'}, -- 0C + {ctype='unsigned char', label='Level'}, -- 10 + {ctype='unsigned char', label='_padding1'}, -- 11 + {ctype='unsigned short', label='Index', fn=index}, -- 12 + {ctype='unsigned int', label='Status', fn=e+{'ws track'}}, -- 14 +} + +-- Widescan Mark +fields.incoming[0x0F6] = L{ + {ctype='unsigned int', label='Type', fn=e+{'ws mark'}}, -- 04 +} + +enums['reraise'] = { + [0x01] = 'Raise dialogue', + [0x02] = 'Tractor dialogue', +} + +-- Reraise Activation +fields.incoming[0x0F9] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned short', label='Index', fn=index}, -- 08 + {ctype='unsigned char', label='Category', fn=e+{'reraise'}}, -- 0A + {ctype='unsigned char', label='_unknown1'}, -- 0B +} + +-- Furniture Interaction +fields.incoming[0x0FA] = L{ + {ctype='unsigned short', label='Item', fn=item}, -- 04 + {ctype='data[6]', label='_unknown1'}, -- 06 Always 00s for me + {ctype='unsigned char', label='Safe Slot'}, -- 0C Safe slot for the furniture being interacted with + {ctype='data[3]', label='_unknown2'}, -- 0D Takes values, but doesn't look particularly meaningful +} + +-- Bazaar item listing +fields.incoming[0x105] = L{ + {ctype='unsigned int', label='Price', fn=gil}, -- 04 + {ctype='unsigned int', label='Count'}, -- 08 + {ctype='unsigned short', label='_unknown1'}, -- 0C + {ctype='unsigned short', label='Item', fn=item}, -- 0E + {ctype='unsigned char', label='Inventory Index'}, -- 10 This is the seller's inventory index of the item +} + +-- Bazaar Seller Info Packet +-- Information on the purchase sent to the buyer when they attempt to buy +-- something from a bazaar (whether or not they are successful) +fields.incoming[0x106] = L{ + {ctype='unsigned int', label='Type', fn=e+{'try'}}, -- 04 + {ctype='char[16]', label='Name'}, -- 08 +} + +-- Bazaar closed +-- Sent when the bazaar closes while you're browsing it +-- This includes you buying the last item which leads to the message: +-- "Player's bazaar was closed midway through your transaction" +fields.incoming[0x107] = L{ + {ctype='char[16]', label='Name'}, -- 04 +} + +-- Bazaar visitor +-- Sent when someone opens your bazaar +fields.incoming[0x108] = L{ + {ctype='unsigned int', label='ID', fn=id}, -- 04 + {ctype='unsigned int', label='Type', fn=e+{'bazaar'}}, -- 08 + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 0C Always zero? + {ctype='unsigned char', label='_unknown2'}, -- 0D Possibly junk, often zero, sometimes random + {ctype='unsigned short', label='Index', fn=index}, -- 0E + {ctype='char[16]', label='Name'}, -- 10 +} + +-- Bazaar Purchase Info Packet +-- Information on the purchase sent to the buyer when the purchase is successful. +fields.incoming[0x109] = L{ + {ctype='unsigned int', label='Buyer ID', fn=id}, -- 04 + {ctype='unsigned int', label='Quantity'}, -- 08 + {ctype='unsigned short', label='Buyer Index', fn=index}, -- 0C + {ctype='unsigned short', label='Bazaar Index', fn=index}, -- 0E + {ctype='char[16]', label='Buyer Name'}, -- 10 + {ctype='unsigned int', label='_unknown1'}, -- 20 Was 05 00 02 00 for me +} + +-- Bazaar Buyer Info Packet +-- Information on the purchase sent to the seller when a sale is successful. +fields.incoming[0x10A] = L{ + {ctype='unsigned int', label='Quantity'}, -- 04 + {ctype='unsigned short', label='Item ID'}, -- 08 + {ctype='char[16]', label='Buyer Name'}, -- 0A + {ctype='unsigned int', label='_unknown1'}, -- 1A Was 00 00 00 00 for me + {ctype='unsigned short', label='_unknown2'}, -- 1C Was 64 00 for me. Seems to be variable length? Also got 32 32 00 00 00 00 00 00 once. +} + +-- Bazaar Open Packet +-- Packet sent when you open your bazaar. +fields.incoming[0x10B] = L{ + {ctype='unsigned int', label='_unknown1'}, -- 04 Was 00 00 00 00 for me +} + +-- Sparks update packet +fields.incoming[0x110] = L{ + {ctype='unsigned int', label='Sparks Total'}, -- 04 + {ctype='unsigned char', label='Unity (Shared) designator'}, -- 08 Unity (Shared) designator (0=A, 1=B, 2=C, etc.) + {ctype='unsigned char', label='Unity (Person) designator '}, -- 09 The game does not distinguish these + {ctype='char[6]', label='_unknown2'}, -- 0A Currently all 0xFF'd, never seen it change. +} + +types.roe_quest = L{ + {ctype='bit[12]', label='RoE Quest ID'}, -- 00 + {ctype='bit[20]', label='RoE Quest Progress'}, -- 01 +} + +-- Eminence Update +fields.incoming[0x111] = L{ + {ref=types.roe_quest, count=30}, -- 04 + {ctype='data[132]', label='_junk'}, -- 7C All 0s observed. Likely reserved in case they decide to expand allowed objectives. + {ctype='bit[12]', label='Limited Time RoE Quest ID'}, -- 100 + {ctype='bit[20]', label='Limited Time RoE Quest Progress'}, -- 101 upper 4 +} + + +-- RoE Quest Log +fields.incoming[0x112] = L{ + {ctype='data[128]', label='RoE Quest Bitfield'}, -- 04 See next line + -- Bitpacked quest completion flags. The position of the bit is the quest ID. + -- Data regarding available quests and repeatability is handled client side or + -- somewhere else + {ctype='unsigned int', label='Order'}, -- 84 0,1,2,3 +} + +--Currency Info (Currencies I) +fields.incoming[0x113] = L{ + {ctype='signed int', label='Conquest Points (San d\'Oria)'}, -- 04 + {ctype='signed int', label='Conquest Points (Bastok)'}, -- 08 + {ctype='signed int', label='Conquest Points (Windurst)'}, -- 0C + {ctype='unsigned short', label='Beastman Seals'}, -- 10 + {ctype='unsigned short', label='Kindred Seals'}, -- 12 + {ctype='unsigned short', label='Kindred Crests'}, -- 14 + {ctype='unsigned short', label='High Kindred Crests'}, -- 16 + {ctype='unsigned short', label='Sacred Kindred Crests'}, -- 18 + {ctype='unsigned short', label='Ancient Beastcoins'}, -- 1A + {ctype='unsigned short', label='Valor Points'}, -- 1C + {ctype='unsigned short', label='Scylds'}, -- 1E + {ctype='signed int', label='Guild Points (Fishing)'}, -- 20 + {ctype='signed int', label='Guild Points (Woodworking)'}, -- 24 + {ctype='signed int', label='Guild Points (Smithing)'}, -- 28 + {ctype='signed int', label='Guild Points (Goldsmithing)'}, -- 2C + {ctype='signed int', label='Guild Points (Weaving)'}, -- 30 + {ctype='signed int', label='Guild Points (Leathercraft)'}, -- 34 + {ctype='signed int', label='Guild Points (Bonecraft)'}, -- 38 + {ctype='signed int', label='Guild Points (Alchemy)'}, -- 3C + {ctype='signed int', label='Guild Points (Cooking)'}, -- 40 + {ctype='signed int', label='Cinders'}, -- 44 + {ctype='unsigned char', label='Synergy Fewell (Fire)'}, -- 48 + {ctype='unsigned char', label='Synergy Fewell (Ice)'}, -- 49 + {ctype='unsigned char', label='Synergy Fewell (Wind)'}, -- 4A + {ctype='unsigned char', label='Synergy Fewell (Earth)'}, -- 4B + {ctype='unsigned char', label='Synergy Fewell (Lightning)'}, -- 4C + {ctype='unsigned char', label='Synergy Fewell (Water)'}, -- 4D + {ctype='unsigned char', label='Synergy Fewell (Light)'}, -- 4E + {ctype='unsigned char', label='Synergy Fewell (Dark)'}, -- 4F + {ctype='signed int', label='Ballista Points'}, -- 50 + {ctype='signed int', label='Fellow Points'}, -- 54 + {ctype='unsigned short', label='Chocobucks (San d\'Oria)'}, -- 58 + {ctype='unsigned short', label='Chocobucks (Bastok)'}, -- 5A + {ctype='unsigned short', label='Chocobucks (Windurst)'}, -- 5C + {ctype='unsigned short', label='Daily Tally'}, -- 5E + {ctype='signed int', label='Research Marks'}, -- 60 + {ctype='unsigned char', label='Wizened Tunnel Worms'}, -- 64 + {ctype='unsigned char', label='Wizened Morion Worms'}, -- 65 + {ctype='unsigned char', label='Wizened Phantom Worms'}, -- 66 + {ctype='char', label='_unknown1'}, -- 67 Currently holds no value + {ctype='signed int', label='Moblin Marbles'}, -- 68 + {ctype='unsigned short', label='Infamy'}, -- 6C + {ctype='unsigned short', label='Prestige'}, -- 6E + {ctype='signed int', label='Legion Points'}, -- 70 + {ctype='signed int', label='Sparks of Eminence'}, -- 74 + {ctype='signed int', label='Shining Stars'}, -- 78 + {ctype='signed int', label='Imperial Standing'}, -- 7C + {ctype='signed int', label='Assault Points (Leujaoam Sanctum)'}, -- 80 + {ctype='signed int', label='Assault Points (M.J.T.G.)'}, -- 84 + {ctype='signed int', label='Assault Points (Lebros Cavern)'}, -- 88 + {ctype='signed int', label='Assault Points (Periqia)'}, -- 8C + {ctype='signed int', label='Assault Points (Ilrusi Atoll)'}, -- 90 + {ctype='signed int', label='Nyzul Tokens'}, -- 94 + {ctype='signed int', label='Zeni'}, -- 98 + {ctype='signed int', label='Jettons'}, -- 9C + {ctype='signed int', label='Therion Ichor'}, -- A0 + {ctype='signed int', label='Allied Notes'}, -- A4 + {ctype='unsigned short', label='A.M.A.N. Vouchers Stored'}, -- A8 + {ctype='unsigned short', label="Login Points"}, -- AA + {ctype='signed int', label='Cruor'}, -- AC + {ctype='signed int', label='Resistance Credits'}, -- B0 + {ctype='signed int', label='Dominion Notes'}, -- B4 + {ctype='unsigned char', label='5th Echelon Battle Trophies'}, -- B8 + {ctype='unsigned char', label='4th Echelon Battle Trophies'}, -- B9 + {ctype='unsigned char', label='3rd Echelon Battle Trophies'}, -- BA + {ctype='unsigned char', label='2nd Echelon Battle Trophies'}, -- BB + {ctype='unsigned char', label='1st Echelon Battle Trophies'}, -- BC + {ctype='unsigned char', label='Cave Conservation Points'}, -- BD + {ctype='unsigned char', label='Imperial Army ID Tags'}, -- BE + {ctype='unsigned char', label='Op Credits'}, -- BF + {ctype='signed int', label='Traverser Stones'}, -- C0 + {ctype='signed int', label='Voidstones'}, -- C4 + {ctype='signed int', label='Kupofried\'s Corundums'}, -- C8 + {ctype='unsigned char', label='Moblin Pheromone Sacks'}, -- CC + {ctype='data[1]', label='_unknown2'}, -- CD + {ctype='unsigned char', label="Rems Tale Chapter 1"}, -- CE + {ctype='unsigned char', label="Rems Tale Chapter 2"}, -- CF + {ctype='unsigned char', label="Rems Tale Chapter 3"}, -- D0 + {ctype='unsigned char', label="Rems Tale Chapter 4"}, -- D1 + {ctype='unsigned char', label="Rems Tale Chapter 5"}, -- D2 + {ctype='unsigned char', label="Rems Tale Chapter 6"}, -- D3 + {ctype='unsigned char', label="Rems Tale Chapter 7"}, -- D4 + {ctype='unsigned char', label="Rems Tale Chapter 8"}, -- D5 + {ctype='unsigned char', label="Rems Tale Chapter 9"}, -- D6 + {ctype='unsigned char', label="Rems Tale Chapter 10"}, -- D7 + {ctype='data[8]', label="_unknown3"}, -- D8 + {ctype='signed int', label="Reclamation Marks"}, -- E0 + {ctype='signed int', label='Unity Accolades'}, -- E4 + {ctype='unsigned short', label="Fire Crystals"}, -- E8 + {ctype='unsigned short', label="Ice Crystals"}, -- EA + {ctype='unsigned short', label="Wind Crystals"}, -- EC + {ctype='unsigned short', label="Earth Crystals"}, -- EE + {ctype='unsigned short', label="Lightning Crystals"}, -- E0 + {ctype='unsigned short', label="Water Crystals"}, -- F2 + {ctype='unsigned short', label="Light Crystals"}, -- F4 + {ctype='unsigned short', label="Dark Crystals"}, -- F6 + {ctype='signed int', label="Deeds"}, -- F8 +} + +-- Fish Bite Info +fields.incoming[0x115] = L{ + {ctype='unsigned short', label='_unknown1'}, -- 04 + {ctype='unsigned short', label='_unknown2'}, -- 06 + {ctype='unsigned short', label='_unknown3'}, -- 08 + {ctype='unsigned int', label='Fish Bite ID'}, -- 0A Unique to the type of fish that bit + {ctype='unsigned short', label='_unknown4'}, -- 0E + {ctype='unsigned short', label='_unknown5'}, -- 10 + {ctype='unsigned short', label='_unknown6'}, -- 12 + {ctype='unsigned int', label='Catch Key'}, -- 14 This value is used in the catch key of the 0x110 packet when catching a fish +} + +-- Equipset Build Response +fields.incoming[0x116] = L{ + {ref=types.equipset_build, lookup={res.slots, 0x00}, count=0x10}, +} + +func.incoming[0x117] = {} +func.incoming[0x117].base = L{ + {ctype='unsigned char', label='Count'}, -- 04 + {ctype='unsigned char[3]', label='_unknown1'}, -- 05 +} + +-- Equipset +fields.incoming[0x117] = function(data, count) + count = count or data:byte(5) + + return func.incoming[0x117].base + L{ + -- Only the number given in Count will be properly populated, the rest is junk + {ref=types.equipset, count=count}, -- 08 + {ctype='data[%u]':format((16 - count) * 4), label='_junk1'}, -- 08 + 4 * count + {ref=types.equipset, lookup={res.slots, 0x00}, count=0x10}, -- 48 + } +end + +-- Currency Info (Currencies2) +fields.incoming[0x118] = L{ + {ctype='signed int', label='Bayld'}, -- 04 + {ctype='unsigned short', label='Kinetic Units'}, -- 08 + {ctype='unsigned char', label='Coalition Imprimaturs'}, -- 0A + {ctype='unsigned char', label='Mystical Canteens'}, -- 0B + {ctype='signed int', label='Obsidian Fragments'}, -- 0C + {ctype='unsigned short', label='Lebondopt Wings Stored'}, -- 10 + {ctype='unsigned short', label='Pulchridopt Wings Stored'}, -- 12 + {ctype='signed int', label='Mweya Plasm Corpuscles'}, -- 14 + {ctype='unsigned char', label='Ghastly Stones Stored'}, -- 18 + {ctype='unsigned char', label='Ghastly Stones +1 Stored'}, -- 19 + {ctype='unsigned char', label='Ghastly Stones +2 Stored'}, -- 1A + {ctype='unsigned char', label='Verdigris Stones Stored'}, -- 1B + {ctype='unsigned char', label='Verdigris Stones +1 Stored'}, -- 1C + {ctype='unsigned char', label='Verdigris Stones +2 Stored'}, -- 1D + {ctype='unsigned char', label='Wailing Stones Stored'}, -- 1E + {ctype='unsigned char', label='Wailing Stones +1 Stored'}, -- 1F + {ctype='unsigned char', label='Wailing Stones +2 Stored'}, -- 20 + {ctype='unsigned char', label='Snowslit Stones Stored'}, -- 21 + {ctype='unsigned char', label='Snowslit Stones +1 Stored'}, -- 22 + {ctype='unsigned char', label='Snowslit Stones +2 Stored'}, -- 23 + {ctype='unsigned char', label='Snowtip Stones Stored'}, -- 24 + {ctype='unsigned char', label='Snowtip Stones +1 Stored'}, -- 25 + {ctype='unsigned char', label='Snowtip Stones +2 Stored'}, -- 26 + {ctype='unsigned char', label='Snowdim Stones Stored'}, -- 27 + {ctype='unsigned char', label='Snowdim Stones +1 Stored'}, -- 28 + {ctype='unsigned char', label='Snowdim Stones +2 Stored'}, -- 29 + {ctype='unsigned char', label='Snoworb Stones Stored'}, -- 2A + {ctype='unsigned char', label='Snoworb Stones +1 Stored'}, -- 2B + {ctype='unsigned char', label='Snoworb Stones +2 Stored'}, -- 2C + {ctype='unsigned char', label='Leafslit Stones Stored'}, -- 2D + {ctype='unsigned char', label='Leafslit Stones +1 Stored'}, -- 2E + {ctype='unsigned char', label='Leafslit Stones +2 Stored'}, -- 2F + {ctype='unsigned char', label='Leaftip Stones Stored'}, -- 30 + {ctype='unsigned char', label='Leaftip Stones +1 Stored'}, -- 31 + {ctype='unsigned char', label='Leaftip Stones +2 Stored'}, -- 32 + {ctype='unsigned char', label='Leafdim Stones Stored'}, -- 33 + {ctype='unsigned char', label='Leafdim Stones +1 Stored'}, -- 34 + {ctype='unsigned char', label='Leafdim Stones +2 Stored'}, -- 35 + {ctype='unsigned char', label='Leaforb Stones Stored'}, -- 36 + {ctype='unsigned char', label='Leaforb Stones +1 Stored'}, -- 37 + {ctype='unsigned char', label='Leaforb Stones +2 Stored'}, -- 38 + {ctype='unsigned char', label='Duskslit Stones Stored'}, -- 39 + {ctype='unsigned char', label='Duskslit Stones +1 Stored'}, -- 3A + {ctype='unsigned char', label='Duskslit Stones +2 Stored'}, -- 3B + {ctype='unsigned char', label='Dusktip Stones Stored'}, -- 3C + {ctype='unsigned char', label='Dusktip Stones +1 Stored'}, -- 3D + {ctype='unsigned char', label='Dusktip Stones +2 Stored'}, -- 3E + {ctype='unsigned char', label='Duskdim Stones Stored'}, -- 3F + {ctype='unsigned char', label='Duskdim Stones +1 Stored'}, -- 40 + {ctype='unsigned char', label='Duskdim Stones +2 Stored'}, -- 41 + {ctype='unsigned char', label='Duskorb Stones Stored'}, -- 42 + {ctype='unsigned char', label='Duskorb Stones +1 Stored'}, -- 43 + {ctype='unsigned char', label='Duskorb Stones +2 Stored'}, -- 44 + {ctype='unsigned char', label='Pellucid Stones Stored'}, -- 45 + {ctype='unsigned char', label='Fern Stones Stored'}, -- 46 + {ctype='unsigned char', label='Taupe Stones Stored'}, -- 47 + {ctype='unsigned short', label='Mellidopt Wings Stored'}, -- 48 + {ctype='unsigned short', label='Escha Beads'}, -- 4A + {ctype='signed int', label='Escha Silt'}, -- 4C + {ctype='signed int', label='Potpourri'}, -- 50 + {ctype='signed int', label='Hallmarks'}, -- 54 + {ctype='signed int', label='Total Hallmarks'}, -- 58 + {ctype='signed int', label='Badges of Gallantry'}, -- 5C + {ctype='signed int', label='Crafter Points'}, -- 60 + {ctype='unsigned char', label='Fire Crystals Set'}, -- 64 + {ctype='unsigned char', label='Ice Crystals Set'}, -- 65 + {ctype='unsigned char', label='Wind Crystals Set'}, -- 66 + {ctype='unsigned char', label='Earth Crystals Set'}, -- 67 + {ctype='unsigned char', label='Lightning Crystals Set'}, -- 68 + {ctype='unsigned char', label='Water Crystals Set'}, -- 69 + {ctype='unsigned char', label='Light Crystals Set'}, -- 6A + {ctype='unsigned char', label='Dark Crystals Set'}, -- 6B + {ctype='unsigned char', label='MC-S-SR01s Set'}, -- 6C + {ctype='unsigned char', label='MC-S-SR02s Set'}, -- 6D + {ctype='unsigned char', label='MC-S-SR03s Set'}, -- 6E + {ctype='unsigned char', label='Liquefaction Spheres Set'}, -- 6F + {ctype='unsigned char', label='Induration Spheres Set'}, -- 70 + {ctype='unsigned char', label='Detonation Spheres Set'}, -- 71 + {ctype='unsigned char', label='Scission Spheres Set'}, -- 72 + {ctype='unsigned char', label='Impaction Spheres Set'}, -- 73 + {ctype='unsigned char', label='Reverberation Spheres Set'}, -- 74 + {ctype='unsigned char', label='Transfixion Spheres Set'}, -- 75 + {ctype='unsigned char', label='Compression Spheres Set'}, -- 76 + {ctype='unsigned char', label='Fusion Spheres Set'}, -- 77 + {ctype='unsigned char', label='Distortion Spheres Set'}, -- 78 + {ctype='unsigned char', label='Fragmentation Spheres Set'}, -- 79 + {ctype='unsigned char', label='Gravitation Spheres Set'}, -- 7A + {ctype='unsigned char', label='Light Spheres Set'}, -- 7B + {ctype='unsigned char', label='Darkness Spheres Set'}, -- 7C + {ctype='data[0x03]', label='_unknown1'}, -- 7D Presumably Unused Padding + {ctype='signed int', label='Silver A.M.A.N. Vouchers Stored'}, -- 80 +} + +types.ability_recast = L{ + {ctype='unsigned short', label='Duration', fn=div+{1}}, -- 00 + {ctype='unsigned char', label='_unknown1', const=0x00}, -- 02 + {ctype='unsigned char', label='Recast', fn=arecast}, -- 03 + {ctype='unsigned int', label='_unknown2'} -- 04 +} + +-- Ability timers +fields.incoming[0x119] = L{ + {ref=types.ability_recast, count=0x1F}, -- 04 +} + +return fields + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/queues.lua b/Data/DefaultContent/Libraries/addons/addons/libs/queues.lua new file mode 100644 index 0000000..4b92370 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/queues.lua @@ -0,0 +1,165 @@ +--[[ + A library providing advanced queue support and better optimizations for queue-based operations. +]] + +_libs = _libs or {} + +require('tables') + +local table = _libs.tables + +local queue = {} + +_libs.queues = queue + +_raw = _raw or {} +_raw.table = _raw.table or {} + +_meta = _meta or {} +_meta.Q = {} +_meta.Q.__index = function(q, k) + if type(k) == 'number' then + if k < 0 then + return rawget(q.data, q.back - k + 1) + else + return rawget(q.data, q.front + k - 1) + end + end + + return rawget(queue, k) or rawget(table, k) +end +_meta.Q.__newindex = function(q, k, v) + error('Cannot assign queue value:', k) +end +_meta.Q.__class = 'Queue' + +function Q(t) + if class(t) == 'Set' then + local q = Q{} + + for el in pairs(t) do + q:push(el) + end + + return q + end + + local q = {} + q.data = setmetatable(t, nil) + q.front = 1 + if class(t) == 'List' then + q.back = t.n + 1 + else + q.back = #t + 1 + end + + return setmetatable(q, _meta.Q) +end + +function queue.empty(q) + return q.front == q.back +end + +function queue.length(q) + return q.back - q.front +end + +function queue.push(q, el) + rawset(q.data, q.back, el) + q.back = q.back + 1 + return q +end + +function queue.pop(q) + if q:empty() then + return nil + end + + local res = rawget(q.data, q.front) + rawset(q.data, q.front, nil) + q.front = q.front + 1 + return res +end + +function queue.insert(q, i, el) + q.back = q.back + 1 + table.insert(q.data, q.front + i - 1, el) + return q +end + +function queue.remove(q, i) + q.back = q.back - 1 + table.remove(q.data, q.front + i - 1) + return q +end + +function queue.it(q) + local key = q.front - 1 + return function() + key = key + 1 + return rawget(q.data, key), key + end +end + +function queue.clear(q) + q.data = {} + q.front = q.back + return q +end + +function queue.copy(q) + local res = {} + + for key = q.front, q.back do + rawset(res, key, rawget(q.data, key)) + end + + res.front = q.front + res.back = q.back + return setmetatable(res, _meta.Q) +end + +function queue.reassign(q, qn) + q:clear() + + for key = qn.front, qn.back do + rawset(q, key, rawget(qn.data, key)) + end + + q.front = qn.front + q.back = qn.back + return q +end + +function queue.sort(q, ...) + _raw.table.sort(q.data, ...) + return q +end + +function queue.tostring(q) + local str = '|' + + for key = q.front, q.back - 1 do + if key > q.front then + str = str .. ' < ' + end + str = str .. tostring(rawget(q.data, key)) + end + + return str .. '|' +end + +_meta.Q.__tostring = queue.tostring + +--[[ +Copyright © 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/readme.md b/Data/DefaultContent/Libraries/addons/addons/libs/readme.md new file mode 100644 index 0000000..318bbc2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/readme.md @@ -0,0 +1 @@ +Shared libraries go here. These can be referenced from any script or addon. diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/resources.lua b/Data/DefaultContent/Libraries/addons/addons/libs/resources.lua new file mode 100644 index 0000000..a33b9cf --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/resources.lua @@ -0,0 +1,226 @@ +--[[ + A library to handle ingame resources, as provided by the Radsources XMLs. It will look for the files in Windower/plugins/resources. +]] + +_libs = _libs or {} + +require('functions') +require('tables') +require('strings') + +local functions, table, string = _libs.functions, _libs.tables, _libs.strings +local files = require('files') +local xml = require('xml') + +local fns = {} + +local slots = {} + +local language_string = _addon and _addon.language and _addon.language:lower() or windower.ffxi.get_info().language:lower() +local language_string_log = language_string .. '_log' +local language_string_short = language_string .. '_short' + +-- The metatable for all sub tables of the root resource table +local resource_mt = {} + +-- The metatable for the root resource table +local resources = setmetatable({}, {__index = function(t, k) + if fns[k] then + t[k] = setmetatable(fns[k](), resource_mt) + return t[k] + end +end}) + +_libs.resources = resources + +local redict = { + name = language_string, + name_log = language_string_log, + name_short = language_string_short, + english = 'en', + japanese = 'ja', + english_log = 'enl', + japanese_log = 'ja', + english_short = 'ens', + japanese_short = 'jas', +} + +-- The metatable for a single resource item (an entry in a sub table of the root resource table) +local resource_entry_mt = {__index = function() + return function(t, k) + return redict[k] and t[redict[k]] or table[k] + end +end()} + +function resource_group(r, fn, attr) + fn = type(fn) == 'function' and fn or functions.equals(fn) + attr = redict[attr] or attr + + local res = {} + for value, id in table.it(r) do + if fn(value[attr]) then + res[id] = value + end + end + + slots[res] = slots[r] + return setmetatable(res, resource_mt) +end + +resource_mt.__class = 'Resource' + +resource_mt.__index = function(t, k) + local res = slots[t] and slots[t]:contains(k) and resource_group:endapply(k) + + if not res then + res = table[k] + if class(res) == 'Resource' then + slots[res] = slots[t] + end + end + + return res +end + +resource_mt.__tostring = function(t) + return '{' .. t:map(table.get:endapply('name')):concat(', ') .. '}' +end + +local resources_path = windower.windower_path .. 'res/' + +local flag_cache = {} +local parse_flags = function(bits, lookup, values) + flag_cache[lookup] = flag_cache[lookup] or {} + + if values and not flag_cache[lookup][bits] and lookup[bits] then + flag_cache[lookup][bits] = S{lookup[bits]} + elseif not flag_cache[lookup][bits] then + local res = S{} + + local rem + local num = bits + local count = 0 + while num > 0 do + num, rem = (num/2):modf() + if rem > 0 then + res:add(values and lookup[2^count] or count) + end + count = count + 1 + end + + flag_cache[lookup][bits] = res + end + + return flag_cache[lookup][bits] +end + +local language_strings = S{'english', 'japanese', 'german', 'french'} + +-- Add resources from files +local post_process +local res_names = S(windower.get_dir(resources_path)):filter(string.endswith-{'.lua'}):map(string.sub-{1, -5}) +for res_name in res_names:it() do + fns[res_name] = function() + local res, slot_table = dofile(resources_path .. res_name .. '.lua') + res = table.map(res, (setmetatable-{resource_entry_mt}):cond(functions.equals('table') .. type)) + slots[res] = S(slot_table) + post_process(res) + return res + end +end + +local lookup = {} +local flag_keys = S{ + 'flags', + 'targets', +} +local fn_cache = {} + +post_process = function(t) + local slot_set = slots[t] + for key in slot_set:it() do + if lookup[key] then + if flag_keys:contains(key) then + fn_cache[key] = function(flags) + return parse_flags(flags, lookup[key], true) + end + else + fn_cache[key] = function(flags) + return parse_flags(flags, lookup[key], false) + end + end + + elseif lookup[key .. 's'] then + fn_cache[key] = function(value) + return value + end + + end + end + + for _, entry in pairs(t) do + for key, fn in pairs(fn_cache) do + if entry[key] ~= nil then + entry[key] = fn(entry[key]) + end + end + end + + for key in pairs(redict) do + slot_set:add(key) + end +end + +lookup = { + elements = resources.elements, + jobs = resources.jobs, + slots = resources.slots, + races = resources.races, + skills = resources.skills, + targets = { + [0x01] = 'Self', + [0x02] = 'Player', + [0x04] = 'Party', + [0x08] = 'Ally', + [0x10] = 'NPC', + [0x20] = 'Enemy', + + [0x60] = 'Object', + [0x9D] = 'Corpse', + }, + flags = { + [0x0001] = 'Flag00', + [0x0002] = 'Flag01', + [0x0004] = 'Flag02', + [0x0008] = 'Flag03', + [0x0010] = 'Can Send POL', + [0x0020] = 'Inscribable', + [0x0040] = 'No Auction', + [0x0080] = 'Scroll', + [0x0100] = 'Linkshell', + [0x0200] = 'Usable', + [0x0400] = 'NPC Tradeable', + [0x0800] = 'Equippable', + [0x1000] = 'No NPC Sale', + [0x2000] = 'No Delivery', + [0x4000] = 'No PC Trade', + [0x8000] = 'Rare', + + [0x6040] = 'Exclusive', + }, +} + +return resources + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/sets.lua b/Data/DefaultContent/Libraries/addons/addons/libs/sets.lua new file mode 100644 index 0000000..0165689 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/sets.lua @@ -0,0 +1,343 @@ +--[[ +A library providing sets as a data structure. +]] + +_libs = _libs or {} + +require('tables') +require('functions') + +local table, functions = _libs.tables, _libs.functions + +set = {} + +local set = set + +_libs.sets = set + +_meta = _meta or {} +_meta.S = {} +_meta.S.__index = function(s, k) return rawget(set, k) or rawget(table, k) end +_meta.S.__class = 'Set' + +function S(t) + t = t or {} + if class(t) == 'Set' then + return t + end + + local s = {} + + if class(t) == 'List' then + for _, val in ipairs(t) do + s[val] = true + end + else + for _, val in pairs(t) do + s[val] = true + end + end + + return setmetatable(s, _meta.S) +end + +function set.empty(s) + return next(s) == nil +end + +function set.flat(s) + for el in pairs(s) do + if type(el) == 'table' then + return false + end + end + + return true +end + +function set.equals(s1, s2) + for el in pairs(s1) do + if not rawget(s2, el) then + return false + end + end + + for el in pairs(s2) do + if not rawget(s1, el) then + return false + end + end + + return true +end + +_meta.S.__eq = set.equals + +function set.union(s1, s2) + if type(s2) ~= 'table' then + s2 = S{s2} + end + + local s = {} + + for el in pairs(s1) do + s[el] = true + end + for el in pairs(s2) do + s[el] = true + end + + return setmetatable(s, _meta.S) +end + +_meta.S.__add = set.union + +function set.intersection(s1, s2) + local s = {} + for el in pairs(s1) do + s[el] = rawget(s2, el) + end + + return setmetatable(s, _meta.S) +end + +_meta.S.__mul = set.intersection + +function set.diff(s1, s2) + if type(s2) ~= 'table' then + s2 = S(s2) + end + + local s = {} + + for el in pairs(s1) do + s[el] = (not rawget(s2, el) and true) or nil + end + + return setmetatable(s, _meta.S) +end + +_meta.S.__sub = set.diff + +function set.sdiff(s1, s2) + local s = {} + for el in pairs(s1) do + s[el] = (not rawget(s2, el) and true) or nil + end + for el in pairs(s2) do + s[el] = (not rawget(s1, el) and true) or nil + end + + return setmetatable(s, _meta.S) +end + +_meta.S.__pow = set.sdiff + +function set.subset(s1, s2) + for el in pairs(s1) do + if not rawget(s2, el) then + return false + end + end + + return true +end + +_meta.S.__le = set.subset + +function set.ssubset(s1, s2) + return s1 <= s2 and s1 ~= s2 +end + +_meta.S.__lt = set.ssubset + +function set.map(s, fn) + local res = {} + for el in pairs(s) do + rawset(res, fn(el), true) + end + + return setmetatable(res, _meta.S) +end + +function set.filter(s, fn) + local res = {} + for el in pairs(s) do + if fn(el) then + rawset(res, el, true) + end + end + + return setmetatable(res, _meta.S) +end + +function set.contains(s, el) + return rawget(s, el) == true +end + +function set.find(s, fn) + if type(fn) ~= 'function' then + fn = functions.equals(fn) + end + + for el in pairs(s) do + if fn(el) then + return el + end + end +end + +function set.add(s, el) + return rawset(s, el, true) +end + +function set.remove(s, el) + return rawset(s, el, nil) +end + +function set.it(s) + local key = nil + return function() + key = next(s, key) + return key + end +end + +function set.clear(s) + for el in pairs(s) do + rawset(s, el, nil) + end + + return s +end + +function set.copy(s, deep) + deep = deep ~= false and true + local res = {} + + for el in pairs(s) do + if deep and type(el) == 'table' then + res[(not rawget(el, 'copy') and el.copy or table.copy)(el)] = true + else + res[el] = true + end + end + + return setmetatable(res, _meta.S) +end + +function set.reassign(s, sn) + return s:clear():union(sn) +end + +function set.tostring(s) + local res = '{' + for el in pairs(s) do + res = res..tostring(el) + if next(s, el) ~= nil then + res = res..', ' + end + end + + return res..'}' +end + +_meta.S.__tostring = set.tostring + +function set.tovstring(s) + local res = '{\n' + for el in pairs(s) do + res = res..'\t'..tostring(el) + if next(s, el) then + res = res..',' + end + res = res..'\n' + end + + return res..'}' +end + +function set.sort(s, ...) + if _libs.lists then + return L(s):sort(...) + end + + return T(s):sort(...) +end + +function set.concat(s, str) + str = str or '' + local res = '' + + for el in pairs(s) do + res = res..tostring(el) + if next(s, el) then + res = res..str + end + end + + return res +end + +function set.format(s, trail, subs) + local first = next(s) + if not first then + return subs or '' + end + + trail = trail or 'and' + + local last + if trail == 'and' then + last = ' and ' + elseif trail == 'or' then + last = ' or ' + elseif trail == 'list' then + last = ', ' + elseif trail == 'csv' then + last = ',' + elseif trail == 'oxford' then + last = ', and ' + elseif trail == 'oxford or' then + last = ', or ' + else + warning('Invalid format for table.format: \''..trail..'\'.') + end + + local res = '' + for v in pairs(s) do + local add = tostring(v) + if trail == 'csv' and add:match('[,"]') then + res = res .. add:gsub('"', '""'):enclose('"') + else + res = res .. add + end + + if next(s, v) then + if next(s, next(s, v)) then + if trail == 'csv' then + res = res .. ',' + else + res = res .. ', ' + end + else + res = res .. last + end + end + end + + return res +end + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/slips.lua b/Data/DefaultContent/Libraries/addons/addons/libs/slips.lua new file mode 100644 index 0000000..3fd7063 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/slips.lua @@ -0,0 +1,187 @@ +_libs = _libs or {} + +require('lists') + +local list = _libs.lists +local math = require('math') + +local slips = {} + +_libs.slips = slips + + +slips.default_storages = {'inventory', 'safe', 'storage', 'locker', 'satchel', 'sack', 'case', 'wardrobe', 'safe2', 'wardrobe2', 'wardrobe3', 'wardrobe4'} +slips.storages = L{29312, 29313, 29314, 29315, 29316, 29317, 29318, 29319, 29320, 29321, 29322, 29323, 29324, 29325, 29326, 29327, 29328, 29329, 29330, 29331, 29332, 29333, 29334, 29335, 29336, 29337, 29338, 29339} +slips.items = { + [slips.storages[1]] = L{16084, 14546, 14961, 15625, 15711, 16085, 14547, 14962, 15626, 15712, 16086, 14548, 14963, 15627, 15713, 16087, 14549, 14964, 15628, 15714, 16088, 14550, 14965, 15629, 15715, 16089, 14551, 14966, 15630, 15716, 16090, 14552, 14967, 15631, 15717, 16091, 14553, 14968, 15632, 15718, 16092, 14554, 14969, 15633, 15719, 16093, 14555, 14970, 15634, 15720, 16094, 14556, 14971, 15635, 15721, 16095, 14557, 14972, 15636, 15722, 16096, 14558, 14973, 15637, 15723, 16097, 14559, 14974, 15638, 15724, 16098, 14560, 14975, 15639, 15725, 16099, 14561, 14976, 15640, 15726, 16100, 14562, 14977, 15641, 15727, 16101, 14563, 14978, 15642, 15728, 16102, 14564, 14979, 15643, 15729, 16103, 14565, 14980, 15644, 15730, 16106, 14568, 14983, 15647, 15733, 16107, 14569, 14984, 15648, 15734, 16108, 14570, 14985, 15649, 15735, 16602, 17741, 18425, 18491, 18588, 18717, 18718, 18850, 18943, 16069, 14530, 14940, 15609, 15695, 16062, 14525, 14933, 15604, 15688, 16064, 14527, 14935, 15606, 15690, 18685, 18065, 17851, 18686, 18025, 18435, 18113, 17951, 17715, 18485, 18408, 18365, 18583, 18417, 18388, 16267, 16268, 16269, 16228, 16229, 15911, 15799, 15800, 15990, 17745, 18121, 16117, 14577, 17857}, -- 168 + [slips.storages[2]] = L{12421, 12549, 12677, 12805, 12933, 13911, 14370, 14061, 14283, 14163, 12429, 12557, 12685, 12813, 12941, 13924, 14371, 14816, 14296, 14175, 13934, 14387, 14821, 14303, 14184, 13935, 14388, 14822, 14304, 14185, 13876, 13787, 14006, 14247, 14123, 13877, 13788, 14007, 14248, 14124, 13908, 14367, 14058, 14280, 14160, 13909, 14368, 14059, 14281, 14161, 16113, 14573, 14995, 15655, 15740, 16115, 14575, 14997, 15657, 15742, 16114, 14574, 14996, 15656, 15741, 16116, 14576, 14998, 15658, 15743, 12818, 18198, 12946, 18043, 12690, 17659, 12296, 12434, 15471, 15472, 15473, 15508, 15509, 15510, 15511, 15512, 15513, 15514, 17710, 17595, 18397, 18360, 18222, 17948, 18100, 15475, 15476, 15477, 15488, 15961, 14815, 14812, 14813, 15244, 15240, 14488, 14905, 15576, 15661, 15241, 14489, 14906, 15577, 15662, 13927, 14378, 14076, 14308, 14180, 13928, 14379, 14077, 14309, 14181, 10438, 10276, 10320, 10326, 10367, 10439, 10277, 10321, 10327, 10368, 10440, 10278, 10322, 10328, 10369, 10441, 10279, 10323, 10329, 10370, 25734, 25735, 25736, 25737, 25738, 25739, 25740, 25741, 25742, 25743, 25744}, -- 155 + [slips.storages[3]] = L{16155, 11282, 15021, 16341, 11376, 16156, 11283, 15022, 16342, 11377, 16157, 11284, 15023, 16343, 11378, 16148, 14590, 15011, 16317, 15757, 16143, 14591, 15012, 16318, 15758, 16146, 14588, 15009, 16315, 15755, 16147, 14589, 15010, 16316, 15756, 15966, 15967, 19041, 17684, 17685, 11636, 15844, 15934, 16258, 18735, 18734, 16291, 16292, 19042, 15935, 16293, 16294, 15936, 18618, 11588, 11545, 16158, 16159, 16160, 16149, 14583, 15007, 16314, 15751, 16141, 14581, 15005, 16312, 15749, 16142, 14582, 15006, 16313, 15750, 10876, 10450, 10500, 11969, 10600, 10877, 10451, 10501, 11970, 10601, 10878, 10452, 10502, 11971, 10602, 19132, 18551, 11798, 11362, 11363, 11625, 15959, 16259, 22299, 26414, 21623, 26219, 21886, 23792, 23793, 23794, 23795, 23796, 22032, 22043}, -- 109 + [slips.storages[4]] = L{12511, 12638, 13961, 14214, 14089, 12512, 12639, 13962, 14215, 14090, 13855, 12640, 13963, 14216, 14091, 13856, 12641, 13964, 14217, 14092, 12513, 12642, 13965, 14218, 14093, 12514, 12643, 13966, 14219, 14094, 12515, 12644, 13967, 14220, 14095, 12516, 12645, 13968, 14221, 14096, 12517, 12646, 13969, 14222, 14097, 13857, 12647, 13970, 14223, 14098, 12518, 12648, 13971, 14099, 14224, 13868, 13781, 13972, 14225, 14100, 13869, 13782, 13973, 14226, 14101, 12519, 12649, 13974, 14227, 14102, 12520, 12650, 13975, 14228, 14103, 15265, 14521, 14928, 15600, 15684, 15266, 14522, 14929, 15601, 15685, 15267, 14523, 14930, 15602, 15686, 16138, 14578, 15002, 15659, 15746, 16139, 14579, 15003, 15660, 15747, 16140, 14580, 15004, 16311, 15748, 16678, 17478, 17422, 17423, 16829, 16764, 17643, 16798, 16680, 16766, 17188, 17812, 17771, 17772, 16887, 17532, 17717, 18702, 17858, 19203, 21461, 21124, 20776, 27786, 27926, 28066, 28206, 28346, 27787, 27927, 28067, 28207, 28347}, -- 138 + [slips.storages[5]] = L{15225, 14473, 14890, 15561, 15352, 15226, 14474, 14891, 15562, 15353, 15227, 14475, 14892, 15563, 15354, 15228, 14476, 14893, 15564, 15355, 15229, 14477, 14894, 15565, 15356, 15230, 14478, 14895, 15566, 15357, 15231, 14479, 14896, 15567, 15358, 15232, 14480, 14897, 15568, 15359, 15233, 14481, 14898, 15569, 15360, 15234, 14482, 14899, 15570, 15361, 15235, 14483, 14900, 15362, 15571, 15236, 14484, 14901, 15572, 15363, 15237, 14485, 14902, 15573, 15364, 15238, 14486, 14903, 15574, 15365, 15239, 14487, 14904, 15575, 15366, 11464, 11291, 15024, 16345, 11381, 11467, 11294, 15027, 16348, 11384, 11470, 11297, 15030, 16351, 11387, 11475, 11302, 15035, 16357, 11393, 11476, 11303, 15036, 16358, 11394, 11477, 11304, 15037, 16359, 11395}, -- 105 + [slips.storages[6]] = L{15072, 15087, 15102, 15117, 15132, 15871, 15073, 15088, 15103, 15118, 15133, 15478, 15074, 15089, 15104, 15119, 15134, 15872, 15075, 15090, 15105, 15120, 15135, 15874, 15076, 15091, 15106, 15121, 15136, 15873, 15077, 15092, 15107, 15122, 15137, 15480, 15078, 15093, 15108, 15123, 15138, 15481, 15079, 15094, 15109, 15124, 15139, 15479, 15080, 15095, 15110, 15125, 15140, 15875, 15081, 15096, 15111, 15126, 15141, 15482, 15082, 15097, 15112, 15127, 15142, 15876, 15083, 15098, 15113, 15128, 15143, 15879, 15084, 15099, 15114, 15129, 15144, 15877, 15085, 15100, 15115, 15130, 15145, 15878, 15086, 15101, 15116, 15131, 15146, 15484, 11465, 11292, 15025, 16346, 11382, 16244, 11468, 11295, 15028, 16349, 11385, 15920, 11471, 11298, 15031, 16352, 11388, 16245, 11478, 11305, 15038, 16360, 11396, 16248, 11480, 11307, 15040, 16362, 11398, 15925}, -- 120 + [slips.storages[7]] = L{15245, 14500, 14909, 15580, 15665, 15246, 14501, 14910, 15581, 15666, 15247, 14502, 14911, 15582, 15667, 15248, 14503, 14912, 15583, 15668, 15249, 14504, 14913, 15584, 15669, 15250, 14505, 14914, 15585, 15670, 15251, 14506, 14915, 15586, 15671, 15252, 14507, 14916, 15587, 15672, 15253, 14508, 14917, 15588, 15673, 15254, 14509, 14918, 15589, 15674, 15255, 14510, 14919, 15590, 15675, 15256, 14511, 14920, 15591, 15676, 15257, 14512, 14921, 15592, 15677, 15258, 14513, 14922, 15593, 15678, 15259, 14514, 14923, 15594, 15679, 11466, 11293, 15026, 16347, 11383, 11469, 11296, 15029, 16350, 11386, 11472, 11299, 15032, 16353, 11389, 11479, 11306, 15039, 16361, 11397, 11481, 11308, 15041, 16363, 11399}, -- 100 + [slips.storages[8]] = L{12008, 12028, 12048, 12068, 12088, 11591, 19253, 12009, 12029, 12049, 12069, 12089, 11592, 19254, 12010, 12030, 12050, 12070, 12090, 11615, 11554, 12011, 12031, 12051, 12071, 12091, 11593, 16203, 12012, 12032, 12052, 12072, 12092, 11594, 16204, 12013, 12033, 12053, 12073, 12093, 11736, 19260, 12014, 12034, 12054, 12074, 12094, 11595, 11750, 12015, 12035, 12055, 12075, 12095, 11616, 11737, 12016, 12036, 12056, 12076, 12096, 11617, 11555, 12017, 12037, 12057, 12077, 12097, 11618, 11738, 12018, 12038, 12058, 12078, 12098, 11596, 16205, 12019, 12039, 12059, 12079, 12099, 11597, 16206, 12020, 12040, 12060, 12080, 12100, 11598, 16207, 12021, 12041, 12061, 12081, 12101, 11599, 16208, 12022, 12042, 12062, 12082, 12102, 11619, 11739, 12023, 12043, 12063, 12083, 12103, 11600, 19255, 12024, 12044, 12064, 12084, 12104, 11601, 16209, 12025, 12045, 12065, 12085, 12105, 11602, 11751, 12026, 12046, 12066, 12086, 12106, 11603, 19256, 12027, 12047, 12067, 12087, 12107, 11620, 19247, 11703, 11704, 11705, 11706, 11707, 11708, 11709, 11710, 11711, 11712, 11713, 11714, 11715, 11716, 11717, 11718, 11719, 11720, 11721, 11722}, -- 160 + [slips.storages[9]] = L{11164, 11184, 11204, 11224, 11244, 11165, 11185, 11205, 11225, 11245, 11166, 11186, 11206, 11226, 11246, 11167, 11187, 11207, 11227, 11247, 11168, 11188, 11208, 11228, 11248, 11169, 11189, 11209, 11229, 11249, 11170, 11190, 11210, 11230, 11250, 11171, 11191, 11211, 11231, 11251, 11172, 11192, 11212, 11232, 11252, 11173, 11193, 11213, 11233, 11253, 11174, 11194, 11214, 11234, 11254, 11175, 11195, 11215, 11235, 11255, 11176, 11196, 11216, 11236, 11256, 11177, 11197, 11217, 11237, 11257, 11178, 11198, 11218, 11238, 11258, 11179, 11199, 11219, 11239, 11259, 11180, 11200, 11220, 11240, 11260, 11181, 11201, 11221, 11241, 11261, 11182, 11202, 11222, 11242, 11262, 11183, 11203, 11223, 11243, 11263}, -- 100 + [slips.storages[10]] = L{11064, 11084, 11104, 11124, 11144, 11065, 11085, 11105, 11125, 11145, 11066, 11086, 11106, 11126, 11146, 11067, 11087, 11107, 11127, 11147, 11068, 11088, 11108, 11128, 11148, 11069, 11089, 11109, 11129, 11149, 11070, 11090, 11110, 11130, 11150, 11071, 11091, 11111, 11131, 11151, 11072, 11092, 11112, 11132, 11152, 11073, 11093, 11113, 11133, 11153, 11074, 11094, 11114, 11134, 11154, 11075, 11095, 11115, 11135, 11155, 11076, 11096, 11116, 11136, 11156, 11077, 11097, 11117, 11137, 11157, 11078, 11098, 11118, 11138, 11158, 11079, 11099, 11119, 11139, 11159, 11080, 11100, 11120, 11140, 11160, 11081, 11101, 11121, 11141, 11161, 11082, 11102, 11122, 11142, 11162, 11083, 11103, 11123, 11143, 11163}, -- 100 + [slips.storages[11]] = L{15297, 15298, 15299, 15919, 15929, 15921, 18871, 16273, 18166, 18167, 18256, 13216, 13217, 13218, 15455, 15456, 181, 182, 183, 184, 129, 11499, 18502, 18855, 19274, 18763, 19110, 15008, 17764, 19101, 365, 366, 367, 15860, 272, 273, 274, 275, 276, 11853, 11956, 11854, 11957, 11811, 11812, 11861, 11862, 3676, 18879, 3647, 3648, 3649, 3677, 18880, 18863, 18864, 15178, 14519, 10382, 11965, 11967, 15752, 15179, 14520, 10383, 11966, 11968, 15753, 10875, 3619, 3620, 3621, 3650, 3652, 10430, 10251, 10593, 10431, 10252, 10594, 10432, 10253, 10595, 10433, 10254, 10596, 10429, 10250, 17031, 17032, 10807, 18881, 10256, 10330, 10257, 10331, 10258, 10332, 10259, 10333, 10260, 10334, 10261, 10335, 10262, 10336, 10263, 10337, 10264, 10338, 10265, 10339, 10266, 10340, 10267, 10341, 10268, 10342, 10269, 10343, 10270, 10344, 10271, 10345, 10446, 10447, 426, 10808, 3654, 265, 266, 267, 269, 270, 271, 18464, 18545, 18563, 18912, 18913, 10293, 10809, 10810, 10811, 10812, 27803, 28086, 27804, 28087, 27805, 28088, 27806, 28089, 27765, 27911, 27760, 27906, 27759, 28661, 286, 27757, 27758, 287, 27899, 28185, 28324, 27898, 28655, 27756, 28511, 21118, 27902, 100, 21117, 87, 20953, 21280, 28652, 28650, 27726, 28509, 28651, 27727, 28510, 27872, 21113, 27873, 21114, 20532, 20533, 27717, 27718}, -- 192 + [slips.storages[12]] = L{2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2074, 2075, 2076, 2077, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725, 2726, 2727}, -- 100 + [slips.storages[13]] = L{10650, 10670, 10690, 10710, 10730, 10651, 10671, 10691, 10711, 10731, 10652, 10672, 10692, 10712, 10732, 10653, 10673, 10693, 10713, 10733, 10654, 10674, 10694, 10714, 10734, 10655, 10675, 10695, 10715, 10735, 10656, 10676, 10696, 10716, 10736, 10657, 10677, 10697, 10717, 10737, 10658, 10678, 10698, 10718, 10738, 10659, 10679, 10699, 10719, 10739, 10660, 10680, 10700, 10720, 10740, 10661, 10681, 10701, 10721, 10741, 10662, 10682, 10702, 10722, 10742, 10663, 10683, 10703, 10723, 10743, 10664, 10684, 10704, 10724, 10744, 10665, 10685, 10705, 10725, 10745, 10666, 10686, 10706, 10726, 10746, 10667, 10687, 10707, 10727, 10747, 10668, 10688, 10708, 10728, 10748, 10669, 10689, 10709, 10729, 10749}, -- 100 + [slips.storages[14]] = L{10901, 10474, 10523, 10554, 10620, 10906, 10479, 10528, 10559, 10625, 10911, 10484, 10533, 10564, 10630, 19799, 18916, 10442, 10280, 16084, 27788, 27928, 28071, 28208, 27649, 27789, 27929, 28072, 28209, 27650, 27790, 27930, 28073, 28210, 27651, 27791, 27931, 28074, 28211, 27652, 27792, 27932, 28075, 28212, 27653, 27793, 27933, 28076, 28213, 27654, 27794, 27934, 28077, 28214, 27655, 27795, 27935, 28078, 28215, 27656, 27796, 27936, 28079, 28216, 27657, 27797, 27937, 28080, 28217, 27658, 27798, 27938, 28081, 28218, 27659, 27799, 27939, 28082, 28219, 27660, 27800, 27940, 28083, 28220, 27661, 27801, 27941, 28084, 28221, 27662, 27802, 27942, 28085, 28222, 21510, 21566, 21622, 21665, 21712, 21769, 21822, 21864, 21912, 21976, 22006, 22088, 22133, 22144, 22219, 26413, 23738, 23741, 23744, 23747, 23750, 23739, 23742, 23745, 23748, 23751, 23740, 23743, 23746, 23749, 23752}, -- 125 + [slips.storages[15]] = L{27663, 27807, 27943, 28090, 28223, 27664, 27808, 27944, 28091, 28224, 27665, 27809, 27945, 28092, 28225, 27666, 27810, 27946, 28093, 28226, 27667, 27811, 27947, 28094, 28227, 27668, 27812, 27948, 28095, 28228, 27669, 27813, 27949, 28096, 28229, 27670, 27814, 27950, 28097, 28230, 27671, 27815, 27951, 28098, 28231, 27672, 27816, 27952, 28099, 28232, 27673, 27817, 27953, 28100, 28233, 27674, 27818, 27954, 28101, 28234, 27675, 27819, 27955, 28102, 28235, 27676, 27820, 27956, 28103, 28236, 27677, 27821, 27957, 28104, 28237, 27678, 27822, 27958, 28105, 28238, 27679, 27823, 27959, 28106, 28239, 27680, 27824, 27960, 28107, 28240, 27681, 27825, 27961, 28108, 28241, 27682, 27826, 27962, 28109, 28242, 27683, 27827, 27963, 28110, 28243}, -- 105 + [slips.storages[16]] = L{27684, 27828, 27964, 28111, 28244, 27685, 27829, 27965, 28112, 28245, 27686, 27830, 27966, 28113, 28246, 27687, 27831, 27967, 28114, 28247, 27688, 27832, 27968, 28115, 28248, 27689, 27833, 27969, 28116, 28249, 27690, 27834, 27970, 28117, 28250, 27691, 27835, 27971, 28118, 28251, 27692, 27836, 27972, 28119, 28252, 27693, 27837, 27973, 28120, 28253, 27694, 27838, 27974, 28121, 28254, 27695, 27839, 27975, 28122, 28255, 27696, 27840, 27976, 28123, 28256, 27697, 27841, 27977, 28124, 28257, 27698, 27842, 27978, 28125, 28258, 27699, 27843, 27979, 28126, 28259, 27700, 27844, 27980, 28127, 28260, 27701, 27845, 27981, 28128, 28261, 27702, 27846, 27982, 28129, 28262, 27703, 27847, 27983, 28130, 28263, 27704, 27848, 27984, 28131, 28264, 27705, 27849, 27985, 28132, 28265, 27706, 27850, 27986, 28133, 28266}, -- 115 + [slips.storages[17]] = L{26624, 26800, 26976, 27152, 27328, 26626, 26802, 26978, 27154, 27330, 26628, 26804, 26980, 27156, 27332, 26630, 26806, 26982, 27158, 27334, 26632, 26808, 26984, 27160, 27336, 26634, 26810, 26986, 27162, 27338, 26636, 26812, 26988, 27164, 27340, 26638, 26814, 26990, 27166, 27342, 26640, 26816, 26992, 27168, 27344, 26642, 26818, 26994, 27170, 27346, 26644, 26820, 26996, 27172, 27348, 26646, 26822, 26998, 27174, 27350, 26648, 26824, 27000, 27176, 27352, 26650, 26826, 27002, 27178, 27354, 26652, 26828, 27004, 27180, 27356, 26654, 26830, 27006, 27182, 27358, 26656, 26832, 27008, 27184, 27360, 26658, 26834, 27010, 27186, 27362, 26660, 26836, 27012, 27188, 27364, 26662, 26838, 27014, 27190, 27366, 26664, 26840, 27016, 27192, 27368, 26666, 26842, 27018, 27194, 27370}, -- 110 + [slips.storages[18]] = L{26625, 26801, 26977, 27153, 27329, 26627, 26803, 26979, 27155, 27331, 26629, 26805, 26981, 27157, 27333, 26631, 26807, 26983, 27159, 27335, 26633, 26809, 26985, 27161, 27337, 26635, 26811, 26987, 27163, 27339, 26637, 26813, 26989, 27165, 27341, 26639, 26815, 26991, 27167, 27343, 26641, 26817, 26993, 27169, 27345, 26643, 26819, 26995, 27171, 27347, 26645, 26821, 26997, 27173, 27349, 26647, 26823, 26999, 27175, 27351, 26649, 26825, 27001, 27177, 27353, 26651, 26827, 27003, 27179, 27355, 26653, 26829, 27005, 27181, 27357, 26655, 26831, 27007, 27183, 27359, 26657, 26833, 27009, 27185, 27361, 26659, 26835, 27011, 27187, 27363, 26661, 26837, 27013, 27189, 27365, 26663, 26839, 27015, 27191, 27367, 26665, 26841, 27017, 27193, 27369, 26667, 26843, 27019, 27195, 27371}, -- 110 + [slips.storages[19]] = L{27715, 27866, 27716, 27867, 278, 281, 284, 3680, 3681, 27859, 28149, 27860, 28150, 21107, 21108, 27625, 27626, 26693, 26694, 26707, 26708, 27631, 27632, 26705, 26706, 27854, 27855, 26703, 26704, 3682, 3683, 3684, 3685, 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694, 3695, 21097, 21098, 26717, 26718, 26728,26719,26720,26889,26890, 21095, 21096, 26738, 26739, 26729, 26730, 26788, 26946, 27281, 27455, 26789, 3698, 20713, 20714, 26798, 26954, 26799, 26955, 3706, 26956, 26957, 3705, 26964, 26965, 27291, 26967, 27293, 26966, 27292, 26968, 27294, 21153, 21154, 21086, 21087, 25606, 26974, 27111, 27296, 27467, 25607, 26975, 27112, 27297, 27468, 14195, 14830, 14831, 13945, 13946, 14832, 13947, 17058, 13948, 14400, 14392, 14393, 14394, 14395, 14396, 14397, 14398, 14399, 11337, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 15819, 15820, 15821, 15822, 15823, 15824, 15825, 15826, 3670, 3672, 3661, 3595, 3665, 3668, 3663, 3674, 3667, 191, 28, 153, 151, 198, 202, 142, 134, 137, 340, 341, 334, 335, 337, 339, 336, 342, 338, 3631, 3632, 3625, 3626, 3628, 3630, 3627, 3633, 3629, 25632, 25633, 25604, 25713, 27325, 25714, 27326, 3651, 25711, 25712, 10925, 10948, 10949, 10950, 10951, 10952, 10953, 10954, 10955, 25657, 25756, 25658, 25757, 25909}, -- 192 + [slips.storages[20]] = L{26740, 26898, 27052, 27237, 27411, 26742, 26900, 27054, 27239, 27413, 26744, 26902, 27056, 27241, 27415, 26746, 26904, 27058, 27243, 27417, 26748, 26906, 27060, 27245, 27419, 26750, 26908, 27062, 27247, 27421, 26752, 26910, 27064, 27249, 27423, 26754, 26912, 27066, 27251, 27425, 26756, 26914, 27068, 27253, 27427, 26758, 26916, 27070, 27255, 27429, 26760, 26918, 27072, 27257, 27431, 26762, 26920, 27074, 27259, 27433, 26764, 26922, 27076, 27261, 27435, 26766, 26924, 27078, 27263, 27437, 26768, 26926, 27080, 27265, 27439, 26770, 26928, 27082, 27267, 27441, 26772, 26930, 27084, 27269, 27443, 26774, 26932, 27086, 27271, 27445, 26776, 26934, 27088, 27273, 27447, 26778, 26936, 27090, 27275, 27449, 26780, 26938, 27092, 27277, 27451, 26782, 26940, 27094, 27279, 27453}, -- 110 + [slips.storages[21]] = L{26741, 26899, 27053, 27238, 27412, 26743, 26901, 27055, 27240, 27414, 26745, 26903, 27057, 27242, 27416, 26747, 26905, 27059, 27244, 27418, 26749, 26907, 27061, 27246, 27420, 26751, 26909, 27063, 27248, 27422, 26753, 26911, 27065, 27250, 27424, 26755, 26913, 27067, 27252, 27426, 26757, 26915, 27069, 27254, 27428, 26759, 26917, 27071, 27256, 27430, 26761, 26919, 27073, 27258, 27432, 26763, 26921, 27075, 27260, 27434, 26765, 26923, 27077, 27262, 27436, 26767, 26925, 27079, 27264, 27438, 26769, 26927, 27081, 27266, 27440, 26771, 26929, 27083, 27268, 27442, 26773, 26931, 27085, 27270, 27444, 26775, 26933, 27087, 27272, 27446, 26777, 26935, 27089, 27274, 27448, 26779, 26937, 27091, 27276, 27450, 26781, 26939, 27093, 27278, 27452, 26783, 26941, 27095, 27280, 27454}, -- 110 + [slips.storages[22]] = L{25639, 25715, 25638, 3707, 3708, 21074, 26406, 25645, 25726, 25648, 25649, 25650, 25758, 25759, 25672, 25673, 282, 279, 280, 268, 25670, 25671, 26520, 25652, 25669, 22017, 22018, 25586, 25587, 10384, 10385, 22019, 22020, 25722, 25585, 25776, 25677, 25678, 25675, 25679, 20668, 20669, 22069, 25755, 3722, 21608, 3713, 3714, 3715, 3717, 3727, 3728, 20577, 3726, 20666, 20667, 21741, 21609, 3723, 26410, 26411, 25850, 21509, 3725, 3720, 21658, 26524, 20665, 26412, 21965, 21966, 21967, 25774, 25838, 25775, 25839, 3724, 3721, 21682, 22072, 21820, 21821, 23731, 26517, 23730, 20573, 20674, 21742, 21860, 22065, 22039, 22124, 22132, 3719, 3738, 26518, 27623, 21867, 21868, 22283, 26516, 20933, 20578, 20568, 3739, 20569, 20570, 22288, 26352, 23737, 22282, 3740, 0, 26545, 21977, 21978, 3742, 26519, 26514, 26515, 3743, 21636, 23753, 23754, 54, 25910, 20571, 23790, 23791, 26489, 22153, 22154, 20574, 20675, 21743, 21861, 22066, 3748, 21638, 23800, 23801, 3749}, -- 142 SE Skipped an index hence the 0 as one of the items + [slips.storages[23]] = L{25659, 25745, 25800, 25858, 25925, 25660, 25746, 25801, 25859, 25926, 25663, 25749, 25804, 25862, 25929, 25664, 25750, 25805, 25863, 25930, 25665, 25751, 25806, 25865, 25931, 25666, 25752, 25807, 25866, 25932, 25661, 25747, 25802, 25860, 25927, 25662, 25748, 25803, 25861, 25928, 25667, 25753, 25808, 25867, 25933, 25668, 25754, 25809, 25868, 25934, 25579, 25779, 25818, 25873, 25940, 25580, 25780, 25819, 25874, 25941, 25590, 25764, 25812, 25871, 25937, 25591, 25765, 25813, 25872, 25938, 25581, 25781, 25820, 25875, 25942, 25582, 25782, 25821, 25876, 25943, 25588, 25762, 25810, 25869, 25935, 25589, 25763, 25811, 25870, 25936, 25583, 25783, 25822, 25877, 25944, 25584, 25784, 25823, 25878, 25945, 25574, 25790, 25828, 25879, 25946, 25575, 25791, 25829, 25880, 25947, 25576, 25792, 25830, 25881, 25948, 25577, 25793, 25831, 25882, 25949, 25578, 25794, 25832, 25883, 25950, 26204, 26205, 26206, 26207, 26208, 25569, 25797, 25835, 25886, 25953, 25573, 25796, 25834, 25885, 25952, 25570, 25798, 25836, 25887, 25954, 25572, 25795, 25833, 25884, 25951, 25571, 25799, 25837, 25888, 25955, 26211, 26210, 26212, 26209, 26213, 21863, 22004, 21744, 21272, 20576, 21761, 26409, 22070, 21681, 22005, 21745, 22068, 21759, 21770, 22067, 21680}, --176 + [slips.storages[24]] = L{23040, 23107, 23174, 23241, 23308, 23041, 23108, 23175, 23242, 23309, 23042, 23109, 23176, 23243, 23310, 23043, 23110, 23177, 23244, 23311, 23044, 23111, 23178, 23245, 23312, 23045, 23112, 23179, 23246, 23313, 23046, 23113, 23180, 23247, 23314, 23047, 23114, 23181, 23248, 23315, 23048, 23115, 23182, 23249, 23316, 23049, 23116, 23183, 23250, 23317, 23050, 23117, 23184, 23251, 23318, 23051, 23118, 23185, 23252, 23319, 23052, 23119, 23186, 23253, 23320, 23053, 23120, 23187, 23254, 23321, 23054, 23121, 23188, 23255, 23322, 23055, 23122, 23189, 23256, 23323, 23056, 23123, 23190, 23257, 23324, 23057, 23124, 23191, 23258, 23325, 23058, 23125, 23192, 23259, 23326, 23059, 23126, 23193, 23260, 23327, 23060, 23127, 23194, 23261, 23328, 23061, 23128, 23195, 23262, 23329, 23062, 23129, 23196, 23263, 23330}, --115 + [slips.storages[25]] = L{23375, 23442, 23509, 23576, 23643, 23376, 23443, 23510, 23577, 23644, 23377, 23444, 23511, 23578, 23645, 23378, 23445, 23512, 23579, 23646, 23379, 23446, 23513, 23580, 23647, 23380, 23447, 23514, 23581, 23648, 23381, 23448, 23515, 23582, 23649, 23382, 23449, 23516, 23583, 23650, 23383, 23450, 23517, 23584, 23651, 23384, 23451, 23518, 23585, 23652, 23385, 23452, 23519, 23586, 23653, 23386, 23453, 23520, 23587, 23654, 23387, 23454, 23521, 23588, 23655, 23388, 23455, 23522, 23589, 23656, 23389, 23456, 23523, 23590, 23657, 23390, 23457, 23524, 23591, 23658, 23391, 23458, 23525, 23592, 23659, 23392, 23459, 23526, 23593, 23660, 23393, 23460, 23527, 23594, 23661, 23394, 23461, 23528, 23595, 23662, 23395, 23462, 23529, 23596, 23663, 23396, 23463, 23530, 23597, 23664, 23397, 23464, 23531, 23598, 23665}, --115 + [slips.storages[26]] = L{23063, 23130, 23197, 23264, 23331, 23064, 23131, 23198, 23265, 23332, 23065, 23132, 23199, 23266, 23333, 23066, 23133, 23200, 23267, 23334, 23067, 23134, 23201, 23268, 23335, 23068, 23135, 23202, 23269, 23336, 23069, 23136, 23203, 23270, 23337, 23070, 23137, 23204, 23271, 23338, 23071, 23138, 23205, 23272, 23339, 23072, 23139, 23206, 23273, 23340, 23073, 23140, 23207, 23274, 23341, 23074, 23141, 23208, 23275, 23342, 23075, 23142, 23209, 23276, 23343, 23076, 23143, 23210, 23277, 23344, 23077, 23144, 23211, 23278, 23345, 23078, 23145, 23212, 23279, 23346, 23079, 23146, 23213, 23280, 23347, 23080, 23147, 23214, 23281, 23348, 23081, 23148, 23215, 23282, 23349, 23082, 23149, 23216, 23283, 23350, 23083, 23150, 23217, 23284, 23351, 23084, 23151, 23218, 23285, 23352}, --110 + [slips.storages[27]] = L{23398, 23465, 23532, 23599, 23666, 23399, 23466, 23533, 23600, 23667, 23400, 23467, 23534, 23601, 23668, 23401, 23468, 23535, 23602, 23669, 23402, 23469, 23536, 23603, 23670, 23403, 23470, 23537, 23604, 23671, 23404, 23471, 23538, 23605, 23672, 23405, 23472, 23539, 23606, 23673, 23406, 23473, 23540, 23607, 23674, 23407, 23474, 23541, 23608, 23675, 23408, 23475, 23542, 23609, 23676, 23409, 23476, 23543, 23610, 23677, 23410, 23477, 23544, 23611, 23678, 23411, 23478, 23545, 23612, 23679, 23412, 23479, 23546, 23613, 23680, 23413, 23480, 23547, 23614, 23681, 23414, 23481, 23548, 23615, 23682, 23415, 23482, 23549, 23616, 23683, 23416, 23483, 23550, 23617, 23684, 23417, 23484, 23551, 23618, 23685, 23418, 23485, 23552, 23619, 23686, 23419, 23486, 23553, 23620, 23687}, --110 + [slips.storages[28]] = L{21515, 21561, 21617, 21670, 21718, 21775, 21826, 21879, 21918, 21971, 22027, 22082, 22108, 22214, 21516, 21562, 21618, 21671, 21719, 21776, 21827, 21880, 21919, 21972, 22028, 22083, 22109, 22215, 21517, 21563, 21619, 21672, 21720, 21777, 21828, 21881, 21920, 21973, 22029, 22084, 22110, 22216, 21518, 21564, 21620, 21673, 21721, 21778, 21829, 21882, 21921, 21974, 22030, 22085, 22111, 22217, 21519, 21565, 21621, 21674, 21722, 21779, 21830, 21883, 21922, 21975, 22031, 22086, 22107, 22218, 22089}, --71 +} + +function slips.get_slip_id(n) + if n > slips.storages:length() then + return nil + end + + return slips.storages[n] +end + +function slips.get_slip_by_id(id) + return slips.items[id] +end + +function slips.get_slip(n) + local id = slips.get_slip_id(n) + + if id == nil then + return nil + end + + return slips.get_slip_by_id(id) +end + +function slips.get_slip_number_by_id(id) + if slips.items[id] == nil then + return nil + end + + return slips.storages:find(id) +end + +function slips.get_slip_id_by_item_id(id) + for _, slip_id in ipairs(slips.storages) do + if slips.items[slip_id]:contains(id) then + return slip_id + end + end + + return nil +end + +function slips.get_slip_by_item_id(id) + local slip_id = slips.get_slip_id_by_item_id(id) + + if slip_id == nil then + return nil + end + + return slips.get_slip_by_id(slip_id) +end + +function slips.get_item_bit_position(id, slip_id) + if slip_id ~= nil then + if slips.items[slip_id] == nil then + return nil + end + + slip = slips.items[slip_id] + else + slip = slips.get_slip_by_item_id(id) + + if slip == nil then + return nil + end + end + + local bit_position = slip:find(id) + + return bit_position +end + +function slips.get_slip_page_by_item_id(id, slip_id) + local bitPosition = slips.get_item_bit_position(id, slip_id) + + if bitPosition == nil then + return nil + end + + return math.floor(bitPosition / 16) + 1 +end + +function slips.player_has_item(id) + local slip_id = slips.get_slip_id_by_item_id(id) + + if slip_id == nil then + return false + end + + local items = windower.ffxi.get_items() + + for _, storage in ipairs(slips.default_storages) do + for _, item in ipairs(items[storage]) do + if item.id == slip_id then + local bit_position = slips.get_item_bit_position(id, slip_id) + local bitmask = item.extdata:byte(math.floor((bit_position - 1) / 8) + 1) + + if bitmask < 0 then + bitmask = bitmask + 256 + end + + local bit = math.floor((bitmask / 2 ^ ((bit_position - 1) % 8)) % 2) + + return bit ~= 0 + end + end + end + + return false +end + +function slips.get_player_items() + local slips_items = T{} + + for _, slip_id in ipairs(slips.storages) do + slips_items[slip_id] = L{} + end + + local items = windower.ffxi.get_items() + + for _, storage in ipairs(slips.default_storages) do + for _, item in ipairs(items[storage]) do + if slips.storages:contains(item.id) then + for bit_position = 0, item.extdata:length() * 8 - 1 do + local bitmask = item.extdata:byte(math.floor(bit_position / 8) + 1) + + if bitmask < 0 then + bitmask = bitmask + 256 + end + + local bit = math.floor((bitmask / 2 ^ (bit_position % 8)) % 2) + + if bit ~= 0 and slips.items[item.id] then + slips_items[item.id]:append(slips.items[item.id][bit_position + 1]) + end + end + end + end + end + + return slips_items +end + +return slips diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket.lua new file mode 100644 index 0000000..d1c0b16 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket.lua @@ -0,0 +1,149 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp4() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/ftp.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/ftp.lua new file mode 100644 index 0000000..bd528ca --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/ftp.lua @@ -0,0 +1,329 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +local PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + address = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local code, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local d, prt, address, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = { self.tp } + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:epsv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function genericform(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +_M.genericform = genericform + +local function sput(u, body) + local putt = genericform(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:epsv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = genericform(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = _M.open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + if type(cmdt.command) == "table" then + local argument = cmdt.argument or {} + local check = cmdt.check or {} + for i,cmd in ipairs(cmdt.command) do + f.try(f.tp:command(cmd, argument[i])) + if check[i] then f.try(f.tp:check(check[i])) end + end + else + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/headers.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/headers.lua new file mode 100644 index 0000000..1eb8223 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/headers.lua @@ -0,0 +1,104 @@ +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/http.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/http.lua new file mode 100644 index 0000000..a386165 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/http.lua @@ -0,0 +1,382 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +-- supported schemes +local SCHEMES = { ["http"] = true } +-- default port for document retrieval +local PORT = 80 + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try((create or socket.tcp)()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port or PORT)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = string.gsub(reqt.authority, "^.-@", "") + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. + url.unescape(reqt.password))) + end + -- if we have proxy authentication information, pass it along + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + if proxy.user and proxy.password then + lower["proxy-authorization"] = + "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) + end + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + host = "", + port = PORT, + path ="/", + scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = PORT end + if not (nreqt.host and nreqt.host ~= "") then + socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") + end + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + local location = headers.location + if not location then return false end + location = string.gsub(location, "%s", "") + if location == "" then return false end + local scheme = string.match(location, "^([%w][%w%+%-%.]*)%:") + if scheme and not SCHEMES[scheme] then return false end + return (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +-- turns an url and a body into a generic request +local function genericform(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t), + target = t + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/smtp.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/smtp.lua new file mode 100644 index 0000000..b113d00 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/smtp.lua @@ -0,0 +1,256 @@ +----------------------------------------------------------------------------- +-- SMTP client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local coroutine = require("coroutine") +local string = require("string") +local math = require("math") +local os = require("os") +local socket = require("socket") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +local headers = require("socket.headers") +local mime = require("mime") + +socket.smtp = {} +local _M = socket.smtp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout for connection +_M.TIMEOUT = 60 +-- default server used to send e-mails +_M.SERVER = "localhost" +-- default port +_M.PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +_M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" +-- default time zone (means we don't know) +_M.ZONE = "-0000" + +--------------------------------------------------------------------------- +-- Low level SMTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function metat.__index:greet(domain) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) +end + +function metat.__index:mail(from) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:rcpt(to) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:data(src, step) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:quit() + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:close() + return self.tp:close() +end + +function metat.__index:login(user, password) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(user) .. "\r\n")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(password) .. "\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:plain(user, password) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:auth(user, password, ext) + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end +end + +-- send message or throw an exception +function metat.__index:send(mailt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i,v in base.ipairs(mailt.rcpt) do + self:rcpt(v) + end + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) +end + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, + _M.TIMEOUT, create)) + local s = base.setmetatable({tp = tp}, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s +end + +-- convert headers to lowercase +local function lower_headers(headers) + local lower = {} + for i,v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +--------------------------------------------------------------------------- +-- Multipart message source +----------------------------------------------------------------------------- +-- returns a hopefully unique mime boundary +local seqno = 0 +local function newboundary() + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) +end + +-- send_message forward declaration +local send_message + +-- yield the headers all at once, it's faster +local function send_headers(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f,v in base.pairs(tosend) do + h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) +end + +-- yield multipart message body from a multipart message table +local function send_multipart(mesgt) + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end +end + +-- yield message body from a source +local function send_source(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function send_string(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) +end + +-- message source +function send_message(mesgt) + if base.type(mesgt.body) == "table" then send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then send_source(mesgt) + else send_string(mesgt) end +end + +-- set defaul headers +local function adjust_headers(mesgt) + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower +end + +function _M.message(mesgt) + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end +end + +--------------------------------------------------------------------------- +-- High level SMTP API +----------------------------------------------------------------------------- +_M.send = socket.protect(function(mailt) + local s = _M.open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() +end) + +return _M
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/tp.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/tp.lua new file mode 100644 index 0000000..b8ebc56 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/tp.lua @@ -0,0 +1,134 @@ +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") + +socket.tp = {} +local _M = socket.tp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +_M.TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + cmd = string.upper(cmd) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = self.c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function _M.connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or _M.TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/socket/url.lua b/Data/DefaultContent/Libraries/addons/addons/libs/socket/url.lua new file mode 100644 index 0000000..d61111e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/socket/url.lua @@ -0,0 +1,309 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed within a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02X", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Unencodes a escaped hexadecimal string into its binary representation +-- Input +-- s: escaped hexadecimal string to be unencoded +-- Returns +-- unescaped binary representation of escaped hexadecimal binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment> +-- <authority> ::= <userinfo>@<host>:<port> +-- <userinfo> ::= <user>[:<password>] +-- <path> :: = {<segment>/}<segment> +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/<path>} is considered part of <path> +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + --local ppath = _M.parse_path(parsed.path or "") + --local url = _M.build_path(ppath) + local url = parsed.path or "" + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + local base_parsed + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local relative_parsed = _M.parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + return _M.build(relative_parsed) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +return _M diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/strings.lua b/Data/DefaultContent/Libraries/addons/addons/libs/strings.lua new file mode 100644 index 0000000..cd7ac1e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/strings.lua @@ -0,0 +1,529 @@ +--[[ + A few string helper functions. +]] + +_libs = _libs or {} + +require('functions') +require('maths') + +local functions, math = _libs.functions, _libs.maths +local table = require('table') + +local string = require('string') + +_libs.strings = string + +_meta = _meta or {} + +debug.setmetatable('', { + __index = function(str, k) + return string[k] or type(k) == 'number' and string.sub(str, k, k) or (_raw and _raw.error or error)('"%s" is not defined for strings':format(tostring(k)), 2) + end, + __unm = functions.negate .. functions.equals, + __unp = functions.equals, +}) + +-- Returns a function that returns the string when called. +function string.fn(str) + return functions.const(str) +end + +-- Returns true if the string contains a substring. +function string.contains(str, sub) + return str:find(sub, nil, true) ~= nil +end + +-- Splits a string into a table by a separator pattern. +function string.psplit(str, sep, maxsplit, include) + maxsplit = maxsplit or 0 + + return str:split(sep, maxsplit, include, false) +end + +local rawsplit = function(str, sep, maxsplit, include, raw) + if not sep or sep == '' then + local res = {} + local key = 0 + for c in str:gmatch('.') do + key = key + 1 + res[key] = c + end + + return res, key + end + + maxsplit = maxsplit or 0 + if raw == nil then + raw = true + end + + local res = {} + local key = 0 + local i = 1 + local startpos, endpos + local match + while i <= #str + 1 do + -- Find the next occurence of sep. + startpos, endpos = str:find(sep, i, raw) + -- If found, get the substring and append it to the table. + if startpos then + match = str:sub(i, startpos - 1) + key = key + 1 + res[key] = match + + if include then + key = key + 1 + res[key] = str:sub(startpos, endpos) + end + + -- If maximum number of splits reached, return + if key == maxsplit - 1 then + key = key + 1 + res[key] = str:sub(endpos + 1) + break + end + i = endpos + 1 + -- If not found, no more separators to split, append the remaining string. + else + key = key + 1 + res[key] = str:sub(i) + break + end + end + + return res, key +end + +-- Splits a string into a table by a separator string. +function string.split(str, sep, maxsplit, include, raw) + local res, key = rawsplit(str, sep, maxsplit, include, raw) + + if _meta.L then + res.n = key + return setmetatable(res, _meta.L) + end + + if _meta.T then + return setmetatable(res, _meta.T) + end + + return res +end + +-- Alias to string.sub, with some syntactic sugar. +function string.slice(str, from, to) + return str:sub(from or 1, to or #str) +end + +-- Inserts a string into a given section of another string. +function string.splice(str, from, to, str2) + return str:sub(1, from - 1)..str2..str:sub(to + 1) +end + +-- Returns an iterator, that goes over every character of the string. +function string.it(str) + return str:gmatch('.') +end + +-- Removes leading and trailing whitespaces and similar characters (tabs, newlines, etc.). +function string.trim(str) + return str:match('^%s*(.-)%s*$') +end + +-- Collapses all types of spaces into exactly one whitespace +function string.spaces_collapse(str) + return str:gsub('%s+', ' '):trim() +end + +-- Removes all characters in chars from str. +function string.stripchars(str, chars) + return (str:gsub('['..chars:escape()..']', '')) +end + +-- Returns the length of a string. +function string.length(str) + return #str +end + +-- Checks it the string starts with the specified substring. +function string.startswith(str, substr) + return str:sub(1, #substr) == substr +end + +-- Checks it the string ends with the specified substring. +function string.endswith(str, substr) + return str:sub(-#substr) == substr +end + +-- Checks if string is enclosed in start and finish. If only one argument is provided, it will check for that string both at the beginning and the end. +function string.enclosed(str, start, finish) + finish = finish or start + return str:startswith(start) and str:endswith(finish) +end + +-- Returns a string with another string prepended. +function string.prepend(str, pre) + return pre..str +end + +-- Returns a string with another string appended. +function string.append(str, post) + return str..post +end + +-- Encloses a string in start and finish. If only one argument is provided, it will enclose it with that string both at the beginning and the end. +function string.enclose(str, start, finish) + finish = finish or start + return start..str..finish +end + +-- Returns the same string with the first letter capitalized. +function string.ucfirst(str) + return str:sub(1, 1):upper()..str:sub(2) +end + +-- Returns the same string with the first letter of every word capitalized. +function string.capitalize(str) + local res = {} + + for _, val in ipairs(str:split(' ')) do + res[#res + 1] = val:ucfirst() + end + + return table.concat(res, ' ') +end + +-- Takes a padding character pad and pads the string str to the left of it, until len is reached. +function string.lpad(str, pad, len) + return (pad:rep(len) .. str):sub(-(len > #str and len or #str)) +end + +-- Takes a padding character pad and pads the string str to the right of it, until len is reached. +function string.rpad(str, pad, len) + return (str .. pad:rep(len)):sub(1, len > #str and len or #str) +end + +-- Returns the string padded with zeroes until the length is len. +function string.zfill(str, len) + return str:lpad('0', len) +end + +-- Checks if a string is empty. +function string.empty(str) + return str == '' +end + +(function() + -- Returns a monowidth hex representation of each character of a string, optionally with a separator between chars. + local hex = string.zfill-{2} .. math.hex .. string.byte + function string.hex(str, sep, from, to) + return str:slice(from, to):split():map(hex):concat(sep or '') + end + + -- Returns a monowidth binary representation of every char of the string, optionally with a separator between chars. + local binary = string.zfill-{8} .. math.binary .. string.byte + function string.binary(str, sep, from, to) + return str:slice(from, to):split():map(binary):concat(sep or '') + end + + -- Returns a string parsed from a hex-represented string. + local hex_r = string.char .. tonumber-{16} + function string.parse_hex(str) + local interpreted_string = str:gsub('0x', ''):gsub('[^%w]', '') + if #interpreted_string % 2 ~= 0 then + (_raw and _raw.error or error)('Invalid input string length', 2) + end + + return (interpreted_string:gsub('%w%w', hex_r)) + end + + -- Returns a string parsed from a binary-represented string. + local binary_r = string.char .. tonumber-{2} + local binary_pattern = '[01]':rep(8) + function string.parse_binary(str) + local interpreted_string = str:gsub('0b', ''):gsub('[^01]', '') + if #interpreted_string % 8 ~= 0 then + (_raw and _raw.error or error)('Invalid input string length', 2) + end + + return (interpreted_string:gsub(binary_pattern, binary_r)) + end +end)() + +-- Returns a string with Lua pattern characters escaped. +function string.escape(str) + return (str:gsub('[[%]%%^$*()%.%+?-]', '%%%1')) +end + +-- Returns a Lua pattern from a wildcard string (with ? and * as placeholders for one and many characters respectively). +function string.wildcard(str) + return (str:gsub('[[%]%%^$()%+-.]', '%%%1'):gsub('*', '.*'):gsub('?', '.')) +end + +-- Returns true if the string matches a wildcard pattern. +string.wmatch = windower.wc_match + +-- Includes the | operator in the pattern for alternative matches in string.find. +function string.mfind(str, full_pattern, ...) + local patterns = full_pattern:split('|') + + local found = {} + for _, pattern in ipairs(patterns) do + local new_found = {str:find(pattern, ...)} + if not found[1] or new_found[1] and new_found[1] < found[1] then + found = new_found + end + end + + return unpack(found) +end + +-- Includes the | operator in the pattern for alternative matches in string.match. +function string.mmatch(str, full_pattern, ...) + local patterns = full_pattern:split('|') + + local found = {} + local index = nil + for _, pattern in ipairs(patterns) do + local start = {str:find(pattern, ...)} + if start and (not index or start < index) then + found = {str:match(pattern, ...)} + index = start + end + end + + return unpack(found) +end + +-- Includes the | operator in the pattern for alternative matches in string.gsub. +function string.mgsub(str, full_pattern, ...) + local patterns = full_pattern:split('|') + + for _, pattern in ipairs(patterns) do + str = str:gsub(pattern, ...) + end + + return str +end + +-- A string.find wrapper for wildcard patterns. +function string.wcfind(str, pattern, ...) + return str:find(pattern:wildcard(), ...) +end + +-- A string.match wrapper for wildcard patterns. +function string.wcmatch(str, pattern, ...) + return str:match(pattern:wildcard(), ...) +end + +-- A string.gmatch wrapper for wildcard patterns. +function string.wcgmatch(str, pattern, ...) + return str:gmatch(pattern:wildcard(), ...) +end + +-- A string.gsub wrapper for wildcard patterns. +function string.wcgsub(str, pattern, ...) + return str:gsub(pattern:wildcard(), ...) +end + +-- Returns a case-insensitive pattern for a given (non-pattern) string. For patterns, see string.ipattern. +function string.istring(str) + return (str:gsub('%a', function(c) return '['..c:upper()..c:lower()..']' end)) +end + +-- Returns a case-insensitive pattern for a given pattern. +function string.ipattern(str) + local res = '' + local percent = false + local val + for c in str:it() do + if c == '%' then + percent = not percent + res = res..c + elseif not percent then + val = string.byte(c) + if val > 64 and val <= 90 or val > 96 and val <= 122 then + res = res..'['..c:upper()..c:lower()..']' + else + res = res..c + end + else + percent = false + res = res..c + end + end + + return res +end + +-- A string.find wrapper for case-insensitive patterns. +function string.ifind(str, pattern, ...) + return str:find(pattern:ipattern(), ...) +end + +-- A string.match wrapper for case-insensitive patterns. +function string.imatch(str, pattern, ...) + return str:match(pattern:ipattern(), ...) +end + +-- A string.gmatch wrapper for case-insensitive patterns. +function string.igmatch(str, pattern, ...) + return str:gmatch(pattern:ipattern(), ...) +end + +-- A string.gsub wrapper for case-insensitive patterns. +function string.igsub(str, pattern, ...) + if not ... then print(debug.traceback()) end + return str:gsub(pattern:ipattern(), ...) +end + +-- A string.find wrapper for case-insensitive wildcard patterns. +function string.iwcfind(str, pattern, ...) + return str:wcfind(pattern:ipattern(), ...) +end + +-- A string.match wrapper for case-insensitive wildcard patterns. +function string.iwcmatch(str, pattern, ...) + return str:wcmatch(pattern:ipattern(), ...) +end + +-- A string.gmatch wrapper for case-insensitive wildcard patterns. +function string.iwcgmatch(str, pattern, ...) + return str:wcgmatch(pattern:ipattern(), ...) +end + +-- A string.gsub wrapper for case-insensitive wildcard patterns. +function string.iwcgsub(str, pattern, ...) + return str:wcgsub(pattern:ipattern(), ...) +end + +-- Returns a string with all instances of ${str} replaced with either a table or function lookup. +function string.keysub(str, sub) + return str:gsub('${(.-)}', sub) +end + +-- Counts the occurrences of a substring in a string. +function string.count(str, sub) + return str:pcount(sub:escape()) +end + +-- Counts the occurrences of a pattern in a string. +function string.pcount(str, pat) + return string.gsub[2](str, pat, '') +end + +-- Splits the original string into substrings of equal size (except for possibly the last one) +function string.chunks(str, size) + local res = {} + local key = 0 + for i = 1, #str, size do + key = key + 1 + rawset(res, key, str:sub(i, i + size - 1)) + end + + if _libs.lists then + res.n = key + return setmetatable(res, _meta.L) + else + return res + end +end + +-- Returns a string decoded given the appropriate encoding. +string.decode = function(str, encoding) + return (str:binary():chunks(encoding.bits):map(table.get+{encoding.charset} .. tonumber-{2}):concat():gsub('%z.*$', '')) +end + +-- Returns a string encoded given the appropriate encoding. +string.encode = function(str, encoding) + local binary = str:map(string.zfill-{encoding.bits} .. math.binary .. table.find+{encoding.charset}) + if encoding.terminator then + binary = binary .. encoding.terminator(str) + end + return binary:rpad('0', (#binary / 8):ceil() * 8):parse_binary() +end + +-- Returns a plural version of a string, if the provided table contains more than one element. +-- Defaults to appending an s, but accepts an option string as second argument which it will the string with. +function string.plural(str, t, replace) + if type(t) == 'number' and t > 1 or #t > 1 then + return replace or str..'s' + end + + return str +end + +-- tonumber wrapper +function string.number(...) + return tonumber(...) +end + +-- Returns a formatted item list for use in natural language representation of a number of items. +-- The second argument specifies how the trailing element is handled: +-- * and: Appends the last element with an "and" instead of a comma. [Default] +-- * csv: Appends the last element with a comma, like every other element. +-- * oxford: Appends the last element with a comma, followed by an and. +-- The third argument specifies an optional output, if the table is empty. +function table.format(t, trail, subs) + local first = next(t) + if not first then + return subs or '' + end + + trail = trail or 'and' + + local last + if trail == 'and' then + last = ' and ' + elseif trail == 'or' then + last = ' or ' + elseif trail == 'list' then + last = ', ' + elseif trail == 'csv' then + last = ',' + elseif trail == 'oxford' then + last = ', and ' + elseif trail == 'oxford or' then + last = ', or ' + else + warning('Invalid format for table.format: \''..trail..'\'.') + end + + local res = '' + for k, v in pairs(t) do + local add = tostring(v) + if trail == 'csv' and add:match('[,"]') then + res = res .. add:gsub('"', '""'):enclose('"') + else + res = res .. add + end + + if next(t, k) then + if next(t, next(t, k)) then + if trail == 'csv' then + res = res .. ',' + else + res = res .. ', ' + end + else + res = res .. last + end + end + end + + return res +end + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/tables.lua b/Data/DefaultContent/Libraries/addons/addons/libs/tables.lua new file mode 100644 index 0000000..24417a2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/tables.lua @@ -0,0 +1,621 @@ +--[[ + A few table helper functions, in addition to a new T-table interface, which enables method indexing on tables. + + To define a T-table with explicit values use T{...}, to convert an existing table t, use T(t). To access table methods of a T-table t, use t:methodname(args). + + For lists, tables with sequential integral indices, use the lists library and the respective L{...} constructor. For sets, tables with unique elements and irrelevant order, use the sets library and the respective S{...} constructor. +]] + +_libs = _libs or {} + +require('maths') +require('functions') + +local math, functions = _libs.maths, _libs.functions + +local table = require('table') + +_libs.tables = table + +_raw = _raw or {} +_raw.table = setmetatable(_raw.table or {}, {__index = table}) + +--[[ + Signatures +]] + +_meta = _meta or {} +_meta.T = _meta.T or {} +_meta.T.__index = table +_meta.T.__class = 'Table' + +_meta.N = {} +_meta.N.__class = 'nil' + +-- Constructor for T-tables. +-- t = T{...} for explicit declaration. +-- t = T(regular_table) to cast to a T-table. +function T(t) + local res + if class(t) == 'Set' then + res = T{} + + local key = 1 + for el in pairs(t) do + if type(el) == 'table' then + res[key] = table.copy(el) + else + res[key] = el + end + key = key + 1 + end + elseif class(t) == 'List' then + res = T{} + + local key = 1 + for _, el in ipairs(t) do + if type(el) == 'table' then + res[key] = table.copy(el) + else + res[key] = el + end + key = key + 1 + end + else + res = t or {} + end + + -- Sets T's metatable's index to the table namespace, which will take effect for all T-tables. + -- This makes every function that tables have also available for T-tables. + return setmetatable(res, _meta.T) +end + +N = function() + local nt = setmetatable({}, _meta.N) + return function() + return nt + end +end() + +function class(o) + local mt = getmetatable(o) + + return mt and mt.__class or type(o) +end + +-- Returns a function that returns the table when called. +function table.fn(t) + return functions.const(t) +end + +-- Checks if a table is an array, only having sequential integer keys. +function table.isarray(t) + local count = 0 + for _, _ in pairs(t) do + count = count + 1 + end + + return count == #t +end + +-- Returns the number of elements in a table. +function table.length(t) + local count = 0 + for _ in pairs(t) do + count = count + 1 + end + + return count +end + +-- Returns the first element of an array, or the element at position n, if provided. +function table.first(t, n) + n = n or 1 + return t[n] +end + +-- Returns the last element of an array, or the element at position (length-n+1), if n provided. +function table.last(t, n) + n = n or 1 + n = n - 1 + return t[#t-n] +end + +-- Returns true if searchval is in t. +function table.contains(t, searchval) + for key, val in pairs(t) do + if val == searchval then + return true + end + end + + return false +end + +-- Returns if the key searchkey is in t. +function table.containskey(t, searchkey) + return rawget(t, searchkey) ~= nil +end + +-- Appends an element to the end of an array table. +function table.append(t, val) + t[#t+1] = val + return t; +end + +-- Appends an array table to the end of another array table. +function table.extend(t, t_extend) + if type(t_extend) ~= 'table' then + return table.append(t, t_extend) + end + for _, val in ipairs(t_extend) do + table.append(t, val) + end + + return t +end + +_meta.T.__add = table.extend + +-- Returns the number of element in the table that satisfy fn. If fn is not a function, counts the number of occurrences of fn. +function table.count(t, fn) + if type(fn) ~= 'function' then + fn = functions.equals(fn) + end + + local count = 0 + for _, val in pairs(t) do + if fn(val) then + count = count + 1 + end + end + + return count +end + +-- Removes all elements from a table. +function table.clear(t) + for key in pairs(t) do + rawset(t, key, nil) + end + + return t +end + +-- Merges two dictionary tables and returns the result. Keys from the new table will overwrite keys. +function table.update(t, t_update, recursive, maxrec, rec) + if t_update == nil then + return t + end + + recursive = recursive or false + maxrec = maxrec or -1 + rec = rec or 0 + + for key, val in pairs(t_update) do + if t[key] ~= nil and recursive and rec ~= maxrec and type(t[key]) == 'table' and type(val) == 'table' and not table.isarray(val) then + t[key] = table.update(t[key], val, true, maxrec, rec + 1) + else + t[key] = val + end + end + + return t +end + +-- Merges two dictionary tables and returns the results. Keys from the new table will not overwrite existing keys. +function table.amend(t, t_amend, recursive, maxrec, rec) + if t_amend == nil then + return t + end + + recursive = recursive or false + maxrec = maxrec or -1 + rec = rec or 0 + + local cmp + for key, val in pairs(t_amend) do + if t[key] ~= nil and recursive and rec ~= maxrec and type(t[key]) == 'table' and type(val) == 'table' and class(val) ~= 'List' and class(val) ~= 'Set' and not table.isarray(val) then + t[key] = table.amend(t[key], val, true, maxrec, rec + 1) + elseif t[key] == nil then + t[key] = val + end + end + + return t +end + +-- Searches elements of a table for an element. If, instead of an element, a function is provided, will search for the first element to satisfy that function. +function table.find(t, fn) + fn = type(fn) ~= 'function' and functions.equals(fn) or fn + + for key, val in pairs(t) do + if fn(val) then + return key, val + end + end +end + +-- Returns the keys of a table in an array. +function table.keyset(t) + local res = {} + if _libs.sets then + for key in pairs(t) do + res[key] = true + end + + return setmetatable(res, _meta.S) + end + + local res = {} + local i = 0 + for key in pairs(t) do + i = i + 1 + res[i] = key + end + + if _libs.lists then + res.n = i + end + + return setmetatable(res, _libs.lists and _meta.L or _meta.T) +end + +-- Flattens a table by splicing all nested tables in at their respective position. +function table.flatten(t, recursive) + recursive = true and (recursive ~= false) + + local res = {} + local key = 1 + local flat = {} + for _, val in ipairs(t) do + if type(val) == 'table' then + if recursive then + flat = table.flatten(val, recursive) + table.extend(res, flat) + key = key + #flat + else + table.extend(res, val) + key = key + #val + end + else + res[key] = val + key = key + 1 + end + end + + return T(res) +end + +-- Returns true if all key-value pairs in t_eq equal all key-value pairs in t. +function table.equals(t, t_eq, depth) + depth = depth or -1 + if depth == 0 then + return t == t_eq + end + if class(t) ~= class(t_eq) then + return false + end + + local seen = {} + + for key, val in pairs(t) do + local cval = rawget(t_eq, key) + if val ~= cval then + if type(val) == 'table' and class(val) == class(cval) then + if not table.equals(val, cval, depth - 1) then + return false + end + else + return false + end + end + seen[key] = true + end + + for key, val in pairs(t_eq) do + if not seen[key] then + return false + end + end + + return true +end + +-- Removes and returns an element from t. +function table.delete(t, el) + for key, val in pairs(t) do + if val == el then + if type(key) == 'number' then + return table.remove(t, key) + else + local ret = t[key] + t[key] = nil + return ret + end + end + end +end + +-- Searches keys of a table according to a function fn. Returns the key and value, if found. +-- Searches keys of a table for an element. If, instead of an element, a function is provided, will search for the first element to satisfy that function. +function table.keyfind(t, fn) + for key, val in pairs(t) do + if fn(key) then + return key, val + end + end +end + +-- Returns a partial table sliced from t, equivalent to t[x:y] in certain languages. +-- Negative indices will be used to access the table from the other end. +function table.slice(t, from, to) + local n = #t + + from = from or 1 + if from < 0 then + -- Modulo the negative index, to get it back into range. + from = (from % n) + 1 + end + to = to or n + if to < 0 then + -- Modulo the negative index, to get it back into range. + to = (to % n) + 1 + end + + -- Copy relevant elements into a blank T-table. + local res = {} + local key = 1 + for i = from, to do + res[key] = t[i] + key = key + 1 + end + + return setmetatable(res, getmetatable(t)) +end + +-- Replaces t[from, to] with the contents of st and returns the table. +function table.splice(t, from, to, st) + local n1 = #t + local n2 = #st + local tcpy = table.copy(t) + + for stkey = 1, n2 do + tkey = from + stkey - 1 + t[tkey] = st[stkey] + end + + for cpykey = to + 1, n1 do + newkey = cpykey + n2 - (to - from) - 1 + t[newkey] = tcpy[cpykey] + end + + local nn = #t + for rmkey = nn - (to - from) + n2, nn do + t[rmkey] = nil + end + + t = res + + return t +end + +-- Returns a reversed array. +function table.reverse(t) + local res = {} + + local n = #t + local rkey = n + for key = 1, n do + res[key] = t[rkey] + rkey = rkey - 1 + end + + return setmetatable(res, getmetatable(t)) +end + +-- Gets a list of arguments and creates a table with key: value pairs alternating the arguments. +function table.dict(...) + local res = type(...) == 'table' and ... or {} + + local start = type(...) == 'table' and 2 or 1 + for k = start, select('#', ...), 2 do + res[select(k, ...)] = select(k + 1, ...) + end + + return setmetatable(res, _meta.T) +end + +-- Finds a table entry based on an attribute. +function table.with(t, attr, val) + val = type(val) ~= 'function' and functions.equals(val) or val + for key, el in pairs(t) do + if type(el) == 'table' and val(el[attr]) then + return el, key + end + end + + return nil, nil +end + +-- Backs up old table sorting function. +_raw.table.sort = _raw.table.sort or table.sort + +-- Returns a sorted table. +function table.sort(t, ...) + _raw.table.sort(t, ...) + return t +end + +-- Returns a table keyed by a specified index of a subtable. Requires a table of tables, and key must be a valid key in every table. Only produces the correct result, if the key is unique. +function table.rekey(t, key) + local res = {} + + for value in table.it(t) do + res[value[key]] = value + end + + return setmetatable(res, getmetatable(t)) +end + +-- Wrapper around unpack(t). Returns table elements as a list of values. Optionally takes a number of keys to unpack. +function table.unpack(t, ...) + local count = select('#', ...); + if count == 0 then + return unpack(t) + end + + local temp = {} + local args = {...} + for i = 1, count do + temp[i] = t[args[i]] + end + + return unpack(temp) +end + +-- Returns the values of the table, extracted into an argument list. Like unpack, but works on dictionaries as well. +function table.extract(t) + local res = {} + -- Convert a (possible) dictionary into an array. + local i = 1 + for value in table.it(t) do + res[i] = value + i = i + 1 + end + + return table.unpack(res) +end + +-- Returns a copy of the table, including metatable and recursed over nested tables. +-- The second argument indicates whether or not to perform a deep copy (defaults to true) +function table.copy(t, deep) + deep = deep ~= false and true + local res = {} + + for value, key in table.it(t) do + -- If a value is a table, recursively copy that. + if type(value) == 'table' and deep then + -- If it has a copy function in its __index metatable (but not main table), use that. + -- Otherwise, default to the table.copy function. + value = (not rawget(value, copy) and value.copy or table.copy)(value) + end + res[key] = value + end + + return setmetatable(res, getmetatable(t)) +end + +-- Returns the first table, reassigned to the second one. +function table.reassign(t, tn) + return table.update(table.clear(t), tn) +end + +-- Returns an array containing values from start to finish. If no finish is specified, returns table.range(1, start) +function table.range(start, finish, step) + if finish == nil then + start, finish = 1, start + end + + step = step or 1 + + local res = {} + for key = start, finish, step do + res[key] = key + end + + return setmetatable(res, _meta.T) +end + +-- Splits an array into an array of arrays of fixed length. +function table.chunks(t, size) + return table.range(math.ceil(t:length()/size)):map(function(i) return t:slice(size*(i - 1) + 1, size*i) end) +end + +-- Backs up old table concat function. +_raw.table.concat = table.concat + +-- Concatenates all objects of a table. Converts to string, if not already so. +function table.concat(t, delim, from, to) + delim = delim or '' + local res = '' + + if from or to then + from = from or 1 + to = to or #t + for key = from, to do + local val = rawget(t, key) + res = res .. tostring(val) + if key < to then + res = res .. delim + end + end + else + for value, key in table.it(t) do + res = res .. tostring(value) + if next(t, key) then + res = res .. delim + end + end + end + + return res +end + +-- Concatenates all elements with a whitespace in between. +function table.sconcat(t) + return table.concat(t, ' ') +end + +-- Check if table is empty. +-- If rec is true, it counts empty nested empty tables as empty as well. +function table.empty(t, rec) + if not rec then + return next(t) == nil + end + + for _, val in pairs(t) do + if type(val) ~= 'table' then + return false; + else + if not table.empty(val, true) then + return false; + end + end + end + + return true +end + +-- Sum up all elements of a table. +function table.sum(t) + return table.reduce(t, math.add, 0) +end + +-- Multiply all elements of a table. +function table.mult(t) + return table.reduce(t, math.mult, 1) +end + +-- Returns the minimum element of the table. +function table.min(t) + return table.reduce(t, math.min) +end + +-- Returns the maximum element of the table. +function table.max(t) + return table.reduce(t, math.max) +end + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/texts.lua b/Data/DefaultContent/Libraries/addons/addons/libs/texts.lua new file mode 100644 index 0000000..2213e06 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/texts.lua @@ -0,0 +1,694 @@ +--[[ + A library to facilitate text primitive creation and manipulation. +]] + +local table = require('table') +local math = require('math') + +local texts = {} +local meta = {} + +windower.text.saved_texts = {} +local dragged + +local events = { + reload = true, + left_click = true, + double_left_click = true, + right_click = true, + double_right_click = true, + middle_click = true, + scroll_up = true, + scroll_down = true, + hover = true, + drag = true, + right_drag = true, +} + +_libs = _libs or {} +_libs.texts = texts + +_meta = _meta or {} +_meta.Text = _meta.Text or {} +_meta.Text.__class = 'Text' +_meta.Text.__index = texts + +local set_value = function(t, key, value) + local m = meta[t] + m.values[key] = value + m.texts[key] = value ~= nil and (m.formats[key] and m.formats[key]:format(value) or tostring(value)) or m.defaults[key] +end + +_meta.Text.__newindex = function(t, k, v) + set_value(t, k, v) + t:update() +end + +--[[ + Local variables +]] + +local default_settings = {} +default_settings.pos = {} +default_settings.pos.x = 0 +default_settings.pos.y = 0 +default_settings.bg = {} +default_settings.bg.alpha = 255 +default_settings.bg.red = 0 +default_settings.bg.green = 0 +default_settings.bg.blue = 0 +default_settings.bg.visible = true +default_settings.flags = {} +default_settings.flags.right = false +default_settings.flags.bottom = false +default_settings.flags.bold = false +default_settings.flags.draggable = true +default_settings.flags.italic = false +default_settings.padding = 0 +default_settings.text = {} +default_settings.text.size = 12 +default_settings.text.font = 'Arial' +default_settings.text.fonts = {} +default_settings.text.alpha = 255 +default_settings.text.red = 255 +default_settings.text.green = 255 +default_settings.text.blue = 255 +default_settings.text.stroke = {} +default_settings.text.stroke.width = 0 +default_settings.text.stroke.alpha = 255 +default_settings.text.stroke.red = 0 +default_settings.text.stroke.green = 0 +default_settings.text.stroke.blue = 0 + +math.randomseed(os.clock()) + +local amend +amend = function(settings, defaults) + for key, val in pairs(defaults) do + if type(val) == 'table' then + settings[key] = amend(settings[key] or {}, val) + elseif settings[key] == nil then + settings[key] = val + end + end + + return settings +end + +local call_events = function(t, event, ...) + if not meta[t].events[event] then + return + end + + -- Trigger registered post-reload events + for _, event in ipairs(meta[t].events[event]) do + event(t, meta[t].root_settings) + end +end + +local apply_settings = function(_, t, settings) + settings = settings or meta[t].settings + texts.pos(t, settings.pos.x, settings.pos.y) + texts.bg_alpha(t, settings.bg.alpha) + texts.bg_color(t, settings.bg.red, settings.bg.green, settings.bg.blue) + texts.bg_visible(t, settings.bg.visible) + texts.color(t, settings.text.red, settings.text.green, settings.text.blue) + texts.alpha(t, settings.text.alpha) + texts.font(t, settings.text.font, unpack(settings.text.fonts)) + texts.size(t, settings.text.size) + texts.pad(t, settings.padding) + texts.italic(t, settings.flags.italic) + texts.bold(t, settings.flags.bold) + texts.right_justified(t, settings.flags.right) + texts.bottom_justified(t, settings.flags.bottom) + texts.visible(t, meta[t].status.visible) + texts.stroke_width(t, settings.text.stroke.width) + texts.stroke_color(t, settings.text.stroke.red, settings.text.stroke.green, settings.text.stroke.blue) + texts.stroke_alpha(t, settings.text.stroke.alpha) + + call_events(t, 'reload') +end + +-- Returns a new text object. +-- settings: If provided, it will overwrite the defaults with those. The structure needs to be similar +-- str: Formatting string, if provided, will set it as default text. Supports named variables: +-- ${name|default|format} +-- If those are found, they will initially be set to default. They can later be adjusted by simply +-- setting the values and it will format them according to the format specifier. Example usage: +-- +-- t = texts.new('The target\'s name is ${name|(None)}, its ID is ${id|0|%.8X}.') +-- -- At this point the text reads: +-- -- The target's name is (None), its ID is 00000000. +-- -- Now, assume the player is currently targeting its Moogle in the Port Jeuno MH (ID 17784938). +-- +-- mob = windower.ffxi.get_mob_by_index(windower.ffxi.get_player()['target_index']) +-- +-- t.name = mob['name'] +-- -- This will instantly change the text to include the mob's name: +-- -- The target's name is Moogle, its ID is 00000000. +-- +-- t.id = mob['id'] +-- -- This instantly changes the ID part of the text, so it all reads: +-- -- The target's name is Moogle, its ID is 010F606A. +-- -- Note that the ID has been converted to an 8-digit hex number, as specified with the "%.8X" format. +-- +-- t.name = nil +-- -- This unsets the name and returns it to its default: +-- -- The target's name is (None), its ID is 010F606A. +-- +-- -- To avoid mismatched attributes, like the name and ID in this case, you can also pass a table to update: +-- t:update(mob) +-- -- Since the mob object contains both a "name" and "id" attribute, and both are used in the text object, +-- -- it will update those with the respective values. The extra values are ignored. +function texts.new(str, settings, root_settings) + if type(str) ~= 'string' then + str, settings, root_settings = '', str, settings + end + + -- Sets the settings table to the provided settings, if not separately provided and the settings are a valid settings table + if not _libs.config then + root_settings = nil + else + root_settings = + root_settings and class(root_settings) == 'Settings' and + root_settings + or settings and class(settings) == 'Settings' and + settings + or + nil + end + + local t = {} + local m = {} + meta[t] = m + m.name = (_addon and _addon.name or 'text') .. '_gensym_' .. tostring(t):sub(8) .. '_%.8X':format(16^8 * math.random()):sub(3) + t._name = m.name + m.settings = settings or {} + m.status = m.status or {visible = false, text = {}} + m.root_settings = root_settings + m.base_str = str + + m.events = {} + + m.keys = {} + m.values = {} + m.textorder = {} + m.defaults = {} + m.formats = {} + m.texts = {} + + windower.text.create(m.name) + + amend(m.settings, default_settings) + if m.root_settings then + config.save(m.root_settings) + end + + if _libs.config and m.root_settings and settings then + _libs.config.register(m.root_settings, apply_settings, t, settings) + else + apply_settings(_, t, settings) + end + + if str then + texts.append(t, str) + else + windower.text.set_text(m.name, '') + end + + -- Cache for deletion + table.insert(windower.text.saved_texts, 1, t) + + return setmetatable(t, _meta.Text) +end + +-- Sets string values based on the provided attributes. +function texts.update(t, attr) + attr = attr or {} + local m = meta[t] + + -- Add possibly new keys + for key, value in pairs(attr) do + m.keys[key] = true + end + + -- Update all text segments + for key in pairs(m.keys) do + set_value(t, key, attr[key] == nil and m.values[key] or attr[key]) + end + + -- Create the string + local str = '' + for _, key in ipairs(meta[t].textorder) do + str = str .. m.texts[key] + end + + windower.text.set_text(m.name, str) + m.status.text.content = str + + return str +end + +-- Restores the original text object not counting updated variables and added lines +function texts.clear(t) + local m = meta[t] + m.keys = {} + m.values = {} + m.textorder = {} + m.texts = {} + m.defaults = {} + m.formats = {} + + texts.append(t, m.base_str or '') +end + +-- Appends new text tokens to be displayed +function texts.append(t, str) + local m = meta[t] + + local i = 1 + local index = #m.textorder + 1 + while i <= #str do + local startpos, endpos = str:find('%${.-}', i) + local rndname = '%s_%u':format(m.name, index) + if startpos then + -- Match before the tag + local match = str:sub(i, startpos - 1) + if match ~= '' then + m.textorder[index] = rndname + m.texts[rndname] = match + index = index + 1 + end + + -- Set up defaults + match = str:sub(startpos + 2, endpos - 1) + local key = match + local default = '' + local format = nil + + -- Match the key + local keystart, keyend = match:find('^.-|') + if keystart then + key = match:sub(1, keyend - 1) + match = match:sub(keyend + 1) + default = match + end + + -- Match the default and format + local defaultstart, defaultend = match:find('^.-|') + if defaultstart then + default = match:sub(1, defaultend - 1) + format = match:sub(defaultend + 1) + end + + m.textorder[index] = key + m.keys[key] = true + m.defaults[key] = default + m.formats[key] = format + + index = index + 1 + i = endpos + 1 + + else + m.textorder[index] = rndname + m.texts[rndname] = str:sub(i) + break + + end + end + + texts.update(t) +end + +-- Returns an iterator over all currently registered variables +function texts.it(t) + local key + local m = meta[t] + + return function() + key = next(m.keys, key) + return key, m.values[key], m.defaults[key], m.formats[key], m.texts[key] + end +end + +-- Appends new text tokens with a line break +function texts.appendline(t, str) + t:append('\n' .. str) +end + +-- Makes the primitive visible +function texts.show(t) + windower.text.set_visibility(meta[t].name, true) + meta[t].status.visible = true +end + +-- Makes the primitive invisible +function texts.hide(t) + windower.text.set_visibility(meta[t].name, false) + meta[t].status.visible = false +end + +-- Returns whether or not the text object is visible +function texts.visible(t, visible) + if visible == nil then + return meta[t].status.visible + end + + windower.text.set_visibility(meta[t].name, visible) + meta[t].status.visible = visible +end + +-- Sets a new text +function texts.text(t, str) + if not str then + return meta[t].status.text.content + end + + meta[t].base_str = str + texts.clear(t) +end + +--[[ + The following methods all either set the respective values or return them, if no arguments to set them are provided. +]] + +function texts.pos(t, x, y) + local m = meta[t] + if not x then + return m.settings.pos.x, m.settings.pos.y + end + + local settings = windower.get_windower_settings() + windower.text.set_location(m.name, x + (m.settings.flags.right and settings.ui_x_res or 0), y + (m.settings.flags.bottom and settings.ui_y_res or 0)) + m.settings.pos.x = x + m.settings.pos.y = y +end + +function texts.pos_x(t, x) + if not x then + return meta[t].settings.pos.x + end + + t:pos(x, meta[t].settings.pos.y) +end + +function texts.pos_y(t, y) + if not y then + return meta[t].settings.pos.y + end + + t:pos(meta[t].settings.pos.x, y) +end + +function texts.extents(t) + return windower.text.get_extents(meta[t].name) +end + +function texts.font(t, ...) + if not ... then + return meta[t].settings.text.font + end + + windower.text.set_font(meta[t].name, ...) + meta[t].settings.text.font = (...) + meta[t].settings.text.fonts = {select(2, ...)} +end + +function texts.size(t, size) + if not size then + return meta[t].settings.text.size + end + + windower.text.set_font_size(meta[t].name, size) + meta[t].settings.text.size = size +end + +function texts.pad(t, padding) + if not padding then + return meta[t].settings.padding + end + + windower.text.set_bg_border_size(meta[t].name, padding) + meta[t].settings.padding = padding +end + +function texts.color(t, red, green, blue) + if not red then + return meta[t].settings.text.red, meta[t].settings.text.green, meta[t].settings.text.blue + end + + windower.text.set_color(meta[t].name, meta[t].settings.text.alpha, red, green, blue) + meta[t].settings.text.red = red + meta[t].settings.text.green = green + meta[t].settings.text.blue = blue +end + +function texts.alpha(t, alpha) + if not alpha then + return meta[t].settings.text.alpha + end + + windower.text.set_color(meta[t].name, alpha, meta[t].settings.text.red, meta[t].settings.text.green, meta[t].settings.text.blue) + meta[t].settings.text.alpha = alpha +end + +-- Sets/returns text transparency. Based on percentage values, with 1 being fully transparent, while 0 is fully opaque. +function texts.transparency(t, transparency) + if not transparency then + return 1 - meta[t].settings.text.alpha/255 + end + + texts.alpha(t,math.floor(255*(1-transparency))) +end + +function texts.right_justified(t, right) + if right == nil then + return meta[t].settings.flags.right + end + + windower.text.set_right_justified(meta[t].name, right) + meta[t].settings.flags.right = right +end + +function texts.bottom_justified(t, bottom) + if bottom == nil then + return meta[t].settings.flags.bottom + end + + -- Enable this once LuaCore implements it + -- windower.text.set_bottom_justified(meta[t].name, bottom) + -- meta[t].settings.flags.bottom = bottom +end + +function texts.italic(t, italic) + if italic == nil then + return meta[t].settings.flags.italic + end + + windower.text.set_italic(meta[t].name, italic) + meta[t].settings.flags.italic = italic +end + +function texts.bold(t, bold) + if bold == nil then + return meta[t].settings.flags.bold + end + + windower.text.set_bold(meta[t].name, bold) + meta[t].settings.flags.bold = bold +end + +function texts.bg_color(t, red, green, blue) + if not red then + return meta[t].settings.bg.red, meta[t].settings.bg.green, meta[t].settings.bg.blue + end + + windower.text.set_bg_color(meta[t].name, meta[t].settings.bg.alpha, red, green, blue) + meta[t].settings.bg.red = red + meta[t].settings.bg.green = green + meta[t].settings.bg.blue = blue +end + +function texts.bg_visible(t, visible) + if visible == nil then + return meta[t].settings.bg.visible + end + + windower.text.set_bg_visibility(meta[t].name, visible) + meta[t].settings.bg.visible = visible +end + +function texts.bg_alpha(t, alpha) + if not alpha then + return meta[t].settings.bg.alpha + end + + windower.text.set_bg_color(meta[t].name, alpha, meta[t].settings.bg.red, meta[t].settings.bg.green, meta[t].settings.bg.blue) + meta[t].settings.bg.alpha = alpha +end + +-- Sets/returns background transparency. Based on percentage values, with 1 being fully transparent, while 0 is fully opaque. +function texts.bg_transparency(t, transparency) + if not transparency then + return 1 - meta[t].settings.bg.alpha/255 + end + + texts.bg_alpha(t, math.floor(255*(1-transparency))) +end + +function texts.stroke_width(t, width) + if not width then + return meta[t].settings.text.stroke.width + end + + windower.text.set_stroke_width(meta[t].name, width) + meta[t].settings.text.stroke.width = width +end + +function texts.stroke_color(t, red, green, blue) + if not red then + return meta[t].settings.text.stroke.red, meta[t].settings.text.stroke.green, meta[t].settings.text.stroke.blue + end + + windower.text.set_stroke_color(meta[t].name, meta[t].settings.text.stroke.alpha, red, green, blue) + meta[t].settings.text.stroke.red = red + meta[t].settings.text.stroke.green = green + meta[t].settings.text.stroke.blue = blue +end + +function texts.stroke_transparency(t, transparency) + if not transparency then + return 1 - meta[t].settings.text.stroke.alpha/255 + end + + texts.stroke_alpha(t,math.floor(255 * (1 - transparency))) +end + +function texts.stroke_alpha(t, alpha) + if not alpha then + return meta[t].settings.text.stroke.alpha + end + + windower.text.set_stroke_color(meta[t].name, alpha, meta[t].settings.text.stroke.red, meta[t].settings.text.stroke.green, meta[t].settings.text.stroke.blue) + meta[t].settings.text.stroke.alpha = alpha +end + +-- Returns true if the coordinates are currently over the text object +function texts.hover(t, x, y) + if not t:visible() then + return false + end + + local pos_x, pos_y = windower.text.get_location(meta[t].name) + local off_x, off_y = windower.text.get_extents(meta[t].name) + + return (pos_x <= x and x <= pos_x + off_x + or pos_x >= x and x >= pos_x + off_x) + and (pos_y <= y and y <= pos_y + off_y + or pos_y >= y and y >= pos_y + off_y) +end + +function texts.destroy(t) + for i, t_needle in ipairs(windower.text.saved_texts) do + if t == t_needle then + table.remove(windower.text.saved_texts, i) + break + end + end + windower.text.delete(meta[t].name) + meta[t] = nil +end + +-- Handle drag and drop +windower.register_event('mouse', function(type, x, y, delta, blocked) + if blocked then + return + end + + -- Mouse drag + if type == 0 then + if dragged then + dragged.text:pos(x - dragged.x, y - dragged.y) + return true + end + + -- Mouse left click + elseif type == 1 then + for _, t in pairs(windower.text.saved_texts) do + local m = meta[t] + if m.settings.flags.draggable and t:hover(x, y) then + local pos_x, pos_y = windower.text.get_location(m.name) + + local flags = m.settings.flags + if flags.right or flags.bottom then + local info = windower.get_windower_settings() + if flags.right then + pos_x = pos_x - info.ui_x_res + elseif flags.bottom then + pos_y = pos_y - info.ui_y_res + end + end + + dragged = {text = t, x = x - pos_x, y = y - pos_y} + return true + end + end + + -- Mouse left release + elseif type == 2 then + if dragged then + if meta[dragged.text].root_settings then + config.save(meta[dragged.text].root_settings) + end + dragged = nil + return true + end + end + + return false +end) + +-- Can define functions to execute every time the settings are reloaded +function texts.register_event(t, key, fn) + if not events[key] then + error('Event %s not available for text objects.':format(key)) + return + end + + local m = meta[t] + m.events[key] = m.events[key] or {} + m.events[key][#m.events[key] + 1] = fn + return #m.events[key] +end + +function texts.unregister_event(t, key, fn) + if not (events[key] and meta[t].events[key]) then + return + end + + if type(fn) == 'number' then + table.remove(meta[t].events[key], fn) + else + for index, event in ipairs(meta[t].events[key]) do + if event == fn then + table.remove(meta[t].events[key], index) + return + end + end + end +end + +return texts + +--[[ +Copyright © 2013-2015, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/timeit.lua b/Data/DefaultContent/Libraries/addons/addons/libs/timeit.lua new file mode 100644 index 0000000..096510a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/timeit.lua @@ -0,0 +1,121 @@ +--[[ +A library providing a timing feature. +]] + +_libs = _libs or {} + +require('functions') +require('tables') + +local functions, table = _libs.functions, _libs.tables +local string = require('string') +local math = require('math') +local logger = require('logger') + +local timeit = {} + +_libs.timeit = timeit + +-- Creates a new timer object. +function timeit.new() + return setmetatable({t = 0}, {__index = timeit}) +end + +-- Starts the timer. +function timeit.start(timer) + timer.t = os.clock() +end + +-- Stops the timer and returns the time passed since the last timer:start() or timer:next(). +function timeit.stop(timer) + local tdiff = os.clock() - timer.t + timer.t = 0 + return tdiff +end + +-- Restarts the timer and returns the time passed since the last timer:start() or timer:next(). +function timeit.next(timer) + local tdiff = os.clock() - timer.t + timer.t = os.clock() + return tdiff +end + +-- Returns the time passed since the last timer:start() or timer:next(), but keeps the timer going. +function timeit.check(timer) + local tdiff = os.clock() - timer.t + return tdiff +end + +-- Returns the normalized time in seconds it took to perform the provided functions rep number of times, with the specified arguments. +function timeit.benchmark(rep, ...) + local args = T{...} + if type(rep) == 'function' then + args:insert(1, rep) + rep = 1000 + end + + local fns = T{} + if type(args[2]) == 'function' then + local i = args:find(function(arg) return type(arg) ~= 'function' end) + if i ~= nil then + fns = args:slice(1, i - 1) + local fnargs = args:slice(i) + fns = fns:map(functions.apply-{fnargs}) + else + fns = args + end + else + fns = args:chunks(2):map(function(x) return x[1]+x[2] end) + end + + local single_timer = timeit.new() + local total_timer = timeit.new() + + local times = T{} + total_timer:start() + for _, fn in ipairs(fns) do + single_timer:start() + for _ = 1, rep do fn() end + times:append(single_timer:stop()/rep) + end + local total = total_timer:stop() + + local bktimes = times:copy() + times:sort() + + local unit = math.floor(math.log(times:last(), 10)) + local len = math.floor(math.log(times:last()/times:first(), 10)) + local dec = math.floor(math.log(rep, 10)) + log(string.format('Ranking of provided functions (time in 10^%ds):', unit)) + local indices = times:map(table.find+{bktimes}) + local str = '#%d:\tFunction %d, execution time: %0'..math.max(len + dec - 2, 0)..'.'..math.max(len + dec - 2, 0)..'f\t%0'..(len+3)..'d%%' + for place, i in ipairs(indices) do + if place == 1 then + log(string.format(str..' (reference value, always 100%%)', place, i, times[place]/10^unit, 100*times[place]/times:first())) + else + log(string.format(str..', ~%d%% slower than function %d', place, i, times[place]/10^unit, 100*times[place]/times:first(), math.round(100*(times[place]/times:first() - 1)), indices:first())) + end + end + log(string.format('Total running time: %2.2fs', total)) + + fns = nil + times = nil + collectgarbage() + + return bktimes +end + +return timeit + +--[[ +Copyright © 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/vectors.lua b/Data/DefaultContent/Libraries/addons/addons/libs/vectors.lua new file mode 100644 index 0000000..db19dcb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/vectors.lua @@ -0,0 +1,192 @@ +--[[ +Vectors for operations in a d-dimensional space. +]] + +_libs = _libs or {} + +require('tables') +require('maths') + +local table, math = _libs.tables, _libs.maths + +vector = {} + +_libs.vectors = vector + +_meta = _meta or {} +_meta.V = {} +_meta.V.__index = vector +_meta.V.__class = 'Vector' + +-- Constructor for vectors. Optionally provide dimension n, to avoid computing the dimension from the table. +function V(t, n) + local res = {} + res.n = n or t.n or #t + for i = 1, res.n do + res[i] = t[i] + end + + return setmetatable(res, _meta.V) +end + +_meta.V.__unp = V:args(1) + +-- Creates a zero-vector of dimension n. +function vector.zero(n) + return vector.fill(n, 0) +end + +-- Creates a vector of dimension n with all values set to k. +function vector.fill(n, k) + local res = {} + for i = 1, n do + res[i] = k + end + + res.n = n + return setmetatable(res, _meta.V) +end + +-- Creates a euclidean unit vector of dimension n for axis i. +function vector.unit(n, i) + local res = {} + for j = 1, n do + res[j] = i == j and 1 or 0 + end + + res.n = n + return setmetatable(res, _meta.V) +end + +-- Returns the length of a vector measured from 0. +function vector.length(v) + local length = 0 + for _, val in ipairs(v) do + length = length + val^2 + end + + return math.sqrt(length) +end + +-- Returns a vector in the same direction as v, normalized to length one. +function vector.normalize(v) + return v:scale(1/v:length()) +end + +-- Returns the dimension of v. Constant. +function vector.dimension(v) + return v.n +end + +-- Returns the dot product between two vectors. +function vector.dot(v1, v2) + local res = 0 + for i, val1 in ipairs(v1) do + res = res + val1*v2[i] + end + + return res +end + +_meta.V.__mul = function(x, y) if type(x) == 'number' then return y:scale(x) elseif type(y) == 'number' then return x:scale(y) else return x:dot(y) end end + +-- Returns the cross product of two R^3 vectors. +function vector.cross(v1, v2) + local res = {} + res[1] = v1[2]*v2[3] - v1[3]*v2[2] + res[2] = v1[3]*v2[1] - v1[1]*v2[3] + res[3] = v1[1]*v2[2] - v1[2]*v2[1] + + res.n = 3 + return setmetatable(res, _meta.V) +end + +-- Returns v multiplied by k, i.e. all elements multiplied by the same factor. +function vector.scale(v, k) + local res = {} + for i, val in ipairs(v) do + res[i] = val*k + end + + res.n = v.n + return setmetatable(res, _meta.V) +end + +-- Returns the vector pointing in the opposite direction of v with the same length. +function vector.negate(v) + return vector.scale(v, -1) +end + +_meta.V.__unm = vector.negate + +-- Returns v1 added to v2. +function vector.add(v1, v2) + local res = {} + for i, val in ipairs(v1) do + res[i] = val+v2[i] + end + + res.n = v1.n + return setmetatable(res, _meta.V) +end + +_meta.V.__add = vector.add + +-- Returns v1 subtracted by v2. +function vector.subtract(v1, v2) + local res = {} + for i, val in ipairs(v1) do + res[i] = val-v2[i] + end + + res.n = v1.n + return setmetatable(res, _meta.V) +end + +_meta.V.__sub = vector.subtract + +-- Returns the angle described by two vectors (in radians). +function vector.angle(v1, v2) + return ((v1 * v2) / (v1:length() * v2:length())):acos() +end + +-- Returns a normalized 2D vector from a radian value. +-- Note that this goes against mathematical convention, which commonly makes the radian go counter-clockwise. +-- This function, instead, goes clockwise, i.e. it will return *(0, -1)* for ''π/2''. +-- This is done to match the game's internal representation, which has the X axis pointing east and the Y axis pointing south. +function vector.from_radian(r) + return V{r:cos(), -r:sin()} +end + +-- Returns the radian that describes the direction of the vector. +function vector.to_radian(v) + return (v[2] < 0 and 1 or -1) * v:normalize()[1]:acos() +end + +-- Returns the vector in string format: (...) +function vector.tostring(v) + local str = '(' + for i, val in ipairs(v) do + if i > 1 then + str = str..', ' + end + str = str..tostring(val) + end + + return str..')' +end + +_meta.V.__tostring = vector.tostring + +--[[ +Copyright © 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/libs/xml.lua b/Data/DefaultContent/Libraries/addons/addons/libs/xml.lua new file mode 100644 index 0000000..fc5c9bc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/libs/xml.lua @@ -0,0 +1,596 @@ +--[[ + Small implementation of a fully-featured XML reader. +]] + +_libs = _libs or {} + +require('tables') +require('lists') +require('sets') +require('strings') + +local table, list, set, string = _libs.tables, _libs.lists, _libs.sets, _libs.strings +local files = require('files') + +local xml = {} + +_libs.xml = xml + +-- Local functions +local entity_unescape +local xml_error +local pcdata +local attribute +local validate_headers +local tokenize +local get_namespace +local classify +local make_namespace_name + +-- Define singleton XML characters that can delimit inner tag strings. +local singletons = '=" \n\r\t/>' +local unescapes = T{ + amp = '&', + gt = '>', + lt = '<', + quot = '"', + apos = '\'' +} +local escapes = T{ + ['&'] = 'amp', + ['>'] = 'gt', + ['<'] = 'lt', + ['"'] = 'quot', + ['\''] = 'apos' +} + +local spaces = S{' ', '\n', '\t', '\r'} + +-- Takes a numbered XML entity as second argument and converts it to the corresponding symbol. +-- Only used internally to index the unescapes table. +function entity_unescape(_, entity) + if entity:startswith('#x') then + return entity:sub(3):number(16):char() + elseif entity:startswith('#') then + return entity:sub(2):number():char() + else + return entity + end +end + +local entities = setmetatable(unescapes, {__index = entity_unescape}) + +function string.xml_unescape(str) + return (str:gsub("&(.-);", entities)) +end + +function string.xml_escape(str) + return str:gsub('.', function(c) + if escapes:containskey(c) then + return '&'..escapes[c]..';' + end + return c + end) +end + +-- Takes a filename and tries to parse the XML in it, after a validity check. +function xml.read(file) + if type(file) == 'string' then + file = _libs.files.new(file) + end + + if not file:exists() then + return xml_error('File not found: '..file.path) + end + + return xml.parse(file:read()) +end + +-- Returns nil as the parsed table and an additional error message with an optional line number. +function xml_error(message, line) + if line == nil then + return nil, 'XML error: '..message + end + return nil, 'XML error, line '..line..': '..message +end + +-- Collapses spaces and xml_unescapes XML entities. +function pcdata(str) + return str:xml_unescape():spaces_collapse() +end + +function attribute(str) + return str:gsub('%s', ' '):xml_unescape() +end + +-- TODO +function validate_headers(headers) + return true +end + +-- Parsing function. Gets a string representation of an XML object and outputs a Lua table or an error message. +function xml.parse(content) + local quote = nil + local headers = T{xmlhead='', dtds=T{}} + local tag = '' + local index = 0 + local mode = 'outer' + local line = 1 + + -- Parse XML header + for c in content:it() do + if c == '\n' then + line = line + 1 + end + + index = index + 1 + if mode == 'quote' then + tag = tag..c + if c == quote then + quote = nil + mode = 'inner' + end + + elseif mode == 'outer' then + if not c:match('%s') then + if c == '<' then + mode = 'inner' + tag = c + else + return xml_error('Malformatted XML headers.') + end + end + + elseif mode == 'inner' then + tag = tag..c + if c == '\'' or c == '"' then + quote = c + mode = 'quote' + elseif c == '>' then + if tag[2] == '?' then + headers['xmlhead'] = tag + tag = '' + elseif tag[2] == '!' then + headers['dtds']:append(tag) + tag = '' + else + index = index - #tag + 1 + break + end + mode = 'outer' + end + end + end + + if not validate_headers(headers) then + return xml_error('Invalid XML headers.') + end + + local tokens, err = tokenize(content:sub(index):trim(), line) + if tokens == nil then + return nil, err + end + + return classify(tokens, headers) +end + +-- Tokenizer. Reads a string and returns an array of lines, each line with a number of valid XML tokens. Valid tokens include: +-- * <\w+(:\w+)? Tag names, possibly including namespace +-- * </\w+>? Tag endings +-- * /> Single tag endings +-- * ".*(?!")\" Attribute values +-- * \w+(:\w+)? Attribute names, possibly including namespace +-- * .*(?!<) PCDATA +-- * .*(?!\]\]>) CDATA +function tokenize(content, line) + local current = '' + local tokens = L{} + for i = 1, line do + tokens:append(L{}) + end + + local quote = nil + local mode = 'inner' + for c in content:it() do + -- Only useful for a line count, to produce more accurate debug messages. + if c == '\n' then + tokens:append(L{}) + end + + if mode == 'quote' then + if c == quote then + tokens:last():append('"'..current..'"') + current = '' + mode = 'tag' + else + current = current..c + end + + elseif mode == 'comment' then + current = current..c + if c == '>' and current:endswith('-->' ) then + if current:sub(5, -4):contains('--') then + return xml_error('Invalid token \'--\' within comment.', #tokens) + end + tokens:last():append(current) + current = '' + mode = 'inner' + end + + elseif mode == 'inner' then + if c == '<' then + tokens:last():append(current:trim()) + current = '<' + mode = 'tag' + else + current = current..c + end + + elseif mode == 'cdata' then + current = current..c + if c == '>' and current:endswith(']]>') then + tokens:last():append(current) + current = '' + mode = 'inner' + end + + else + if singletons:contains(c) then + if spaces:contains(c) then + if #current > 0 then + tokens:last():append(current) + current = '' + end + + elseif c == '=' then + if #current > 0 then + tokens:last():append(current) + end + tokens:last():append('=') + current = '' + + elseif c == '"' or c == '\'' then + quote = c + mode = 'quote' + + elseif c == '/' then + if current:startswith('<') and current:length() > 1 then + tokens:last():append(current) + current = '' + end + current = current..c + + elseif c == '>' then + current = current..c + tokens:last():append(current) + current = '' + mode = 'inner' + + else + return xml_error('Unexpected token \''..c..'\'.', tokens:length()) + end + + else + if c:match('[%w%d-_%.%:![%]]') ~= nil then + current = current..c + if c == '-' and current == '<!-' then + mode = 'comment' + elseif c == '[' and current == '<![CDATA[' then + mode = 'cdata' + end + else + return xml_error('Unexpected character \''..c..'\'.', tokens:length()) + end + end + end + end + + for array, line in tokens:it() do + tokens[line] = array:filter(-'') + end + + return tokens +end + +-- Definition of a DOM object. +local dom = T{} +function dom.new(t) + return T{ + type = '', + name = '', + namespace = nil, + value = nil, + children = L{}, + cdata = nil + }:update(t) +end + +-- Returns the name of the element and the namespace, if present. +function get_namespace(token) + local splits = token:split(':') + if #splits > 2 then + return + elseif #splits == 2 then + return splits[2], splits[1] + end + return token +end + +-- Classifies the tokens parsed by tokenize into valid XML values, and returns a DOM hierarchy. +function classify(tokens, var) + if tokens == nil then + return nil, var + end + + -- This doesn't do anything yet. + local headers = var + + local mode = 'inner' + local parsed = L{dom.new()} + local name = nil + for line, intokens in ipairs(tokens) do + for _, token in ipairs(intokens) do + if token:startswith('<![CDATA[') then + parsed:last().children:append(dom.new({type = 'text', value = token:sub(10, -4), cdata = true})) + + elseif token:startswith('<!--') then + parsed:last().children:append(dom.new({type = 'comment', value = token:sub(5, -4)})) + + elseif token:startswith('</') then + if token:sub(3, -2) == parsed:last(1).name then + parsed:last(2).children:append(parsed:remove()) + else + return xml_error('Mismatched tag ending: '..token, line) + end + + elseif token:startswith('<') then + if token:endswith('>') then + name, namespace = get_namespace(token:sub(2, -2)) + else + name, namespace = get_namespace(token:sub(2)) + end + if name == nil then + return xml_error('Invalid namespace definition.', line) + end + namespace = namespace or '' + + parsed:append(dom.new({type = 'tag', name = name, namespace = namespace})) + name, namespace = nil, nil + if token:endswith('>') then + mode = 'inner' + else + mode = 'tag' + end + + elseif token:endswith('/>') then + if mode == 'tag' then + parsed:last(2).children:append(parsed:remove()) + mode = 'inner' + else + return xml_error('Illegal token inside a tag: '..token, line) + end + + elseif token:endswith('>') then + if mode ~= 'tag' then + return xml_error('Unexpected token \'>\'.', line) + end + mode = 'inner' + + elseif token == '=' then + if mode ~= 'eq' then + return xml_error('Unexpected \'=\'.') + end + mode = 'value' + + else + if mode == 'tag' then + if parsed:last().children:find(function (el) return el.type == 'attribute' and el.name == token end) ~= nil then + return xml_error('Attribute '..token..' already defined. Multiple assignment not allowed.', line) + end + name, namespace = get_namespace(token) + namespace = tmpnamespace or parsed:last(1).namespace + mode = 'eq' + + elseif mode == 'value' then + parsed:last().children:append(dom.new({ + type = 'attribute', + name = name, + namespace = namespace, + value = attribute(token:sub(2,-2)) + })) + name = nil + namespace = '' + mode = 'tag' + + elseif mode == 'inner' then + parsed:last().children:append(dom.new({ + type = 'text', + value = pcdata(token) + })) + end + end + end + end + + local roots = parsed:remove().children + if #roots > 1 then + return xml_error('Multiple root elements not allowed.') + elseif #roots == 0 then + return xml_error('Missing root element not allowed.') + end + + return roots[1] +end + +-- Returns a non-shitty XML representation: +-- Tree of nodes, each node can be a tag or a value. A tag has a name, list of attributes and children. +-- In case of a node, the following is provided: +-- * type node.value: Value of node. Only provided if type was set. +-- * list node.children: List of child nodes (tag or text nodes) +-- * string node.name: Name of the tag +-- * iterator node.it: Function that iterates over all children +-- * table node.attributes: Dictionary containing all attributes +function table.undomify(node, types) + local node_type = types and types[node.name] or nil + local res = T{} + res.attributes = T{} + local children = L{} + local ctype + + for _, child in ipairs(node.children) do + ctype = child.type + if ctype == 'attribute' then + res.attributes[child.name] = child.value + elseif ctype == 'tag' then + children:append(child:undomify(types)) + elseif ctype == 'text' then + children:append(child.value) + end + end + + if node_type then + local val = children[1] or '' + if node_type == 'set' then + res.children = val:split(','):map(string.trim):filter(-'') + res.value = S(children) + elseif node_type == 'list' then + res.value = val:split(','):map(string.trim):filter(-'') + res.children = res.value + elseif node_type == 'number' then + res.value = tonumber(val) + res.children = L{res.value} + elseif node_type == 'boolean' then + res.value = val == 'true' + res.children = L{res.value} + end + end + + if res.children == nil then + res.children = children + end + + res.get = function(t, val) + for child in t.children:it() do + if child.name == val then + return child + end + end + end + + res.name = node.name + + return setmetatable(res, {__index = function(t, k) + return t.children[k] + end}) +end + +-- Returns a namespace-formatted string of a DOM node. +function make_namespace_name(node) + if node.namespace ~= '' then + return node.namespace..':'..node.name + end + + return node.name +end + +-- Convert a DOM hierarchy to well-formed XML. +function xml.realize(node, indentlevel) + if node.type ~= 'tag' then + return xml_error('Only DOM objects of type \'tag\' can be realized to XML.') + end + + indentlevel = indentlevel or 0 + local indent = ('\t'):rep(indentlevel) + local str = indent..'<'..make_namespace_name(node) + + local attributes = T{} + local children = T{} + local childtypes = T{} + for _, child in ipairs(node.children) do + if child.type == 'attribute' then + attributes:append(child) + elseif child.type ~= 'attribute' then + children:append(child) + childtypes[child.type] = true + else + return xml_error('Unknown type \''..child.type..'\'.') + end + end + + if #attributes ~= 0 then + for _, attribute in ipairs(attributes) do + local nsstring = '' + if attribute.namespace ~= node.namespace then + nsstring = make_namespace_name(attribute) + else + nsstring = attribute.name + end + str = str..' '..nsstring..'="'..attribute.value:xml_escape()..'"' + end + end + + if #children == 0 then + str = str..' />\n' + return str + end + str = str..'>\n' + + local innerindent = '\t'..indent + for _, child in ipairs(children) do + if child.type == 'text' then + if child.value:match('%s%s') or child.value:match('^%s') or child.value:match('%s$') then + str = str..innerindent..'<![CDATA['..child.value..']]>\n' + else + str = str..innerindent..child.value:xml_escape()..'\n' + end + elseif child.type == 'comment' then + str = str..innerindent..'<!--'..child.value:xml_escape()..'-->\n' + else + str = str..indent..xml.realize(child, indentlevel + 1) + end + end + + str = str..indent..'</'..node.name..'>\n' + + return str +end + +-- Make an XML representation of a table. +function table.to_xml(t, indentlevel) + indentlevel = indentlevel or 0 + local indent = (' '):rep(4*indentlevel) + + local str = '' + for key, val in pairs(t) do + if type(key) == 'number' then + key = 'node' + end + if type(val) == 'table' and next(val) then + str = str..indent..'<'..key..'>\n' + str = str..table.to_xml(val, indentlevel + 1) + str = str..indent..'</'..key..'>\n' + else + if type(val) == 'table' then + val = '' + end + str = str..indent..'<'..key..'>'..val:xml_escape()..'</'..key..'>\n' + end + end + + return str +end + +return xml + +--[[ +Copyright © 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/linker/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/linker/data/settings.xml new file mode 100644 index 0000000..ec3209e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/linker/data/settings.xml @@ -0,0 +1,59 @@ +<?xml version="1.1" ?> +<settings> + <!-- + URL settings for Linker. If you want to add a site to open without a + search feature, add a new DOM node under the <raw> tag: + + <command>url</command> + + Once you do and reload Linker, you will be able to open the URL + provided by typing the following Windower command: + + web command + + If you want the site to be searchable, you need to define a searchable + URL under the <search> section, with the search keyword replaced with + ${query}. Once you do that and restart Linker, you can search as + follows: + + web command query + + "query" is the search term. It supports all of the features the + regular search of that site provides. For example, searching on FFXIDB + allows for wildcards, meaning you can search for "Estoq*body" and it + will find "Estoqueur's seal: body". This means that you can also do + that in the chat log: + + web db estoq*body + + That command will bring you to this page: http://ffxidb.com/items/3134 + + Note when adding new URLs: + Remember to replace & in URLs with & to be XML-comform. + --> + <global> + <raw> + <ah>http://ffxiah.com/</ah> + <ahf>http://www.ffxiah.com/forum</ahf> + <bg>http://wiki.bluegartr.com/bg/Main_Page</bg> + <bgf>http://www.bluegartr.com/forum.php</bgf> + <db>http://ffxidb.com/</db> + <g>http://google.com</g> + <ge>http://ffxi.gamerescape.com/wiki/Main_Page</ge> + <gw>http://guildwork.com</gw> + <of>http://forum.square-enix.com/ffxi/forum.php</of> + <wa>http://wolframalpha.com</wa> + <wikia>http://wiki.ffxiclopedia.org/wiki/Main_Page</wikia> + <win>http://windower.net</win> + </raw> + <search> + <ah>http://ffxiah.com/search/item?q=${query}</ah> + <bg>http://wiki.bluegartr.com/index.php?title=Special:Search&search=${query}</bg> + <db>http://ffxidb.com/search?q=${query}</db> + <g>http://google.com/?q=${query}</g> + <ge>http://ffxi.gamerescape.com/wiki/Special:Search?search=${query}</ge> + <wa>http://wolframalpha.com/?i=${query}</wa> + <wikia>http://wiki.ffxiclopedia.org/wiki/index.php?search=${query}&fulltext=Search</wikia> + </search> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/linker/linker.lua b/Data/DefaultContent/Libraries/addons/addons/linker/linker.lua new file mode 100644 index 0000000..13e6d40 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/linker/linker.lua @@ -0,0 +1,76 @@ +require('luau') +local url = require('socket.url') + +_addon.name = 'Linker' +_addon.author = 'Arcon' +_addon.version = '1.1.0.0' +_addon.command = 'linker' +_addon.commands = {'web'} +_addon.language = 'English' + +defaults = {} +defaults.raw = {} + +-- FFXI info sites +defaults.raw.db = 'http://ffxidb.com/' +defaults.raw.ah = 'http://ffxiah.com/' +defaults.raw.bg = 'http://wiki.bluegartr.com/bg/Main_Page' +defaults.raw.ge = 'http://ffxi.gamerescape.com/wiki/Main_Page' +defaults.raw.wikia = 'http://wiki.ffxiclopedia.org/wiki/Main_Page' + +-- FFXI community sites +defaults.raw.of = 'http://forum.square-enix.com/ffxi/forum.php' +defaults.raw.bgf = 'http://www.bluegartr.com/forum.php' +defaults.raw.ahf = 'http://www.ffxiah.com/forum' +defaults.raw.gw = 'http://guildwork.com/' + +-- Windower +defaults.raw.win = 'http://windower.net/' +defaults.raw.winf = 'http://forums.windower.net/' +defaults.raw.winw = 'http://wiki.windower.net/' + +-- Miscallenous sites +defaults.raw.g = 'http://google.com/' +defaults.raw.wa = 'http://wolframalpha.com/' + +defaults.search = {} + +-- FFXI info sites +defaults.search.db = 'http://ffxidb.com/search?q=${query}' +defaults.search.ah = 'http://ffxiah.com/search/item?q=${query}' +defaults.search.bg = 'http://wiki.bluegartr.com/index.php?title=Special:Search&search=${query}' +defaults.search.ge = 'http://ffxi.gamerescape.com/wiki/Special:Search?search=${query}' +defaults.search.wikia = 'https://ffxiclopedia.fandom.com/wiki/Special:Search?query=${query}' + +-- Miscallenous sites +defaults.search.g = 'http://google.com/?q=${query}' +defaults.search.wa = 'http://wolframalpha.com/?i=${query}' + +settings = config.load(defaults) + +-- Interpreter + +windower.register_event('addon command', function(command, ...) + if not ... or not settings.search[command] and settings.raw[command] then + windower.open_url(settings.raw[command]) + elseif settings.search[command] then + local query_string = url.escape(L{...}:concat(' ')) + local adjusted_query_string = query_string:gsub('%%', '%%%%') + windower.open_url((settings.search[command]:gsub('${query}', adjusted_query_string))) + else + error('Command "' .. command .. '" not found.') + end +end) + +--[[ +Copyright (c) 2013-2014, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/linker/readme.md b/Data/DefaultContent/Libraries/addons/addons/linker/readme.md new file mode 100644 index 0000000..76d8393 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/linker/readme.md @@ -0,0 +1,28 @@ +# Linker + +Opens certain links via simple console commands, with an optional search string. If the search string is omitted, the home page of that site will open. + +## Commands + +#### FFXI info sites: +``` +//web db [query] # FFXIDB +//web ah [query] # FFXIAH +//web bg [query] # BlueGartr Wiki +//web ge [query] # GamerEscape Wiki +//web wikia [query] # FFXIclopedia +``` + +#### FFXI community sites +``` +//web of # Official forums +//web bgf # BlueGartr forums +//web ahf # FFXIAH forums +//web gw # Guildwork.com +``` + +#### Miscellanous sites +``` +//web g [query] # Google +//web wa [query] # Wolfram|Alpha +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/lottery/lottery.lua b/Data/DefaultContent/Libraries/addons/addons/lottery/lottery.lua new file mode 100644 index 0000000..6ab8347 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/lottery/lottery.lua @@ -0,0 +1,29 @@ +_addon.name = 'Lottery' +_addon.author = 'Arcon' +_addon.language = 'english' +_addon.version = '1.0.0.0' + +windower.register_event('outgoing chunk', function(id, data, modified, injected, blocked) + if not blocked and id == 0x41 then + windower.send_ipc_message('pass ' .. tostring(data:sub(5, 5):byte())) + end +end) + +windower.register_event('ipc message', function(message) + if message:sub(1, 4) == 'pass' then + windower.ffxi.pass_item(tonumber(message:sub(6))) + end +end) + +--[[ +Copyright (c) 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/macrochanger/data/settings.txt b/Data/DefaultContent/Libraries/addons/addons/macrochanger/data/settings.txt new file mode 100644 index 0000000..194d491 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/macrochanger/data/settings.txt @@ -0,0 +1,58 @@ +Release Date: 9:00 PM, 4-01-13. +Author Comment: This document is whitespace sensitive, which means that you need the same number of spaces between things as exist in this initial settings file. +Author Comment: It looks at the first two words separated by spaces and then takes anything as the value in question if the first two words are relevant. +Author Comment: If you ever mess it up so that it does not work, you can just delete it and battlemod will regenerate it upon reload. +Author Comment: For the output customization lines, simply place the book and page number that you would like to change to upon a job change.. +Author Comment: If 2 jobs share a book, you can place the same book number for each job, then put their individual pages.. +Author Comment: Example: BLM and SCH both use Macro Book 2: BLM uses page 3. SCH uses page 1.. +Author Comment: Put BLM Book: 2, BLM Page: 3, SCH Book: 2, SCH Page: 1.. +Author Comment: If you wish to disable auto-macro Changing for a specific job, type "disabled". (e.g. BLM Book: disabled) +Author Comment: The design of the settings file is credited to Byrthnoth as well as the creation of the settings file. + + +File Settings: Fill in below +WAR Book: 1 +WAR Page: 1 +MNK Book: 2 +MNK Page: 1 +WHM Book: 3 +WHM Page: 1 +BLM Book: 20 +BLM Page: 1 +RDM Book: 5 +RDM Page: 1 +THF Book: 6 +THF Page: 1 +PLD Book: 7 +PLD Page: 1 +DRK Book: 8 +DRK Page: 1 +BST Book: 9 +BST Page: 1 +BRD Book: 10 +BRD Page: 1 +RNG Book: 11 +RNG Page: 1 +SAM Book: 12 +SAM Page: 1 +NIN Book: 13 +NIN Page: 1 +DRG Book: 14 +DRG Page: 1 +SMN Book: 15 +SMN Page: 1 +BLU Book: 16 +BLU Page: 1 +COR Book: 17 +COR Page: 1 +PUP Book: 18 +PUP Page: 1 +DNC Book: 19 +DNC Page: 1 +SCH Book: 20 +SCH Page: 1 +GEO Book: 5 +GEO Page: 1 +RUN Book: 4 +RUN Page: 1 + diff --git a/Data/DefaultContent/Libraries/addons/addons/macrochanger/macrochanger.lua b/Data/DefaultContent/Libraries/addons/addons/macrochanger/macrochanger.lua new file mode 100644 index 0000000..1d9aac7 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/macrochanger/macrochanger.lua @@ -0,0 +1,168 @@ +--Copyright (c) 2013, Banggugyangu +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'MacroChanger' +_addon.author = 'Banggugyangu' +_addon.version = '1.0.0.1' +_addon.commands = {'mc','macrochanger'} + +require('strings') +require('logger') + +windower.register_event('load', function() + globaldisable = 0 + macros = { + WAR = {Book = '', Page = ''}, + MNK = {Book = '', Page = ''}, + WHM = {Book = '', Page = ''}, + BLM = {Book = '', Page = ''}, + RDM = {Book = '', Page = ''}, + THF = {Book = '', Page = ''}, + PLD = {Book = '', Page = ''}, + DRK = {Book = '', Page = ''}, + BST = {Book = '', Page = ''}, + BRD = {Book = '', Page = ''}, + RNG = {Book = '', Page = ''}, + SAM = {Book = '', Page = ''}, + NIN = {Book = '', Page = ''}, + DRG = {Book = '', Page = ''}, + SMN = {Book = '', Page = ''}, + BLU = {Book = '', Page = ''}, + COR = {Book = '', Page = ''}, + PUP = {Book = '', Page = ''}, + DNC = {Book = '', Page = ''}, + SCH = {Book = '', Page = ''}, + GEO = {Book = '', Page = ''}, + RUN = {Book = '', Page = ''}, + } + options_load() +end) + +function options_load() + local f = io.open(windower.addon_path..'data/settings.txt', "r") + if f == nil then + local g = io.open(windower.addon_path..'data/settings.txt', "w") + g:write('Release Date: 9:00 PM, 4-01-13\46\n') + g:write('Author Comment: This document is whitespace sensitive, which means that you need the same number of spaces between things as exist in this initial settings file\46\n') + g:write('Author Comment: It looks at the first two words separated by spaces and then takes anything as the value in question if the first two words are relevant\46\n') + g:write('Author Comment: If you ever mess it up so that it does not work, you can just delete it and MacroChanger will regenerate it upon reload\46\n') + g:write('Author Comment: For the output customization lines, simply place the book and page number that you would like to change to upon a job change.\46\n') + g:write('Author Comment: If 2 jobs share a book, you can place the same book number for each job, then put their individual pages.\46\n') + g:write('Author Comment: Example: BLM and SCH both use Macro Book 2: BLM uses page 3. SCH uses page 1.\46\n') + g:write('Author Comment: Put BLM Book: 2, BLM Page: 3, SCH Book: 2, SCH Page: 1.\46\n') + g:write('Author Comment: If you wish to disable auto-macro Changing for a specific job, type "disabled" instead of a book number. (e.g. BLM Book: disabled)\n') + g:write('Author Comment: The design of the settings file is credited to Byrthnoth as well as the creation of the settings file.\n\n\n') + g:write('File Settings: Fill in below\n') + g:write('Disable All: 0\n') + g:write('WAR Book: 1\nWAR Page: 1\nMNK Book: 2\nMNK Page: 1\nWHM Book: 3\nWHM Page: 1\nBLM Book: 4\nBLM Page: 1\nRDM Book: 5\nRDM Page: 1\nTHF Book: 6\nTHF Page: 1\n') + g:write('PLD Book: 7\nPLD Page: 1\nDRK Book: 8\nDRK Page: 1\nBST Book: 9\nBST Page: 1\nBRD Book: 10\nBRD Page: 1\nRNG Book: 11\nRNG Page: 1\nSAM Book: 12\nSAM Page: 1\n') + g:write('NIN Book: 13\nNIN Page: 1\nDRG Book: 14\nDRG Page: 1\nSMN Book: 15\nSMN Page: 1\nBLU Book: 16\nBLU Page: 1\nCOR Book: 17\nCOR Page: 1\nPUP Book: 18\nPUP Page: 1\n') + g:write('DNC Book: 19\nDNC Page: 1\nSCH Book: 20\nSCH Page: 1\nGEO Book: 20\nGEO Page: 1\nRUN Book: 20\nRUN Page: 1\n') + g:close() + DisableAll = 0 + macros = { + WAR = {Book = '1', Page = '1'}, + MNK = {Book = '2', Page = '1'}, + WHM = {Book = '3', Page = '1'}, + BLM = {Book = '4', Page = '1'}, + RDM = {Book = '5', Page = '1'}, + THF = {Book = '6', Page = '1'}, + PLD = {Book = '7', Page = '1'}, + DRK = {Book = '8', Page = '1'}, + BST = {Book = '9', Page = '1'}, + BRD = {Book = '10', Page = '1'}, + RNG = {Book = '11', Page = '1'}, + SAM = {Book = '12', Page = '1'}, + NIN = {Book = '13', Page = '1'}, + DRG = {Book = '14', Page = '1'}, + SMN = {Book = '15', Page = '1'}, + BLU = {Book = '16', Page = '1'}, + COR = {Book = '17', Page = '1'}, + PUP = {Book = '18', Page = '1'}, + DNC = {Book = '19', Page = '1'}, + SCH = {Book = '20', Page = '1'}, + GEO = {Book = '20', Page = '1'}, + RUN = {Book = '20', Page = '1'}, + } + print('Default settings file created') + notice('MacroChanger created a settings file and loaded!') + else + f:close() + for curline in io.lines(windower.addon_path..'data/settings.txt') do + local splat = curline:gsub(':',''):split(' ') + local cmd = '' + if splat[1] and macros[splat[1]:upper()] and splat[2] ~=nil and (splat[2]:lower() == 'book' or splat[2]:lower() == 'page') and splat[3] then + macros[splat[1]:upper()][splat[2]:ucfirst()] = splat[3] -- Instead of a number, this can also be 'disabled' + elseif splat[1] and splat[2] and (splat[1]..' '..splat[2]) == 'disable all' and tonumber(splat[3]) then + globaldisable = tonumber(splat[3]) + end + end + notice('MacroChanger read from a settings file and loaded!') + end +end + +windower.register_event('job change',function () +-- Could use the job ID passed into this function, but the addon would have to include the resources library + local job = windower.ffxi.get_player().main_job + local book = '' + local page = '' + if globaldisable == 0 then + if job and macros[job] then + book = macros[job].Book + page = macros[job].Page + end + if ((book == 'disabled') or (page == 'disabled')) then + notice('Auto Macro Switching Disabled for ' .. job ..'.') + else + log('Job changed to ' .. job .. ' - switched to Book: ' .. book .. ' and Page: ' .. page) + windower.chat.input('/macro book ' .. book) + coroutine.sleep(1.2) + windower.chat.input('/macro set ' .. page) + end + elseif globaldisable == 1 then + notice('Auto Macro Switching Disabled for All Jobs.') + end +end) + +windower.register_event('addon command', function(...) + local args = {...} + local mjob = windower.ffxi.get_player().main_job + if args[1] == 'disableall' then + if args[2] == 'on' then + globaldisable = 1 + notice('All automated macro switching disabled.') + elseif args[2] == 'off' then + globaldisable = 0 + notice('Automated macro switching enabled.') + end + elseif args[1]:lower() == 'help' then + log('MacroChanger Commands:') + log('disableall [on|off]') + log(' on - Disables all automated macro switching') + log(' off - Enables all automated macro switching not disabled individually') + log('Resets to what is stored in settings upon unloading of addon. To Permanently change, please change the option in the settings file.') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/obiaway/Obiaway.lua b/Data/DefaultContent/Libraries/addons/addons/obiaway/Obiaway.lua new file mode 100644 index 0000000..523b13d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/obiaway/Obiaway.lua @@ -0,0 +1,509 @@ +-- +-- obiaway v1.0.7 +-- +-- Copyright ©2013-2015, ReaperX, Bangerang +-- All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of obiaway nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL ReaperX or Bangerang 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. +-- +-- Puts elemental obi away when they are no longer needed due +-- to day/weather/storm effect change and gets elemental obi based +-- on same conditions. Uses itemizer addon. +-- +-- The advantage of this system compared to moving obi back during +-- aftercast is that it avoids excessive inventory movement, +-- so malfunctions due to inventory filling up completely are +-- less likely, and timing issues with very fast spells (spell +-- fires before obi is moved) occur at worst on the first spell +-- not but subsequent ones. +-- +-- Known bugs: +-- +-- 1. Using the get, put, or sort commands too quickly can have +-- undesirable effects, often not moving obi at all. This is due to +-- limitations on how fast items can be moved from one inventory to +-- another. +-- +-- 2. When weather changes due to zoning, get_obi_in_inventory() +-- is called before inventory has loaded and returns nothing. +-- +-- 3. Obi is not moved when currently equipped. +-- +-- +-- To-do: +-- - Add support for other languages. +-- +-- + +-- addon info +_addon.name = "obiaway" +_addon.author = "ReaperX, Bangerang" +_addon.version = "1.0.7" +_addon.commands = {'oa', 'ob', 'obi', 'obiaway'} +_addon.language = 'english' + +-- library includes +require('sets') +require('logger') +config = require('config') +res = require('resources') + +-- settings +default_settings = {} +default_settings.notify = true +default_settings.location = 'sack' +default_settings.lock = false +default_settings.color = 209 +default_settings.ignore_zones = S{ + "Ru'Lude Gardens", "Upper Jeuno", "Lower Jeuno", "Port Jeuno", "Port Windurst", "Windurst Waters", "Windurst Woods", "Windurst Walls", + "Heavens Tower", "Port San d'Oria", "Northern San d'Oria", "Southern San d'Oria", "Chateau d'Oraguille", "Port Bastok", "Bastok Markets", + "Bastok Mines", "Metalworks", "Aht Urhgan Whitegate", "Tavnazian Safehold", "Nashmau", "Selbina", "Mhaura", "Norg", "Rabao", "Kazham", + "Eastern Adoulin", "Western Adoulin", "Leafallia", "Celennia Memorial Library", "Mog Garden"} +settings = config.load(default_settings) + +-- tokens +tokens = {} +tokens.lock_warned = false +tokens.inv_full_warned = false +tokens.bag_full_warned = false + +-- lists +obi_names = T{ + Light = 'Korin', + Dark = 'Anrin', + Fire = 'Karin', + Earth = 'Dorin', + Water = 'Suirin', + Wind = 'Furin', + Ice = 'Hyorin', + Lightning = 'Rairin' +} + + +---------------------------------------- +--- UTITLITY Functions +---------------------------------------- + +-- Automatically formats output text for addon. Accepts msg as a string. +function obi_output(msg) + prefix = 'Obi: ' + windower.add_to_chat(settings.color, prefix..msg) +end + +-- Accepts a boolean value and returns an appropriate string value. i.e. true -> 'on' +function bool_to_str(bool) + return bool and 'on' or 'off' +end + +-- checks if a value is in a table and then returns its key. Ex: a table contains Key = Value. returns Key. returns false if no match. +function in_table(tbl, item) + for key, value in pairs(tbl) do + if value == item then return key end + end + return false +end + +-- converts name of a bag (str) to an id number. returns 0 (inventory) when no argument passed. +function inv_str_to_id(str) + if not str then return 0 end + if str == 'inventory' then + return 0 + elseif str == 'safe' then + return 1 + elseif str == 'storage' then + return 2 + elseif str == 'locker' then + return 3 + elseif str == 'satchel' then + return 5 + elseif str == 'sack' then + return 6 + elseif str == 'case' then + return 7 + elseif str == 'wardrobe' then + return 8 + else + print('Obiaway: Function inv_str_to_id invalid argument') + return + end +end + +-- Function which counts how many slots remain in a bag. if no location is passed, checks inventory. +function free_space(location) + local id = inv_str_to_id(location) + + local inv = windower.ffxi.get_bag_info(id) + n = inv.max - inv.count + return n +end + +-- check's if an inventory is full. returns true if full. if no location argument is passed checks main inventory. +-- also handle's an inventory full warning. requires two tokens which keep track of whether or not an inventory +-- full warning was already given. one for the main inventory and a separate for a bag. this is to prevent +-- "Inventory full." spam in the chat log. +function inventory_full(command, location) + local id = inv_str_to_id(location) + if id == 0 then location = 'inventory' end + + if free_space(location) == 0 then + if command then + obi_output('%s is full.':format(string.ucfirst(location))) + elseif not tokens.inv_full_warned and id == 0 then + tokens.inv_full_warned = true + obi_output('%s is full.':format(string.ucfirst(location))) + elseif not tokens.bag_full_warned then + tokens.bag_full_warned = true + obi_output('%s is full.':format(string.ucfirst(location))) + end + return true + elseif free_space(location) > 0 then -- resets the tokens when space is free + if id == 0 then tokens.inv_full_warned = false else tokens.bag_full_warned = false end + return false + end + + print('Obiaway: Function inventory_full unknown error.') +end + + +---------------------------------------- +--- ADDON functions +---------------------------------------- + +-- locks obi sorting. accepts true or false. +function lock_obi(toggle) + if toggle then + settings.lock = true + tokens.lock_warned = true + if settings.notify then + obi_output('Obi locked.') + end + elseif not toggle then + settings.lock = false + tokens.lock_warned = false + if settings.notify then + obi_output('Unlocking obi...') + end + end +end + +-- Builds a table of boolean values that indicate which obi are in inventory and how many. +-- Ex: +-- obi = { +-- "Fire" = false +-- "Ice" = false +-- "Wind" = true +-- "Earth" = false +-- "Lightning" = true +-- "Water" = false +-- "Light" = false +-- "Dark" = true +-- n = 3 +-- } +-- +-- function designer: ReaperX +function get_obi_in_inventory(location) + local id = inv_str_to_id(location) + local obi = {} + local inv = windower.ffxi.get_items(id) + if not inv then return end + + for i=1,inv.max do + id = inv[i].id + if ( id>=15435 and id<=15442) then + obi["Fire"] = obi["Fire"] or (id == 15435) + obi["Ice"] = obi["Ice"] or (id == 15436) + obi["Wind"] = obi["Wind"] or (id == 15437) + obi["Earth"] = obi["Earth"] or (id == 15438) + obi["Lightning"] = obi["Lightning"] or (id == 15439) + obi["Water"] = obi["Water"] or (id == 15440) + obi["Light"] = obi["Light"] or (id == 15441) + obi["Dark"] = obi["Dark"] or (id == 15442) + end + end + + -- count obi in inventory + obi["n"] = table.count(obi, true) + + return obi +end + +-- Builds a table (elements) which contains storm buffs, weather effects, and day of the week is active. +-- Adds +1 to corresponding element variable for each effect active. +-- Ex: If fire weather and earthsday are active will return: +-- elements = { +-- "Fire" = true +-- "Earth" = true +-- "Water" = false +-- "Wind" = false +-- "Ice" = false +-- "Lightning" = false +-- "Light" = false +-- "Dark" = false +-- "None" = false +-- } +-- +-- function designer: ReaperX +function get_all_elements() + local elements = {} + elements["Fire"] = 0 + elements["Earth"] = 0 + elements["Water"] = 0 + elements["Wind"] = 0 + elements["Ice"] = 0 + elements["Lightning"] = 0 + elements["Light"] = 0 + elements["Dark"] = 0 + elements["None"] = 0 + + -- check for active Day and Weather + local info = windower.ffxi.get_info() + local day_element = res.elements[res.days[info.day].element].english + elements[day_element] = elements[day_element] + 1 + local weather_element = res.elements[res.weather[info.weather].element].english + elements[weather_element] = elements[weather_element] + 1 + + -- check for active SCH buffs + local buffs = windower.ffxi.get_player().buffs + if in_table(buffs, 178) then + elements["Fire"] = elements["Fire"] + 1 + elseif in_table(buffs, 183) then + elements["Water"] = elements["Water"] + 1 + elseif in_table(buffs, 181) then + elements["Earth"] = elements["Earth"] + 1 + elseif in_table(buffs, 180) then + elements["Wind"] = elements["Wind"] + 1 + elseif in_table(buffs, 179) then + elements["Ice"] = elements["Ice"] + 1 + elseif in_table(buffs, 182) then + elements["Lightning"] = elements["Lightning"] + 1 + elseif in_table(buffs, 184) then + elements["Light"] = elements["Light"] + 1 + elseif in_table(buffs, 185) then + elements["Dark"] = elements["Dark"] + 1 + end + + return elements +end + +---------------------------------------- +--- SORTING functions +---------------------------------------- + +function get_needed_obi(command) + if inventory_full(command) then return false end + local obi = get_obi_in_inventory() + local elements = get_all_elements() + + for name, element in obi_names:it() do + if not obi[element] and elements[element] > 0 then + windower.send_command('get "%s Obi" %s;':format(name, settings.location)) + if settings.notify then + obi_output('Getting %s Obi from %s.':format(name, settings.location)) + end + coroutine.sleep(.5) + end + end + + return true +end + +function put_unneeded_obi(command) + if inventory_full(command, settings.location) then return false end + local obi = get_obi_in_inventory() + local elements = get_all_elements() + + for name, element in obi_names:it() do + if obi[element] and elements[element] == 0 then + windower.send_command('put "%s Obi" %s;':format(name, settings.location)) + if settings.notify then + obi_output('Putting %s Obi away into %s.':format(name, settings.location)) + end + coroutine.sleep(.5) + end + end + + return true +end + +function get_all_obi(command) + if inventory_full(command) then return false end + local obi = get_obi_in_inventory() + local obi_bag = get_obi_in_inventory(settings.location) + if free_space() < obi_bag["n"] then + obi_output('Not enough space in inventory...') + return false + end + + local elements = get_all_elements() + local obi = get_obi_in_inventory() + + for name, element in obi_names:it() do + if not obi[element] then + windower.send_command('get "%s Obi" %s;':format(name, settings.location)) + if settings.notify then + obi_output('Getting %s Obi from %s.':format(name, settings.location)) + end + coroutine.sleep(.5) + end + end + + return true +end + +function put_all_obi(command) + if inventory_full(command, settings.location) then return false end + local obi = get_obi_in_inventory() + if free_space(settings.location) < obi["n"] then + obi_output('Not enough space in %s...':format(settings.location)) + return false + end + + local elements = get_all_elements() + + for name, element in obi_names:it() do + if obi[element] then + windower.send_command('put "%s Obi" %s;':format(name, settings.location)) + if settings.notify then + obi_output('Putting %s Obi away into %s.':format(name, settings.location)) + end + coroutine.sleep(.5) + end + end + + return true +end + +-- function called on automatic events. sorts obi based on location. +function auto_sort_obi() + -- if inventory and obi bag are full at the same time, do nothing. 'cause we can't. + if inventory_full(false) and inventory_full(false, settings.location) then return false end + + if not settings.lock then -- if sorting lock is not on, then do this stuff: + if not settings.ignore_zones:contains(res.zones[windower.ffxi.get_info().zone].english) then -- Not in a city: + put_unneeded_obi(false) + get_needed_obi(false) + else -- In a city: + put_all_obi(false) + end + end + + return true +end + +---------------------------------------- +--- EVENTS +---------------------------------------- + +windower.register_event('gain buff', auto_sort_obi:cond(function(id) return id >= 178 and id <= 185 end)) +windower.register_event('lose buff', auto_sort_obi:cond(function(id) return id >= 178 and id <= 185 end)) +windower.register_event('day change', 'weather change', auto_sort_obi) +windower.register_event('addon command', function(command, ...) + command = command and command:lower() or 'help' + params = L{...}:map(string.lower) + + + if command == 'help' or command == 'h' then + obi_output("obiaway v".._addon.version..". Authors: ".._addon.author) + obi_output("//obiaway [options]") + obi_output(" help : Displays this help text.") + obi_output(" sort : Automatically sorts obi.") + obi_output(" get [ all | needed ]") + obi_output(" Gets obi from bag.") + obi_output(" put [ all | unneeded ] [ sack | satchel | case | wardrobe ]") + obi_output(" Puts obi away. Can optionally specify a location.") + obi_output(" lock [ on | off ] (%s)":format(bool_to_str(settings.lock))) + obi_output(" Locks obi to current location.") + obi_output(" notify [ on | off ] (%s)":format(bool_to_str(settings.notify))) + obi_output(" Sets obiaway notifcations on or off.") + obi_output(" location [ sack | satchel | case | wardrobe ] (%s)":format(settings.location)) + obi_output(" Sets inventory from which to get and put obi.") + elseif command == 'sort' or command == 's' then + if settings.lock then lock_obi(false) end + if settings.notify then + obi_output('Sorting obi...') + coroutine.sleep(0.5) + end + auto_sort_obi(true) + elseif command == 'get' or command == 'g' then + if params[1] == 'all' or params [1] == 'a' then + obi_output('Getting all obi from %s...':format(settings.location)) + get_all_obi(true) + coroutine.sleep(1) + elseif params[1] == 'needed' or params [1] == 'n' then + obi_output('Getting needed obi from %s...':format(settings.location)) + get_needed_obi(true) + else + error("Invalid argument. Usage: //obiaway get [ all | needed ]") + end + elseif command == 'put' or command == 'p' then + if params[1] == 'all' or params [1] == 'a' then + if S{'sack','case','satchel','wardrobe'}:contains(params[2]) then + settings.location = params[2] + obi_output("Obiaway location set to: %s":format(settings.location)) + end + obi_output('Putting all obi into %s...':format(settings.location)) + put_all_obi(true) + coroutine.sleep(1) + elseif params[1] == 'unneeded' or params[1] == 'needed' or params [1] == 'n' then + if S{'sack','case','satchel','wardrobe'}:contains(params[2]) then + settings.location = params[2] + obi_output("Obiaway location set to: %s":format(settings.location)) + else + obi_output('Putting unneeded obi into %s...':format(settings.location)) + put_unneeded_obi(true) + end + else + error("Invalid argument. Usage: //obiaway put [ all | needed ] [ sack | satchel | case | wardrobe ]") + end + elseif command == 'lock' or command == 'l' then + if params[1] == 'on' then + lock_obi(true) + elseif params[1] == 'off' then + lock_obi(false) + else + error("Invalid argument. Usage: //obiaway lock [ on | off ]") + end + elseif command == 'unlock' then + lock_obi(false) + elseif command == 'notify' or command == 'n' then + if params[1] == 'on' then + settings.notify = true + obi_output("Notifications are now on") + elseif params[1] == 'off' then + settings.notify = false + obi_output("Notifications are now off.") + else + error("Invalid argument. Usage: //obiaway notify [ on | off ]") + end + elseif command == 'location' or command == 'loc' then + if S{'sack','case','satchel','wardrobe'}:contains(params[1]) then + settings.location = params[1] + obi_output("Obiaway location set to: %s":format(settings.location)) + else + error("Invalid argument. Usage: //obiaway location [ sack | satchel | case | wardrobe ]") + end + else + error("Unrecognized command. See //obiaway help.") + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/obiaway/README.md b/Data/DefaultContent/Libraries/addons/addons/obiaway/README.md new file mode 100644 index 0000000..9534222 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/obiaway/README.md @@ -0,0 +1,17 @@ +Author: ReaperX, bangerang +Version: 1.0.7 + +Addon to automatically get and store elemental obis based on day/weather/storm +conditions. The addon is triggered on day change, weather change, and scholar +storm buff gain and loss. + +Command listing: + +//obiaway, //obi, //ob, or //oa + (h)elp - display help information. + (s)ort - force obiaway to put away and get appropriate obis. + (g)et [ (a)ll | (n)eeded ] - get obi + (p)ut [ (a)ll| u(n)needed ] [ sack | satchel | case | wardrobe] - put obi. location optional + (l)ock [ on | off ] - lock obi sorting + (n)otify [ on | off ] - sets notifications on or off. + (loc)ation [ sack | satchel | case | wardrobe] - sets where to put obis away. diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/Readme.md b/Data/DefaultContent/Libraries/addons/addons/ohShi/Readme.md new file mode 100644 index 0000000..679684b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/Readme.md @@ -0,0 +1,39 @@ +**Author:** Ricky Gall +**Version:** 2.55 +**Description:** +Replacement for yarnregex for Windower 4 I made for a friend. Uses the chat log so filters must be off. At least until i figure out another way to do it. Keeps track of various event related things. Such as, VW proc messages, mob casting, mob tp moves, TH procs and cor rolls, as well as others. Digi of shiva created the icon and was the driving force behind testing/giving me the idea to do this. Digi also created the default mob list/danger list and chose the name. + +**Abbreviation:** //ohShi + +**Commands:** + 1. help - Brings up this menu. + 2. showrolls | selfrolls - Show corsair rolls in tracker | only own rolls. + 3. staggeronly - Only show voidwatch stagger notices. + 4. track(on/off) <abyssea/dangerous/legion/meebles/other/voidwatch> (name) - Begin or stop tracking (type (default: other)) of mob (name). + 5. spell/ws(on/off) <name> - Start or stop watching for <name> spell|ws. +**The following commands all correspond to the tracker:** + 6. fonttype <name> - change to (name) font + 7. fontsize <size> - change to (size) font + 8. pos <x> <y> - change boxes x/y coordinates (can click/drag as well) + 9. bgcolor <r> <g> <b> - change background color (r: red) (g: green) (b: blue) + 10. txtcolor <r> <g> <b> - change text color (r: red) (g: green) (b: blue) + 11. duration <time> - Changes the duration things stay in tracker. + 12. settings - shows current textbox settings + 13. show/hide - toggles visibility of the tracker so you can make changes. + +**Changes:** +* v2.55 + * Added vagary weakness tracking +* v2.5 + * Complete overhaul. + * Added ability to click/drag the text box + * Add selfrolls command (if this is on only your rolls will show) + * Due to the overhaul, all of your settings (except your moblist) will be reset to default. +* v2.1 + * On load/help command announce addon version +* v2.0 + * Fixed issue with nil value on ws or blue magic cast from player + * Fixed magic messages due to the spell resources being different + * Added confirmation and boxflash on settings change + * Settings file updated. moblist.xml deprecated + diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/ohShi/data/settings.xml new file mode 100644 index 0000000..8baab64 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/data/settings.xml @@ -0,0 +1,42 @@ +<?xml version="1.1" ?> +<settings> + <global> + <bg> + <alpha>255</alpha> + <blue>0</blue> + <green>0</green> + <red>0</red> + </bg> + <dangerwords> + <spells>Aeroga IV, Aeroja, Blizzaga IV, Blizzaja, Breakga, Death, Firaga IV, Firaja, Kaustra, Meteor, Stonega IV, Stoneja, Thundaga IV, Thundaja</spells> + <weaponskills>Arm Cannon, Astral Flow, Ballistic Kick, Beastruction, Chainspell, Danse Macabre, Divesting Gale, Eradicator, Extreme Purgitation, Frog Chorus, Frog Song, Fulmination, Gates of Hades, Geirrothr, Gorge, Mandible Massacre, Nerve Gas, Oblivion's Mantle, Pawn's Penumbra, Raksha Stance, Rancid Reflux, Slimy Proposal, Thundris Shriek, Yama's Judgment, Zantetsuken</weaponskills> + </dangerwords> + <duration>10</duration> + <moblist> + <abyssea>Alfard, Carabosse, Glavoid, Isgebind, Orthrus</abyssea> + <dangerous>Apademak, Provenance Watcher</dangerous> + <legion>Lofty, Mired, Paramount, Soaring, Veiled</legion> + <meebles>Dreyruk, Goldwing, Grannus, Izyx, Melisseus, Samursk, Silagilith, Surtr, Svaha, Umagrhk</meebles> + <other>Bloodthirsty, Cerberus, Dvergr, Enraged, Hydra, Khimaira, Khrysokhimaira, Odin, Tiamat</other> + <voidwatch>Aello, Agathos, Akvan, Asb, Belphoebe, Bismarck, Botulus Rex, Celaeno, Cherufe, Gasha, Gaunab, Giltine, Goji, Gugalanna, Hahava, Ig-Alima, Kaggen, Kalasutrax, Kholomodumo, Mellonia, Morta, Ocythoe, Pil, Provenance Watcher, Qilin, Rukh, Sarbaz, Shah, Taweret, Uptala, Wazir</voidwatch> + </moblist> + <padding>0</padding> + <pos> + <x>400</x> + <y>300</y> + </pos> + <selfrolls>false</selfrolls> + <showrolls>true</showrolls> + <staggeronly>false</staggeronly> + <text> + <alpha>255</alpha> + <blue>255</blue> + <content /> + <font>Consolas</font> + <green>255</green> + <red>255</red> + <size>10</size> + </text> + <visible>false</visible> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/data/warning.png b/Data/DefaultContent/Libraries/addons/addons/ohShi/data/warning.png Binary files differnew file mode 100644 index 0000000..23c294d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/data/warning.png diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/default_settings.lua b/Data/DefaultContent/Libraries/addons/addons/ohShi/default_settings.lua new file mode 100644 index 0000000..7e19ca5 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/default_settings.lua @@ -0,0 +1,81 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +]] + +--Declaring default settings +defaults = T{} +--Addon settings +defaults.staggeronly = false +defaults.showrolls = true +defaults.selfrolls = false +defaults.duration = 10 + +--Textbox settings +defaults.bg = {} +defaults.bg.alpha = 100 +defaults.bg.red = 0 +defaults.bg.blue = 0 +defaults.bg.green = 0 +defaults.pos = {} +defaults.pos.x = 400 +defaults.pos.y = 300 +defaults.text = {} +defaults.text.red = 255 +defaults.text.green = 255 +defaults.text.blue = 255 +defaults.text.font = 'Consolas' +defaults.text.size = 10 + +--Moblist defaults +defaults.moblist = T{} +defaults.moblist['voidwatch'] = S{"Qilin", "Celaeno", "Morta", "Bismarck", "Ig-Alima", "Kalasutrax", "Ocythoe", "Gaunab", "Hahava", "Cherufe", "Botulus Rex", "Taweret", "Agathos", "Goji", "Gugalanna", "Gasha", "Giltine", "Mellonia", "Kaggen", "Akvan", "Pil", "Belphoebe", "Kholomodumo", "Aello", "Uptala", "Sarbaz", "Shah", "Wazir", "Asb", "Rukh", "Provenance Watcher"} +defaults.moblist['abyssea'] = S{"Alfard", "Orthrus", "Carabosse", "Glavoid", "Isgebind"} +defaults.moblist['legion'] = S{"Veiled", "Lofty", "Soaring", "Mired", "Paramount"} +defaults.moblist['meebles'] = S{"Goldwing", "Silagilith", "Surtr", "Dreyruk", "Samursk", "Umagrhk", "Izyx", "Grannus", "Svaha", "Melisseus"} +defaults.moblist['other'] = S{"Tiamat", "Khimaira", "Khrysokhimaira", "Cerberus", "Dvergr", "Bloodthirsty", "Hydra", "Enraged", "Odin"} +defaults.moblist['dangerous'] = S{"Provenance Watcher", "Apademak"} +defaults.dangerwords = T{} +defaults.dangerwords['weaponskills'] = S{"Zantetsuken", "Geirrothr", "Astral Flow", "Chainspell", "Beastruction", "Mandible Massacre", "Oblivion's Mantle", "Divesting Gale", "Frog Song", "Frog Chorus", "Danse Macabre", "Raksha Stance", "Yama's Judgment", "Ballistic Kick", "Eradicator", "Arm Cannon", "Gorge", "Extreme Purgitation", "Slimy Proposal", "Rancid Reflux", "Pawn's Penumbra", "Gates of Hades", "Fulmination", "Nerve Gas", "Thundris Shriek"} +defaults.dangerwords['spells'] = S{"Death", "Meteor", "Kaustra", "Breakga", "Thundaga IV", "Thundaja", "Firaga IV", "Firaja", "Aeroga IV", "Aeroja", "Blizzaga IV", "Blizzaja", "Stonega IV", "Stoneja"} + +--Fill settings from either defaults table or settings.xml +settings = config.load(defaults) +ohShi_tb = texts.new(settings) + +--create tables to be used throughout the addon +tracking = T{} +prims = S{} +--[[ keeping in case the function below doesn't work. +trusts = S{'Kupipi','Excenmille','Naji','Ayame','Zeid','Curilla', + 'NanaaMihgo','Trion','Shantotto','Volker','Ajido-Marujido', + 'MihliAliapoh','Valaineral','Joachim','Lion','Prishe','Ulmia', + 'Ironeater','Gadalar','NajaSalaheem','Cherukiki','Nashmeira', + 'Zazarg','Ingrid','LhekoHabhoka','Ovjang','Mnejing','Sakura', + 'Luzaf','Najelith', 'Maat','Gessho','Aldo','Moogle','Fablinix', + 'D.Shantotto','Elvira','Noillurie','LhuMhakaracca','FerreousCoffin', + 'StarSibyl','Mumor'}]] + trusts = S(res.spells:type('Trust'):map(string.gsub-{' ', ''} .. table.get-{'name'})) diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/helper_functions.lua b/Data/DefaultContent/Libraries/addons/addons/ohShi/helper_functions.lua new file mode 100644 index 0000000..e08fb21 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/helper_functions.lua @@ -0,0 +1,146 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +]] + +--This function checks the string sent to it against your mob list +--returns true if it's found and false if not. +function mCheck(str) + for category,_ in pairs(settings.moblist) do + for name,_ in pairs(settings.moblist[category]) do + if str:lower():contains(name:lower()) then + return true + end + end + end + return false +end + +--This function checks the string sent to it against your mob list +--returns true if it's found and false if not. +function mDanger(str) + for name,_ in pairs(settings.moblist.dangerous) do + if str:lower():contains(name:lower()) then + return true + end + end + return false +end + +--This function checks the string sent to it against your danger list +--returns true if it's found and false if not. +function dCheck(typ, sid) + --log('DEBUG TYP: '..typ..' ID: '..sid) + sid = tonumber(sid) + if typ == 'spell' then + if settings.dangerwords.spells:find(string.imatch-{res.spells[sid].english .. '$'}) then + return true + end + elseif sid <= 255 then + if settings.dangerwords.weaponskills:find(string.imatch-{res.weapon_skills[sid].english .. '$'}) then + return true + end + else + if settings.dangerwords.weaponskills:find(string.imatch-{res.monster_abilities[sid].english .. '$'}) then + return true + end + end + + return false +end + +--Check if the actor is actually an npc rather than a player +function isMob(id) + if not trusts:contains(windower.ffxi.get_mob_by_id(id)['name']) then + return windower.ffxi.get_mob_by_id(id)['is_npc'] + end + return false +end + +--This function is used to parse the windower resources +--to fill tables with ability/spell names/ids. +--Created by Byrth +function parse_resources(lines_file) + local ignore_fields = S{'german','french','japanese','index','recast','fr','frl','de','del','jp','jpl'} + local completed_table = {} + local counter = 0 + for i in ipairs(lines_file) do + local str = tostring(lines_file[i]) + local g,h,typ,key = string.find(str,'<(%w+) id="(%d+)" ') + if typ == 's' then + g,h,key = string.find(str,'index="(%d+)" ') + end + if key ~=nil then + completed_table[tonumber(key)]={} + local q = 1 + while q <= str:len() do + local a,b,ind,val = string.find(str,'(%w+)="([^"]+)"',q) + if ind~=nil then + if not ignore_fields[ind] then + if val == "true" or val == "false" then + completed_table[tonumber(key)][ind] = str2bool(val) + else + completed_table[tonumber(key)][ind] = val:gsub('"','\42'):gsub(''','\39') + end + end + q = b+1 + else + q = str:len()+1 + end + end + local k,v,english = string.find(str,'>([^<]+)</') + if english~=nil then + completed_table[tonumber(key)]['english']=english + end + end + end + + return completed_table +end + +--This function was made by Byrth. It's used to split strings +--at a specific character and store them in a table +function split(msg, match) + if msg == nil then return '' end + local length = msg:len() + local splitarr = {} + local u = 1 + while u <= length do + local nextanch = msg:find(match,u) + if nextanch ~= nil then + splitarr[#splitarr+1] = msg:sub(u,nextanch-match:len()) + if nextanch~=length then + u = nextanch+match:len() + else + u = length + end + else + splitarr[#splitarr+1] = msg:sub(u,length) + u = length+1 + end + end + return splitarr +end diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/ohShi.lua b/Data/DefaultContent/Libraries/addons/addons/ohShi/ohShi.lua new file mode 100644 index 0000000..567b15f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/ohShi.lua @@ -0,0 +1,273 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'OhShi' +_addon.version = '2.55' +_addon.author = 'Nitrous (Shiva)' +_addon.command = 'ohshi' + +--Requiring libraries used in this addon +--These should be saved in addons/libs +require('logger') +require('tables') +require('strings') +require('sets') +config = require('config') +files = require('files') +chat = require('chat') +texts = require('texts') +res = require('resources') +require('default_settings') +require('text_handling') +require('helper_functions') + +--This function is called when the addon loads. Defines aliases and +--registers functions, as well as filling the resource tables. +windower.register_event('load', initText) + +--Used when the addon is unloaded to save settings. +windower.register_event('unload',function() + settings:update(ohShi_tb._settings) + settings:save('all') +end) + +function saveSettings() + addText('OhShi Settings Updated') + settings:save('all') +end + +--This function is used to process addon commands +--like //ohshi help and the like. +windower.register_event('addon command', function(...) + local args = T{...} + if args[1] == nil then args[1] = 'help' end + if args[1] ~= nil then + local comm = table.remove(args,1):lower() + + if S{'showrolls','selfrolls'}:contains(comm) then + settings[comm] = not settings[comm] + settings.staggeronly = false + if comm == 'selfrolls' and not settings.showrolls then + settings.showrolls = true + elseif comm == 'showrolls' and settings.selfrolls then + settings.selfrolls = false + end + print('OhShi Showrolls:', settings.showrolls) + print('OhShi Selfrolls:', settings.selfrolls) + settings:save('all') + elseif comm == "staggeronly" then + settings.staggeronly = not settings.sstaggeronly + print('OhShi Stagger Only mode:', settings.staggeronly) + elseif comm == 'duration' then + if tonumber(args[1]) then + settings.duration = tonumber(args[1]) + print('OhShi Duration:',settings.duration) + saveSettings() + end + elseif S{'trackon','trackoff'}:contains(comm) then + local typ = '' + if S{'abyssea','dangerous','legion','meebles','other','voidwatch'}:contains(args[1]) then + typ = table.remove(args,1):lower() + else + typ = 'other' + end + local list = args:concat(' '):capitalize() + if comm == 'trackon' then + if not settings.moblist[typ]:find(string.imatch-{list}) then + settings.moblist[typ]:add(list) + notice(list..' added to '..typ..' table.') + end + else + if settings.moblist[typ]:find(string.imatch-{list}) then + settings.moblist[typ]:remove(settings.moblist[typ]:find(string.imatch-{list})) + notice(list..' removed from '..typ..' table.') + end + end + settings:save('all') + elseif S{'spellon','spelloff','wson','wsoff'}:contains(comm) then + local typ = '' + if S{'spellon','spelloff'}:contains(comm) then + typ = 'spells' + else + typ = 'weaponskills' + end + local list = args:concat(' '):capitalize() + if comm:find('on$') then + if not settings.dangerwords[typ]:find(string.imatch-{list..'$'}) then + settings.dangerwords[typ]:add(list) + notice(list..' added to '..typ..' table.') + end + else + if settings.dangerwords[typ]:find(string.imatch-{list..'$'}) then + settings.dangerwords[typ]:remove(settings.dangerwords[typ]:find(string.imatch-{list..'$'})) + notice(list..' removed from '..typ..' table.') + end + end + settings:save('all') + elseif S{'fonttype','fontsize','pos','bgcolor','txtcolor'}:contains(comm) then + if comm == 'fonttype' then ohShi_tb:font(args[1] or nil) + elseif comm == 'fontsize' then ohShi_tb:size(args[1] or nil) + elseif comm == 'pos' then ohShi_tb:pos(args[1] or nil,args[2] or nil) + elseif comm == 'bgcolor' then ohShi_tb:bgcolor(args[1] or nil,args[2] or nil,args[3] or nil) + elseif comm == 'txtcolor' then ohShi_tb:color(args[1] or nil,args[2] or nil,args[3] or nil) + end + settings:update(ohShi_tb._settings) + settings.bg.alpha = nil + settings.padding = nil + settings.text.alpha = nil + settings.text.content = nil + settings.visible = nil + saveSettings() + elseif comm == 'clear' then + tracking:clear() + textUpdate() + elseif S{'show','hide','settings'}:contains(comm) then + if comm == 'show' then + ohShi_tb:text('ohShi showing for settings') + ohShi_tb:show() + elseif comm == 'hide' then + settings:update(ohShi_tb._settings) + settings.bg.alpha = nil + settings.padding = nil + settings.text.alpha = nil + settings.text.content = nil + settings.visible = nil + textUpdate() + ohShi_tb:hide() + settings:save('all') + elseif comm == 'settings' then + windower.add_to_chat(207,'OhShi - Current Textbox Settings') + windower.add_to_chat(207,' BG: R: '..settings.bg.red..' G: '..settings.bg.green..' B: '..settings.bg.blue) + windower.add_to_chat(207,' Font: '..settings.text.font..' Size: '..settings.text.size) + windower.add_to_chat(207,' Text: R: '..settings.text.red..' G: '..settings.text.green..' B: '..settings.text.blue) + windower.add_to_chat(207,' Pos: X: '..settings.pos.x..' Y: '..settings.pos.y) + end + else + local helptext = [[OhShi - Command List: + 1. help - Brings up this menu. + 2. showrolls | selfrolls - Show corsair rolls in tracker | only own rolls. + 3. staggeronly - Only show voidwatch stagger notices. + 4. track(on/off) [abyssea/dangerous/legion/meebles/other/voidwatch] <name> + - Begin or stop tracking <type (default: other)> of mob named <name>. + 5. spell/ws(on/off) <name> - Start or stop watching for <name> spell|ws. + 6. clear - Clears the textbox and the tracking table (use if textbox locks up) + The following all correspond to the tracker: + fonttype <name> | fontsize <size> | pos <x> <y> - can also click/drag + bgcolor <r> <g> <b> | txtcolor <r> <g> <b> + duration <time> - Changes the duration things appear in tracker. + settings - shows current textbox settings + show/hide - toggles visibility of the tracker so you can make changes.]] + for _, line in ipairs(helptext:split('\n')) do + windower.add_to_chat(207, line..chat.controls.reset) + end + end + end +end) + +--This event happens when an action packet is received. +windower.register_event('action', function(act) + local curact = T(act) + local actor = T{} + actor.id = curact.actor_id + if windower.ffxi.get_mob_by_id(actor.id) then + actor.name = windower.ffxi.get_mob_by_id(actor.id).name + else + return + end + local extparam = curact.param + local targets = curact.targets + local party = T(windower.ffxi.get_party()) + local typ = '' + local danger = false + local player = T(windower.ffxi.get_player()) + + if not settings.staggeronly then + if settings.showrolls and curact.category == 6 and res.job_abilities[extparam].type == 'CorsairRoll' then + local allyroller = false + local selfroll = false + for pt,member in pairs(party) do + if type(member) == 'table' and member.name == actor.name then + allyroller = true + break + end + end + if allyroller or selfroll then + if actor.id == player.id then selfroll = true end + if settings.selfrolls and not selfroll then return end + addText(actor.name, 'roll', extparam, targets[1].actions[1].param) + end + elseif isMob(actor.id) and S{7,8}:contains(curact.category) and extparam ~= 28787 then + local inact = targets[1].actions[1] + if curact.category == 8 then typ = 'spell' + else typ = 'ws' end + if (mCheck(actor.name) or dCheck(typ,inact.param)) and inact.message ~= 0 then + addText(actor.name, typ, inact.param, mDanger(actor.name), dCheck(typ,inact.param)) + end + end + end +end) + +--Catches statuses wearing on mobs you applied them to +windower.register_event('action message',function(actor_id, target_id, actor_index, target_index, message_id, param_1, param_2, param_3) + if not settings.staggeronly then + local actor = T(windower.ffxi.get_mob_by_id(actor_id)) + local player = T(windower.ffxi.get_player()) + local target = T(windower.ffxi.get_mob_by_id(target_id)) + if S{204,205,206}:contains(message_id) and isMob(target_id) then + if actor.id == player.id then + if mCheck(target.name) then + if message_id == 204 then + addText(target.name .. ' is no longer ' .. res.buffs[param_1].english_log) + elseif message_id == 205 then + addText(target.name .. ' gains the effect of ' .. res.buffs[param_1].english_log) + else + addText(target.name .. ' ' .. res.buffs[param_1].english_log .. ' effect wears off.') + end + end + end + end + end +end) + +--This event happens whenever text is incoming to the chatlog +windower.register_event('incoming text', function(old,new,color,newcolor) + if string.find(old,'(%w+)\'s attack devastates the fiend%p') then + addText('devastates',string.find(old,'(%w+)\'s attack devastates the fiend%p')) + elseif string.find(old,'Blue: (%d+)%% / Red: (%d+)%%') then + addText('bluered',string.find(old,'Blue: (%d+)%% / Red: (%d+)%%')) + elseif string.find(old,'Blue: (%d+)') then + addText('blue',string.find(old,'Blue: (%d+)')) + elseif string.find(old,'Red: (%d+)') then + addText('red',string.find(old,'Red: (%d+)')) + elseif string.find(old,'The fiend appears(.*)vulnerable to ([%w%s]+)!') then + addText('vulnerable',string.find(old,'The fiend appears(.*)vulnerable to ([%w%s]+)!')) + elseif string.find(old,'(%w+) is the key to victory!') then + addText('victory',string.find(old,'(%w+) is the key to victory')) + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/ohShi/text_handling.lua b/Data/DefaultContent/Libraries/addons/addons/ohShi/text_handling.lua new file mode 100644 index 0000000..fb0d56e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/ohShi/text_handling.lua @@ -0,0 +1,145 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +]] + +texts = require 'texts' + +--Create the textbox +function initText() + ohShi_tb = texts.new(settings) + tracking:append('ohShi initialized ') + textUpdate() + coroutine.schedule(remText, settings.duration or 7) +end + +--Removes first line of a textbox +function remText() + if tracking:length() > 0 then + table.remove(tracking,1) + textUpdate() + end +end + +--Add text to textbox. Anytime text is added this is called. +function addText(name, abtype, abil, dMob, dangerous) + if tracking:length() > 9 then + tracking:clear() + textUpdate() + end + if abtype == 'ws' then + abil = tonumber(abil) + doit = true + if abil <= 255 then + abilname = res.weapon_skills[abil].english + else + abilname = res.monster_abilities[abil].english + end + elseif abtype == 'spell' then + abil = tonumber(abil) + doit = true + abilname = res.spells[abil].english + elseif abtype == 'roll' then + abil = tonumber(abil) + doit = true + abilname = res.job_abilities[abil].english .. ' [' .. dMob .. ']' + dMob = nil + dangerous = nil + elseif name == 'vulnerable' then + if dMob == ' extremely ' then + tracking:append(' \\cs(255,100,100)Weakness 5: '..dangerous:capitalize()..'\\cr') + elseif dMob == ' highly ' then + tracking:append(' \\cs(255,100,100)Weakness 3: '..dangerous:capitalize()..'\\cr') + else + tracking:append(' Weakness 1: '..dangerous:capitalize()) + end + elseif name == 'bluered' then + tracking:append(' Blue: '..dMob..'% Red: '..dangerous..'%') + elseif name == 'red' then + tracking:append(' Red: '..dMob..'%') + elseif name == 'blue' then + tracking:append(' Blue: '..dMob..'%') + elseif name == 'devastates' then + tracking:append(' Fiend devastated by: '..dMob) + elseif name == 'victory' then + tracking:append(' Key to Victory: '..dMob) + else + tracking:append(' '..name) + end + if doit then + local str = name..': '..abilname + if dangerous or dMob then + tracking:append(' \\cs(255,100,100)'..str..'\\cr') + flashImage() + else + tracking:append(' '..str) + end + end + coroutine.schedule(remText, settings.duration or 7) + textUpdate() +end + +--Called anytime text is added to the tracking table +--Refreshes the textbox and hides/shows it if needed. +function textUpdate() + if #tracking > 0 then + local txt = '' + for inc = 1, #tracking do + txt = txt..tracking[inc] + if inc < #tracking then + txt = txt..'\n' + end + end + ohShi_tb:text(txt) + ohShi_tb:show() + else + ohShi_tb:text('') + ohShi_tb:hide() + end +end + +--image handling +--This function is used to flash the warning image +--when a danger tp/spell is used. +function flashImage() + local name = 'ohShi'..tostring(math.random(10000000,99999999)) + prims:add(name) + windower.prim.create(name) + windower.prim.set_color(name,255,255,255,255) + windower.prim.set_fit_to_texture(name,false) + windower.prim.set_texture(name,windower.addon_path..'data/warning.png') + windower.prim.set_repeat(name,1,1) + windower.prim.set_visibility(name,true) + windower.prim.set_position(name,settings.pos.x-30,settings.pos.y-10) + windower.prim.set_size(name,30,30) + coroutine.schedule(deleteImage:prepare(name), settings.duration or 7) +end + +--Called to delete the image after it's time is up. +function deleteImage(str) + prims:remove(str) + windower.prim.delete(str) +end diff --git a/Data/DefaultContent/Libraries/addons/addons/organizer/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/organizer/ReadMe.md new file mode 100644 index 0000000..fc1e527 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/organizer/ReadMe.md @@ -0,0 +1,89 @@ +# Organizer (//org) + +A multi-purpose inventory management solution. Similar to GearCollector; uses packets. + +For the purpose of this addon, a `bag` is: "Safe", "Storage", "Locker", "Satchel", "Sack", "Case", "Wardrobe", "Safe 2". + +For commands that use a filename, if one is not specified, it defaults to Name_JOB.lua, e.g., Rooks_PLD.lua +For commands that specify a bag, if one is not specified, it defaults to all, and will cycle through all of them. + +The addon command is `org`, so `org freeze` will freeze, etc. + +This utility is still in development and there are at least a couple of known issues (it does not always move out gear that is currently equipped, argument parsing could be better). It is designed to work simplest as a snapshotting utility (freeze and organize without arugments), but it should work no matter what you want to do with it. + +### Settings + +#### auto_heal +Setting this feature to anything other than false will cause Organizer to use /heal after getting/storing gear. + +#### bag_priority +The order that bags will be looked in for requested gear. + +#### dump_bags +The order that bags will be filled with unspecified gear from your inventory. + +#### item_delay +A delay, in seconds, between item storage/retrieval. Defaults to 0 (no delay) + + +### Commands +Commands below are written with their arguments indicated using square brackets, but you should not use square brackets when entering the commands in game. Default options are italicized. + +#### Freeze + +``` +freeze [bag] [filename] +``` + +Freezes the current contents of a `bag` or **all bags** to the specified `filename` or **Name_ShortJob.lua** in the respective data directory/directories. This effectively takes a snapshot of your inventory for that job. So using `//org freeze` as a Dancer named Pablo would result in freezing all of your bags in files named Pablo_DNC.lua. + +#### Get + +``` +get [bag] [filename] +``` + +Thaws the frozen state specified by `filename` or **Name_ShortJob.lua** and `bag` or **all bags** and makes one attempt to move towards that state. + + +#### Tidy + +``` +tidy [bag] [filename] +``` + +Thaws a frozen state specified by `filename` or **Name_ShortJob.lua** and `bag` or **all bags** and makes one attempt to purge anything currently in inventory that shouldn't be into dump bags. + +#### Organize + +``` +organize [bag] [filename] +``` + +Thaws a frozen state specified by `filename` or **Name_ShortJob.lua** and `bag` or **all bags** and executes repeated Get and Tidy commands until a steady state is reached (aka. you have your gear). With no arguments, it will attempt to restore the entire thawed snapshot. + +### Gearswap integration +Additionally, Organizer integrates with GearSwap. In your lua, just add this: + +``` +include('organizer-lib') +``` + +And then in your Mog House, after changing jobs: + +``` +//gs org +``` + +And it will fill your inventory with the items from your sets, and put everything else away (it does a very good job, even when there are space concerns, but it's not perfect. Make sure to do a "//gs validate" after!) + +Additionally, if you have extra items you want to bring along, simply define a table named `organizer_items` like so: + +``` +organizer_items = { + echos="Echo Drops", + shihei="Shihei", + orb="Macrocosmic Orb" +} +``` + diff --git a/Data/DefaultContent/Libraries/addons/addons/organizer/items.lua b/Data/DefaultContent/Libraries/addons/addons/organizer/items.lua new file mode 100644 index 0000000..20d9160 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/organizer/items.lua @@ -0,0 +1,469 @@ +--Copyright (c) 2015, Byrthnoth and Rooks +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +local Items = {} +local items = {} +local bags = {} +local item_tab = {} + +do + local names = {'Nomad Moogle'} -- don't add Pilgrim Moogle to this list, organizer currently does not work in Odyssey. + local moogles = {} + local poked = false + local block_menu = false + + clear_moogles = function() + moogles = {} + poked = false + end + + local poke_moogle = function(npc) + local p = packets.new('outgoing', 0x1a, { + ["Target"] = npc.id, + ["Target Index"] = npc.index, + }) + poked = true + block_menu = true + packets.inject(p) + repeat + coroutine.sleep(0.4) + until not block_menu + end + + nomad_moogle = function() + if #moogles == 0 then + for _,name in ipairs(names) do + local npcs = windower.ffxi.get_mob_list(name) + for index in pairs(npcs) do + table.insert(moogles,index) + end + end + end + + local player = windower.ffxi.get_mob_by_target('me') + for _, moo_index in ipairs(moogles) do + local moo = windower.ffxi.get_mob_by_index(moo_index) + if moo and (moo.x - player.x)^2 + (moo.y - player.y)^2 < 36 then + if not poked then + poke_moogle(moo) + end + return moo.name + end + end + return false + end + + windower.register_event('incoming chunk',function(id) + if id == 0x02E and block_menu then + block_menu = false + return true + end + end) +end + +windower.register_event('zone change',function() + clear_moogles() +end) + +local function validate_bag(bag_table) + if type(bag_table) == 'table' and windower.ffxi.get_bag_info(bag_table.id) then + if bag_table.access == 'Everywhere' then + return true + elseif bag_table.access == 'Mog House' then + if windower.ffxi.get_info().mog_house then + return true + elseif nomad_moogle() and bag_table.english ~= 'Storage' then -- Storage is not available at Nomad Moogles + return true + end + end + end + return false +end + +local function validate_id(id) + return (id and id ~= 0 and id ~= 0xFFFF) -- Not empty or gil +end + +local function wardrobecheck(bag_id,id) + return _static.wardrobe_ids[bag_id]==nil or ( res.items[id] and (res.items[id].type == 4 or res.items[id].type == 5) ) +end + +function Items.new(loc_items,bool) + loc_items = loc_items or windower.ffxi.get_items() + new_instance = setmetatable({}, {__index = function (t, k) if rawget(t,k) then return rawget(t,k) else return rawget(items,k) end end}) + for bag_id,bag_table in pairs(res.bags) do + org_debug("items", "Items.new::bag_id: "..bag_id) + bag_name = bag_table.english:lower():gsub(' ', '') + org_debug("items", "Items.new::bag_name: "..bag_name) + if (bool or validate_bag(bag_table)) and (loc_items[bag_id] or loc_items[bag_name]) then + org_debug("items", "Items.new: new_instance for ID#"..bag_id) + local cur_inv = new_instance:new(bag_id) + for inventory_index,item_table in pairs(loc_items[bag_id] or loc_items[bag_name]) do + if type(item_table) == 'table' and validate_id(item_table.id) then + org_debug("items", "Items.new: inventory_index="..inventory_index.." item_table.id="..item_table.id.." ("..res.items[item_table.id].english..")") + cur_inv:new(item_table.id,item_table.count,item_table.extdata,item_table.augments,item_table.status,inventory_index) + end + end + end + end + return new_instance +end + +function items:new(key) + org_debug("items", "New items instance with key "..key) + local new_instance = setmetatable({_parent = self,_info={n=0,bag_id=key}}, {__index = function (t, k) if rawget(t,k) then return rawget(t,k) else return rawget(bags,k) end end}) + self[key] = new_instance + return new_instance +end + +function items:find(item) + for bag_name,bag_id in pairs(settings.bag_priority) do + real_bag_id = s_to_bag(bag_name) + org_debug("find", "Searching "..bag_name.." for "..res.items[item.id].english..".") + if self[real_bag_id] and self[real_bag_id]:contains(item) then + org_debug("find", "Found "..res.items[item.id].english.." in "..bag_name..".") + return real_bag_id, self[real_bag_id]:contains(item) + else + org_debug("find", "Didn't find "..res.items[item.id].english.." in "..bag_name..".") + end + end + org_debug("find", "Didn't find "..res.items[item.id].english.." in any bags.") + return false +end + +function items:route(start_bag,start_ind,end_bag,count) + count = count or self[start_bag][start_ind].count + local success = true + local initial_ind = start_ind + local inventory_max = windower.ffxi.get_bag_info(0).max + if start_bag ~= 0 and self[0]._info.n < inventory_max then + start_ind = self[start_bag][start_ind]:move(0,0x52,count) + elseif start_bag ~= 0 and self[0]._info.n >= inventory_max then + success = false + org_warning('Cannot move more than '..inventory_max..' items into inventory') + end + + local destination_enabled = windower.ffxi.get_bag_info(end_bag).enabled + local destination_max = windower.ffxi.get_bag_info(end_bag).max + + if not destination_enabled then + success = false + org_warning('Cannot move to '..tostring(end_bag)..' because it is disabled') + elseif start_ind and end_bag ~= 0 and self[end_bag]._info.n < destination_max then + self[0][start_ind]:transfer(end_bag,count) + elseif not start_ind then + success = false + org_warning('Initial movement of the route failed. ('..tostring(start_bag)..' '..tostring(initial_ind)..' '..tostring(start_ind)..' '..tostring(end_bag)..')') + elseif self[end_bag]._info.n >= destination_max then + success = false + org_warning('Cannot move more than '..destination_max..' items into that inventory ('..end_bag..')') + end + return success +end + +function items:it() + local i = 0 + local bag_priority_list = {} + for i,v in pairs(settings.bag_priority) do + bag_priority_list[v] = i + end + return function () + while i < #bag_priority_list do + i = i + 1 + local id = s_to_bag(bag_priority_list[i]) + if not id then + org_error('The bag name ("'..tostring(bag_priority_list[i])..'") with priority '..tostring(i)..' in the ../addons/organizer/data/settings.xml file is not valid.\nValid options are '..tostring(res.bags)) + end + if self[id] and validate_bag(res.bags[id]) then + return id, self[id] + end + end + end + +end + +function bags:new(id,count,ext,augments,status,index) + local max_size = windower.ffxi.get_bag_info(self._info.bag_id).max + if self._info.n >= max_size then org_warning('Attempting to add another item to a full bag') return end + if index and table.with(self,'index',index) then org_warning('Cannot assign the same index twice') return end + self._info.n = self._info.n + 1 + index = index or self:first_empty() + status = status or 0 + augments = augments or ext and id and extdata.decode({id=id,extdata=ext}).augments + if augments then augments = table.filter(augments,-functions.equals('none')) end + self[index] = setmetatable({_parent=self,id=id,count=count,extdata=ext,index=index,status=status, + name=res.items[id][_global.language]:lower(),log_name=res.items[id][_global.language_log]:lower(),augments=augments}, + {__index = function (t, k) + if not t or not k then print('table index is nil error',t,k) end + if rawget(t,k) then + return rawget(t,k) + else + return rawget(item_tab,k) + end + end}) + return index +end + +function bags:it() + local max = windower.ffxi.get_bag_info(self._info.bag_id).max + local i = 0 + return function () + while i < max do + i = i + 1 + if self[i] then return i, self[i] end + end + end +end + +function bags:first_empty() + local max = windower.ffxi.get_bag_info(self._info.bag_id).max + for i=1,max do + if not self[i] then return i end + end +end + +function bags:remove(index) + if not rawget(self,index) then org_warning('Attempting to remove an index that does not exist') return end + self._info.n = self._info.n - 1 + rawset(self,index,nil) +end + +function bags:find_all_instances(item,bool,first) + local instances = L{} + for i,v in self:it() do + org_debug("find_all", "find_all_instances: slot="..i.." v="..res.items[v.id].english.." item="..res.items[item.id].english.." ") + if (bool or not v:annihilated()) and v.id == item.id then -- and v.count >= item.count then + if not item.augments or table.length(item.augments) == 0 or v.augments and extdata.compare_augments(item.augments,v.augments) then + -- May have to do a higher level comparison here for extdata. + -- If someone exports an enchanted item when the timer is + -- counting down then this function will return false for it. + instances:append(i) + if first then + return instances + end + end + end + end + if instances.n ~= 0 then + return instances + else + return false + end +end + +function bags:contains(item,bool) + bool = bool or false -- Default to only looking at unannihilated items + org_debug("contains", "contains: searching for "..res.items[item.id].english.." in "..self._info.bag_id) + local instances = self:find_all_instances(item,bool,true) + if instances then + return instances:it()() + end + return false +end + +function bags:find_unfinished_stack(item,bool) + local tab = self:find_all_instances(item,bool,false) + if tab then + for i in tab:it() do + if res.items[self[i].id] and res.items[self[i].id].stack > self[i].count then + return i + end + end + end + return false +end + +function item_tab:transfer(dest_bag,count) + -- Transfer an item to a specific bag. + if not dest_bag then org_warning('Destination bag is invalid.') return false end + count = count or self.count + local parent = self._parent + local targ_inv = parent._parent[dest_bag] + + local parent_bag_id = parent._info.bag_id + local target_bag_id = targ_inv._info.bag_id + + if not (target_bag_id == 0 or parent_bag_id == 0) then + org_warning('Cannot move between two bags that are not inventory bags.') + else + while parent[self.index] and targ_inv:find_unfinished_stack(parent[self.index]) do + org_debug("stacks", "Moving ("..res.items[self.id].english..') from '..res.bags[parent_bag_id].en..' to '..res.bags[target_bag_id].en..'') + local rv = parent[self.index]:move(dest_bag,targ_inv:find_unfinished_stack(parent[self.index]),count) + if not rv then + org_debug("stacks", "FAILED moving ("..res.items[self.id].english..') from '..res.bags[parent_bag_id].en..' to '..res.bags[target_bag_id].en..'') + break + end + end + if parent[self.index] then + parent[self.index]:move(dest_bag) + end + return true + end + return false +end + +function item_tab:move(dest_bag,dest_slot,count) + if not dest_bag then org_warning('Destination bag is invalid.') return false end + count = count or self.count + local parent = self._parent + local targ_inv = parent._parent[dest_bag] + dest_slot = dest_slot or 0x52 + + local parent_bag_id = parent._info.bag_id + local parent_bag_name = res.bags[parent_bag_id].en:lower() + + local target_bag_id = targ_inv._info.bag_id + + org_debug("move", "move(): Item: "..res.items[self.id].english) + org_debug("move", "move(): Parent bag: "..parent_bag_id) + org_debug("move", "move(): Target bag: "..target_bag_id) + + -- issues with bazaared items makes me think we shouldn't screw with status'd items at all + if(self.status > 0) then + if(self.status == 5) then + org_verbose('Skipping item: ('..res.items[self.id].english..') because it is currently equipped.') + return false + elseif(self.status == 19) then + org_verbose('Skipping item: ('..res.items[self.id].english..') because it is an equipped linkshell.') + return false + elseif(self.status == 25) then + org_verbose('Skipping item: ('..res.items[self.id].english..') because it is in your bazaar.') + return false + end + end + + -- check the 'retain' lists + if((parent_bag_id == 0) and _retain[self.id]) then + org_verbose('Skipping item: ('..res.items[self.id].english..') because it is set to be retained ('.._retain[self.id]..')') + return false + end + + if((parent_bag_id == 0) and settings.retain and settings.retain.items) then + local cat = res.items[self.id].category + if(cat ~= 'Weapon' and cat ~= 'Armor') then + org_verbose('Skipping item: ('..res.items[self.id].english..') because non-equipment is set be retained') + return false + end + end + + -- respect the ignore list + if(_ignore_list[parent_bag_name] and _ignore_list[parent_bag_name][res.items[self.id].english]) then + org_verbose('Skipping item: ('..res.items[self.id].english..') because it is on the ignore list') + return false + end + + -- Make sure the source can be pulled from + if not _valid_pull[parent_bag_id] then + org_verbose('Skipping item: ('..res.items[self.id].english..') - can not be pulled from '..res.bags[parent_bag_id].en..') ') + return false + end + + -- Make sure the target can be pushed to + if not _valid_dump[target_bag_id] then + org_verbose('Skipping item: ('..res.items[self.id].english..') - can not be pushed to '..res.bags[target_bag_id].en..') ') + return false + end + + if not self:annihilated() and + (not dest_slot or not targ_inv[dest_slot] or (targ_inv[dest_slot] and res.items[targ_inv[dest_slot].id].stack < targ_inv[dest_slot].count + count)) and + (targ_inv._info.bag_id == 0 or parent._info.bag_id == 0) and + wardrobecheck(targ_inv._info.bag_id,self.id) and + self:free() then + windower.packets.inject_outgoing(0x29,string.char(0x29,6,0,0)..'I':pack(count)..string.char(parent._info.bag_id,dest_bag,self.index,dest_slot)) + org_verbose('Moving item! ('..res.items[self.id].english..') from '..res.bags[parent._info.bag_id].en..' '..parent._info.n..' to '..res.bags[dest_bag].en..' '..targ_inv._info.n..')') + local new_index = targ_inv:new(self.id, count, self.extdata, self.augments) + --print(parent._info.bag_id,dest_bag,self.index,new_index) + parent:remove(self.index) + return new_index + elseif not dest_slot then + org_warning('Cannot move the item ('..res.items[self.id].english..'). Target inventory is full ('..res.bags[dest_bag].en..')') + elseif targ_inv[dest_slot] and res.items[targ_inv[dest_slot].id].stack < targ_inv[dest_slot].count + count then + org_warning('Cannot move the item ('..res.items[self.id].english..'). Target inventory slot would be overly full ('..(targ_inv[dest_slot].count + count)..' items in '..res.bags[dest_bag].en..')') + elseif (targ_inv._info.bag_id ~= 0 and parent._info.bag_id ~= 0) then + org_warning('Cannot move the item ('..res.items[self.id].english..'). Attempting to move from a non-inventory to a non-inventory bag ('..res.bags[parent._info.bag_id].en..' '..res.bags[dest_bag].en..')') + elseif self:annihilated() then + org_warning('Cannot move the item ('..res.items[self.id].english..'). It has already been moved.') + elseif not wardrobecheck(targ_inv._info.bag_id,self.id) then + org_warning('Cannot move the item ('..res.items[self.id].english..') to the wardrobe. Wardrobe cannot hold an item of its type ('..tostring(res.items[self.id].type)..').') + elseif not self:free() then + org_warning('Cannot free the item ('..res.items[self.id].english..'). It has an unaddressable item status ('..tostring(self.status)..').') + end + return false +end + +function item_tab:put_away(usable_bags) + org_debug("move", "Putting away "..res.items[self.id].english) + local current_items = self._parent._parent + usable_bags = usable_bags or _static.usable_bags + local bag_free + for _,v in ipairs(usable_bags) do + local bag_max = windower.ffxi.get_bag_info(v).max + if current_items[v]._info.n < bag_max and wardrobecheck(v,self.id) then + bag_free = v + break + end + end + if bag_free then + self:transfer(bag_free,self.count) + end +end + +function item_tab:free() + if self.status == 5 then + local eq = windower.ffxi.get_items().equipment + for _,v in pairs(res.slots) do + local ind_name = v.english:lower():gsub(' ','_') + local bag_name = ind_name..'_bag' + local ind, bag = eq[ind_name],eq[bag_name] + if self.index == ind and self._parent._info.bag_id == bag then + windower.packets.inject_outgoing(0x50,string.char(0x50,0x04,0,0,self._parent._info.bag_id,v.id,0,0)) + break + end + end + elseif self.status ~= 0 then + return false + end + return true +end + +function item_tab:annihilate(count) + count = count or rawget(item_tab,'count') + local a_count = (rawget(item_tab,'a_count') or 0) + count + if a_count >count then + org_warning('Moving more of an item ('..item_tab.id..' : '..a_count..') than possible ('..count..'.') + end + rawset(self,'a_count',a_count) +end + +function item_tab:annihilated() + return ( (rawget(self,'a_count') or 0) >= rawget(self,'count') ) +end + +function item_tab:available_amount() + return ( rawget(self,'count') - (rawget(self,'a_count') or 0) ) +end + +return Items diff --git a/Data/DefaultContent/Libraries/addons/addons/organizer/organizer.lua b/Data/DefaultContent/Libraries/addons/addons/organizer/organizer.lua new file mode 100644 index 0000000..8e526d9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/organizer/organizer.lua @@ -0,0 +1,565 @@ +--Copyright (c) 2015, Byrthnoth and Rooks +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +res = require('resources') +files = require('files') +require('pack') +Items = require('items') +extdata = require('extdata') +logger = require('logger') +require('tables') +require('lists') +require('functions') +config = require('config') +slips = require('slips') +packets = require('packets') + +_addon.name = 'Organizer' +_addon.author = 'Byrth, maintainer: Rooks' +_addon.version = 0.20210721 +_addon.commands = {'organizer','org'} + +_static = { + bag_ids = { + inventory=0, + safe=1, + storage=2, + temporary=3, + locker=4, + satchel=5, + sack=6, + case=7, + wardrobe=8, + safe2=9, + wardrobe2=10, + wardrobe3=11, + wardrobe4=12, + }, + wardrobe_ids = {[8]=true,[10]=true,[11]=true,[12]=true}, + usable_bags = {1,9,4,2,5,6,7,8,10,11,12} +} + +_global = { + language = 'english', + language_log = 'english_log', +} + +_ignore_list = {} +_retain = {} +_valid_pull = {} +_valid_dump = {} + +default_settings = { + dump_bags = {['Safe']=1,['Safe2']=2,['Locker']=3,['Storage']=4}, + bag_priority = {['Safe']=1,['Safe2']=2,['Locker']=3,['Storage']=4,['Satchel']=5,['Sack']=6,['Case']=7,['Inventory']=8,['Wardrobe']=9,['Wardrobe2']=10,['Wardrobe3']=11,['Wardrobe4']=12,}, + item_delay = 0, + ignore = {}, + retain = { + ["moogle_slip_gear"]=false, + ["seals"]=false, + ["items"]=false, + ["slips"]=false, + }, + auto_heal = false, + default_file='default.lua', + verbose=false, +} + +_debugging = { + debug = { + ['contains']=true, + ['command']=true, + ['find']=true, + ['find_all']=true, + ['items']=true, + ['move']=true, + ['settings']=true, + ['stacks']=true + }, + debug_log = 'data\\organizer-debug.log', + enabled = false, + warnings = false, -- This mode gives warnings about impossible item movements and crash conditions. +} + +debug_log = files.new(_debugging.debug_log) + +function s_to_bag(str) + if not str and tostring(str) then return end + for i,v in pairs(res.bags) do + if v.en:lower():gsub(' ', '') == str:lower() then + return v.id + end + end +end + +windower.register_event('load',function() + debug_log:write('Organizer loaded at '..os.date()..'\n') + + if debugging then windower.debug('load') end + options_load() +end) + +function options_load( ) + if not windower.dir_exists(windower.addon_path..'data\\') then + org_debug("settings", "Creating data directory") + windower.create_dir(windower.addon_path..'data\\') + if not windower.dir_exists(windower.addon_path..'data\\') then + org_error("unable to create data directory!") + end + end + + for bag_name, bag_id in pairs(_static.bag_ids) do + if not windower.dir_exists(windower.addon_path..'data\\'..bag_name) then + org_debug("settings", "Creating data directory for "..bag_name) + windower.create_dir(windower.addon_path..'data\\'..bag_name) + if not windower.dir_exists(windower.addon_path..'data\\'..bag_name) then + org_error("unable to create"..bag_name.."directory!") + end + end + end + + -- We can't just do a: + -- + -- settings = config.load('data\\settings.xml', default_settings) + -- + -- because the config library will try to merge them, and it will + -- add back anything a user has removed (like items in bag_priority) + + if windower.file_exists(windower.addon_path..'data\\settings.xml') then + org_debug("settings", "Loading settings from file") + settings = config.load('data\\settings.xml') + else + org_debug("settings", "Saving default settings to file") + settings = config.load('data\\settings.xml', default_settings) + end + + -- Build the ignore list + if(settings.ignore) then + for bn,i_list in pairs(settings.ignore) do + bag_name = bn:lower() + _ignore_list[bag_name] = {} + for _,ignore_name in pairs(i_list) do + org_verbose("Adding "..ignore_name.." in the "..bag_name.." to the ignore list") + _ignore_list[bag_name][ignore_name] = 1 + end + end + end + + -- Build a hard-wired pull list + for bag_name,_ in pairs(settings.bag_priority) do + org_verbose("Adding "..bag_name.." to the pull list") + _valid_pull[s_to_bag(bag_name)] = 1 + end + + -- Build a hard-wired dump list + for bag_name,_ in pairs(settings.dump_bags) do + org_verbose("Adding "..bag_name.." to the push list") + _valid_dump[s_to_bag(bag_name)] = 1 + end + + -- Build the retain lists + if(settings.retain) then + if(settings.retain.moogle_slip_gear == true) then + org_verbose("Moogle slip gear set to retain") + slip_lists = require('slips') + for slip_id,slip_list in pairs(slip_lists.items) do + for item_id in slip_list:it() do + if item_id ~= 0 then + _retain[item_id] = "moogle slip" + org_debug("settings", "Adding ("..res.items[item_id].english..') to slip retain list') + end + end + end + end + + if(settings.retain.seals == true) then + org_verbose("Seals set to retain") + seals = {1126,1127,2955,2956,2957} + for _,seal_id in pairs(seals) do + _retain[seal_id] = "seal" + org_debug("settings", "Adding ("..res.items[seal_id].english..') to slip retain list') + end + end + + if(settings.retain.items == true) then + org_verbose("Non-equipment items set to retain") + end + + if(settings.retain.slips == true) then + org_verbose("Slips set to retain") + for _,slips_id in pairs(slips.storages) do + _retain[slips_id] = "slips" + org_debug("settings", "Adding ("..res.items[slips_id].english..') to slip retain list') + end + end + end + + -- Always allow inventory and wardrobe, obviously + _valid_dump[0] = 1 + _valid_pull[0] = 1 + _valid_dump[8] = 1 + _valid_pull[8] = 1 + _valid_dump[10] = 1 + _valid_pull[10] = 1 + _valid_dump[11] = 1 + _valid_pull[11] = 1 + _valid_dump[12] = 1 + _valid_pull[12] = 1 + +end + + + +windower.register_event('addon command',function(...) + local inp = {...} + -- get (g) = Take the passed file and move everything to its defined location. + -- tidy (t) = Take the passed file and move everything that isn't in it out of my active inventory. + -- organize (o) = get followed by tidy. + local command = table.remove(inp,1):lower() + if command == 'eval' then + assert(loadstring(table.concat(inp,' ')))() + return + end + + local moogle = nomad_moogle() + if moogle then + org_debug("command", "Using '" .. moogle .. "' for Mog House interaction") + end + + local bag = 'all' + if inp[1] and (_static.bag_ids[inp[1]:lower()] or inp[1]:lower() == 'all') then + bag = table.remove(inp,1):lower() + end + + org_debug("command", "Using '"..bag.."' as the bag target") + + + file_name = table.concat(inp,' ') + if string.length(file_name) == 0 then + file_name = default_file_name() + end + + if file_name:sub(-4) ~= '.lua' then + file_name = file_name..'.lua' + end + org_debug("command", "Using '"..file_name.."' as the file name") + + + if (command == 'g' or command == 'get') then + org_debug("command", "Calling get with file_name '"..file_name.."' and bag '"..bag.."'") + get(thaw(file_name, bag)) + elseif (command == 't' or command == 'tidy') then + org_debug("command", "Calling tidy with file_name '"..file_name.."' and bag '"..bag.."'") + tidy(thaw(file_name, bag)) + elseif (command == 'f' or command == 'freeze') then + + org_debug("command", "Calling freeze command") + local items = Items.new(windower.ffxi.get_items(),true) + local frozen = {} + items[3] = nil -- Don't export temporary items + if _static.bag_ids[bag] then + org_debug("command", "Bag: "..bag) + freeze(file_name,bag,items) + else + for bag_id,item_list in items:it() do + org_debug("command", "Bag ID: "..bag_id) + -- infinite loop protection + if(frozen[bag_id]) then + org_warning("Tried to freeze ID #"..bag_id.." twice, aborting") + return + end + frozen[bag_id] = 1 + freeze(file_name,res.bags[bag_id].english:lower():gsub(' ', ''),items) + end + end + elseif (command == 'o' or command == 'organize') then + org_debug("command", "Calling organize command") + organize(thaw(file_name, bag)) + end + + if settings.auto_heal and tostring(settings.auto_heal):lower() ~= 'false' then + org_debug("command", "Automatically healing") + windower.send_command('input /heal') + end + + if moogle then + clear_moogles() + org_debug("command", "Clearing '" .. moogle .. "' status") + end + + org_debug("command", "Organizer complete") + +end) + +function get(goal_items,current_items) + org_verbose('Getting!') + if goal_items then + count = 0 + failed = 0 + current_items = current_items or Items.new() + goal_items, current_items = clean_goal(goal_items,current_items) + for bag_id,inv in goal_items:it() do + for ind,item in inv:it() do + if not item:annihilated() then + local start_bag, start_ind = current_items:find(item) + -- Table contains a list of {bag, pos, count} + if start_bag then + if not current_items:route(start_bag,start_ind,bag_id) then + org_warning('Unable to move item.') + failed = failed + 1 + else + count = count + 1 + end + simulate_item_delay() + else + -- Need to adapt this for stacking items somehow. + org_warning(res.items[item.id].english..' not found.') + end + end + end + end + org_verbose("Got "..count.." item(s), and failed getting "..failed.." item(s)") + end + return goal_items, current_items +end + +function freeze(file_name,bag,items) + org_debug("command", "Entering freeze function with bag '"..bag.."'") + local lua_export = T{} + local counter = 0 + for _,item_table in items[_static.bag_ids[bag]]:it() do + counter = counter + 1 + if(counter > 80) then + org_warning("We hit an infinite loop in freeze()! ABORT.") + return + end + org_debug("command", "In freeze loop for bag '"..bag.."'") + org_debug("command", "Processing '"..item_table.log_name.."'") + + local temp_ext,augments = extdata.decode(item_table) + if temp_ext.augments then + org_debug("command", "Got augments for '"..item_table.log_name.."'") + augments = table.filter(temp_ext.augments,-functions.equals('none')) + end + lua_export:append({name = item_table.name,log_name=item_table.log_name, + id=item_table.id,extdata=item_table.extdata:hex(),augments = augments,count=item_table.count}) + end + -- Make sure we have something in the bag at all + if lua_export[1] then + org_verbose("Freezing "..tostring(bag)..".") + local export_file = files.new('/data/'..bag..'/'..file_name,true) + export_file:write('return '..lua_export:tovstring({'augments','log_name','name','id','count','extdata'})) + else + org_debug("command", "Got nothing, skipping '"..bag.."'") + end +end + +function tidy(goal_items,current_items,usable_bags) + org_debug("command", "Entering tidy()") + usable_bags = usable_bags or get_dump_bags() + -- Move everything out of items[0] and into other inventories (defined by the passed table) + if goal_items and goal_items[0] and goal_items[0]._info.n > 0 then + current_items = current_items or Items.new() + goal_items, current_items = clean_goal(goal_items,current_items) + for index,item in current_items[0]:it() do + if not goal_items[0]:contains(item,true) then + org_debug("command", "Putting away "..item.log_name) + current_items[0][index]:put_away(usable_bags) + simulate_item_delay() + end + end + end + return goal_items, current_items +end + +function organize(goal_items) + org_message('Starting...') + local current_items = Items.new() + local dump_bags = get_dump_bags() + + local inventory_max = windower.ffxi.get_bag_info(0).max + if current_items[0].n == inventory_max then + tidy(goal_items,current_items,dump_bags) + end + if current_items[0].n == inventory_max then + org_error('Unable to make space, aborting!') + return + end + + local remainder = math.huge + while remainder do + goal_items, current_items = get(goal_items,current_items) + + goal_items, current_items = clean_goal(goal_items,current_items) + goal_items, current_items = tidy(goal_items,current_items,dump_bags) + remainder = incompletion_check(goal_items,remainder) + if(remainder) then + org_verbose("Remainder: "..tostring(remainder)..' Current: '..current_items[0]._info.n,1) + else + org_verbose("No remainder, so we found everything we were looking for!") + end + end + goal_items, current_items = tidy(goal_items,current_items,dump_bags) + + local count,failures = 0,T{} + for bag_id,bag in goal_items:it() do + for ind,item in bag:it() do + if item:annihilated() then + count = count + 1 + else + item.bag_id = bag_id + failures:append(item) + end + end + end + org_message('Done! - '..count..' items matched and '..table.length(failures)..' items missing!') + if table.length(failures) > 0 then + for i,v in failures:it() do + org_verbose('Item Missing: '..i.name..' '..(i.augments and tostring(T(i.augments)) or '')) + end + end +end + +function clean_goal(goal_items,current_items) + for i,inv in goal_items:it() do + for ind,item in inv:it() do + local potential_ind = current_items[i]:contains(item) + if potential_ind then + -- If it is already in the right spot, delete it from the goal items and annihilate it. + local count = math.min(goal_items[i][ind].count,current_items[i][potential_ind].count) + goal_items[i][ind]:annihilate(goal_items[i][ind].count) + current_items[i][potential_ind]:annihilate(current_items[i][potential_ind].count) + end + end + end + return goal_items, current_items +end + +function incompletion_check(goal_items,remainder) + -- Does not work. On cycle 1, you fill up your inventory without purging unnecessary stuff out. + -- On cycle 2, your inventory is full. A gentler version of tidy needs to be in the loop somehow. + local remaining = 0 + for i,v in goal_items:it() do + for n,m in v:it() do + if not m:annihilated() then + remaining = remaining + 1 + end + end + end + return remaining ~= 0 and remaining < remainder and remaining +end + +function thaw(file_name,bag) + local bags = _static.bag_ids[bag] and {[bag]=file_name} or table.reassign({},_static.bag_ids) -- One bag name or all of them if no bag is specified + if settings.default_file:sub(-4) ~= '.lua' then + settings.default_file = settings.default_file..'.lua' + end + for i,v in pairs(_static.bag_ids) do + bags[i] = bags[i] and windower.file_exists(windower.addon_path..'data/'..i..'/'..file_name) and file_name or default_file_name() + end + bags.temporary = nil + local inv_structure = {} + for cur_bag,file in pairs(bags) do + local f,err = loadfile(windower.addon_path..'data/'..cur_bag..'/'..file) + if f and not err then + local success = false + success, inv_structure[cur_bag] = pcall(f) + if not success then + org_warning('User File Error (Syntax) - '..inv_structure[cur_bag]) + inv_structure[cur_bag] = nil + end + elseif bag and cur_bag:lower() == bag:lower() then + org_warning('User File Error (Loading) - '..err) + end + end + -- Convert all the extdata back to a normal string + for i,v in pairs(inv_structure) do + for n,m in pairs(v) do + if m.extdata then + inv_structure[i][n].extdata = string.parse_hex(m.extdata) + end + end + end + return Items.new(inv_structure) +end + +function org_message(msg,col) + windower.add_to_chat(col or 8,'Organizer: '..msg) + flog(_debugging.debug_log, 'Organizer [MSG] '..msg) +end + +function org_warning(msg) + if _debugging.warnings then + windower.add_to_chat(123,'Organizer: '..msg) + end + flog(_debugging.debug_log, 'Organizer [WARN] '..msg) +end + +function org_debug(level, msg) + if(_debugging.enabled) then + if (_debugging.debug[level]) then + flog(_debugging.debug_log, 'Organizer [DEBUG] ['..level..']: '..msg) + end + end +end + + +function org_error(msg) + error('Organizer: '..msg) + flog(_debugging.debug_log, 'Organizer [ERROR] '..msg) +end + +function org_verbose(msg,col) + if tostring(settings.verbose):lower() ~= 'false' then + windower.add_to_chat(col or 8,'Organizer: '..msg) + end + flog(_debugging.debug_log, 'Organizer [VERBOSE] '..msg) +end + +function default_file_name() + player = windower.ffxi.get_player() + job_name = res.jobs[player.main_job_id]['english_short'] + return player.name..'_'..job_name..'.lua' +end + +function simulate_item_delay() + if settings.item_delay and settings.item_delay > 0 then + coroutine.sleep(settings.item_delay) + end +end + +function get_dump_bags() + local dump_bags = {} + for i,v in pairs(settings.dump_bags) do + if i and s_to_bag(i) then + dump_bags[tonumber(v)] = s_to_bag(i) + elseif i then + org_error('The bag name ("'..tostring(i)..'") in dump_bags entry #'..tostring(v)..' in the ../addons/organizer/data/settings.xml file is not valid.\nValid options are '..tostring(res.bags)) + return + end + end + return dump_bags +end diff --git a/Data/DefaultContent/Libraries/addons/addons/pet_fix/pet_fix.lua b/Data/DefaultContent/Libraries/addons/addons/pet_fix/pet_fix.lua new file mode 100644 index 0000000..5852de6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pet_fix/pet_fix.lua @@ -0,0 +1,223 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'Pet Fix' +_addon.ver = 0 +_addon.author = 'Byrth' + +windower.register_event('incoming chunk',function (id,original,modified,is_injected,is_blocked) + if debugging then windower.debug('incoming chunk '..id) end + local pref = modified:sub(1,4) + local data = modified:sub(5) + +-------------- ACTION PACKET --------------- + if id == 0x28 then + local act = {} + act.do_not_need = get_bit_packed(data,0,8) + act.actor_id = get_bit_packed(data,8,40) + act.target_count = get_bit_packed(data,40,50) + act.category = get_bit_packed(data,50,54) + act.param = get_bit_packed(data,54,70) + act.unknown = get_bit_packed(data,70,86) + act.recast = get_bit_packed(data,86,118) + act.targets = {} + local offset = 118 + for i = 1,act.target_count do + act.targets[i] = {} + act.targets[i].id = get_bit_packed(data,offset,offset+32) + act.targets[i].action_count = get_bit_packed(data,offset+32,offset+36) + offset = offset + 36 + act.targets[i].actions = {} + for n = 1,act.targets[i].action_count do + act.targets[i].actions[n] = {} + act.targets[i].actions[n].reaction = get_bit_packed(data,offset,offset+5) + act.targets[i].actions[n].animation = get_bit_packed(data,offset+5,offset+16) + act.targets[i].actions[n].effect = get_bit_packed(data,offset+16,offset+21) + act.targets[i].actions[n].stagger = get_bit_packed(data,offset+21,offset+27) + act.targets[i].actions[n].param = get_bit_packed(data,offset+27,offset+44) + act.targets[i].actions[n].message = get_bit_packed(data,offset+44,offset+54) + act.targets[i].actions[n].unknown = get_bit_packed(data,offset+54,offset+85) + act.targets[i].actions[n].has_add_effect = get_bit_packed(data,offset+85,offset+86) + offset = offset + 86 + if act.targets[i].actions[n].has_add_effect == 1 then + act.targets[i].actions[n].has_add_effect = true + act.targets[i].actions[n].add_effect_animation = get_bit_packed(data,offset,offset+6) + act.targets[i].actions[n].add_effect_effect = get_bit_packed(data,offset+6,offset+10) + act.targets[i].actions[n].add_effect_param = get_bit_packed(data,offset+10,offset+27) + act.targets[i].actions[n].add_effect_message = get_bit_packed(data,offset+27,offset+37) + offset = offset + 37 + else + act.targets[i].actions[n].has_add_effect = false + act.targets[i].actions[n].add_effect_animation = 0 + act.targets[i].actions[n].add_effect_effect = 0 + act.targets[i].actions[n].add_effect_param = 0 + act.targets[i].actions[n].add_effect_message = 0 + end + act.targets[i].actions[n].has_spike_effect = get_bit_packed(data,offset,offset+1) + offset = offset +1 + if act.targets[i].actions[n].has_spike_effect == 1 then + act.targets[i].actions[n].has_spike_effect = true + act.targets[i].actions[n].spike_effect_animation = get_bit_packed(data,offset,offset+6) + act.targets[i].actions[n].spike_effect_effect = get_bit_packed(data,offset+6,offset+10) + act.targets[i].actions[n].spike_effect_param = get_bit_packed(data,offset+10,offset+24) + act.targets[i].actions[n].spike_effect_message = get_bit_packed(data,offset+24,offset+34) + offset = offset + 34 + else + act.targets[i].actions[n].has_spike_effect = false + act.targets[i].actions[n].spike_effect_animation = 0 + act.targets[i].actions[n].spike_effect_effect = 0 + act.targets[i].actions[n].spike_effect_param = 0 + act.targets[i].actions[n].spike_effect_message = 0 + end + end + end + + local pet_indices = {} + for i,v in pairs(windower.ffxi.get_mob_array()) do + if v.pet_index then + pet_indices[v.pet_index] = true + end + end + + local actor = windower.ffxi.get_mob_by_id(act.actor_id) + if actor and pet_indices[actor.index] then + act.category = 0 + end + + for i,v in pairs(act.targets) do + local mob = windower.ffxi.get_mob_by_id(v.id) + if mob and pet_indices[mob.index] then + act.category = 0 + for n,m in pairs(act.targets[i].actions) do + act.targets[i].actions[n].animation = 0 + act.targets[i].actions[n].add_effect_animation = 0 + act.targets[i].actions[n].spike_effect_animation = 0 + end + end + end + + local react = assemble_bit_packed('',act.do_not_need,0,8) + react = assemble_bit_packed(react,act.actor_id,8,40) + react = assemble_bit_packed(react,act.target_count,40,50) + react = assemble_bit_packed(react,act.category,50,54) + react = assemble_bit_packed(react,act.param,54,70) + react = assemble_bit_packed(react,act.unknown,70,86) + react = assemble_bit_packed(react,act.recast,86,118) + + local offset = 118 + for i = 1,act.target_count do + react = assemble_bit_packed(react,act.targets[i].id,offset,offset+32) + react = assemble_bit_packed(react,act.targets[i].action_count,offset+32,offset+36) + offset = offset + 36 + for n = 1,act.targets[i].action_count do + react = assemble_bit_packed(react,act.targets[i].actions[n].reaction,offset,offset+5) + react = assemble_bit_packed(react,act.targets[i].actions[n].animation,offset+5,offset+16) + react = assemble_bit_packed(react,act.targets[i].actions[n].effect,offset+16,offset+21) + react = assemble_bit_packed(react,act.targets[i].actions[n].stagger,offset+21,offset+27) + react = assemble_bit_packed(react,act.targets[i].actions[n].param,offset+27,offset+44) + react = assemble_bit_packed(react,act.targets[i].actions[n].message,offset+44,offset+54) + react = assemble_bit_packed(react,act.targets[i].actions[n].unknown,offset+54,offset+85) + + react = assemble_bit_packed(react,act.targets[i].actions[n].has_add_effect,offset+85,offset+86) + offset = offset + 86 + if act.targets[i].actions[n].has_add_effect then + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_animation,offset,offset+6) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_effect,offset+6,offset+10) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_param,offset+10,offset+27) + react = assemble_bit_packed(react,act.targets[i].actions[n].add_effect_message,offset+27,offset+37) + offset = offset + 37 + end + react = assemble_bit_packed(react,act.targets[i].actions[n].has_spike_effect,offset,offset+1) + offset = offset + 1 + if act.targets[i].actions[n].has_spike_effect then + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_animation,offset,offset+6) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_effect,offset+6,offset+10) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_param,offset+10,offset+24) + react = assemble_bit_packed(react,act.targets[i].actions[n].spike_effect_message,offset+24,offset+34) + offset = offset + 34 + end + end + end + while #react < #data do + react = react..data:sub(#react+1,#react+1) + end + + return pref..react + end +end) + +function get_bit_packed(dat_string,start,stop) + local newval = 0 + + local c_count = math.ceil(stop/8) + while c_count >= math.ceil((start+1)/8) do + -- Grabs the most significant byte first and works down towards the least significant. + local cur_val = dat_string:byte(c_count) + local scal = 256 + + if c_count == math.ceil(stop/8) then -- Take the least significant bits of the most significant byte + -- Moduluses by 2^number of bits into the current byte. So 8 bits in would %256, 1 bit in would %2, etc. + -- Cuts off the top. + cur_val = cur_val%(2^((stop-1)%8+1)) -- -1 and +1 set the modulus result range from 1 to 8 instead of 0 to 7. + end + + if c_count == math.ceil((start+1)/8) then -- Take the most significant bits of the least significant byte + -- Divides by the significance of the final bit in the current byte. So 8 bits in would /128, 1 bit in would /1, etc. + -- Cuts off the bottom. + cur_val = math.floor(cur_val/(2^(start%8))) + scal = 2^(8-start%8) + end + + newval = newval*scal + cur_val -- Need to multiply by 2^number of bits in the next byte + c_count = c_count - 1 + end + return newval +end + +function assemble_bit_packed(init,val,initial_length,final_length,debug_val) + if type(val) == 'boolean' then + if val then val = 1 else val = 0 end + end + local bits = initial_length%8 + local byte_length = math.ceil(final_length/8) + + local out_val = 0 + if bits > 0 then + out_val = init:byte(#init) -- Initialize out_val to the remainder in the active byte. + init = init:sub(1,#init-1) -- Take off the active byte + end + out_val = out_val + val*2^bits -- left-shift val by the appropriate amount and add it to the remainder (now the lsb-s in val) + if debug_val then print(out_val..' '..#init) end + + while out_val > 0 do + init = init..string.char(out_val%256) + out_val = math.floor(out_val/256) + end + while #init < byte_length do + init = init..string.char(0) + end + return init +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/plasmon/README.md b/Data/DefaultContent/Libraries/addons/addons/plasmon/README.md new file mode 100644 index 0000000..ed4ce00 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plasmon/README.md @@ -0,0 +1,140 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20131021 + +# Plasmon # +This addon tracks plasm, killed mobs and dropped airlixirs during a delve. + +## Commands ## +### help ### +Shows the help text. + +``` +plasmon help +``` + +### test ### +Fills the chat log with some messages to show how the plugin will work. + +``` +plasmon test +``` + +### reset ### +sets current gained plasm, monster kill count and dropped airlixirs to 0. + +``` +plasmon reset +``` + +### full-reset ### +sets both current and total gained plasm, monster kill count and dropped airlixirs to 0. + +``` +plasmon full-reset +``` + +### show ### +Shows the tracking window. + +``` +plasmon show +``` + +### hide ### +Hides the tracking window. + +``` +plasmon hide +``` + +### toggle ### +Toggles the tracking window's visibility. + +``` +plasmon toggle +``` + +### light ### +Enables or disables light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the _enabled_ parameter is not specified, the help text will be shown. + +``` +plasmon light <enabled> +``` +* **enabled:** specifies the status of the light mode. **default**, **false** or **0** mean disabled. **true** or **1** mean enabled. + +### timer ### +Enables or disables the timer. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the _enabled_ parameter is not specified, the help text will be shown. + +``` +plasmon timer <enabled> +``` +* **enabled:** specifies the status of the timer. **false** or **0** mean disabled. **default**, **true** or **1** mean enabled. + +### position ### +Sets the horizontal and vertical position of the window relative to the upper-left corner. If no parameter is specified, the help text will be shown. + +``` +plasmon position [[-h]|[-x <x>] [-y <y>]] +``` +* **-h:** shows the help text. +* **-x _x_:** specifies the horizontal position of the window. +* **-y _y_:** specifies the vertical position of the window. + +### font ### +Sets the style of the font used in the window. If no parameter is specified, the help text will be shown. + +``` +plasmon font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]] +``` +* **-h:** shows the help text. +* **-f _font_:** specifies the text's font. +* **-s _size_:** specifies the text's size. +* **-a _alpha_:** specifies the text's transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive. +* **-b [ _bold_ ]:** specifies if the text should be rendered bold. **default**, **false** or **0** mean disabled. **true**, **1** or no value mean enabled. +* **-i [ _italic_ ]:** specifies if the text should be rendered italic. **default**, **false** or **0** mean disabled. **true**, **1** or no value mean enabled. + +### color ### +Sets the colors of the various elements present in the addon's window. If no parameter is specified, the help text will be shown. + +``` +plasmon color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]] +``` +* **-h:** shows the help text. +* **-o _objects_:** specifies the item/s which will have its/their color changed. If this parameter is missing all the objects will be changed. The accepted values are: **all**, **background**, **bg**, **title**, **label**, **value**, **plasmon**, **plasmon.title**, **plasmon.label**, **plasmon.value**, **airlixir**, **airlixir.title**, **airlixir.label**, **airlixir.value**. +* **-d:** sets the red, green, blue and alpha values of the specified objects to their default values. +* **-r _red_:** specifies the intensity of the red color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-g _green_:** specifies the intensity of the greencolor. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-b _blue_:** specifies the intensity of the blue color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-a _alpha_:** specifies the text's transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive. + +---- + +## Changelog ## + +### v1.20130613 ### +* **add**: Stop tracking on zone change. + +### v1.20130610 ### +* **add**: Added a function to enable/disable the fracture timer. + +### v1.20130609 ### +* **fix**: Fix for ally leaders and mobs counting. + +### v1.20130604 ### +* **add**: Added a 45 minutes timer. Requires Timers plugin's custom timers function. + +### v1.20130529 ### +* **change**: Aligned to Windower's addon development guidelines. + +### v1.20130528 ### +* **add:** Added a recovery mode in case of crash/reload. +* **fix:** Fixed the mob kill count. + +### v1.20130517 ### +* **fix:** Fixed a bug that kept the addon from counting airlixirs. + +### v1.20130516 ### +* **change:** A "light mode" has been added. while active, the window will be kept hidden and only a summary will be shown at the end of the run. + +### v1.20130515### +* First release. diff --git a/Data/DefaultContent/Libraries/addons/addons/plasmon/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/plasmon/data/settings.xml new file mode 100644 index 0000000..7366749 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plasmon/data/settings.xml @@ -0,0 +1,62 @@ +<?xml version="1.1" ?> +<settings> + <global> + <colors> + <airlixir> + <label> + <b>152</b> + <g>161</g> + <r>42</r> + </label> + <title> + <b>47</b> + <g>50</g> + <r>220</r> + </title> + <value> + <b>161</b> + <g>161</g> + <r>147</r> + </value> + </airlixir> + <background> + <a>200</a> + <b>54</b> + <g>43</g> + <r>0</r> + </background> + <delve> + <label> + <b>210</b> + <g>139</g> + <r>38</r> + </label> + <title> + <b>47</b> + <g>50</g> + <r>220</r> + </title> + <value> + <b>161</b> + <g>161</g> + <r>147</r> + </value> + </delve> + </colors> + <first_run>true</first_run> + <font> + <a>255</a> + <bold>false</bold> + <family>Arial</family> + <italic>false</italic> + <size>10</size> + </font> + <light>false</light> + <position> + <x>0</x> + <y>350</y> + </position> + <v>0</v> + </global> +</settings> + diff --git a/Data/DefaultContent/Libraries/addons/addons/plasmon/icon.png b/Data/DefaultContent/Libraries/addons/addons/plasmon/icon.png Binary files differnew file mode 100644 index 0000000..5a10fd3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plasmon/icon.png diff --git a/Data/DefaultContent/Libraries/addons/addons/plasmon/plasmon.lua b/Data/DefaultContent/Libraries/addons/addons/plasmon/plasmon.lua new file mode 100644 index 0000000..19c611b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plasmon/plasmon.lua @@ -0,0 +1,859 @@ +--[[ +plasmon v1.20140530 + +Copyright (c) 2013, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of plasmon nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require('logger') +config = require('config') + +_addon.name = 'plasmon' +_addon.author = 'Zohno' +_addon.version = '1.20140530' +_addon.command = 'plasmon' + +tb_name = 'addon:gr:plasmon' +track = false +visible = false +recovery_mode = false + +stats = T{} +stats.plasm = 0 +stats.tot_plasm = 0 +stats.mobs = 0 +stats.tot_mobs = 0 +stats.airlixirs = 0 +stats.tot_airlixirs = 0 +stats.airlixirs1 = 0 +stats.tot_airlixirs1 = 0 +stats.airlixirs2 = 0 +stats.tot_airlixirs2 = 0 + +defaults = T{} +defaults.light = false +defaults.timer = true + +defaults.position = T{} +defaults.position.x = 0 +defaults.position.y = 350 + +defaults.font = T{} +defaults.font.family = 'Arial' +defaults.font.size = 10 +defaults.font.a = 255 +defaults.font.bold = false +defaults.font.italic = false + +defaults.colors = T{} +defaults.colors.background = T{} +defaults.colors.background.r = 0 +defaults.colors.background.g = 43 +defaults.colors.background.b = 54 +defaults.colors.background.a = 200 + +defaults.colors.delve = T{} +defaults.colors.delve.title = T{} +defaults.colors.delve.title.r = 220 +defaults.colors.delve.title.g = 50 +defaults.colors.delve.title.b = 47 + +defaults.colors.delve.label = T{} +defaults.colors.delve.label.r = 38 +defaults.colors.delve.label.g = 139 +defaults.colors.delve.label.b = 210 + +defaults.colors.delve.value = T{} +defaults.colors.delve.value.r = 147 +defaults.colors.delve.value.g = 161 +defaults.colors.delve.value.b = 161 + +defaults.colors.airlixir = T{} +defaults.colors.airlixir.title = T{} +defaults.colors.airlixir.title.r = 220 +defaults.colors.airlixir.title.g = 50 +defaults.colors.airlixir.title.b = 47 + +defaults.colors.airlixir.label = T{} +defaults.colors.airlixir.label.r = 42 +defaults.colors.airlixir.label.g = 161 +defaults.colors.airlixir.label.b = 152 + +defaults.colors.airlixir.value = T{} +defaults.colors.airlixir.value.r = 147 +defaults.colors.airlixir.value.g = 161 +defaults.colors.airlixir.value.b = 161 + +settings = config.load(defaults) + +-- plugin functions + +function parse_options(args) + local options = T{} + + while #args > 0 do + if not args[1]:match('^-%a') then + break + end + + local option = args:remove(1):sub(2) + + if args[1] ~= nil and not args[1]:match('^-%a') then + options[option] = args:remove(1) + else + options[option] = true + end + end + + return options +end + +function test() + windower.add_to_chat(148, 'Now permeating the mists surrounding the fracture.') + windower.add_to_chat(148, 'You receive 50 corpuscles of mweya plasm.') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(148, 'You receive 50 corpuscles of mweya plasm.') + windower.add_to_chat(148, 'You receive 50 corpuscles of mweya plasm.') + windower.add_to_chat(148, 'You receive 150 corpuscles of mweya plasm.') + windower.add_to_chat(148, 'You receive 50 corpuscles of mweya plasm.') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(148, 'You receive 500 corpuscles of mweya plasm.') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(121, 'You find an airlixir on the Mob') + windower.add_to_chat(121, 'You find an airlixir +1 on the Mob') + windower.add_to_chat(121, 'You find an airlixir +2 on the Mob') + windower.add_to_chat(146, 'Your time has expired for this battle. Now exiting...') + show_window() +end + +function start_tracking() + reset_stats() + log('The Delve has begun!') + start_timer() + + track = true + + if recovery_mode then + recovery_mode = false + end + + if settings.light == false then + show_window() + end +end + +function stop_tracking() + stats.scores = T{} + stats.bonuses = T{} + track = false + + log('The Delve has ended.') + stop_timer() + hide_window() + show_report() +end + +function start_timer() + windower.send_command('timers create Delve 2700 down ../../../addons/plasmon/icon') +end + +function stop_timer() + windower.send_command('timers delete Delve') +end + +function refresh_window() + if visible == false then + return + end + + local delve_colors = settings.colors.delve + local airlixir_colors = settings.colors.airlixir + local text = T{ + ' \\cs('..delve_colors.title.r..', '..delve_colors.title.g..', '..delve_colors.title.b..')--== DELVE ==--\\cr \n', + ' \\cs('..delve_colors.label.r..', '..delve_colors.label.g..', '..delve_colors.label.b..')Plasm:\\cr', + ' \\cs('..delve_colors.value.r..', '..delve_colors.value.g..', '..delve_colors.value.b..')'..stats.plasm..'/'..stats.tot_plasm..'\\cr \n', + ' \\cs('..delve_colors.label.r..', '..delve_colors.label.g..', '..delve_colors.label.b..')Mobs:\\cr', + ' \\cs('..delve_colors.value.r..', '..delve_colors.value.g..', '..delve_colors.value.b..')'..stats.mobs..'/'..stats.tot_mobs..'\\cr \n', + ' \\cs('..airlixir_colors.title.r..', '..airlixir_colors.title.g..', '..airlixir_colors.title.b..')--== AIRLIXIRS ==--\\cr \n', + ' \\cs('..airlixir_colors.label.r..', '..airlixir_colors.label.g..', '..airlixir_colors.label.b..')Airlixir:\\cr', + ' \\cs('..airlixir_colors.value.r..', '..airlixir_colors.value.g..', '..airlixir_colors.value.b..')'..stats.airlixirs..'/'..stats.tot_airlixirs..'\\cr \n', + ' \\cs('..airlixir_colors.label.r..', '..airlixir_colors.label.g..', '..airlixir_colors.label.b..')Airlixir +1:\\cr', + ' \\cs('..airlixir_colors.value.r..', '..airlixir_colors.value.g..', '..airlixir_colors.value.b..')'..stats.airlixirs1..'/'..stats.tot_airlixirs1..'\\cr \n', + ' \\cs('..airlixir_colors.label.r..', '..airlixir_colors.label.g..', '..airlixir_colors.label.b..')Airlixir +2:\\cr', + ' \\cs('..airlixir_colors.value.r..', '..airlixir_colors.value.g..', '..airlixir_colors.value.b..')'..stats.airlixirs2..'/'..stats.tot_airlixirs2..'\\cr' + } + + windower.text.set_text(tb_name, text:concat('')) +end + +function reset_stats() + stats.plasm = 0 + stats.mobs = 0 + stats.airlixirs = 0 + stats.airlixirs1 = 0 + stats.airlixirs2 = 0 + refresh_window() +end + +function full_reset_stats() + stats.tot_plasm = 0 + stats.tot_mobs = 0 + stats.tot_airlixirs = 0 + stats.tot_airlixirs1 = 0 + stats.tot_airlixirs2 = 0 + reset_stats() + refresh_window() +end + +function show_window() + visible = true + windower.text.set_visibility(tb_name, true) + refresh_window() +end + +function hide_window() + visible = false + windower.text.set_visibility(tb_name, false) +end + +function toggle_window() + if visible then + hide_window() + else + show_window() + end +end + +function show_report() + log('[Plasm '..(stats.plasm..'/'..stats.tot_plasm):color(258)..'] [Mobs '..(stats.mobs..'/'..stats.tot_mobs):color(258)..'] [Airlixir '..(stats.airlixirs..'/'..stats.tot_airlixirs):color(258)..' | +1 '..(stats.airlixirs1..'/'..stats.tot_airlixirs1):color(258)..' | +2 '..(stats.airlixirs2..'/'..stats.tot_airlixirs2):color(258)..']') +end + +function initialize() + local background = settings.colors.background + + windower.text.create(tb_name) + windower.text.set_location(tb_name, settings.position.x, settings.position.y) + windower.text.set_bg_color(tb_name, background.a, background.r, background.g, background.b) + windower.text.set_color(tb_name, settings.font.a, 147, 161, 161) + windower.text.set_font(tb_name, settings.font.family) + windower.text.set_font_size(tb_name, settings.font.size) + windower.text.set_bold(tb_name, settings.font.bold) + windower.text.set_italic(tb_name, settings.font.italic) + windower.text.set_text(tb_name, '') + windower.text.set_bg_visibility(tb_name, true) + + if windower.ffxi.get_info().zone == 271 or windower.ffxi.get_info().zone == 264 then + recovery_mode = true + end +end + +function dispose() + windower.text.delete(tb_name) + windower.send_command('timers delete Delve') +end + +-- windower events + +windower.register_event('load', initialize:cond(function() return windower.ffxi.get_info().logged_in end)) + +windower.register_event('login', initialize) + +windower.register_event('logout', 'unload', dispose) + +windower.register_event('zone change', stop_tracking:cond(function(_,id) return (id == 271 or id == 264) and track end)) + +windower.register_event('incoming text', function(original, modified, mode) + local match + + original = original:strip_format() + + if track or recovery_mode then + if mode == 148 then + match = original:match('You receive (%d+) corpuscles of mweya plasm%.') + + if match then + if recovery_mode then + start_tracking() + end + + match = tonumber(match) + stats.plasm = stats.plasm + match + stats.tot_plasm = stats.tot_plasm + match + + if match % 50 == 0 and match % 500 ~= 0 and match % 750 ~= 0 and match % 10000 ~= 0 then + mobs = match / 50 + else + mobs = 1 + end + + stats.mobs = stats.mobs + mobs + stats.tot_mobs = stats.tot_mobs + mobs + refresh_window() + + return modified, mode + end + elseif mode == 121 then + match = original:match('You find an airlixir %+1') + + if match then + if recovery_mode then + start_tracking() + end + + stats.airlixirs1 = stats.airlixirs1 + 1 + stats.tot_airlixirs1 = stats.tot_airlixirs1 + 1 + refresh_window() + + return modified, mode + end + + match = original:match('You find an airlixir %+2') + + if match then + if recovery_mode then + start_tracking() + end + + stats.airlixirs2 = stats.airlixirs2 + 1 + stats.tot_airlixirs2 = stats.tot_airlixirs2 + 1 + refresh_window() + + return modified, mode + end + + match = original:match('You find an airlixir') + + if match then + if recovery_mode then + start_tracking() + end + + stats.airlixirs = stats.airlixirs + 1 + stats.tot_airlixirs = stats.tot_airlixirs + 1 + refresh_window() + + return modified, mode + end + elseif mode == 146 then + match = original:match('Your time has expired for this battle%. Now exiting%.%.%.') + + if match then + stop_tracking() + + return modified, mode + end + end + elseif mode > 20 then + --mode == 148 or mode == 151 then + --old zones + match = original:match('Now permeating the mists surrounding the fracture%.') + if match then + start_tracking() + + return modified, mode + end + + --new zones + match = original:match('Now permeating the mists surrounding the obscured domain%.') + if match then + start_tracking() + + return modified, mode + end + end + + return modified, mode +end) + +windower.register_event('addon command', function(...) + local args = T({...}) + + if args[1] == nil then + windower.send_command('plasmon help') + return + end + + local cmd = args:remove(1):lower() + + if cmd == 'help' then + log(' help -- shows the help text.') + log(' test -- fills the chat log with some messages to show how the plugin will work.') + log(' reset -- sets current gained plasm, monster kill count and dropped airlixirs to 0.') + log(' full-reset -- sets both current and total gained plasm, monster kill count and dropped airlixirs to 0.') + log(' show -- shows the tracking window.') + log(' hide -- hides the tracking window.') + log(' toggle -- toggles the tracking window\'s visibility.') + log(' light [<enabled>] -- enables or disables light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the enabled parameter is not specified, the help text will be shown.') + log(' timer [<enabled>] -- enables or disables the timer. When enabled, the addon will start a 45 minutes timer when entering a fracture. If the enabled parameter is not specified, the help text will be shown.') + log(' position [[-h]|[-x <x>] [-y <y>]] -- sets the horizontal and vertical position of the window relative to the upper-left corner. If no parameter is specified, the help text will be shown.') + log(' font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]] -- sets the style of the font used in the window. If the no parameter is specified, the help text will be shown.') + log(' color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]] -- sets the colors of the various elements present in the addon\'s window. If no parameter is specified, the help text will be shown.') + elseif cmd == 'test' then + test() + elseif cmd == 'reset' then + reset_stats() + elseif cmd == 'full-reset' then + full_reset_stats() + elseif cmd == 'show' then + show_window() + elseif cmd == 'hide' then + hide_window() + elseif cmd == 'toggle' then + toggle_window() + elseif cmd == 'light' then + if type(args[1]) == 'nil' then + log('Enables or disables light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the enabled parameter is not specified, the help text will be shown.') + log('Usage: plasmon light <enabled>') + log('Positional arguments:') + log(' <enabled> specifies the status of the light mode. "default", "false" or "0" mean disabled. "true" or "1" mean enabled.') + else + local light + + if args[1] == 'default' then + light = defaults.light + elseif args[1] == 'true' or args[1] == '1' then + light = true + elseif args[1] == 'false' or args[1] == '0' then + light = false + end + + if light == true then + hide_window() + elseif track == true then + show_window() + end + + if type(light) ~= "boolean" then + error('Please specify a valid status') + + return + end + + settings.light = light + + refresh_window() + settings:save('all') + end + elseif cmd == 'timer' then + if type(args[1]) == 'nil' then + log('Enables or disables the timer. When enabled, the addon will start a 45 minutes timer when entering a fracture. If the enabled parameter is not specified, the help text will be shown.') + log('Usage: plasmon timer <enabled>') + log('Positional arguments:') + log(' <enabled> specifies the status of the timer. "false" or "0" mean disabled. "default", "true" or "1" mean enabled.') + else + local timer + + if args[1] == 'true' or args[1] == '1' then + timer = true + elseif args[1] == 'false' or args[1] == '0' then + timer = false + end + + if args[1] == 'default' then + timer = defaults.timer + elseif timer == true then + start_timer()() + elseif track == true then + stop_timer() + end + + if type(timer) ~= "boolean" then + error('Please specify a valid status') + + return + end + + settings.timer = timer + + refresh_window() + settings:save('all') + end + else + local options = parse_options(args) + + if cmd == 'position' then + if options:containskey('h') or options:length() == 0 then + log('Sets the horizontal and vertical position of the window relative to the upper-left corner. If the no parameter is specified, the help text will be shown.') + log('Usage: plasmon position [[-h]|[-x <x>] [-y <y>]]') + log('Optional arguments:') + log(' -h shows the help text.') + log(' -x <x> specifies the horizontal position of the window.') + log(' -y <y> specifies the vertical position of the window.') + elseif options:length() > 0 then + local x = settings.position.x + local y = settings.position.y + + for key, value in pairs(options) do + if key == 'x' then + if options['x'] == 'default' then + x = defaults.position.x + else + x = tonumber(options['x']) + + if type(x) ~= "number" then + error('Please specify a valid horizontal position.') + + return + end + end + elseif key == 'y' then + if options['y'] == 'default' then + y = defaults.position.y + else + y = tonumber(options['y']) + + if type(y) ~= "number" then + error('Please specify a valid vertical position.') + + return + end + end + + else + error('"'..key..'" is not a recognized parameter') + + return + end + end + + settings.position.x = x + settings.position.y = y + + windower.text.set_location(tb_name, x, y) + settings:save('all') + log('The window\'s position has been set.') + end + elseif cmd == 'font' then + if options:containskey('h') or options:length() == 0 then + log('Sets the style of the font used in the window. If the no parameter is specified, the help text will be shown.') + log('Usage: plasmon font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]]') + log('Optional arguments:') + log(' -h shows the help text.') + log(' -f <font> specifies the text\'s font.') + log(' -s <size> specifies the text\'s size.') + log(' -a <alpha> specifies the text\'s transparency. the value must be set between 0 (transparent) and 255 (opaque), inclusive.') + log(' -b [<bold>] specifies if the text should be rendered bold. "default", "false" or "0" mean disabled. "true", "1" or no value mean enabled.') + log(' -i [<italic>] specifies if the text should be rendered italic. "default", "false" or "0" mean disabled. "true", "1" or no value mean enabled.') + elseif options:length() > 0 then + local family = settings.font.family + local size = settings.font.size + local bold = settings.font.bold + local italic = settings.font.italic + local a = settings.font.a + + for key, value in pairs(options) do + if key == 'f' then + if options['f'] == 'default' then + family = defaults.font.family + else + family = options['f'] + end + elseif key == 's' then + if options['s'] == 'default' then + size = defaults.position.size + else + size = tonumber(options['s']) + + if type(size) ~= "number" then + error('Please specify a valid font size.') + + return + end + end + elseif key == 'b' then + if options['b'] == 'default' then + bold = defaults.position.bold + elseif options['b'] == true or options['b'] == '1' or options['b'] == 'true' or options['b'] == 'null' then + bold = true + elseif options['b'] == '0' or options['b'] == 'false' then + bold = false + else + error('Please specify a valid bold status.') + + return + end + elseif key == 'i' then + if options['i'] == 'default' then + italic = defaults.position.italic + elseif options['b'] == true or options['i'] == '1' or options['i'] == 'true' or options['i'] == 'null' then + italic = true + elseif options['i'] == '0' or options['i'] == 'false' then + italic = false + else + error('Please specify a valid italic status.') + + return + end + elseif key == 'a' then + if options['a'] == 'default' then + a = defaults.position.a + else + a = tonumber(options['a']) + + if type(a) ~= "number" then + error('Please specify a valid alpha value.') + + return + else + a = math.min(255, math.max(0, a)) + end + end + else + error('"'..key..'" is not a recognized parameter') + + return + end + end + + settings.font.family = family + settings.font.size = size + settings.font.bold = bold + settings.font.italic = italic + settings.font.a = a + + windower.text.set_color(tb_name, a, 147, 161, 161) + windower.text.set_font(tb_name, family, size) + windower.text.set_bold(tb_name, bold) + windower.text.set_italic(tb_name, italic) + settings:save('all') + log('The font\'s style has been set.') + end + elseif cmd == 'color' then + local validObjects = T{ + 'all', 'background', 'bg', 'title', 'label', 'value', + 'plasmon', 'plasmon.title', 'plasmon.label', 'plasmon.value', + 'airlixir', 'airlixir.title', 'airlixir.label', 'airlixir.value' + } + + if options:containskey('h') or options:length() == 0 then + log('Sets the colors of the various elements present in the addon\'s window. If the no parameter is specified, the help text will be shown.') + log('Usage: plasmon color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]]') + log('Optional arguments:') + log(' -h shows the help text.') + log(' -o <objects> specifies the item/s which will have its/their color changed. If this parameter is missing all the objects will be changed. The accepted values are: "'..validObjects:concat('", "')..'"') + log(' -d sets the red, green, blue and alpha values of the specified objects to their default values.') + log(' -r <red> specifies the intensity of the red color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(' -g <green> specifies the intensity of the greencolor. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(' -b <blue> specifies the intensity of the blue color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(' -a <alpha> specifies the text\'s transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive.') + elseif options:length() > 0 then + local r = -1 + local g = -1 + local b = -1 + local a = -1 + local objects + + if options:containskey('o') then + if validObjects:contains(options['o']) then + if options['o'] == 'background' or options['o'] == 'bg' then + objects = T{'background'} + elseif options['o'] == 'title' then + objects = T{ + 'plasmon.title', + 'airlixir.title' + } + elseif options['o'] == 'label' then + objects = T{ + 'plasmon.label', + 'airlixir.label' + } + elseif options['o'] == 'value' then + objects = T{ + 'plasmon.value', + 'airlixir.value' + } + elseif options['o'] == 'plasmon' then + objects = T{ + 'plasmon.title', + 'plasmon.label', + 'plasmon.value' + } + elseif options['o'] == 'airlixir' then + objects = T{ + 'airlixir.title', + 'airlixir.label', + 'airlixir.value' + } + elseif options['o'] == 'plasmon.title' then + objects = T{'plasmon.title'} + elseif options['o'] == 'plasmon.label' then + objects = T{'plasmon.label'} + elseif options['o'] == 'plasmon.value' then + objects = T{'plasmon.value'} + elseif options['o'] == 'airlixir.title' then + objects = T{'airlixir.title'} + elseif options['o'] == 'airlixir.label' then + objects = T{'airlixir.label'} + elseif options['o'] == 'airlixir.value' then + objects = T{'airlixir.value'} + end + else + error('Please specify a valid object or set of objects.') + + return + end + else + objects = T{ + 'background', + 'plasmon.title', 'plasmon.label', 'plasmon.value', + 'airlixir.title', 'airlixir.label', 'airlixir.value' + } + end + + if not options:containskey('d') then + for key, value in pairs(options) do + if key == 'r' then + if options['r'] == 'default' then + r = -1 + else + r = tonumber(options['r']) + + if type(r) ~= "number" then + error('Please specify a valid red value.') + + return + else + r = math.min(255, math.max(0, r)) + end + end + elseif key == 'g' then + if options['g'] == 'default' then + g = -1 + else + g = tonumber(options['g']) + + if type(g) ~= "number" then + error('Please specify a valid green value.') + + return + else + g = math.min(255, math.max(0, g)) + end + end + elseif key == 'b' then + if options['b'] == 'default' then + b = -1 + else + b = tonumber(options['b']) + + if type(b) ~= "number" then + error('Please specify a valid blue value.') + + return + else + b = math.min(255, math.max(0, b)) + end + end + elseif key == 'a' then + if options['a'] == 'default' then + a = -1 + else + a = tonumber(options['a']) + + if type(a) ~= "number" then + error('Please specify a valid alpha value.') + + return + else + a = math.min(255, math.max(0, a)) + end + end + elseif key == 'o' then + else + error('"'..key..'" is not a recognized parameter.') + + return + end + end + end + + for key, object in pairs(objects) do + local indexes = T(object:split('.')) + + if indexes:length() == 2 then + if r == -1 then + settings.colors[indexes[1]][indexes[2]].r = defaults.colors[indexes[1]][indexes[2]].r + else + settings.colors[indexes[1]][indexes[2]].r = r + end + + if g == -1 then + settings.colors[indexes[1]][indexes[2]].g = defaults.colors[indexes[1]][indexes[2]].g + else + settings.colors[indexes[1]][indexes[2]].g = g + end + + if b == -1 then + settings.colors[indexes[1]][indexes[2]].b = defaults.colors[indexes[1]][indexes[2]].b + else + settings.colors[indexes[1]][indexes[2]].b = b + end + elseif indexes:length() == 1 then + if r == -1 then + settings.colors[indexes[1]].r = defaults.colors[indexes[1]].r + else + settings.colors[indexes[1]].r = r + end + + if g == -1 then + settings.colors[indexes[1]].g = defaults.colors[indexes[1]].g + else + settings.colors[indexes[1]].g = g + end + + if b == -1 then + settings.colors[indexes[1]].b = defaults.colors[indexes[1]].b + else + settings.colors[indexes[1]].b = b + end + + if a == -1 then + settings.colors[indexes[1]].a = defaults.colors[indexes[1]].a + else + settings.colors[indexes[1]].a = a + end + + windower.text.set_bg_color( + tb_name, + settings.colors[indexes[1]].a, + settings.colors[indexes[1]].r, + settings.colors[indexes[1]].g, + settings.colors[indexes[1]].b + ) + end + end + + refresh_window() + settings:save('all') + log('The objects\' color has been set.') + end + else + windower.send_command('plasmon help') + end + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/plugin_manager/README.md b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/README.md new file mode 100644 index 0000000..dc4cbd0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/README.md @@ -0,0 +1,29 @@ +**Author:** Byrth + +**Version:** 1.0 + +**Date:** 15/11/13 + +# Plugin Manager + +**Abbreviation:** None + +**Commands:** None + +**Purpose:** To allow player-customizable plugin management. + +## Installation + +1. Start your copy of Windower 4 and select the "Addons" menu up top in the Launcher. +1. Click the icon next to "plugin_manager" +1. Log in to the game! +1. Alter the settings as you wish. (An example file has been included) +1. `//lua r plugin_manager` will reload and give you the new settings. + +> **Note:** Plugin Manager will *not* automatically disable or unload any plugins or addons that you have enabled in the Windower launcher. Anything enabled in the launcher will be loaded for all characters, regardless of how you have configured your Plugin Manager settings. +> If you want Plugin Manager to handle a plugin or addon, be sure to disable it in the launcher. + +## Settings Files +The settings file for Plugin Manager are found in a single XML file with a format similar to the settings files for plugins. + +* `data/settings.xml` - contains plugins and addons specific to each character. <global> is loaded if there is not a set of plugins/addons inside tags that are a case-sensitive match to the player's name. diff --git a/Data/DefaultContent/Libraries/addons/addons/plugin_manager/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/data/settings.xml new file mode 100644 index 0000000..66aecce --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/data/settings.xml @@ -0,0 +1,19 @@ +<settings> + <global> + <!-- This will be used for all the characters that you don't make a specific set of plugins/addons for --> + <plugin>Timers</plugin> + </global> + <YourName> <!-- This is just an example. You need to replace "YourName" to the left and below with the character name that you want the set of plugins to work for. --> + <plugin>Binder</plugin> + <plugin>ConsoleBG</plugin> + <plugin>FFOChat</plugin> + <plugin>Itemizer</plugin> + <plugin>SSOrganizer</plugin> + <plugin>Timers</plugin> + <plugin>Guildwork</plugin> + + <addon>send</addon> + <addon>battlemod</addon> + <addon>ffocolor</addon> + </YourName> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/plugin_manager/plugin_manager.lua b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/plugin_manager.lua new file mode 100644 index 0000000..f5ac131 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/plugin_manager/plugin_manager.lua @@ -0,0 +1,208 @@ +--Copyright (c) 2013-2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +file = require 'files' +require 'tables' +xml = require 'xml' + + +_addon.version = '1.0' +_addon.author = 'Byrth' +_addon.name = 'plugin_manager' +_addon.commands = {} + +windower.register_event('addon command',function(...) + local cmd = {...} + if cmd[1] == 'load' then + load_plugins(cmd[2]) + elseif cmd[1] == 'unload' then + unload_plugins(cmd[2]) + end +end) + +windower.register_event('load',function() + loader_array = {} -- Expansion of the settings file + general_array = {} -- List of every addon/plugin that gets loaded. true = loaded sometimes. false = loaded all the time. nil = blocked or not used. + general_array['addon'] = {} + general_array['plugin'] = {} + load_command = {plugin='load ',addon='lua l '} + unload_command = {plugin='unload ',addon='lua u '} + load_settings() + + -- Iterate over the list of plugins/addons and determine which ones are loaded by all profiles + -- Load those plugins once and set them to "false" in the general_array + local firstrun,length = '@',0 + for i,v in pairs(loader_array) do + length = length + 1 + end + for q,r in pairs(general_array) do + for n,m in pairs(r) do + local counter = 0 + if m == true then + for i,v in pairs(loader_array) do + if v[q]:contains(n) then + counter = counter + 1 + end + end + if counter == length then + firstrun = firstrun..load_command[q]..n..';' + general_array[q][n] = false + end + end + end + end + + windower.send_command(firstrun) + if windower.ffxi.get_player() then + coroutine.sleep(3) -- Wait for firstrun to finish + unload_plugins() + coroutine.sleep(3) -- Wait for the unload command spam to finish + load_plugins() + end +end) + +function load_settings() + if not file.exists('data/settings.xml') then + error('plugin_manager: settings.xml is missing.') + else + -- Iterate over the settings file and simply it, as well as creating a list of all plugins + local settingtab = xml.read('data/settings.xml') + if settingtab then + settingtab = settingtab:undomify() + else + error('plugin_manager: settings.xml has an error in it. Try using an XML validator.') + return + end + for child in settingtab.children:it() do + -- Global/Names layer + loader_array[child.name:lower()] = {} + loader_array[child.name:lower()]['addon'] = T{} + loader_array[child.name:lower()]['plugin'] = T{} + for child2 in child.children:it() do + if child2.name:lower() == 'addon' or child2.name:lower() == 'plugin' then + -- Addon/Plugin layer <name>children[1]</name> + loader_array[child.name:lower()][child2.name:lower()][#loader_array[child.name:lower()][child2.name:lower()]+1] = child2.children[1]:lower() + general_array[child2.name:lower()][child2.children[1]:lower()] = true + end + end + end + + -- Iterate over the blacklist and set blocked plugins to nil. + local blacklisttab = xml.read('../../updates/manifest.xml') + if not blacklisttab then + windower.add_to_chat(123,'Plugin_Manager: Cannot read windower/updates/manifest.xml, so plugin blacklist is broken') + else + blacklisttab = blacklisttab:undomify() + for child in blacklisttab:it() do -- plugins + for child2 in child:it() do -- plugin + local blockload,name = false + for child3 in child2:it() do --name, autoload, description, etc. + if child3.name == 'autoload' and child3.children[1] == 'false' then + blockload = true + end + if child3.name:lower() == 'name' then + name = child3.children[1]:lower() + end + if blockload and name then + general_array.plugin[name:lower()] = nil + end + end + end + end + end + + local blacklistadd = xml.read('../../updates/addons.xml') + if not blacklistadd then + windower.add_to_chat(123,'Plugin_Manager: Cannot read windower/updates/addons.xml, so addon blacklist is broken') + else + blacklistadd = blacklistadd:undomify() + for child in blacklistadd:it() do -- plugins + local blockload,name = false + for child2 in child:it() do --name, autoload, description, etc. + if child2.name == 'autoload' and child2.children[1] == 'false' then + blockload = true + end + if child2.name:lower() == 'name' then + name = child2.children[1]:lower() + end + if blockload and name then + general_array.addon[name:lower()] = nil + end + end + end + end + end +end + +function load_plugins(name) + name = make_name(name) + local working_array,commandstr = {},'@' + + for q,r in pairs(general_array) do + for i,v in pairs(loader_array[name][q]) do + if general_array[q][v] then + commandstr = commandstr..load_command[q]..v..';' + end + end + end + windower.send_command(commandstr) +end + +function unload_plugins(name) + name = make_name(name) + local commandstr = '' + for i,v in pairs(loader_array[name]) do + for n,m in pairs(v) do + if general_array[i][m] then + commandstr = commandstr..unload_command[i]..m..';' + end + end + end + windower.send_command(commandstr) +end + +windower.register_event('login',function(name) + coroutine.sleep(3) + load_plugins(name) +end) + +windower.register_event('logout',function(name) + unload_plugins(name) +end) + +function make_name(name) + if name then + name = name:lower() + elseif windower.ffxi.get_player() then + name = windower.ffxi.get_player().name:lower() + end + + if name == nil or name == '' or not loader_array[name] then + name = 'global' + end + return name or 'global' +end diff --git a/Data/DefaultContent/Libraries/addons/addons/pointwatch/FIXING POINTWATCH.txt b/Data/DefaultContent/Libraries/addons/addons/pointwatch/FIXING POINTWATCH.txt new file mode 100644 index 0000000..61bb39c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pointwatch/FIXING POINTWATCH.txt @@ -0,0 +1,35 @@ +Updating Message IDs: A guide + + Pointwatch relies on zone-based message IDs for some of its features. +These IDs change on a regular basis. Fortunately, their positions relative +to one another rarely change, which means that most of the time you only +have to change one number and the entire zone will work again. + +I have written this guide to explain how to change those numbers in the +hopes that members of the community will maintain pointwatch's message- +dependent features. + +Method 1 - POLUtils +1) Download POLUtils (if you don't already have it) from the google code site +2) Install it and run it +3) Click "FFXI Data Browser" +4) Go to the "Dialog Tables" menu and select the zone you are interested in +5) Wait for it to load, then export the entire table as xml +6) Open the xml file up in a text editor, like Notepad or Notepad++. +7) Control-F for the key phrase. +8) The associated message ID is the offset value for that zone. + +Method 2 - message_printing +1) Load Pointwatch (//lua l pointwatch) +2) Enable message printing (//pw message_printing) +3) Generate the key message (for instance, by resting in Abyssea) +4) This will print message IDs to your windower console. They appear in the + order they were generated. Select the one that you want (for instance, + the second one when you rest in Abyssea). +5) That value is your offset. +6) Disable message printing (//pw message_printing) + +Both methods: +* Open message_ids.lua +* Find the zone and replace the zone.offset value with the correct value. +* Submit your file to Github, so that everyone can have a working copy! diff --git a/Data/DefaultContent/Libraries/addons/addons/pointwatch/ReadMe.txt b/Data/DefaultContent/Libraries/addons/addons/pointwatch/ReadMe.txt new file mode 100644 index 0000000..b3f1964 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pointwatch/ReadMe.txt @@ -0,0 +1,79 @@ +=== PointWatch === +Allows you to monitor your XP/CP gains and keep track of the Dynamis time limit. + +Abbreviation: //pw + +Text Box Commands: +* show - Shows the text box. +* hide - Hides the text box. +* pos <X> <Y> - Moves the upper left corner of the text box to the coordinates X/Y. +* font <font name> - Changes the text's font. +* size <point size> - Changes the size of the text's font. +* color <R> <G> <B> - Changes the text color. Numbers should be between 0 and 255. +* bg_color <R> <G> <B> - Changes the background color. Numbers should be between 0 and 255. +* bg_transparency <number> - Changes the background transparency. Number should be between 0 and 1 +** pos_x, pos_y, pad, transparency, alpha, and bg_alpha are also valid commands and are documented in the texts library. + +Other Commands: +* message_printing - See FIXING POINTWATCH.txt in this directory for a full explanation. + + + +Strings Options: +The two strings options in settings.xml are loaded as Lua code and run accordingly, +so you can do things like adjust numbers and format things as you wish. The default +is designed to look somewhat like the Attainment plugin, but you are free to change +it however you wish. Be aware that the code will give very unhelpful errors when it fails. + +Here are the available values: +* xp.current = Current Experience Points (number from 0 to 55,999 XP) +* xp.tnl = Number of Experience Points in your current level (number from 500 to 56,000) +* xp.rate = Current XP gain rate per hour. This is calculated over a 10 minute window and requires at least two gains within the window. +* xp.total = Total Experience Points gained since the last time the addon was loaded (number) + +* lp.current = Current Experience Points (number from 0 to 55,999 XP) +* lp.tnl = Similar to a "To Next Level", but this value is always 10,000 because that's always the number of Limit Points per merit point. +* lp.number_of_merits = Number of merit points you have. +* lp.maximum_merits = Maximum number of merits you can store. + +* cp.current = Current Capacity Points (number from 0 to 29,999 CP) +* cp.rate = Current CP gain rate per hour. This is calculated over a 10 minute window and requires at least two gains within the window. +* cp.total = Total Capacity Points gained since the last time the addon was loaded (number) +* cp.tnjp = Similar to a "To Next Level", but this value is always 30,000 because that's always the number of CPs per job point. +* cp.number_of_job_points = Number of job points you currently have on your current job. + +* sparks.current = Current number of RoE Sparks (number between 0 and 50,000) +* sparks.maximum = Maximum number of RoE Sparks (always 50,000) + +* accolades.current = Current number of Unity Accolades (number between 0 and 50,000) +* accolades.maximum = Maximum number of Unity Accolades (always 50,000) + +* dynamis.KIs = Series of Xs and Os indicating whether or not you have the 5 KIs. +* dynamis.entry_time = Your Dynamis entry time, in seconds. -- If the addon is loaded in dynamis, this will be the time of addon load. +* dynamis.time_limit = Your current Dynamis time limit, in seconds. -- If the addon is loaded in dynamis, you will need to gain a KI for this to be accurate. +* dynamis.time_remaining = The current dynamis time remaining, in seconds. -- Will not be accurate if the addon is loaded in dynamis. + +* abyssea.amber = Amber light estimation +* abyssea.azure = Azure light estimation +* abyssea.ruby = Ruby light estimation +* abyssea.pearlescent = Pearlescent light estimation +* abyssea.golden = Gold light estimation +* abyssea.silvery = Silvery light estimation +* abyssea.ebon = Ebon light estimation +* abyssea.last_time = The last time you got a time message, in seconds. -- Not implemented +* abyssea.time_limit = The current abyssea time remaining, in seconds. -- Approximate to the minute, not fully implemented +* abyssea.time_remaining = The current abyssea time remaining, in seconds. -- Approximate to the minute, not fully implemented + + + +Version History: +0.150811 - Changed job_points from a char to a short. +0.150201 - Added Unity Accolades. +0.141111 - Adjusted Pointwatch to account for a recent packet change. +0.141101 - Reversed my versioning scheme, adjusted the limit point and experience point calculations slightly. +0.101214 - Made pointwatch hide itself while zoning. +0.062014 - Added lp.maximum_merits. +0.050214 - Fixed the Dynamis clock. Added Abyssea lights. +0.042314 - Addition of strings system +0.042014 - Addition of strings system +0.041214 - Initial commit
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/pointwatch/message_ids.lua b/Data/DefaultContent/Libraries/addons/addons/pointwatch/message_ids.lua new file mode 100644 index 0000000..3cd91ff --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pointwatch/message_ids.lua @@ -0,0 +1,189 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +-- Offset messages: +-- Abyssea's offset message is the one that reports your Pearlescent, Ebon, Gold, and Silvery light when you /heal. +-- Do a Control-F for Pearlescent to find it if using the POLUtils method. It should be the first result. + +-- SEE THE "FIXING POINTWATCH" FILE IN THIS FOLDER FOR INSTRUCTIONS ON HOW TO FIX POINTWATCH. + +local messages = { + z15 = { + name = 'Abyssea - Konschtat', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z132 = { + name = 'Abyssea - La Theine', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z45 = { + name = 'Abyssea - Tahrongi', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z215 = { + name = 'Abyssea - Attohwa', + offset = 7215, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, -- Could also be 7194, the singular message. That is less than 10 minutes though, so shouldn't need to update for it. + visitant_status_extend = 12, + visitant_status_gain = 45, -- Could also be 7228 or 7229 + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z216 = { + name = 'Abyssea - Misareaux', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z217 = { + name = 'Abyssea - Vunkerl', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z218 = { + name = 'Abyssea - Altepa', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z254 = { + name = 'Abyssea - Grauberg', + offset = 7315, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, + z253 = { + name = 'Abyssea - Uleguerand', + offset = 7215, + pearl_ebon_gold_silvery = 0, + azure_ruby_amber = 1, + visitant_status_update = 9, + visitant_status_wears_off = 10, + visitant_status_extend = 12, + visitant_status_gain = 45, + pearlescent_light = 183, + golden_light = 184, + silvery_light = 185, + ebon_light = 186, + azure_light = 187, + ruby_light = 188, + amber_light = 189, + }, +} + +return messages diff --git a/Data/DefaultContent/Libraries/addons/addons/pointwatch/pointwatch.lua b/Data/DefaultContent/Libraries/addons/addons/pointwatch/pointwatch.lua new file mode 100644 index 0000000..8eac64d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pointwatch/pointwatch.lua @@ -0,0 +1,328 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +texts = require 'texts' +config = require 'config' +require 'sets' +res = require 'resources' +require 'statics' +messages = require 'message_ids' +require 'pack' + +_addon.name = 'PointWatch' +_addon.author = 'Byrth' +_addon.version = 0.150811 +_addon.command = 'pw' + +settings = config.load('data\\settings.xml',default_settings) +config.register(settings,initialize) + +box = texts.new('${current_string}',settings.text_box_settings,settings) +box.current_string = '' +box:show() + +initialize() + +windower.register_event('incoming chunk',function(id,org,modi,is_injected,is_blocked) + if is_injected then return end + if id == 0x29 then -- Action Message, used in Abyssea for xp + local val = org:unpack('I',0xD) + local msg = org:unpack('H',0x19)%1024 + exp_msg(val,msg) + elseif id == 0x2A then -- Resting message + local zone = 'z'..windower.ffxi.get_info().zone + if settings.options.message_printing then + print('Message ID: '..(org:unpack('H',0x1B)%2^14)) + end + + if messages[zone] then + local msg = org:unpack('H',0x1B)%2^14 + for i,v in pairs(messages[zone]) do + if tonumber(v) and v + messages[zone].offset == msg then + local param_1 = org:unpack('I',0x9) + local param_2 = org:unpack('I',0xD) + local param_3 = org:unpack('I',0x11) + local param_4 = org:unpack('I',0x15) + -- print(param_1,param_2,param_3,param_4) -- DEBUGGING STATEMENT ------------------------- + if zone_message_functions[i] then + zone_message_functions[i](param_1,param_2,param_3,param_4) + end + if i:contains("visitant_status_") then + abyssea.update_time = os.clock() + end + end + end + end + elseif id == 0x2D then + local val = org:unpack('I',0x11) + local msg = org:unpack('H',0x19)%1024 + exp_msg(val,msg) + elseif id == 0x55 then + if org:byte(0x85) == 3 then + local dyna_KIs = math.floor((org:byte(6)%64)/2) -- 5 bits (32, 16, 8, 4, and 2 originally -> shifted to 16, 8, 4, 2, and 1) + dynamis._KIs = { + ['Crimson'] = dyna_KIs%2 == 1, + ['Azure'] = math.floor(dyna_KIs/2)%2 == 1, + ['Amber'] = math.floor(dyna_KIs/4)%2 == 1, + ['Alabaster'] = math.floor(dyna_KIs/8)%2 == 1, + ['Obsidian'] = math.floor(dyna_KIs/16) == 1, + } + if dynamis_map[dynamis.zone] then + dynamis.time_limit = 3600 + for KI,TE in pairs(dynamis_map[dynamis.zone]) do + if dynamis._KIs[KI] then + dynamis.time_limit = dynamis.time_limit + TE*60 + end + end + update_box() + end + end + elseif id == 0x61 then + xp.current = org:unpack('H',0x11) + xp.tnl = org:unpack('H',0x13) + accolades.current = math.floor(org:byte(0x5A)/4) + org:byte(0x5B)*2^6 + org:byte(0x5C)*2^14 + elseif id == 0x63 and org:byte(5) == 2 then + lp.current = org:unpack('H',9) + lp.number_of_merits = org:byte(11)%128 + lp.maximum_merits = org:byte(0x0D)%128 + elseif id == 0x63 and org:byte(5) == 5 then + local offset = windower.ffxi.get_player().main_job_id*6+13 -- So WAR (ID==1) starts at byte 19 + cp.current = org:unpack('H',offset) + cp.number_of_job_points = org:unpack('H',offset+2) + elseif id == 0x110 then + sparks.current = org:unpack('I',5) + elseif id == 0xB and box:visible() then + zoning_bool = true + box:hide() + elseif id == 0xA and zoning_bool then + zoning_bool = nil + box:show() + end +end) + +windower.register_event('zone change',function(new,old) + if res.zones[new].english:sub(1,7) == 'Dynamis' then + dynamis.entry_time = os.clock() + abyssea.update_time = 0 + abyssea.time_remaining = 0 + dynamis.time_limit = 3600 + dynamis.zone = new + cur_func,loadstring_err = loadstring("current_string = "..settings.strings.dynamis) + elseif res.zones[new].english:sub(1,7) == 'Abyssea' then + abyssea.update_time = os.clock() + abyssea.time_remaining = 5 + dynamis.entry_time = 0 + dynamis.time_limit = 0 + dynamis.zone = 0 + cur_func,loadstring_err = loadstring("current_string = "..settings.strings.abyssea) + else + abyssea.update_time = 0 + abyssea.time_remaining = 0 + dynamis.entry_time = 0 + dynamis.time_limit = 0 + dynamis.zone = 0 + cur_func,loadstring_err = loadstring("current_string = "..settings.strings.default) + end + if not cur_func or loadstring_err then + cur_func = loadstring("current_string = ''") + error(loadstring_err) + end +end) + +windower.register_event('addon command',function(...) + local commands = {...} + local first_cmd = table.remove(commands,1):lower() + if approved_commands[first_cmd] and #commands >= approved_commands[first_cmd].n then + local tab = {} + for i,v in ipairs(commands) do + tab[i] = tonumber(v) or v + if i <= approved_commands[first_cmd].n and type(tab[i]) ~= approved_commands[first_cmd].t then + print('Pointwatch: texts library command ('..first_cmd..') requires '..approved_commands[first_cmd].n..' '..approved_commands[first_cmd].t..'-type input'..(approved_commands[first_cmd].n > 1 and 's' or '')) + return + end + end + texts[first_cmd](box,unpack(tab)) + settings.text_box_settings = box._settings + config.save(settings) + elseif first_cmd == 'reload' then + windower.send_command('lua r pointwatch') + elseif first_cmd == 'unload' then + windower.send_command('lua u pointwatch') + elseif first_cmd == 'reset' then + initialize() + elseif first_cmd == 'message_printing' then + settings.options.message_printing = not settings.options.message_printing + print('Pointwatch: Message printing is '..tostring(settings.options.message_printing)..'.') + elseif first_cmd == 'eval' then + assert(loadstring(table.concat(commands, ' ')))() + end +end) + +windower.register_event('prerender',function() + if frame_count%30 == 0 and box:visible() then + update_box() + end + frame_count = frame_count + 1 +end) + +function update_box() + if not windower.ffxi.get_info().logged_in or not windower.ffxi.get_player() then + box.current_string = '' + return + end + cp.rate = analyze_points_table(cp.registry) + xp.rate = analyze_points_table(xp.registry) + if dynamis.entry_time ~= 0 and dynamis.entry_time+dynamis.time_limit-os.clock() > 0 then + dynamis.time_remaining = os.date('!%H:%M:%S',dynamis.entry_time+dynamis.time_limit-os.clock()) + dynamis.KIs = X_or_O(dynamis._KIs.Crimson)..X_or_O(dynamis._KIs.Azure)..X_or_O(dynamis._KIs.Amber)..X_or_O(dynamis._KIs.Alabaster)..X_or_O(dynamis._KIs.Obsidian) + elseif abyssea.update_time ~= 0 then + local time_less_then = math.floor((os.clock() - abyssea.update_time)/60) + abyssea.time_remaining = abyssea.time_remaining-time_less_then + if time_less_then >= 1 then + abyssea.update_time = os.clock() + end + else + dynamis.time_remaining = 0 + dynamis.KIs = '' + end + assert(cur_func)() + + if box.current_string ~= current_string then + box.current_string = current_string + end +end + +function X_or_O(bool) + if bool then return 'O' else return 'X' end +end + +function analyze_points_table(tab) + local t = os.clock() + local running_total = 0 + local maximum_timestamp = 29 + for ts,points in pairs(tab) do + local time_diff = t - ts + if t - ts > 600 then + tab[ts] = nil + else + running_total = running_total + points + if time_diff > maximum_timestamp then + maximum_timestamp = time_diff + end + end + end + + local rate + if maximum_timestamp == 29 then + rate = 0 + else + rate = math.floor((running_total/maximum_timestamp)*3600) + end + + return rate +end + +zone_message_functions = { + amber_light = function(p1,p2,p3,p4) + abyssea.amber = math.min(abyssea.amber + 8,255) + end, + azure_light = function(p1,p2,p3,p4) + abyssea.azure = math.min(abyssea.azure + 8,255) + end, + ruby_light = function(p1,p2,p3,p4) + abyssea.ruby = math.min(abyssea.ruby + 8,255) + end, + pearlescent_light = function(p1,p2,p3,p4) + abyssea.pearlescent = math.min(abyssea.pearlescent + 5,230) + end, + ebon_light = function(p1,p2,p3,p4) + abyssea.ebon = math.min(abyssea.ebon + p1+1,200) -- NM kill = 1, faint = 1, mild = 2, strong = 3 + end, + silvery_light = function(p1,p2,p3,p4) + abyssea.silvery = math.min(abyssea.silvery + 5*(p1+1),200) -- faint = 5, mild = 10, strong = 15 + end, + golden_light = function(p1,p2,p3,p4) + abyssea.golden = math.min(abyssea.golden + 5*(p1+1),200) -- faint = 5, mild = 10, strong = 15 + end, + pearl_ebon_gold_silvery = function(p1,p2,p3,p4) + abyssea.pearlescent = p1 + abyssea.ebon = p2 + abyssea.golden = p3 + abyssea.silvery = p4 + end, + azure_ruby_amber = function(p1,p2,p3,p4) + abyssea.azure = p1 + abyssea.ruby = p2 + abyssea.amber = p3 + end, + visitant_status_gain = function(p1,p2,p3,p4) + abyssea.time_remaining = p1 + end, + visitant_status_update = function(p1,p2,p3,p4) + abyssea.time_remaining = p1 + end, + visitant_status_wears_off = function(p1,p2,p3,p4) + abyssea.time_remaining = p1 + end, + visitant_status_extend = function(p1,p2,p3,p4) + abyssea.time_remaining = abyssea.time_remaining + p1 + end, +} + +function exp_msg(val,msg) + local t = os.clock() + if msg == 718 or msg == 735 then + cp.registry[t] = (cp.registry[t] or 0) + val + cp.total = cp.total + val + cp.current = cp.current + val + if cp.current > cp.tnjp and cp.number_of_job_points ~= cp.maximum_job_points then + cp.number_of_job_points = math.min(cp.number_of_job_points + math.floor(cp.current/cp.tnjp),cp.maximum_job_points) + cp.current = cp.current%cp.tnjp + end + elseif msg == 8 or msg == 105 then + xp.registry[t] = (xp.registry[t] or 0) + val + xp.total = xp.total + val + xp.current = math.min(xp.current + val,55999) + -- 98 to 99 is 56000 XP, so 55999 is the most you can ever have + if xp.current > xp.tnl then + -- I have capped all jobs, but I assume that a 0x61 packet is sent after you + -- level up, which will update the TNL and make this adjustment meaningless. + xp.current = xp.current - xp.tnl + end + elseif msg == 371 or msg == 372 then + lp.registry[t] = (lp.registry[t] or 0) + val + lp.current = lp.current + val + if lp.current >= lp.tnm and lp.number_of_merits ~= lp.maximum_merits then + -- Merit Point gained! + lp.number_of_merits = math.min(lp.number_of_merits + math.floor(lp.current/lp.tnm),lp.maximum_merits) + lp.current = lp.current%lp.tnm + else + -- If a merit point was not gained, + lp.current = math.min(lp.current,lp.tnm-1) + end + end + update_box() +end diff --git a/Data/DefaultContent/Libraries/addons/addons/pointwatch/statics.lua b/Data/DefaultContent/Libraries/addons/addons/pointwatch/statics.lua new file mode 100644 index 0000000..902cc46 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/pointwatch/statics.lua @@ -0,0 +1,159 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +--Default settings file: +default_settings = { + strings = { + default = "xp.current..'/'..xp.tnl..'XP '..lp.current..'/'..lp.tnm..'LP ['..lp.number_of_merits..'/'..lp.maximum_merits..'] XP/hr:'..string.format('%.1f',math.floor(xp.rate/100)/10)..'k '..cp.current..'/'..cp.tnjp..'CP ['..cp.number_of_job_points..'] CP/hr:'..string.format('%.1f',math.floor(cp.rate/100)/10)..'k'", + dynamis = "xp.current..'/'..xp.tnl..'XP '..lp.current..'/'..lp.tnm..'LP ['..lp.number_of_merits..'/'..lp.maximum_merits..'] XP/hr:'..string.format('%.1f',math.floor(xp.rate/100)/10)..'k '..cp.current..'/'..cp.tnjp..'CP ['..cp.number_of_job_points..'] '..dynamis.KIs..' '..dynamis.time_remaining", + abyssea = "xp.current..'/'..xp.tnl..'XP '..lp.current..'/'..lp.tnm..'LP ['..lp.number_of_merits..'/'..lp.maximum_merits..'] XP/hr:'..string.format('%.1f',math.floor(xp.rate/100)/10)..'k Amber:'..(abyssea.amber or 0)..'/Azure:'..(abyssea.azure or 0)..'/Ruby:'..(abyssea.ruby or 0)..'/Pearlescent:'..(abyssea.pearlescent or 0)..'/Ebon:'..(abyssea.ebon or 0)..'/Silvery:'..(abyssea.silvery or 0)..'/Golden:'..(abyssea.golden or 0)..'/Time Remaining:'..(abyssea.time_remaining or 0)" + }, + text_box_settings = { + pos = { + x = 0, + y = 0, + }, + bg = { + alpha = 255, + red = 0, + green = 0, + blue = 0, + visible = true + }, + flags = { + right = false, + bottom = false, + bold = false, + italic = false + }, + padding = 0, + text = { + size = 12, + font = 'Consolas', + fonts = {}, + alpha = 255, + red = 255, + green = 255, + blue = 255 + } + }, + options = { + message_printing = false, + }, +} + +-- Approved textbox commands: +approved_commands = S{'show','hide','pos','pos_x','pos_y','font','size','pad','color','alpha','transparency','bg_color','bg_alpha','bg_transparency'} +approved_commands = {show={n=0},hide={n=0},pos={n=2,t='number'},pos_x={n=1,t='number'},pos_y={n=1,t='number'}, + font={n=2,t='string'},size={n=1,t='number'},pad={n=1,t='number'},color={n=3,t='number'},alpha={n=1,t='number'}, + transparency={n=1,t='number'},bg_color={n=3,t='number'},bg_alpha={n=1,t='number'},bg_transparency={n=1,t='number'}} + + +-- Dynamis TE lists: +city_table = {Crimson=10,Azure=10,Amber=10,Alabaster=15,Obsidian=15} +other_table = {Crimson=10,Azure=10,Amber=10,Alabaster=10,Obsidian=20} + +-- Mapping of zone ID to TE list: +dynamis_map = {[185]=city_table,[186]=city_table,[187]=city_table,[188]=city_table, + [134]=other_table,[135]=other_table,[39]=other_table,[40]=other_table,[41]=other_table,[42]=other_table} + + +-- Not technically static, but sets the initial values for all features: +function initialize() + cp = { + registry = {}, + current = 0, + rate = 0, + total = 0, + tnjp = 30000, + number_of_job_points = 0, + maximum_job_points = 500, + } + + + xp = { + registry = {}, + total = 0, + rate = 0, + current = 0, + tnl = 0, + } + + lp = { + registry = xp.registry, + current = 0, + tnm = 10000, + number_of_merits = 0, + maximum_merits = 30, + } + + sparks = { + current = 0, + maximum = 99999, + } + + accolades = { + current = 0, + maximum = 99999, + } + + abyssea = { + amber = 0, + azure = 0, + ruby = 0, + pearlescent = 0, + ebon = 0, + silvery = 0, + golden = 0, + update_time = 0, + time_remaining = 0, + } + + + local info = windower.ffxi.get_info() + + frame_count = 0 + + dynamis = { + KIs = '', + _KIs = {}, + entry_time = 0, + time_limit = 0, + zone = 0, + } + if info.logged_in and res.zones[info.zone].english:sub(1,7) == 'Dynamis' then + cur_func = loadstring("current_string = "..settings.strings.dynamis) + setfenv(cur_func,_G) + dynamis.entry_time = os.clock() + dynamis.zone = info.zone + windower.add_to_chat(123,'Loading PointWatch in Dynamis results in an inaccurate timer. Number of KIs is displayed.') + elseif info.logged_in then + cur_func = loadstring("current_string = "..settings.strings.default) + setfenv(cur_func,_G) + end + +end diff --git a/Data/DefaultContent/Libraries/addons/addons/porter/README.md b/Data/DefaultContent/Libraries/addons/addons/porter/README.md new file mode 100644 index 0000000..4efb5ce --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/porter/README.md @@ -0,0 +1,39 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20130529 + +# Porter # +This addon shows the slips' items highlighting those that are stored. + +## Commands ## +### porter ### +Shows the specified slip or slip's page. if "owned" is specified, only the owned items will be shown. if no parameter is specified, all the owned slips will be shown. + +``` +porter [<slip> [<page>]] [owned] +``` +* **_slip_:** the number of the slip you want to show. +* **_page_:** the page of the slip you want to show. +* **owned:** shows only the items you own. +``` +porter find +``` +Shows storable items found in all inventory bags. +---- + +##changelog## +### v1.20200419 +* **add**: New command, porter find. +* **change**: Adjusted resource handling. + +### v1.20130529 +* **fix**: Fixed parameters validation. +* **change**: Aligned to Windower's addon development guidelines. + +### v1.20130525.1 +* **add**: Added the "owned" param. if present, only the owned items will be shown. + +### v1.20130525 +* **change**: If no parameter is specified all the owned slips will be shown. + +### v1.20130524 +* First release.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/porter/porter.lua b/Data/DefaultContent/Libraries/addons/addons/porter/porter.lua new file mode 100644 index 0000000..74e12e9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/porter/porter.lua @@ -0,0 +1,159 @@ +--[[ +porter v1.20130529 + +Copyright (c) 2013, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of porter nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require 'chat' +require 'logger' +require 'sets' +require 'strings' +res = require 'resources' +slips = require 'slips' + +_addon.name = 'porter' +_addon.version = '1.20210302' +_addon.command = 'porter' +_addon.author = 'Zohno' + +function show_slip(slip_number, slip_page, owned_only) + + owned_only = owned_only or false + + local player_items = slips.get_player_items() + + if slip_number ~= nil then + if slip_number < 1 or slip_number > slips.storages:length() then + error('That slip doesn\'t exist, kupo!') + + return + end + + slips_storage = L{slips.get_slip_id(slip_number)} + else + slips_storage = slips.storages + end + + for _, slip_id in ipairs(slips_storage) do + local slip = slips.get_slip_by_id(slip_id) + local player_slip_items = S(player_items[slip_id]) + local printable_slip_number = tostring(slips.get_slip_number_by_id(slip_id)):lpad('0', 2) + + if slip_number ~= nil + or slip_number == nil and player_slip_items:length() > 0 + then + local slip_items + + if slip_number == nil or slip_page == nil then + slip_items = slip + else + local offset = (slip_page - 1) * 16 + 1 + + if offset < 1 or offset > slip:length() then + error('Slip '..tostring(slip_number):lpad('0', 2)..' has no page '..slip_page..', kupo.') + + return + end + + slip_items = slip:slice(offset, offset + 15) + end + + for item_position, item_id in ipairs(slip_items) do + if item_id ~= 0 then + local is_contained = player_slip_items:contains(item_id) + + if owned_only == false or owned_only == true and is_contained == true then + windower.add_to_chat( + 55, + ('slip '..printable_slip_number..'/page '..tostring(slip_page and slip_page or math.ceil(item_position / 16)):lpad('0', 2)..':'):color(259)..' '.. + res.items[item_id].name:color(is_contained and 258 or 261) + ) + end + end + end + end + end +end + +function show_bags() + + local n = 0 + + for _, bag in ipairs(slips.default_storages) do + for _, item in ipairs(windower.ffxi.get_items(bag)) do + local slip_id = slips.get_slip_id_by_item_id(item.id) + + if slip_id and item.id ~= 0 then + n = n + 1 + windower.add_to_chat(207, 'slip %02d: %s %s':format(slips.get_slip_number_by_id(slip_id), bag, res.items[item.id].name:color(258))) + end + end + end + + windower.add_to_chat(207, 'Found %s storable items in all bags':format(n)) +end + +windower.register_event('addon command',function (slip_number, slip_page, owned_only) + if tonumber(slip_number) == nil then + slip_page = nil + + if slip_number == 'owned' then + slip_number = nil + owned_only = true + elseif slip_number == 'find' then + show_bags() + return + elseif slip_number ~= nil then + error('That\'s not a valid slip number, kupo!') + + return + end + else + slip_number = tonumber(slip_number, 10) + + if tonumber(slip_page) == nil then + if slip_page == 'owned' then + slip_page = nil + owned_only = true + elseif slip_page ~= nil then + error('That\'s not a valid page number, kupo!') + + return + end + else + slip_page = tonumber(slip_page, 10) + + if owned_only == 'owned' then + owned_only = true + else + owned_only = nil + end + end + end + + show_slip(slip_number, slip_page, owned_only) +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/position_manager/README.md b/Data/DefaultContent/Libraries/addons/addons/position_manager/README.md new file mode 100644 index 0000000..d695b64 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/position_manager/README.md @@ -0,0 +1,42 @@ +# Position Manager + +Set and save screen position per-character. Requires the WinControl addon. + +Commands: +`//pm set <pos_x> <pos_y> [name]` +`//pm size <width> <height> [name]` +`//pm size reset [name]` + +`pos_x`, `pos_y`, `width` and `height` are obligatory and must be numbers. +`size reset` will restore the original windows size for that character (as specified in the Windower profile). +`name` is optional. If no name is provided, settings will be saved for the current character. +`:all` is a special name that can be used to set the default position. + +**Note**: Characters are only moved or resized after they're logged in. The `:all` position will be used for the character login screen as well. + +**Note**: On some systems with very fast or very slow disks, it can happen that the `WinControl` addon does not get loaded in time for `position_manager` to send the proper command. In that case, you can use this command: +`//pm delay <seconds> [name]` +(where `seconds` is obligatory and must be a positive number, and `name` follows the same rules as before), to set a delay that will hopefully let the plugin load in time. + +### Examples: +`//pm set 0 0` +Will set your _current_ character to the position X: 0, Y: 0. + +`//pm set 0 60 :all` +Will set the default positioning for all characters to X: 0 and Y: 60 (the height of the Windows 10 taskbar with 150% UI scaling.), and delete all other character-specific settings. + +`//pm set 1920 0 Yourname` +Will set the default position for the character called "Yourname" to X: 1920 and Y: 0. +This will make the character appear on the secondary screen that is to the right of the main screen - useful for multi-screen setups. + +`//pm set delay 1 all` +`//pm set 0 40 Yourman` +`//pm set 800 40 Youralt` +Will set delay to 1 for all characters, then set the position of your main to X: 0, Y: 40, and your alt to X: 800, Y: 40. +If your laptop screen is 1600px wide, and your instances are both set at 800x600, this will put them side by side. + +**Warning:** the `all` name will delete every other character-specific settings that are already saved! It's best to use it only once after you install the addon, to set default position and delay for non-specified characters. + +IPC is supported. Setting a character's position or size from another character's window, will cause the affected character to update automatically. + +Enjoy. diff --git a/Data/DefaultContent/Libraries/addons/addons/position_manager/position_manager.lua b/Data/DefaultContent/Libraries/addons/addons/position_manager/position_manager.lua new file mode 100644 index 0000000..4df4123 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/position_manager/position_manager.lua @@ -0,0 +1,194 @@ +--Copyright © 2020, Lili +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of position_manager nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL Lili BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'position_manager' +_addon.author = 'Lili' +_addon.version = '2.0.1' +_addon.command = 'pm' + +if not windower.file_exists(windower.windower_path .. '\\plugins\\WinControl.dll') then + print('position_manager: error - Please install the WinControl plugin in the launcher.') + windower.send_command('lua u position_manager') + return +else + print('position_manager: loading WinControl...') + windower.send_command('load wincontrol') +end + +local config = require('config') + +local default = { + x = 0, + y = 0, + width = -1, + height = -1, + delay = 0, +} + +local settings = config.load(default) + +function get_name(name) + if name ~= nil and type(name) ~= 'string' then + err('invalid name provided') + return false + elseif not name then + return windower.ffxi.get_player().name + elseif name == ':all' then + return 'all' + end + return name +end + +function err(reason) + windower.add_to_chat(207, 'position_manager: ERROR - %s.':format(err)) + show_help() + return +end + +function show_help() + windower.add_to_chat(207, 'position_manager: Commands:') + windower.add_to_chat(207, ' //pm set <x> <y> [name]') + windower.add_to_chat(207, ' //pm size <width> <height> [name]') + windower.add_to_chat(207, ' //pm delay <seconds> [name]') + windower.add_to_chat(207, 'position_manager: See the readme for details.') +end + +function move(settings) + if settings.delay > 0 then + coroutine.sleep(settings.delay) + end + + windower.send_command('wincontrol move %s %s':format(settings.x, settings.y)) + --print('::wincontrol move %s %s':format(settings.x, settings.y)) +end + +function resize(settings) + if settings.width == -1 and settings.height == -1 then + windower.send_command('wincontrol resize reset') + return + end + + if settings.delay > 0 then + coroutine.sleep(settings.delay) + end + + local width = settings.width == -1 and windower.get_windower_settings().ui_x_res or settings.width + local height = settings.height == -1 and windower.get_windower_settings().ui_y_res or settings.height + + windower.send_command('wincontrol resize %s %s':format(width, height)) + --print('::wincontrol resize %s %s':format(width, height)) +end + +function handle_commands(cmd, ...) + cmd = cmd and cmd:lower() + + if cmd == 'r' then + windower.send_command('lua r position_manager') + return + elseif cmd == 'set' or cmd == 'size' then + local arg = {...} + local name = get_name(arg[3]) + + if not name then + return + end + + arg[1] = arg[1] == 'default' and -1 or tonumber(arg[1]) + arg[2] = arg[2] == 'default' and -1 or tonumber(arg[2]) + + if arg[1] and arg[2] then + if cmd == 'set' then + settings.x = arg[1] + settings.y = arg[2] + + if settings.x and settings.y then + config.save(settings, name) + windower.add_to_chat(207, 'position_manager: Position set to %s, %s for %s.':format(settings.x, settings.y, name)) + else + err('invalid position provided.') + return false + end + elseif cmd == 'size' then + settings.width = arg[1] + settings.height = arg[2] + + if settings.width and settings.height then + config.save(settings, name) + windower.add_to_chat(207, 'position_manager: Window size set to %s, %s for %s.':format(settings.width, settings.height, name)) + else + err('invalid window size provided.') + return false + end + end + else + err('invalid arguments provided.') + return false + end + + if player_name and name:lower() == player_name:lower() then + if cmd == 'set' then + move(settings) + elseif cmd == 'size' then + resize(settings) + end + end + + windower.send_ipc_message(name) + return true + + elseif cmd == 'delay' then + settings.delay = tonumber(arg[2]) + + if settings.delay > 0 then + config.save(settings, name) + windower.add_to_chat(207, 'position_manager: Delay set to %s for %s.':format(settings.delay, name)) + else + err('invalid delay provided') + return false + end + return true + + elseif cmd ~= 'help' then + windower.add_to_chat(207, 'position_manager: %s command not found.':format(cmd)) + end + show_help() +end + +config.register(settings, move) +config.register(settings, resize) + +windower.register_event('addon command', handle_commands) + +windower.register_event('load','login','logout', function(name) + local player = windower.ffxi.get_player() + player_name = player and player.name +end) + +windower.register_event('ipc message', function(msg) + if msg == player_name or msg == 'all' then + config.reload(settings) + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/reive/README.md b/Data/DefaultContent/Libraries/addons/addons/reive/README.md new file mode 100644 index 0000000..af4ea8a --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/reive/README.md @@ -0,0 +1,135 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20131021 + +# Reive # +This addon tracks exp, bayld, momentum scores and bonuses during a reive. + +## Commands ## +### help ### +Shows the help text. +``` +reive help +``` + +### test ### +Fills the chat log with some messages to show how the plugin will work. +``` +reive test +``` + +### reset ### +Sets gained exp and bayld to 0. +``` +reive reset +``` + +### full-reset ### +Sets gained exp/total exp and bayld/total bayld to 0. +``` +reive full-reset +``` + +### show ### +Shows the tracking window. +``` +reive show +``` + +### hide ### +Hides the tracking window. +``` +reive hide +``` + +### toggle ### +Toggles the tracking window's visibility. +``` +reive toggle +``` + +### light ### +Enables or disabled light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the _enabled_ parameter is not specified, the help text will be shown. +``` +reive light <enabled> +``` +* **enabled:** specifies the status of the light mode. **default**, **false** or **0** mean disabled. **true** or **1** mean enabled. + +### max-scores ### +Sets the max amount of scores to show in the window. If the _amount_ parameter is not specified, the help text will be shown. +``` +reive max-scores <amount> +``` +* **amount:** specifies the max amount of status scores that will be show. By default this value is 5. setting this value to 0 will hide the scores section. + +### track ### +Specifies the visibility of a bonus in the window. +``` +reive track <bonus> <visible> +``` +* **bonus:** specifies the item which will have its visibility changed. The accepted values are **abilities-recovery**, **hp-mp-boost**, **hp-recovery**, **mp-recovery**, **status-recovery**, **stoneskin**, **tp-recovery**. +* **visible:** specifies the visibility of the bonus. **false** or **0** mean disabled. **default**, **true** or **1** mean enabled. + +### position ### +Sets the horizontal and vertical position of the window relative to the upper-left corner. If no parameter is specified, the help text will be shown. +``` +reive position [[-h]|[-x <x>] [-y <y>]] +``` +* **-h:** shows the help text. +* **-x _x_:** specifies the horizontal position of the window. +* **-y _y_:** specifies the vertical position of the window. + +### font ### +Sets the style of the font used in the window. If no parameter is specified, the help text will be shown. +``` +reive font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]] +``` +* **-h:** shows the help text. +* **-f _font_:** specifies the text's font. +* **-s _size_:** specifies the text's size. +* **-a _alpha_:** specifies the text's transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive. +* **-b [ _bold_ ]:** specifies if the text should be rendered bold. **default**, **false** or **0** mean disabled. **true**, **1** or no value mean enabled. +* **-i [ _italic_ ]:** specifies if the text should be rendered italic. **default**, **false** or **0** mean disabled. **true**, **1** or no value mean enabled. + +### color ### +Sets the colors of the various elements present in the addon's window. If no parameter is specified, the help text will be shown. +``` +reive color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]] +``` +* **-h:** shows the help text. +* **-o _objects_:** specifies the item/s which will have its/their color changed. If this parameter is missing all the objects will be changed. the accepted values are **all**, **background**, **bg**, **title**, **label**, **value**, **reive**, **reive.title**, **reive.label**, **reive.value**, **score**, **score.title**, **score.label**, **bonus**, **bonus.title**, **bonus.label**, **bonus.value**. +* **-d:** sets the red, green, blue and alpha values of the specified objects to their default values. +* **-r _red_:** specifies the intensity of the red color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-g _green_:** specifies the intensity of the greencolor. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-b _blue_:** specifies the intensity of the blue color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense. +* **-a _alpha_:** specifies the text's transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive. + +---- + +## Changelog ## + +### v1.20130802 ### +* **fix**: 4.1 compatibility fix. + +### v1.20130603 ### +* **fix**: Fixed an issue that prevented the addon to start tracking info. + +### v1.20130529 ### +* **change**: Aligned to Windower's addon development guidelines. + +### v1.20130525 ### +* **fix:** Fixed a bug that prevented the addon from tracking correctly the total gained exp. + +### v1.20130516 ### +* **add:** A "light mode" has been added. While active, the window will be kept hidden and only a summary will be shown at the end of the run. + +### v1.20130514 ### +* **change:** Current/total bayld/exp will be shown at the same time. "reset_on_start" has no use anymore and has been removed. + +### v1.20130419 ### +* **add:** The user can change the settings from the console or the text box. + +### v1.20130417 ### +* **add:** The user can decide which bonuses will appear in the window. + +### v1.20130416 ### +* First release. diff --git a/Data/DefaultContent/Libraries/addons/addons/reive/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/reive/data/settings.xml new file mode 100644 index 0000000..92341cd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/reive/data/settings.xml @@ -0,0 +1,84 @@ +<?xml version="1.1" ?> +<settings> + <global> + <colors> + <background> + <a>200</a> + <b>54</b> + <g>43</g> + <r>0</r> + </background> + <bonus> + <label> + <b>0</b> + <g>153</g> + <r>133</r> + </label> + <title> + <b>47</b> + <g>50</g> + <r>220</r> + </title> + <value> + <b>161</b> + <g>161</g> + <r>147</r> + </value> + </bonus> + <reive> + <label> + <b>210</b> + <g>139</g> + <r>38</r> + </label> + <title> + <b>47</b> + <g>50</g> + <r>220</r> + </title> + <value> + <b>161</b> + <g>161</g> + <r>147</r> + </value> + </reive> + <score> + <label> + <b>152</b> + <g>161</g> + <r>42</r> + </label> + <title> + <b>47</b> + <g>50</g> + <r>220</r> + </title> + </score> + </colors> + <first_run>true</first_run> + <font> + <a>255</a> + <bold>false</bold> + <family>Arial</family> + <italic>false</italic> + <size>10</size> + </font> + <light>false</light> + <max_scores>5</max_scores> + <position> + <x>0</x> + <y>350</y> + </position> + <track> + <abilities_recovery>true</abilities_recovery> + <hp_mp_boost>true</hp_mp_boost> + <hp_recovery>true</hp_recovery> + <mp_recovery>true</mp_recovery> + <status_recovery>true</status_recovery> + <stoneskin>true</stoneskin> + <tp_recovery>true</tp_recovery> + </track> + <v>0</v> + </global> +</settings> + diff --git a/Data/DefaultContent/Libraries/addons/addons/reive/reive.lua b/Data/DefaultContent/Libraries/addons/addons/reive/reive.lua new file mode 100644 index 0000000..854ef29 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/reive/reive.lua @@ -0,0 +1,892 @@ +--[[ +reive v1.20131021 + +Copyright (c) 2013, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of reive nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require 'chat' +require 'logger' +require 'strings' + +local config = require 'config' + + +_addon.name = 'reive' +_addon.author = 'Zohno' +_addon.version = '1.20131221' +_addon.command = 'reive' + +tb_name = 'addon:gr:reive' +track = false +visible = false + +stats = T{} +stats.exp = 0 +stats.bayld = 0 +stats.tot_exp = 0 +stats.tot_bayld = 0 +stats.scores = T{} +stats.bonuses = T{} + +bonuses_map = { + ['HP recovery'] = 'hp_recovery', + ['MP recovery'] = 'mp_recovery', + ['TP recovery'] = 'tp_recovery', + ['Status ailment recovery'] = 'status_recovery', + ['Stoneskin'] = 'stoneskin', + ['Ability cast recovery'] = 'abilities_recovery', + ['Increased maximum MP and HP'] = 'hp_mp_boost' +} + +defaults = T{} +defaults.reset_on_start = false -- deprecated +defaults.max_scores = 5 +defaults.light = false + +defaults.track = T{} +defaults.track.hp_recovery = true +defaults.track.mp_recovery = true +defaults.track.tp_recovery = true +defaults.track.status_recovery = true +defaults.track.stoneskin = true +defaults.track.abilities_recovery = true +defaults.track.hp_mp_boost = true + +defaults.position = T{} +defaults.position.x = 0 +defaults.position.y = 350 + +defaults.font = T{} +defaults.font.family = 'Arial' +defaults.font.size = 10 +defaults.font.a = 255 +defaults.font.bold = false +defaults.font.italic = false + +defaults.colors = T{} +defaults.colors.background = T{} +defaults.colors.background.r = 0 +defaults.colors.background.g = 43 +defaults.colors.background.b = 54 +defaults.colors.background.a = 200 + +defaults.colors.reive = T{} +defaults.colors.reive.title = T{} +defaults.colors.reive.title.r = 220 +defaults.colors.reive.title.g = 50 +defaults.colors.reive.title.b = 47 + +defaults.colors.reive.label = T{} +defaults.colors.reive.label.r = 38 +defaults.colors.reive.label.g = 139 +defaults.colors.reive.label.b = 210 + +defaults.colors.reive.value = T{} +defaults.colors.reive.value.r = 147 +defaults.colors.reive.value.g = 161 +defaults.colors.reive.value.b = 161 + +defaults.colors.score = T{} +defaults.colors.score.title = T{} +defaults.colors.score.title.r = 220 +defaults.colors.score.title.g = 50 +defaults.colors.score.title.b = 47 + +defaults.colors.score.label = T{} +defaults.colors.score.label.r = 42 +defaults.colors.score.label.g = 161 +defaults.colors.score.label.b = 152 + +defaults.colors.bonus = T{} +defaults.colors.bonus.title = T{} +defaults.colors.bonus.title.r = 220 +defaults.colors.bonus.title.g = 50 +defaults.colors.bonus.title.b = 47 + +defaults.colors.bonus.label = T{} +defaults.colors.bonus.label.r = 133 +defaults.colors.bonus.label.g = 153 +defaults.colors.bonus.label.b = 0 + +defaults.colors.bonus.value = T{} +defaults.colors.bonus.value.r = 147 +defaults.colors.bonus.value.g = 161 +defaults.colors.bonus.value.b = 161 + +settings = config.load(defaults) + +-- plugin functions + +function parse_options(args) + local options = T{} + + while #args > 0 do + if not args[1]:match('^-%a') then + break + end + + local option = args:remove(1):sub(2) + + if type(args[1]) ~= 'nil' and not args[1]:match('^-%a') then + options[option] = args:remove(1) + else + options[option] = true + end + end + + return options +end + +function test() + start_tracking() + windower.add_to_chat(121, 'Reive momentum score: HP recovery.') + windower.add_to_chat(121, 'Momentum bonus: Ability cast recovery!') + windower.add_to_chat(121, 'Reive momentum score: Damage taken.') + windower.add_to_chat(121, 'Momentum bonus: Status ailment recovery!') + windower.add_to_chat(121, 'Reive momentum score: Physical attack.') + windower.add_to_chat(121, 'Momentum bonus: Stoneskin!') + windower.add_to_chat(121, 'Reive momentum score: Attack success.') + windower.add_to_chat(121, 'Momentum bonus: HP recovery!') + windower.add_to_chat(121, 'Reive momentum score: HP recovery.') + windower.add_to_chat(121, 'Momentum bonus: TP recovery!') + windower.add_to_chat(121, 'Reive momentum score: Damage taken.') + windower.add_to_chat(121, 'Momentum bonus: Increased maximum HP and MP!') + windower.add_to_chat(121, 'Reive momentum score: Physical attack.') + windower.add_to_chat(131, 'Player gains 408 limit points.') + windower.add_to_chat(121, 'Player obtained 291 bayld!') + windower.add_to_chat(121, 'Player obtained 329 bayld!') + windower.add_to_chat(121, 'Player obtained 405 bayld!') + windower.add_to_chat(131, 'Player gains 426 limit points.') + stop_tracking() + show_window() +end + +function start_tracking() + reset_stats() + log('The Reive has begun!') + + track = true + + if settings.light == false then + show_window() + end +end + +function stop_tracking() + stats.scores = T{} + stats.bonuses = T{} + track = false + + log('The Reive has ended.') + hide_window() + show_report() +end + +function refresh() + if visible == false then + return + end + + local reive_colors = settings.colors.reive + local text = + ' \\cs('..reive_colors.title.r..', '..reive_colors.title.g..', '..reive_colors.title.b..')--== REIVE ==--\\cr \n'.. + ' \\cs('..reive_colors.label.r..', '..reive_colors.label.g..', '..reive_colors.label.b..')Bayld:\\cr'.. + ' \\cs('..reive_colors.value.r..', '..reive_colors.value.g..', '..reive_colors.value.b..')'..stats.bayld..'/'..stats.tot_bayld..'\\cr \n'.. + ' \\cs('..reive_colors.label.r..', '..reive_colors.label.g..', '..reive_colors.label.b..')EXP:\\cr'.. + ' \\cs('..reive_colors.value.r..', '..reive_colors.value.g..', '..reive_colors.value.b..')'..stats.exp..'/'..stats.tot_exp..'\\cr ' + + local scores_colors = settings.colors.score + local scores = ''; + + if #stats.scores > 0 then + local base = math.max(0, #stats.scores - settings.max_scores) + + for index, score in pairs(stats.scores:slice(base + 1, #stats.scores)) do + scores = scores.. + '\n \\cs('..scores_colors.label.r..', '..scores_colors.label.g..', '..scores_colors.label.b..')'..(base + index)..'. '..score..'\\cr ' + end + + text = text..'\n \\cs('..scores_colors.title.r..', '..scores_colors.title.g..', '..scores_colors.title.b..')--== MOMENTUM SCORES ==--\\cr '..scores + end + + local bonuses_colors = settings.colors.bonus + local bonuses = ''; + + for index, bonus in ipairs(stats.bonuses:keyset():sort()) do + if type(bonuses_map[bonus]) == 'nil' or settings.track[bonuses_map[bonus]] == true then + local amount = stats.bonuses[bonus] + + bonuses = bonuses.. + '\n \\cs('..bonuses_colors.label.r..', '..bonuses_colors.label.g..', '..bonuses_colors.label.b..')'..bonus..':\\cr'.. + ' \\cs('..bonuses_colors.value.r..', '..bonuses_colors.value.g..', '..bonuses_colors.value.b..')'..amount..'\\cr ' + end + end + + if #bonuses > 0 then + text = text..'\n \\cs('..bonuses_colors.title.r..', '..bonuses_colors.title.g..', '..bonuses_colors.title.b..')--== MOMENTUM BONUSES ==--\\cr '..bonuses + end + + windower.text.set_text(tb_name, text) +end + +function reset_stats() + stats.exp = 0 + stats.bayld = 0 + refresh() +end + +function full_reset_stats() + stats.tot_exp = 0 + stats.tot_bayld = 0 + reset_stats() + refresh() +end + +function show_window() + visible = true + windower.text.set_visibility(tb_name, true) + refresh() +end + +function hide_window() + visible = false + windower.text.set_visibility(tb_name, false) +end + +function toggle_window() + if visible then + hide_window() + else + show_window() + end +end + +function show_report() + log('[EXP '..(stats.exp..'/'..stats.tot_exp):color(258)..'] [Bayld '..(stats.bayld..'/'..stats.tot_bayld):color(258)..']') +end + +-- windower events + +windower.register_event('load', function() + local background = settings.colors.background + + windower.text.create(tb_name) + windower.text.set_location(tb_name, settings.position.x, settings.position.y) + windower.text.set_bg_color(tb_name, background.a, background.r, background.g, background.b) + windower.text.set_color(tb_name, settings.font.a, 147, 161, 161) + windower.text.set_font(tb_name, settings.font.family) + windower.text.set_font_size(tb_name, settings.font.size) + windower.text.set_bold(tb_name, settings.font.bold) + windower.text.set_italic(tb_name, settings.font.italic) + windower.text.set_text(tb_name, '') + windower.text.set_bg_visibility(tb_name, true) + + local player = windower.ffxi.get_player() + if player and T(player['buffs']):contains(511) then + start_tracking() + end +end) + +windower.register_event('unload', function() + windower.text.delete(tb_name) +end) + +windower.register_event('gain buff', start_tracking:cond(function(id) return id == 511 end)) +windower.register_event('lose buff', stop_tracking:cond(function(id) return id == 511 end)) + +windower.register_event('incoming text', function(original, modified, mode) + local match + + if mode == 121 then + match = original:match('Reive momentum score: ([%s%w]+)%.') + + if match then + stats.scores:append(match) + refresh() + + return modified, mode + end + + match = original:match('Momentum bonus: ([%s%w]+)!') + + if match then + if type(stats.bonuses[match]) == 'nil' then + stats.bonuses[match] = 0 + end + + stats.bonuses[match] = stats.bonuses[match] + 1 + refresh() + + return modified, mode + end + + match = original:match('obtained (%d+) bayld!') + + if match and track then + stats.bayld = stats.bayld + match + stats.tot_bayld = stats.tot_bayld + match + refresh() + end + elseif mode == 131 and track then + match = original:match('gains (%d+) limit points%.') + + if match then + stats.exp = stats.exp + match + stats.tot_exp = stats.tot_exp + match + refresh() + + return modified, mode + end + + match = original:match('gains (%d+) experience points%.') + + + if match then + stats.exp = stats.exp + match + stats.tot_exp = stats.tot_exp + match + refresh() + + return modified, mode + end + end + + return modified, mode +end) + +windower.register_event('addon command', function(...) + local args = T({...}) + + if args[1] == nil then + windower.send_command('reive help') + return + end + + local cmd = args:remove(1):lower() + + if cmd == 'help' then + log(chat.chars.wsquare..' reive help -- shows the help text.') + log(chat.chars.wsquare..' reive test -- fills the chat log with some messages to show how the plugin will work.') + log(chat.chars.wsquare..' reive reset -- sets gained exp and bayld to 0.') + log(chat.chars.wsquare..' reive full-reset -- sets both current and total gained exp and bayld to 0.') + log(chat.chars.wsquare..' reive show -- shows the tracking window.') + log(chat.chars.wsquare..' reive hide -- hides the tracking window.') + log(chat.chars.wsquare..' reive toggle -- toggles the tracking window\'s visibility.') + log(chat.chars.wsquare..' reive light [<enabled>] -- enables or disabled light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. If the enabled parameter is not specified, the help text will be shown.') + log(chat.chars.wsquare..' reive max-scores <amount> -- sets the max amount of scores to show in the window. if the amount parameter is not specified, the help text will be shown.') + log(chat.chars.wsquare..' reive track <score> <visible> -- specifies the visibility of a bonus in the window.') + log(chat.chars.wsquare..' reive position [[-h]|[-x <x>] [-y <y>]] -- sets the horizontal and vertical position of the window relative to the upper-left corner. If the no parameter is specified, the help text will be shown.') + log(chat.chars.wsquare..' reive font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]] -- sets the style of the font used in the window. if the no parameter is specified, the help text will be shown.') + log(chat.chars.wsquare..' reive color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]] -- sets the colors of the various elements present in the addon\'s window. If the no parameter is specified, the help text will be shown.') + elseif cmd == 'test' then + test() + elseif cmd == 'reset' then + reset_stats() + elseif cmd == 'full-reset' then + full_reset_stats() + elseif cmd == 'show' then + show_window() + elseif cmd == 'hide' then + hide_window() + elseif cmd == 'toggle' then + toggle_window() + elseif cmd == 'light' then + if type(args[1]) == 'nil' then + log('Enables or disabled light mode. When enabled, the addon will never show the window and just print a summary in the chat box at the end of the run. Ff the enabled parameter is not specified, the help text will be shown.') + log('Usage: reive light <enabled>') + log('Positional arguments:') + log(chat.chars.wsquare..' <enabled> specifies the status of the light mode. "default", "false" or "0" mean disabled. "true" or "1" mean enabled.') + else + local light + + if args[1] == 'default' then + light = defaults.light + elseif args[1] == 'true' or args[1] == '1' then + light = true + elseif args[1] == 'false' or args[1] == '0' then + light = false + end + + if light == true then + hide_window() + elseif track == true then + show_window() + end + + if type(light) ~= "boolean" then + error('Please specify a valid status') + + return + end + + settings.light = light + + refresh() + settings:save('all') + log('The light mode has been set.') + end + elseif cmd == 'max-scores' then + local max_scores + + if type(args[1]) == 'nil' then + log('Sets the max amount of scores to show in the window. If the amount parameter is not specified, the help text will be shown.') + log('Usage: reive max-scores <amount>') + log('Positional arguments:') + log(chat.chars.wsquare..' <amount> specifies the max amount of status scores that will be show. By default this value is 5. Setting this value to 0 will hide the scores section.') + elseif args[1] == 'default' then + max_scores = defaults.max_scores + else + max_scores = tonumber(args[1]) + end + + if type(max_scores) ~= "number" then + error('Please specify a valid amount of scores.') + end + + if errors:length() == 0 then + settings.max_scores = max_scores + + refresh() + settings:save('all') + notice('The max amount of scores has been set.') + end + elseif cmd == 'track' then + local object + local visible + local validObjects = T{ + 'abilities-recovery', + 'hp-mp-boost', + 'hp-recovery', + 'mp-recovery', + 'status-recovery', + 'stoneskin', + 'tp-recovery' + } + + if type(args[1]) == 'nil' then + log('Specifies the visibility of a bonus in the window.') + log('Usage: reive track <bonus> <visible>') + log('Positional arguments:') + log(chat.chars.wsquare..' <bonus> specifies the item which will have its visibility changed. The accepted values are : '..validObjects:concat(', ')) + log(chat.chars.wsquare..' <visible> specifies the visibility of the bonus. "false" or "0" mean disabled. "default", "true" or "1" mean enabled.') + elseif validObjects:contains(args[1]) then + object = args[1]:gsub('-', '_') + + if args[2] == 'true' or args[2] == '1' or args[2] == 'default' then + visible = true + elseif args[2] == 'false' or args[2] == '0' then + visible = false + else + error('Please specify a valid visible status.') + + return + end + + settings.track[object] = visible + + refresh() + settings:save('all') + notice('The bonus\' visibility has been set.') + else + error('Please specify a valid bonus.') + + return + end + else + local options = parse_options(args) + + if cmd == 'position' then + if options:containskey('h') or options:length() == 0 then + log('Sets the horizontal and vertical position of the window relative to the upper-left corner. If no parameter is specified, the help text will be shown.') + log('Usage: reive position [[-h]|[-x <x>] [-y <y>]]') + log('Optional arguments:') + log(chat.chars.wsquare..' -h shows the help text.') + log(chat.chars.wsquare..' -x <x> specifies the horizontal position of the window.') + log(chat.chars.wsquare..' -y <y> specifies the vertical position of the window.') + elseif options:length() > 0 then + local x = settings.position.x + local y = settings.position.y + + for key, value in pairs(options) do + if key == 'x' then + if options['x'] == 'default' then + x = defaults.position.x + else + x = tonumber(options['x']) + + if type(x) ~= "number" then + error('Please specify a valid horizontal position.') + + return + end + end + elseif key == 'y' then + if options['y'] == 'default' then + y = defaults.position.y + else + y = tonumber(options['y']) + + if type(y) ~= "number" then + error('Please specify a valid vertical position.') + + return + end + end + + else + error('"'..key..'" is not a recognized parameter') + + return + end + end + + settings.position.x = x + settings.position.y = y + + windower.text.set_location(tb_name, x, y) + settings:save('all') + notice('The window\'s position has been set.') + end + elseif cmd == 'font' then + if options:containskey('h') or options:length() == 0 then + log('Sets the style of the font used in the window. if the no parameter is specified, the help text will be shown.') + log('Usage: reive font [[-h]|[-f <font>] [-s <size>] [-a <alpha>] [-b [<bold>]] [-i [<italic>]]]') + log('Optional arguments:') + log(chat.chars.wsquare..' -h shows the help text.') + log(chat.chars.wsquare..' -f <font> specifies the text\'s font.') + log(chat.chars.wsquare..' -s <size> specifies the text\'s size.') + log(chat.chars.wsquare..' -a <alpha> specifies the text\'s transparency. the value must be set between 0 (transparent) and 255 (opaque), inclusive.') + log(chat.chars.wsquare..' -b [<bold>] specifies if the text should be rendered bold. "default", "false" or "0" mean disabled. "true", "1" or no value mean enabled.') + log(chat.chars.wsquare..' -i [<italic>] specifies if the text should be rendered italic. "default", "false" or "0" mean disabled. "true", "1" or no value mean enabled.') + elseif options:length() > 0 then + local family = settings.font.family + local size = settings.font.size + local bold = settings.font.bold + local italic = settings.font.italic + local a = settings.font.a + + for key, value in pairs(options) do + if key == 'f' then + if options['f'] == 'default' then + family = defaults.font.family + else + family = options['f'] + end + elseif key == 's' then + if options['s'] == 'default' then + size = defaults.position.size + else + size = tonumber(options['s']) + + if type(size) ~= "number" then + error('Please specify a valid font size.') + + return + end + end + elseif key == 'b' then + if options['b'] == 'default' then + bold = defaults.position.bold + elseif options['b'] == true or options['b'] == '1' or options['b'] == 'true' or options['b'] == 'null' then + bold = true + elseif options['b'] == '0' or options['b'] == 'false' then + bold = false + else + error('Please specify a valid bold status.') + + return + end + elseif key == 'i' then + if options['i'] == 'default' then + italic = defaults.position.italic + elseif options['b'] == true or options['i'] == '1' or options['i'] == 'true' or options['i'] == 'null' then + italic = true + elseif options['i'] == '0' or options['i'] == 'false' then + italic = false + else + error('Please specify a valid italic status.') + + return + end + elseif key == 'a' then + if options['a'] == 'default' then + a = defaults.position.a + else + a = tonumber(options['a']) + + if type(a) ~= "number" then + error('Please specify a valid alpha value.') + + return + else + a = math.min(255, math.max(0, a)) + end + end + else + error('"'..key..'" is not a recognized parameter') + + return + end + end + + settings.font.family = family + settings.font.size = size + settings.font.bold = bold + settings.font.italic = italic + settings.font.a = a + + windower.text.set_color(tb_name, a, 147, 161, 161) + windower.text.set_font(tb_name, family, size) + windower.text.set_bold(tb_name, bold) + windower.text.set_italic(tb_name, italic) + settings:save('all') + log('The font\'s style has been set.') + end + elseif cmd == 'color' then + local validObjects = T{ + 'all', 'background', 'bg', 'title', 'label', 'value', + 'reive', 'reive.title', 'reive.label', 'reive.value', + 'score', 'score.title', 'score.label', + 'bonus', 'bonus.title', 'bonus.label', 'bonus.value' + } + + if options:containskey('h') or options:length() == 0 then + log('Sets the colors of the various elements present in the addon\'s window. If the no parameter is specified, the help text will be shown.') + log('Usage: reive color [[-h]|[-o <objects>] [-d] [-r <red>] [-g <green>] [-b <blue>] [-a <alpha>]]') + log('Optional arguments:') + log(chat.chars.wsquare..' -h shows the help text.') + log(chat.chars.wsquare..' -o <objects> specifies the item/s which will have its/their color changed. If this parameter is missing all the objects will be changed. The accepted values are: "'..validObjects:concat('", "')..'"') + log(chat.chars.wsquare..' -d sets the red, green, blue and alpha values of the specified objects to their default values.') + log(chat.chars.wsquare..' -r <red> specifies the intensity of the red color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(chat.chars.wsquare..' -g <green> specifies the intensity of the greencolor. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(chat.chars.wsquare..' -b <blue> specifies the intensity of the blue color. The value must be set between 0 and 255, inclusive, where 0 is less intense and 255 is most intense.') + log(chat.chars.wsquare..' -a <alpha> specifies the text\'s transparency. The value must be set between 0 (transparent) and 255 (opaque), inclusive.') + elseif options:length() > 0 then + local r = -1 + local g = -1 + local b = -1 + local a = -1 + local objects + + if options:containskey('o') then + if validObjects:contains(options['o']) then + if options['o'] == 'background' or options['o'] == 'bg' then + objects = T{'background'} + elseif options['o'] == 'title' then + objects = T{ + 'reive.title', + 'score.title', + 'bonus.title' + } + elseif options['o'] == 'label' then + objects = T{ + 'reive.label', + 'score.label', + 'bonus.label' + } + elseif options['o'] == 'value' then + objects = T{ + 'reive.value', + 'bonus.value' + } + elseif options['o'] == 'reive' then + objects = T{ + 'reive.title', + 'reive.label', + 'reive.value' + } + elseif options['o'] == 'score' then + objects = T{ + 'score.title', + 'score.label' + } + elseif options['o'] == 'bonus' then + objects = T{ + 'bonus.title', + 'bonus.label', + 'bonus.value' + } + elseif options['o'] == 'reive.title' then + objects = T{'reive.title'} + elseif options['o'] == 'reive.label' then + objects = T{'reive.label'} + elseif options['o'] == 'reive.value' then + objects = T{'reive.value'} + elseif options['o'] == 'score.title' then + objects = T{'score.title'} + elseif options['o'] == 'score.label' then + objects = T{'score.label'} + elseif options['o'] == 'bonus.title' then + objects = T{'bonus.title'} + elseif options['o'] == 'bonus.label' then + objects = T{'bonus.label'} + elseif options['o'] == 'bonus.value' then + objects = T{'bonus.value'} + end + else + error('Please specify a valid object or set of objects.') + end + else + objects = T{ + 'background', + 'reive.title', 'reive.label', 'reive.value', + 'score.title', 'score.label', + 'bonus.title', 'bonus.label', 'bonus.value' + } + end + + if not options:containskey('d') then + for key, value in pairs(options) do + if key == 'r' then + if options['r'] == 'default' then + r = -1 + else + r = tonumber(options['r']) + + if type(r) ~= "number" then + error('Please specify a valid red value.') + + return + else + r = math.min(255, math.max(0, r)) + end + end + elseif key == 'g' then + if options['g'] == 'default' then + g = -1 + else + g = tonumber(options['g']) + + if type(g) ~= "number" then + error('Please specify a valid green value.') + + return + else + g = math.min(255, math.max(0, g)) + end + end + elseif key == 'b' then + if options['b'] == 'default' then + b = -1 + else + b = tonumber(options['b']) + + if type(b) ~= "number" then + error('Please specify a valid blue value.') + + return + else + b = math.min(255, math.max(0, b)) + end + end + elseif key == 'a' then + if options['a'] == 'default' then + a = -1 + else + a = tonumber(options['a']) + + if type(a) ~= "number" then + error('Please specify a valid alpha value.') + + return + else + a = math.min(255, math.max(0, a)) + end + end + elseif key == 'o' then + else + error('"'..key..'" is not a recognized parameter.') + + return + end + end + end + + for key, object in pairs(objects) do + local indexes = T(object:split('.')) + + if indexes:length() == 2 then + if r == -1 then + settings.colors[indexes[1]][indexes[2]].r = defaults.colors[indexes[1]][indexes[2]].r + else + settings.colors[indexes[1]][indexes[2]].r = r + end + + if g == -1 then + settings.colors[indexes[1]][indexes[2]].g = defaults.colors[indexes[1]][indexes[2]].g + else + settings.colors[indexes[1]][indexes[2]].g = g + end + + if b == -1 then + settings.colors[indexes[1]][indexes[2]].b = defaults.colors[indexes[1]][indexes[2]].b + else + settings.colors[indexes[1]][indexes[2]].b = b + end + elseif indexes:length() == 1 then + if r == -1 then + settings.colors[indexes[1]].r = defaults.colors[indexes[1]].r + else + settings.colors[indexes[1]].r = r + end + + if g == -1 then + settings.colors[indexes[1]].g = defaults.colors[indexes[1]].g + else + settings.colors[indexes[1]].g = g + end + + if b == -1 then + settings.colors[indexes[1]].b = defaults.colors[indexes[1]].b + else + settings.colors[indexes[1]].b = b + end + + if a == -1 then + settings.colors[indexes[1]].a = defaults.colors[indexes[1]].a + else + settings.colors[indexes[1]].a = a + end + + windower.text.set_bg_color( + tb_name, + settings.colors[indexes[1]].a, + settings.colors[indexes[1]].r, + settings.colors[indexes[1]].g, + settings.colors[indexes[1]].b + ) + end + end + + refresh() + settings:save('all') + log('The objects\' color has been set.') + end + else + windower.send_command('reive help') + end + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/remember/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/remember/ReadMe.md new file mode 100644 index 0000000..b644b22 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/remember/ReadMe.md @@ -0,0 +1,3 @@ +# Remember + +This addon is designed to detect allies that have been forgotten by the server and send information request packets that will cause them to spawn.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/remember/remember.lua b/Data/DefaultContent/Libraries/addons/addons/remember/remember.lua new file mode 100644 index 0000000..4fa2492 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/remember/remember.lua @@ -0,0 +1,109 @@ +--Copyright (c) 2015~2016, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +-- Deals with refreshing player information and loading user settings -- + +_addon.name = 'Remember' +_addon.author = 'Byrth' +_addon.command = 'remember' +_addon.version = '1.0.4.0' + +packets = require('packets') + +valid_indices = {} +novel_indices = {} +indices_under_investigation = {} +last_novel = nil + +function distance(packet,player) + return math.sqrt((packet.X-player.x)^2+(packet.Y-player.y)^2+(packet.Z-player.z)^2) +end + +windower.register_event('zone change',function() + valid_indices = {} + novel_indices = {} + indices_under_investigation = {} + last_novel = nil +end) + +windower.register_event('incoming chunk',function(id,org,mod,inj,blk) + local seq_id = org:byte(4)+org:byte(3)*256 + if last_novel and seq_id ~= last_novel then + local player = windower.ffxi.get_mob_by_target('<me>') + for i,v in pairs(indices_under_investigation) do + local character = windower.ffxi.get_mob_by_index(i) + if v~=nil and character and character.id ~= 0 then + -- If the entry is now valid, move it to the valid entries table and reset it in the other tables + valid_indices[i] = distance({X=character.x,Y=character.y,Z=character.z},player) + indices_under_investigation[i] = nil + novel_indices[i] = nil + elseif v~=nil and os.clock()-v > 4.5 then + -- If you asked for an update more than 4.5 seconds ago and haven't gotten one yet, try again + indices_under_investigation[i] = nil + elseif v~= nil then + -- Asked for an update 4.5 or fewer seconds ago, so don't ask again. + novel_indices[i] = nil + end + end + + -- After clearing out the ones that finally have proper information and those that you're waiting on, you're left with the novel ones that you haven't attempted to contact anyone about yet + for i,v in pairs(novel_indices) do + local character = windower.ffxi.get_mob_by_index(i) + if v~= nil and (not character or character.id == 0) and ((seq_id<2 and (seq_id + 256*256)) or seq_id) >= v+2 then + -- We've waited a packet cycle since the first occurance of the index and real information hasn't shown up yet, so request another packet + --print('Sent out an information request for index',i) + windower.packets.inject_outgoing(0x016,string.char(0x016,4,0,0,i%256,math.floor(i/256),0,0)) + indices_under_investigation[i] = os.clock() + elseif v~=nil and character and character.id ~= 0 then + -- Novel index, successfully updated through normal methods. + valid_indices[i] = distance({X=character.x,Y=character.y,Z=character.z},player) + indices_under_investigation[i] = nil + novel_indices[i] = nil + end + end + + last_novel = nil + end + if not inj and id == 0x00D or id == 0x00E then + local packet = packets.parse('incoming',org) + local player = windower.ffxi.get_mob_by_target('<me>') + if valid_indices[packet['Index']] then + -- The monster previously existed and was still in range at its last position update + local dist = distance(packet,player) + if dist > 50 then + valid_indices[packet['Index']] = nil + else + valid_indices[packet['Index']] = dist + end + elseif distance(packet,player) < 50 then + -- The monster was not in range at its last position update or has never loaded before + novel_indices[packet['Index']] = novel_indices[packet['Index']] or seq_id + last_novel = seq_id + end + end + +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/respond/README.md b/Data/DefaultContent/Libraries/addons/addons/respond/README.md new file mode 100644 index 0000000..0f11958 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/respond/README.md @@ -0,0 +1,11 @@ +Author: Byrth + +Version: 1.2 + +Respond + +Abbreviation: N/A + +Commands: N/A + +Purpose: Simply plugin that allows text responses. Specifically, it aliases "input /tell <name>" to 'r' so you can use //r to respond to the last person who sent you a tell.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/respond/respond.lua b/Data/DefaultContent/Libraries/addons/addons/respond/respond.lua new file mode 100644 index 0000000..232fcb1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/respond/respond.lua @@ -0,0 +1,58 @@ +--Copyright (c) 2013, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +_addon.name = 'Respond' +_addon.version = '1.2' +_addon.commands = {'r','respond'} + +current_mode = '/tell' + +windower.register_event('addon command',function(...) + if current_r then + windower.send_command('input '..current_mode..' '..current_r..' '..table.concat({...},' ')) + end +end) + +windower.register_event('chat message',function (message, player, mode, isGM) + if mode==3 and (player~=current_r or current_mode ~= '/tell') then + current_r=player + current_mode = '/tell' + end +end) + +windower.register_event('incoming text',function (original, modified, color) + if original:sub(1,4) == '[PM]' then + a,b = string.find(original,'>>') + if a~=6 then + local name = original:sub(6,a-1) + if name~=current_r or current_mode ~= '/pm' then + current_r = name + current_mode = '/pm' + end + end + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/roe/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/roe/ReadMe.md new file mode 100644 index 0000000..a3e3dc2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/roe/ReadMe.md @@ -0,0 +1,26 @@ +**Author:** Cair<br> +**Version:** 1.0<br> +**Date:** Oct. 30, 2017<br> + +### ROE ### + +This addon lets you save your currently set objectives to profiles that can be loaded. It can also be configured to remove quests for you. By default, ROE will remove quests that are not in a profile only if their progress is zero. You can customize this to your liking. + + + +#### Commands: #### +1. help - Displays this help menu. +2. save <profile name> : saves the currently set ROE to the named profile +3. set <profile name> : attempts to set the ROE objectives in the profile + - Objectives may be canceled automatically based on settings. + - The default setting is to only cancel ROE that have 0 progress if space is needed +4. unset <profile name> : removes currently set objectives + - if a profile name is specified, every objective in that profile will be removed + - if a profile name is not specificed, all objectives will be removed (based on your settings) +5. settings <settings name> : toggles the specified setting + * settings: + * clear : removes objectives if space is needed (default true) + * clearprogress : remove objectives even if they have non-zero progress (default false) + * clearall : clears every objective before setting new ones (default false) +6. blacklist [add|remove] <id> : blacklists a quest from ever being removed + - I do not currently have a mapping of quest IDs to names diff --git a/Data/DefaultContent/Libraries/addons/addons/roe/roe.lua b/Data/DefaultContent/Libraries/addons/addons/roe/roe.lua new file mode 100644 index 0000000..49dd52d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/roe/roe.lua @@ -0,0 +1,302 @@ +-- Copyright © 2017, Cair +-- All rights reserved. + +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are met: + + -- * Redistributions of source code must retain the above copyright + -- notice, this list of conditions and the following disclaimer. + -- * Redistributions in binary form must reproduce the above copyright + -- notice, this list of conditions and the following disclaimer in the + -- documentation and/or other materials provided with the distribution. + -- * Neither the name of ROE nor the + -- names of its contributors may be used to endorse or promote products + -- derived from this software without specific prior written permission. + +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL Cair BE LIABLE FOR ANY +-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +_addon = {} +_addon.name = 'ROE' +_addon.version = '1.1' +_addon.author = "Cair" +_addon.commands = {'roe'} + +packets = require('packets') +config = require('config') +require('logger') + + +local defaults = T{ + profiles = T{ + default = S{}, + }, + blacklist = S{}, + clear = true, + clearprogress = false, + clearall = false, +} + +settings = config.load(defaults) + +_roe = T{ + active = T{}, + complete = T{}, + max_count = 30, +} + + +local function cancel_roe(id) + id = tonumber(id) + + if not id then return end + + if settings.blacklist[id] or not _roe.active[id] then return end + + local p = packets.new('outgoing', 0x10d, {['RoE Quest'] = id }) + packets.inject(p) +end + +local function accept_roe(id) + id = tonumber(id) + + if not id or _roe.complete[id] or _roe.active[id] then return end + if id and id >= 4008 and id <= 4021 then return end + + local p = packets.new('outgoing', 0x10c, {['RoE Quest'] = id }) + packets.inject(p) +end + +local function eval(...) + assert(loadstring(table.concat({...}, ' ')))() +end + +local function save(name) + if not type(name) == "string" then + error('`save` : specify a profile name') + return + end + + name = name:lower() + + settings.profiles[name] = S(_roe.active:keyset()) + settings:save('global') + notice('saved %d objectives to the profile %s':format(_roe.active:length(), name)) +end + + +local function list() + notice('You have saved the following profiles: ') + notice(settings.profiles:keyset()) +end + +local function set(name) + if not type(name) == "string" then + error('`set` : specify a profile name') + return + end + + name = name:lower() + + if not settings.profiles[name] then + error('`set` : the profile \'%s\' does not exist':format(name)) + return + end + + local needed_quests = settings.profiles[name]:diff(_roe.active:keyset()) + local available_slots = _roe.max_count - _roe.active:length() + local to_remove = S{} + + if settings.clearall then + to_remove:update(_roe.active:keyset()) + elseif settings.clear then + for id,progress in pairs(_roe.active) do + if (needed_quests:length() - to_remove:length()) <= available_slots then + break + end + if (progress == 0 or settings.clearprogress) and not settings.blacklist[id] then + to_remove:add(id) + end + end + end + + + if (needed_quests:length() - to_remove:length()) > available_slots then + error('you do not have enough available quest slots') + return + end + + for id in to_remove:it() do + cancel_roe(id) + coroutine.sleep(.5) + end + + for id in needed_quests:it() do + accept_roe(id) + coroutine.sleep(.5) + end + + notice('loaded the profile \'%s\'':format(name)) + +end + +local function unset(name) + + name = name and name:lower() + + if name and settings.profiles[name] then + for id in _roe.active:keyset():intersection(settings.profiles[name]):it() do + cancel_roe(id) + coroutine.sleep(.5) + end + notice('unset the profile \'%s\'':format(name)) + elseif name then + error('`unset` : the profile \'%s\' does not exist':format(name)) + elseif not name then + notice('clearing ROE objectives.') + for id,progress in pairs(_roe.active:copy()) do + if progress == 0 or settings.clearprogress then + cancel_roe(id) + coroutine.sleep(.5) + end + end + end + +end + +local true_strings = S{'true','t','y','yes','on'} +local false_strings = S{'false','f','n','no','off'} +local bool_strings = true_strings:union(false_strings) + +local function handle_setting(setting,val) + setting = setting and setting:lower() or setting + val = val and val:lower() or val + + if not setting or not settings:containskey(setting) then + error('specified setting (%s) does not exist':format(setting or '')) + elseif type(settings[setting]) == "boolean" then + if not val or not bool_strings:contains(val) then + settings[setting] = not settings[setting] + elseif true_strings:contains(val) then + settings[setting] = true + else + settings[setting] = false + end + + notice('%s setting is now %s':format(setting, tostring(settings[setting]))) + end + +end + +local function blacklist(add_remove,id) + add_remove = add_remove and add_remove:lower() + id = id and tonumber(id) + + + if add_remove and id then + if add_remove == 'add' then + settings.blacklist:add(id) + notice('roe quest %d added to the blacklist':format(id)) + elseif add_remove == 'remove' then + if id >= 4008 and id <= 4021 then + return + else + settings.blacklist:remove(id) + notice('roe quest %d removed from the blacklist':format(id)) + end + else + error('`blacklist` specify \'add\' or \'remove\'') + end + else + error('`blacklist` requires two args, [add|remove] <quest id>') + end + +end + + +local function help() + notice([[ROE - Command List: +1. help - Displays this help menu. +2. save <profile name> : saves the currently set ROE to the named profile +3. set <profile name> : attempts to set the ROE objectives in the profile + - Objectives may be canceled automatically based on settings. + - The default setting is to only cancel ROE that have 0 progress if space is needed +4. unset : removes currently set objectives + - By default, this will only remove objectives without progress made +5. settings <settings name> : toggles the specified setting + * settings: + * clear : removes objectives if space is needed (default true) + * clearprogress : remove objectives even if they have non-zero progress (default false) + * clearall : clears every objective before setting new ones (default false) +6. blacklist [add|remove] <id> : blacklists a quest from ever being removed + - I do not currently have a mapping of quest IDs to names]]) +end + +local cmd_handlers = { + eval = eval, + save = save, + list = list, + set = set, + unset = unset, + settings = handle_setting, + blacklist = blacklist, + help = help, +} + + +local function inc_chunk_handler(id,data) + if id == 0x111 then + _roe.active:clear() + for i = 1, _roe.max_count do + local offset = 5 + ((i - 1) * 4) + local id,progress = data:unpack('b12b20', offset) + if id > 0 then + _roe.active[id] = progress + end + end + elseif id == 0x112 then + local complete = T{data:unpack('b1':rep(1024),4)}:key_map( + function(k) + return (k + 1024*data:unpack('H', 133) - 1) + end):map( + function(v) + return (v == 1) + end) + _roe.complete:update(complete) + end +end + +local function addon_command_handler(command,...) + local cmd = command and command:lower() or "help" + if cmd_handlers[cmd] then + cmd_handlers[cmd](...) + else + error('unknown command `%s`':format(cmd or '')) + end + +end + +local function load_handler() + for k,v in pairs(settings.profiles) do + if type(v) == "string" then + settings.profiles[k] = S(v:split(','):map(tonumber)) + end + end + + local last_roe = windower.packets.last_incoming(0x111) + if last_roe then inc_chunk_handler(0x111,last_roe) end + +end + +windower.register_event('incoming chunk', inc_chunk_handler) +windower.register_event('addon command', addon_command_handler) +windower.register_event('load', load_handler) diff --git a/Data/DefaultContent/Libraries/addons/addons/rolltracker/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/rolltracker/data/settings.xml new file mode 100644 index 0000000..df8d487 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/rolltracker/data/settings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" ?> +<settings> + <!-- + This controls the settings for the Roll Tracker Plugin. + ::::Autostop:::: + Autostop: 0: Automatically stops rolls on lucky numbers, and displays the message (Currently the default option) + Autostop: 1: Disables the stop functionality. + ::::[Chance to Bust]:::: + bust: 0 Doesn't display chance to bust + bust: 1 Displays chance to bust (the default option) + ::::[Effected Members Number]:::: + effected: 0 doesn't show up + effected:1 shows up + ::::Fold Stopper:::: + fold 0: Doesn't stop fold + fold 1: Stops fold when no busts and 1 roll is active. + --> + <global> + <Autostop>0</Autostop> + <bust>1</bust> + <effected>1</effected> + <fold>1</fold> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/rolltracker/readme.md b/Data/DefaultContent/Libraries/addons/addons/rolltracker/readme.md new file mode 100644 index 0000000..6341a7b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/rolltracker/readme.md @@ -0,0 +1,11 @@ +Author: Balloon(Cerberus) + +Roll Tracker, for COR + +This addon instantly displays your roll number, it's effect, and whether the roll is lucky or not. (As opposed to the 5 second delay you find with the chatlog. + +If you have rolled a lucky roll, it will also prompt you and automatically stop you from doubling up on it (which can be bypassed by either doubling up again, or typing //rolltracker autostop. This currently doesn't work if you are also using Gearswap. + +To have the Lucky and Unlucky numbers of a roll displayed once per roll type //rolltracker luckyinfo + +Enjoy. diff --git a/Data/DefaultContent/Libraries/addons/addons/rolltracker/rolltracker.lua b/Data/DefaultContent/Libraries/addons/addons/rolltracker/rolltracker.lua new file mode 100644 index 0000000..b688d09 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/rolltracker/rolltracker.lua @@ -0,0 +1,370 @@ +--Copyright (c) 2013-2014, Thomas Rogers +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of RollTracker nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL THOMAS ROGERS BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'RollTracker' +_addon.version = '1.8.0.0' +_addon.author = 'Balloon' +_addon.commands = {'rolltracker','rt'} + +require('luau') +chat = require('chat') +chars = require('chat.chars') +packets = require('packets') + +defaults = {} +defaults.autostopper = true +defaults.bust = 1 +defaults.effected = 1 +defaults.fold = 1 +defaults.luckyinfo = true + +settings = config.load(defaults) + +windower.register_event('addon command',function (...) + cmd = {...} + if cmd[1] ~= nil then + if cmd[1]:lower() == "help" then + log('To toggle rolltracker from allowing/stopping rolls type: //rolltracker autostop') + log('To toggle rolltracker from showing/hiding Lucky Info type: //rolltracker luckyinfo') + elseif cmd[1]:lower() == "autostop" then + if settings.autostopper then + settings.autostopper = false + log('Will no longer stop Double-UP on a Lucky Roll.') + else + settings.autostopper = true + log('Will stop Double-UP on a Lucky Roll.') + end + elseif cmd[1]:lower() == "luckyinfo" then + if settings.luckyinfo then + settings.luckyinfo = false + log('Lucky/Unlucky Info will no longer be displayed.') + else + settings.luckyinfo = true + log('Lucky/Unlucky Info will now be displayed.') + end + end + config.save(settings, 'all') + end +end) + +--This is done because GearSwap swaps out too fast, and sometimes things aren't reported in memory. +--Therefore, we store it within rolltracker so we can do the check locally. +windower.register_event('incoming chunk', function(id, data) + if id == 0x050 then + local packet = packets.parse('incoming', data) + local slot = windower.ffxi.get_items(packet['Inventory Bag'])[packet['Inventory Index']] + gearTable[packet['Equipment Slot']] = slot ~= nil and slot.id or 0 + elseif id == 0x0DD then + local packetParty = packets.parse('incoming', data) + + end +end) + +function getGear(slot) + local equip = windower.ffxi.get_items()['equipment'] + return windower.ffxi.get_items(equip[slot..'_bag'])[equip[slot]]~= nil and windower.ffxi.get_items(equip[slot..'_bag'])[equip[slot]].id or 0 +end + +windower.register_event('load', function() + + --We need a gear table, and we need to initialise it when we load + --So that if someone doesn't swap gear, at least it still works. + gearTable = { + [0]=getGear('main'),[1]=getGear('sub'),[2]=getGear('range'),[3]=getGear('ammo'), + [4]=getGear('head'),[9]=getGear('neck'),[11]=getGear('left_ear'),[12]=getGear('right_ear'), + [5]=getGear('body'),[6]=getGear('hands'),[13]=getGear('left_ring'),[14]=getGear('right_ring'), + [15]=getGear('back'),[10]=getGear('waist'),[7]=getGear('legs'),[8]=getGear('feet') + } + buffId = S{309} + S(res.buffs:english(string.endswith-{' Roll'})):map(table.get-{'id'}) + partyColour = { + p0 = string.char(0x1E, 247), + p1 = string.char(0x1F, 204), + p2 = string.char(0x1E, 156), + p3 = string.char(0x1E, 238), + p4 = string.char(0x1E, 5), + p5 = string.char(0x1E, 6) + } + local rollInfoTemp = { + -- Okay, this goes 1-11 boost, Bust effect, Effect, Lucky, Unlucky, +1 Phantom Roll Effect, Job Bonus, Bonus Equipment and Effect, + ['Allies\''] = {6,7,17,9,11,13,15,17,17,5,17,'?','% Skillchain Damage',3,10, 1,{nil,0},{6,11120, 27084, 27085, 5}},--Needs Eval + ['Avenger\'s'] = {'?','?','?','?','?','?','?','?','?','?','?','?',' Counter Rate',4,8, 0,{nil,0}}, + ['Beast'] = {64,80,96,256,112,128,160,32,176,192,320,'0','% Pet: Attack Bonus',4,8, 32,{'bst',100}},--/1024 Confirmed + ['Blitzer\'s'] = {2,3.4,4.5,11.3,5.3,6.4,7.2,8.3,1.5,10.2,12.1,'-3','% Attack delay reduction',4,9, 1,{nil,0},{4,11080, 26772, 26773, 5}},--Limited Testing for Bust, Needs more data for sure probably /1024 + ['Bolter\'s'] = {0.3,0.3,0.8,0.4,0.4,0.5,0.5,0.6,0.2,0.7,1.0,'0','% Movement Speed',3,9, 0.2,{nil,0}}, + ['Caster\'s'] = {6,15,7,8,9,10,5,11,12,13,20,'-10','% Fast Cast',2,7, 3,{nil,0},{7, 11140, 27269, 27269, 10}}, + ['Chaos'] = {64,80,96,256,112,128,160,32,176,192,320,"-9.76",'% Attack!', 4,8, 32,{'drk',100}},--/1024 Confirmed + ['Choral'] = {8,42,11,15,19,4,23,27,31,35,50,'+25','- Spell Interruption Rate',2,6, 4,{'brd',25}},--SE listed Values and hell if I'm testing this + ['Companion\'s'] = {{4,20},{20,50},{6,20},{8,20},{10,30},{12,30},{14,30},{16,40},{18,40},{3,10},{25,60},'0',' Pet: Regen/Regain',2,10, {2,5},{nil,0}}, + ['Corsair\'s'] = {10, 11, 11, 12, 20, 13, 15, 16, 8, 17, 24,'-6','% Experience Bonus',5,9, 2,{'cor',5}},--Needs Eval on Bust/Job Bonus + ['Courser\'s'] = {'?','?','?','?','?','?','?','?','?','?','?','?',' Snapshot',3,9, 0,{nil,0}}, --11160, 27443, 27444 + ['Dancer\'s'] = {3,4,12,5,6,7,1,8,9,10,16,'-4',' Regen',3,7, 2,{'dnc',4}},--Confirmed + ['Drachen'] = {10,13,15,40,18,20,25,5,28,30,50,'0',' Pet: Accuracy Bonus',4,8, 5,{'drg',15}},--Confirmed + ['Evoker\'s'] = {1,1,1,1,3,2,2,2,1,3,4,'-1',' Refresh!',5,9, 1,{'smn',1}},--Confirmed + ['Fighter\'s'] = {2,2,3,4,12,5,6,7,1,9,18,'-4','% Double-Attack!',5,9, 1,{'war',5}}, + ['Gallant\'s'] = {48,60,200,72,88,104,32,120,140,160,240,'-11.72','% Defense Bonus',3,7, 24,{'pld',120}},--/1024 Confirmed + ['Healer\'s'] = {3,4,12,5,6,7,1,8,9,10,16,'-4','% Cure Potency Received',3,7, 1,{'whm',4}},--Confirmed + ['Hunter\'s'] = {10,13,15,40,18,20,25,5,28,30,50,'-15',' Accuracy Bonus',4,8, 5,{'rng',15}},--Confirmed + ['Magus\'s'] = {5,20,6,8,9,3,10,13,14,15,25,'-8',' Magic Defense Bonus',2,6, 2,{'blu',8}}, + ['Miser\'s'] = {30,50,70,90,200,110,20,130,150,170,250,'0',' Save TP',5,7, 15,{nil,0}}, + ['Monk\'s'] = {8,10,32,12,14,15,4,20,22,24,40,'-?',' Subtle Blow', 3,7, 4,{'mnk',10}}, + ['Naturalist\'s'] = {6,7,15,8,9,10,5,11,12,13,20,'-5','% Enhancing Magic Duration',3,7, 1,{'geo',5}},--Confirmed + ['Ninja'] = {10,13,15,40,18,20,25,5,28,30,50,'-15',' Evasion Bonus',4,8, 5,{'nin',15}},--Confirmed + ['Puppet'] = {5,8,35,11,14,18,2,22,26,30,40,'-8',' Pet: MAB/MAcc',3,7, 3,{'pup',12}}, + ['Rogue\'s'] = {2,2,3,4,12,5,6,6,1,8,14,'-6','% Critical Hit Rate!',5,9, 1,{'thf',5}}, + ['Runeist\'s'] = {10,13,15,40,18,20,25,5,28,30,50,'-15',' Evasion Bonus',4,8, 5,{'run',15}},--Needs Eval + ['Samurai'] = {8,32,10,12,14,4,16,20,22,24,40,'-10',' Store TP Bonus',2,6, 4,{'sam',10}},--Confirmed 1(Was bad),2,3,4,5,6,7,8,11 (I Wing Test) + ['Scholar\'s'] = {2,10,3,4,4,1,5,6,7,7,12,'-3','% Conserve MP',2,6, 1,{'sch',3}}, --Needs Eval Source ATM: JP Wiki + ['Tactician\'s'] = {10,10,10,10,30,10,10,0,20,20,40,'-10',' Regain',5,8, 2,{nil,0},{5, 11100, 26930, 26931, 10}},--Confirmed + ['Warlock\'s'] = {10,13,15,40,18,20,25,5,28,30,50,'-15',' Magic Accuracy Bonus',4,8, 5,{'rdm',15}},-- + ['Wizard\'s'] = {4,6,8,10,25,12,14,17,2,20,30, "-10",' MAB',5,9, 2,{'blm',10}}, + } + + rollInfo = {} + for key, val in pairs(rollInfoTemp) do + rollInfo[res.job_abilities:with('english', key .. ' Roll').id] = {key, unpack(val)} + end + + settings = config.load(defaults) +end) + +windower.register_event('load', 'login', function() + isLucky = false + rollPlusBonus = false + lastRoll = 0 + player = windower.ffxi.get_player() +end) + +windower.register_event('incoming text', function(old, new, color) + --Hides Battlemod + if old:match("Roll.* The total.*") or old:match('.*Roll.*' .. string.char(0x81, 0xA8)) or old:match('.*uses Double.*The total') and color ~= 123 then + return true + end + + --Hides Vanilla + if old:match('.* receives the effect of .* Roll.') ~= nil then + return true + end + + return new, color +end) + +windower.register_event('action', function(act) + if act.category == 6 and table.containskey(rollInfo, act.param) then + --This is used later to allow/disallow busting + --If you are not the rollActor you will not be disallowed to bust. + rollActor = act.actor_id + local rollID = act.param + local rollNum = act.targets[1].actions[1].param + + -- anonymous function that checks if the player.id is in the targets without wrapping it in another layer of for loops. + if + function(act) + for i = 1, #act.targets do + if act.targets[i].id == player.id then + return true + end + end + return false + end(act) + then + local party = windower.ffxi.get_party() + rollMembers = {} + for partyMem in pairs(party) do + for effectedTarget = 1, #act.targets do + --if mob is nil then the party member is not in zone, will fire an error. + if type(party[partyMem]) == 'table' and party[partyMem].mob and act.targets[effectedTarget].id == party[partyMem].mob.id then + rollMembers[effectedTarget] = partyColour[partyMem] .. party[partyMem].name .. chat.controls.reset + end + end + end + + local membersHit = table.concat(rollMembers, ', ') + --fake 'ternary' assignment. if settings.effected is 1 then it'll show numbers, otherwise it won't. + local amountHit = settings.effected == 1 and '[' .. #rollMembers .. '] ' or '' + local rollBonus = RollEffect(rollID, rollNum+1) + local luckChat = '' + isLucky = false + if rollNum == rollInfo[rollID][15] or rollNum == 11 then + isLucky = true + windower.add_to_chat(158,'Lucky roll!') + luckChat = string.char(31,158).." (Lucky!)" + elseif rollNum == rollInfo[rollID][16] then + luckChat = string.char(31,167).." (Unlucky!)" + end + + + if rollNum == 12 and #rollMembers > 0 then + windower.add_to_chat(1, string.char(31,167)..amountHit..'Bust! '..chat.controls.reset..chars.implies..' '..membersHit..' '..chars.implies..' ('..rollInfo[rollID][rollNum+1]..rollInfo[rollID][14]..')') + else + windower.add_to_chat(1, amountHit..membersHit..chat.controls.reset..' '..chars.implies..' '..rollInfo[rollID][1]..' Roll '..chars['circle' .. rollNum]..luckChat..string.char(31,13)..' (+'..rollBonus..')'..BustRate(rollNum, rollActor)..ReportRollInfo(rollID, rollActor)) + end + end + end +end) + + +function RollEffect(rollid, rollnum) + if rollnum == 13 then + return + end + + --There's gotta be a better way to do this. + local rollName = rollInfo[rollid][1] + local rollVal = rollInfo[rollid][rollnum] + + if lastRoll ~= rollid then + lastRoll = rollid + rollPlusBonus = false + gearBonus = false + jobBonus = false + end + + --I'm handling one roll a bit odd, so I need to deal with it separately. + --Which is stupid, I know, but look at how I've done most of this. + if rollName == "Companion\'s" then + local hpVal = rollVal[1] + local tpVal = rollVal[2] + if gearTable[9] == 26038 or rollPlusBonus then + hpVal = hpVal + (rollInfo[rollid][17][1]*7) + tpVal = tpVal + (rollInfo[rollid][17][2]*7) + rollPlusBonus = true + elseif gearTable[13] == 28548 or gearTable[14]== 28548 or rollPlusBonus then + hpVal = hpVal + (rollInfo[rollid][17][1]*5) + tpVal = tpVal + (rollInfo[rollid][17][2]*5) + rollPlusBonus = true + elseif gearTable[13] == 28547 or gearTable[14] == 28547 or rollPlusBonus then + hpVal = hpVal + (rollInfo[rollid][17][1]*3) + tpVal = tpVal + (rollInfo[rollid][17][2]*3) + rollPlusBonus = true + end + return "Pet:"..hpVal.." Regen".." +"..tpVal.." Regain" + end + + --If there's no Roll Val can't add to it + if rollVal ~= '?' then + if gearTable[9] == 26038 or rollPlusBonus then + rollVal = rollVal + (rollInfo[rollid][17]*7) + rollPlusBonus = true + elseif gearTable[13] == 28548 or gearTable[14] == 28548 or rollPlusBonus then + rollVal = rollVal + (rollInfo[rollid][17]*5) + rollPlusBonus = true + elseif gearTable[13] == 28547 or gearTable[14] == 28547 or rollPlusBonus then + rollVal = rollVal + (rollInfo[rollid][17]*3) + rollPlusBonus = true + end + end + + --Handle Job Bonus + if (rollInfo[rollid][18][1] ~= nil) then + --Add Job is in party check + if jobBonus then + rollVal = rollVal + rollInfo[rollid][18][2] + else + jobBonus = true + rollVal = rollVal + rollInfo[rollid][18][2] + end + end + + --Handle Emp +2, 109 and 119 gear bonus + if (rollInfo[rollid][19] ~= nil) then + local bonusVal = (gearTable[rollInfo[rollid][19][1]] == rollInfo[rollid][19][2] or gearTable[rollInfo[rollid][19][1]] == rollInfo[rollid][19][3] or gearTable[rollInfo[rollid][19][1]] == rollInfo[rollid][19][4]) and rollInfo[rollid][19][5] or 0 + if gearBonus == true then + rollVal = rollVal + rollInfo[rollid][19][5] + else + gearBonus = true + rollVal = rollVal + bonusVal + end + end + + -- Convert Bolter's to Movement Speed based on 5.0 being 100% + if (rollName == "Bolter\'s") then + rollVal = '%.0f':format(100*((5+rollVal) / 5 - 1)) + end + + --Convert Beast/Chaos/Gallant's to % with 2 decimals + if (rollName == "Chaos") or (rollName == "Gallant\'s") or (rollName == "Beast") then + rollVal = '%.2f':format(rollVal/1024 * 100) + end + + return rollVal..rollInfo[rollid][14] +end + + +function BustRate(rollNum, rollActor) + if rollNum <= 5 or rollNum == 11 or rollActor ~= player.id or settings.bust == 0 then + return '' + end + return '\7 [Chance to Bust]: ' .. '%.1f':format((rollNum-5)*16.67) .. '%' +end + +--Display Lucky/Unlucky #s and check if it's already been reported once. +reportedOnce = false +function ReportRollInfo(rollID, rollActor) + if rollActor ~= player.id or not settings.luckyinfo then + return '' + elseif reportedOnce then + reportedOnce = false + return '' + else + reportedOnce = true + return '\7 '..rollInfo[rollID][1]..' Roll\'s Lucky #: ' ..rollInfo[rollID][15]..' Unlucky #: '..rollInfo[rollID][16] + end +end + +--Checks to see if the below event has ran more than twice to enable busting +ranMultiple = false +windower.register_event('outgoing text', function(original, modified) + cleaned = windower.convert_auto_trans(original) + modified = original + if cleaned:match('/jobability \"?Double.*Up') or cleaned:match('/ja \"?Double.*Up') then + if isLucky and settings.autostopper and rollActor == player.id then + windower.add_to_chat(159,'Attempting to Doubleup on a Lucky Roll: Re-double up to continue.') + isLucky = false + modified = "" + end + end + + if settings.fold == 1 and (cleaned:match('/jobability \"?Fold') or cleaned:match('/ja \"?Fold')) then + local count = 0 + local canBust = false + + --Check to see how many buffs are active + local cor_buffs = S(player.buffs) * buffId + canBust = cor_buffs:contains(res.buffs:with('name', 'Bust').id) or cor_buffs:length() > 1 + if canBust or ranMultiple then + modified = cleaned + ranMultiple = false + else + windower.add_to_chat(159, 'No \'Bust\'. Fold again to continue.') + ranMultiple = true + modified = "" + end + + return modified + end + return modified + +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/salvage2/README.md b/Data/DefaultContent/Libraries/addons/addons/salvage2/README.md new file mode 100644 index 0000000..7ed00e8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/salvage2/README.md @@ -0,0 +1,23 @@ +Author: Krizz + +Version: .5 + +Date: 20130601 + +Salvage2 + +Abbreviation: //s2 + +Commands: +* help - Shows a menu of commands in game +* pos <x> <y> - Positions the pathos remaining box. Default location is 1000,250. +* [hide/show] - Toggle display of the pathos remaining box. +* timer [start/stop] - Will start or stop the 100 minute zone timer. +* remove <pathos> - Removes the pathos from the list. + +To do: +* Change zone comparison to use zone IDs. + +Known Issues: +* May not always display the box upon zoning in until a pathos is removed. + diff --git a/Data/DefaultContent/Libraries/addons/addons/salvage2/salvage2.lua b/Data/DefaultContent/Libraries/addons/addons/salvage2/salvage2.lua new file mode 100644 index 0000000..1d9eb7c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/salvage2/salvage2.lua @@ -0,0 +1,175 @@ +--Copyright (c) 2013, Krizz +--All rights reserved. +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of salvage2 nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL KRIZZ BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'salvage2' +_addon.version = 0.1 +_addon.command = 's2' +_addon.author = 'Bahamut.Krizz' + +require('tables') +require('strings') +require('maths') +require('logger') +texts = require ('texts') +config = require('config') +----------------------------- + + +--variables +pathos_ident = {'Main Weapon/Sub-Weapon restriction', 'Ranged Weapon/Ammo restriction', 'Head/Neck equipment restriction', 'Body equipment restriction', 'Hand equipment restriction', 'Earrings/Rings restriction', 'Back/Waist equipment restriction', 'Leg/Foot equipment restriction', 'Support Job restriction', 'Job Abilities restriction', 'Spellcasting restriction', 'Max HP Down', 'Max MP Down', 'STR Down', 'DEX Down', 'AGI Down', 'MND Down', 'INT Down', 'CHR Down', 'VIT Down'} +pathos_short = {'Weapon', 'Ranged', 'Head/Neck', 'Body', 'Hand', 'Earrings/Rings', 'Back/Waist', 'Leg/Foot', 'Support Job', 'Job Abilities', 'Spellcasting', 'MaxHP', 'MaxMP', 'STR', 'DEX', 'AGI', 'MND', 'INT', 'CHR', 'VIT'} +salvage_zones = S{73, 74, 75, 76} + +defaults = {} +defaults.pos = {} +defaults.pos.x = 1000 +defaults.pos.y = 150 +defaults.color = {} +defaults.color.alpha = 200 +defaults.color.red = 200 +defaults.color.green = 200 +defaults.color.blue = 200 +defaults.bg = {} +defaults.bg.alpha = 200 +defaults.bg.red = 30 +defaults.bg.green = 30 +defaults.bg.blue = 30 + +settings = config.load(defaults) +salvage_box2 = texts.new('No pathos', settings) + +windower.register_event('addon command',function (...) +local params = {...}; + if #params < 1 then + return + end + if params[1] then + if params[1]:lower() == "help" then + print('Salvage2 available commands:') + print('s2 help : Shows this help message') + print('s2 pos <x> <y> : Positions the list') + print('s2 [hide/show] : Hides the box') + print('s2 timer [start/stop] : Starts or stops the zone timer') + print('s2 remove <pathos> : Removes the pathos from the remaining list') + elseif params[1]:lower() == "pos" then + if params[3] then + local posx, posy = tonumber(params[2]), tonumber(params[3]) + windower.text.set_location('salvage_box2', posx, posy) + end + elseif params[1]:lower() == "hide" then + salvage_box2:hide() + elseif params[1]:lower() == "show" then + salvage_box2:show() + elseif params[1]:lower() == "timer" then + if params[2] == "start" then + windower.send_command('timers c Remaining 6000 up') + elseif params[2] == "stop" then + windower.send_command('timers d Remaining') + end + elseif params[1]:lower() == "debug" then + if params[2]:lower() == "start" then + windower.send_command('timers c Remaining 6000 up') + settings_create() + salvage_box2:show() + initialize() + elseif params[2]:lower() == "stop" then + windower.send_command('timers d Remaining') + salvage_box2:hide() + end + elseif params[1]:lower() == "remove" then + for i=1, #pathos_short do + if pathos_short[i]:lower() == params[2]:lower() then + pathos_ident[pathos_ident[i]] = 0 + initialize() + end + end + end + end +end) + +windower.register_event('login', function(name) + player = name +end) + +function settings_create() + -- get player's name + player = windower.ffxi.get_player()['name'] + -- set all pathos as needed + for i=1, #pathos_ident do + if pathos_ident[i] ~= nil then + pathos_ident[pathos_ident[i]] = 1 + end + end +end + +windower.register_event('zone change', function(id) + if salvage_zones:contains(id) then + windower.send_command('timers c Remaining 6000 up') + settings_create() + initialize() + salvage_box2:show() + else + windower.send_command('timers d Remaining') + settings_create() + initialize() + salvage_box2:hide() + end +end) + +windower.register_event('incoming text',function (original, new, color) + original = original:strip_format() + local pathos, name = original:match('(.*) removed for (%w+)') + if pathos ~= nil then + --print('Pathos found '..pathos) + if name == player then + for i=1, #pathos_ident do + if pathos_ident[i]:lower() == pathos:lower() then + if pathos_ident[pathos_ident[i]] == 1 then + pathos_ident[pathos_ident[i]] = 0 + initialize() + end + end + end + end + return new, color + end +end) + +function initialize() + pathos_remain = (" Pathos Remaining: \n ") + for i=1, #pathos_ident do + if pathos_ident[pathos_ident[i]] == 1 then + item = pathos_short[i] + pathos_remain = (pathos_remain..item..' \n ') + end + end + salvage_box2:text(pathos_remain) + if pathos_remain == (" Pathos Remaining: \n ") then + salvage_box2:hide() + end +end + +windower.register_event('unload',function () + windower.text.delete('salvage_box2') + windower.send_command('timers d Remaining') +end ) diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/damagedb.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/damagedb.lua new file mode 100644 index 0000000..4b22b61 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/damagedb.lua @@ -0,0 +1,179 @@ +local Player = require 'player' +local MergedPlayer = require 'mergedplayer' + +local DamageDB = { + db = T{}, + filter = T{} +} + +--[[ +DamageDB.player_stat_fields = T{ + 'mmin', 'mmax', 'mavg', + 'rmin', 'rmax', 'ravg', + 'wsmin', 'wsmax', 'wsavg' +} +]] + +DamageDB.player_stat_fields = T{ + 'mavg', 'mrange', 'critavg', 'critrange', + 'ravg', 'rrange', 'rcritavg', 'rcritrange', + 'acc', 'racc', 'crit', 'rcrit', + 'wsavg', 'wsacc' +} + +function DamageDB:new (o) + o = o or {} + setmetatable(o, self) + self.__index = self + + return o +end + + +function DamageDB:iter() + local k, v + return function () + k, v = next(self.db, k) + while k and not self:_filter_contains_mob(k) do + k, v = next(self.db, k) + end + + if k then + return k, v + end + end +end + + +function DamageDB:get_filters() + return self.filter +end + + +function DamageDB:_filter_contains_mob(mob_name) + if self.filter:empty() then + return true + end + + for _, mob_pattern in ipairs(self.filter) do + if mob_name:lower():find(mob_pattern:lower()) then + return true + end + end + return false +end + + +function DamageDB:clear_filters() + self.filter = T{} +end + + +function DamageDB:add_filter(mob_pattern) + if mob_pattern then self.filter:append(mob_pattern) end +end + + +-- Returns the corresponding Player instance. Will create it if necessary. +function DamageDB:_get_player(mob, player_name) + if not self.db[mob] then + self.db[mob] = T{} + end + + if not self.db[mob][player_name] then + self.db[mob][player_name] = Player:new{name = player_name} + end + + return self.db[mob][player_name] +end + + +-- Returns a table {player1 = stat1, player2 = stat2...}. +-- For WS queries, the stat value is a sub-table of {ws1 = ws_stat1, ws2 = ws_stat2}. +function DamageDB:query_stat(stat, player_name) + local players = T{} + + if player_name and player_name:match('^[a-zA-Z]+$') then + player_name = player_name:lower():ucfirst() + end + + -- Gather a table mapping player names to all of the corresponding Player instances + for mob, mob_players in self:iter() do + for name, player in pairs(mob_players) do + if player_name and player_name == name or + not player_name and not player.is_sc then + if players[name] then + players[name]:append(player) + else + players[name] = T{player} + end + end + end + end + + -- Flatten player subtables into the merged stat we desire + for name, instances in pairs(players) do + local merged = MergedPlayer:new{players = instances} + players[name] = MergedPlayer[stat](merged) + end + + return players +end + + +function DamageDB:empty() + return self.db:empty() +end + + +function DamageDB:reset() + self.db = T{} +end + + +--[[ +The following player dispatchers all fetch the correct +instance of Player for a given mob and then dispatch the +method for data accmulation. +]]-- +function DamageDB:add_m_hit(m, p, d) self:_get_player(m, p):add_m_hit(d) end +function DamageDB:add_m_crit(m, p, d) self:_get_player(m, p):add_m_crit(d) end +function DamageDB:add_r_hit(m, p, d) self:_get_player(m, p):add_r_hit(d) end +function DamageDB:add_r_crit(m, p, d) self:_get_player(m, p):add_r_crit(d) end +function DamageDB:incr_misses(m, p) self:_get_player(m, p):incr_m_misses() end +function DamageDB:incr_r_misses(m, p) self:_get_player(m, p):incr_r_misses() end +function DamageDB:incr_ws_misses(m, p) self:_get_player(m, p):incr_ws_misses() end +function DamageDB:add_damage(m, p, d) self:_get_player(m, p):add_damage(d) end +function DamageDB:add_ws_damage(m, p, d, id) self:_get_player(m, p):add_ws_damage(id, d) end + + +return DamageDB + +--[[ +Copyright (c) 2013, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/scoreboard/data/settings.xml new file mode 100644 index 0000000..2478be1 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/data/settings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" ?> +<settings> + <!-- + This file controls the settings for the Scoreboard plugin. + Settings in the <global> section apply to all characters + + The available settings are: + posX - x coordinate for position + posY - y coordinate for position + numPlayers - The maximum number of players to display damage for + bgTransparency - Transparency level for the background. 0-255 range + --> + <global> + <posX>10</posX> + <posY>250</posY> + <bgTransparency>200</bgTransparency> + <numPlayers>8</numPlayers> + <UpdateFrequency>0.5</UpdateFrequency> + </global> + + <!-- + You may also override specific settings on a per-character basis here. + --> +</settings> + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/display.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/display.lua new file mode 100644 index 0000000..b2582d6 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/display.lua @@ -0,0 +1,443 @@ +-- Display object +local texts = require('texts') + +local Display = { + visible = true, + settings = nil, + tb_name = 'scoreboard' +} + +local valid_fonts = T{ + 'fixedsys', + 'lucida console', + 'courier', + 'courier new', + 'ms mincho', + 'consolas', + 'dejavu sans mono' +} + +local valid_fields = T{ + 'name', + 'dps', + 'percent', + 'total', + 'mavg', + 'mrange', + 'critavg', + 'critrange', + 'ravg', + 'rrange', + 'rcritavg', + 'rcritrange', + 'acc', + 'racc', + 'crit', + 'rcrit', + 'wsavg' +} + + +function Display:set_position(posx, posy) + self.text:pos(posx, posy) +end + +function Display:new(settings, db) + local repr = setmetatable({db = db}, self) + self.settings = settings + self.__index = self + self.visible = settings.visible + + self.text = texts.new(settings.display, settings) + + if not valid_fonts:contains(self.settings.display.text.font:lower()) then + error('Invalid font specified: ' .. self.settings.display.text.font) + self.text:font(self.settings.display.text.font) + self.text:size(self.settings.display.text.fontsize) + else + self.text:font(self.settings.display.text.font, 'consolas', 'courier new', 'monospace') + self.text:size(self.settings.display.text.size) + end + + self:visibility(self.visible) + + return repr +end + + +function Display:visibility(v) + if v then + self.text:show() + else + self.text:hide() + end + + self.visible = v + self.settings.visible = v + self.settings:save() +end + + +function Display:report_filters() + local mob_str + local filters = self.db:get_filters() + + if filters:empty() then + mob_str = "Scoreboard filters: None (Displaying damage for all mobs)" + else + mob_str = "Scoreboard filters: " .. filters:concat(', ') + end + windower.add_to_chat(55, mob_str) + +end + + +-- Returns the string for the scoreboard header with updated info +-- about current mob filtering and whether or not time is currently +-- contributing to the DPS value. +function Display:build_scoreboard_header() + local mob_filter_str + local filters = self.db:get_filters() + + if filters:empty() then + mob_filter_str = 'All' + else + mob_filter_str = table.concat(filters, ', ') + end + + local labels + if self.db:empty() then + labels = '\n' + else + labels = '%32s%7s%9s\n':format('Tot', 'Pct', 'DPS') + end + + local dps_status + if dps_clock:is_active() then + dps_status = 'Active' + else + dps_status = 'Paused' + end + + local dps_clock_str = '' + if dps_clock:is_active() or dps_clock.clock > 1 then + dps_clock_str = ' (%s)':format(dps_clock:to_string()) + end + + local dps_chunk = 'DPS: %s%s':format(dps_status, dps_clock_str) + + return '%s%s\nMobs: %-9s\n%s':format(dps_chunk, ' ':rep(29 - dps_chunk:len()) .. '//sb help', mob_filter_str, labels) +end + + +-- Returns following two element pair: +-- 1) table of sorted 2-tuples containing {player, damage} +-- 2) integer containing the total damage done +function Display:get_sorted_player_damage() + -- In order to sort by damage, we have to first add it all up into a table + -- and then build a table of sortable 2-tuples and then finally we can sort... + local mob, players + local player_total_dmg = T{} + + if self.db:empty() then + return {}, 0 + end + + for mob, players in self.db:iter() do + -- If the filter isn't active, include all mobs + for player_name, player in pairs(players) do + if player_total_dmg[player_name] then + player_total_dmg[player_name] = player_total_dmg[player_name] + player.damage + else + player_total_dmg[player_name] = player.damage + end + end + end + + local sortable = T{} + local total_damage = 0 + for player, damage in pairs(player_total_dmg) do + total_damage = total_damage + damage + sortable:append({player, damage}) + end + + table.sort(sortable, function(a, b) + return a[2] > b[2] + end) + + return sortable, total_damage +end + + +-- Updates the main display with current filter/damage/dps status +function Display:update() + if not self.visible then + -- no need build a display while it's hidden + return + end + + if self.db:empty() then + self:reset() + return + end + local damage_table, total_damage + damage_table, total_damage = self:get_sorted_player_damage() + + local display_table = T{} + local player_lines = 0 + local alli_damage = 0 + for k, v in pairs(damage_table) do + if player_lines < self.settings.numplayers then + local dps + if dps_clock.clock == 0 then + dps = "N/A" + else + dps = '%.2f':format(math.round(v[2] / dps_clock.clock, 2)) + end + + local percent + if total_damage > 0 then + percent = '(%.1f%%)':format(100 * v[2] / total_damage) + else + percent = '(0%)' + end + display_table:append('%-25s%7d%8s %7s':format(v[1], v[2], percent, dps)) + end + alli_damage = alli_damage + v[2] -- gather this even for players not displayed + player_lines = player_lines + 1 + end + + if self.settings.showallidps and dps_clock.clock > 0 then + display_table:append('-':rep(17)) + display_table:append('Alli DPS: ' .. '%7.1f':format(alli_damage / dps_clock.clock)) + end + + self.text:text(self:build_scoreboard_header() .. table.concat(display_table, '\n')) +end + + +local function build_input_command(chatmode, tell_target) + local input_cmd = 'input ' + if chatmode then + input_cmd = input_cmd .. '/' .. chatmode .. ' ' + if tell_target then + input_cmd = input_cmd .. tell_target .. ' ' + end + end + + return input_cmd +end + +-- Takes a table of elements to be wrapped across multiple lines and returns +-- a table of strings, each of which fits within one FFXI line. +local function wrap_elements(elements, header, sep) + local max_line_length = 120 -- game constant + if not sep then + sep = ', ' + end + + local lines = T{} + local current_line = nil + local line_length + + local i = 1 + while i <= #elements do + if not current_line then + current_line = T{} + line_length = header:len() + lines:append(current_line) + end + + local new_line_length = line_length + elements[i]:len() + sep:len() + if new_line_length > max_line_length then + current_line = T{} + lines:append(current_line) + new_line_length = elements[i]:len() + sep:len() + end + + current_line:append(elements[i]) + line_length = new_line_length + i = i + 1 + end + + local baked_lines = lines:map(function (ls) return ls:concat(sep) end) + if header:len() > 0 and #baked_lines > 0 then + baked_lines[1] = header .. baked_lines[1] + end + + return baked_lines +end + + +local function slow_output(chatprefix, lines, limit) + -- this is funky but if we don't wait like this, the lines will spew too fast and error + windower.send_command(lines:map(function (l) return chatprefix .. l end):concat('; wait 1.2 ; ')) +end + + +function Display:report_summary (...) + local chatmode, tell_target = table.unpack({...}) + + local damage_table, total_damage + damage_table, total_damage = self:get_sorted_player_damage() + + local elements = T{} + for k, v in pairs(damage_table) do + elements:append('%s %d(%.1f%%)':format(v[1], v[2], 100 * v[2]/total_damage)) + end + + -- Send the report to the specified chatmode + slow_output(build_input_command(chatmode, tell_target), + wrap_elements(elements:slice(1, self.settings.numplayers), 'Dmg: '), self.settings.numplayers) +end + +-- This is a table of the line aggregators and related utilities +Display.stat_summaries = {} + + +Display.stat_summaries._format_title = function (msg) + local line_length = 40 + local msg_length = msg:len() + local border_len = math.floor(line_length / 2 - msg_length / 2) + + return ' ':rep(border_len) .. msg .. ' ':rep(border_len) + end + + +Display.stat_summaries['range'] = function (stats, filters, options) + + local lines = T{} + for name, pair in pairs(stats) do + lines:append('%-20s %d min %d max':format(name, pair[1], pair[2])) + end + + if #lines > 0 and options and options.name then + sb_output(Display.stat_summaries._format_title('-= '..options.name..' (' .. filters .. ') =-')) + sb_output(lines) + end + end + + +Display.stat_summaries['average'] = function (stats, filters, options) + + local lines = T{} + for name, pair in pairs(stats) do + if options and options.percent then + lines:append('%-20s %.2f%% (%d sample%s)':format(name, 100 * pair[1], pair[2], + pair[2] == 1 and '' or 's')) + else + lines:append('%-20s %d (%ds)':format(name, pair[1], pair[2])) + end + end + + if #lines > 0 and options and options.name then + sb_output(Display.stat_summaries._format_title('-= '..options.name..' (' .. filters .. ') =-')) + sb_output(lines) + end + end + + +-- This is a closure around a hash-based dispatcher. Some conveniences are +-- defined for the actual stat display functions. +Display.show_stat = function() + return function (self, stat, player_filter) + local stats = self.db:query_stat(stat, player_filter) + local filters = self.db:get_filters() + local filter_str + + if filters:empty() then + filter_str = 'All mobs' + else + filter_str = filters:concat(', ') + end + + Display.stat_summaries[Display.stat_summaries._all_stats[stat].category](stats, filter_str, Display.stat_summaries._all_stats[stat]) + end +end() + + +-- TODO: This needs to be factored somehow to take better advantage of similar +-- code already written for reporting and stat queries. +Display.stat_summaries._all_stats = T{ + ['acc'] = {percent=true, category="average", name='Accuracy'}, + ['racc'] = {percent=true, category="average", name='Ranged Accuracy'}, + ['crit'] = {percent=true, category="average", name='Melee Crit. Rate'}, + ['rcrit'] = {percent=true, category="average", name='Ranged Crit. Rate'}, + ['wsavg'] = {percent=false, category="average", name='WS Average'}, + ['wsacc'] = {percent=true, category="average", name='WS Accuracy'}, + ['mavg'] = {percent=false, category="average", name='Melee Non-Crit. Avg. Damage'}, + ['mrange'] = {percent=false, category="range", name='Melee Non-Crit. Range'}, + ['critavg'] = {percent=false, category="average", name='Melee Crit. Avg. Damage'}, + ['critrange'] = {percent=false, category="range", name='Melee Crit. Range'}, + ['ravg'] = {percent=false, category="average", name='Ranged Non-Crit. Avg. Damage'}, + ['rrange'] = {percent=false, category="range", name='Ranged Non-Crit. Range'}, + ['rcritavg'] = {percent=false, category="average", name='Ranged Crit. Avg. Damage'}, + ['rcritrange'] = {percent=false, category="range", name='Ranged Crit. Range'},} +function Display:report_stat(stat, args) + if Display.stat_summaries._all_stats:containskey(stat) then + local stats = self.db:query_stat(stat, args.player) + + local elements = T{} + local header = Display.stat_summaries._all_stats[stat].name .. ': ' + for name, stat_pair in pairs(stats) do + if stat_pair[2] > 0 then + if Display.stat_summaries._all_stats[stat].category == 'range' then + elements:append({stat_pair[1], ('%s %d~%d'):format(name, stat_pair[1], stat_pair[2])}) + elseif Display.stat_summaries._all_stats[stat].percent then + elements:append({stat_pair[1], ('%s %.2f%% (%ds)'):format(name, stat_pair[1] * 100, stat_pair[2])}) + else + elements:append({stat_pair[1], ('%s %d (%ds)'):format(name, stat_pair[1], stat_pair[2])}) + end + end + end + table.sort(elements, function(a, b) + return a[1] > b[1] + end) + + -- Send the report to the specified chatmode + local wrapped = wrap_elements(elements:slice(1, self.settings.numplayers):map(function (p) return p[2] end), header) + slow_output(build_input_command(args.chatmode, args.telltarget), wrapped, self.settings.numplayers) + end +end + + +function Display:reset() + -- the number of spaces here was counted to keep the table width + -- consistent even when there's no data being displayed + self.text:text(self:build_scoreboard_header() .. + 'Waiting for results...' .. + ' ':rep(17)) +end + + +return Display + +--[[ +Copyright © 2013-2014, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] + + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/dpsclock.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/dpsclock.lua new file mode 100644 index 0000000..ba3a267 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/dpsclock.lua @@ -0,0 +1,96 @@ +-- Object to encapsulate DPS Clock functionality + +local DPSClock = { + clock = 0, + prev_time = 0, + active = false +} + +function DPSClock:new (o) + o = o or {} + setmetatable(o, self) + self.__index = self + + return o +end + + +function DPSClock:advance() + local now = os.time() + + if self.prev_time == 0 then + self.prev_time = now + end + + self.clock = self.clock + (now - self.prev_time) + self.prev_time = now + + self.active = true +end + + +function DPSClock:pause() + self.active = false + self.prev_time = 0 +end + + +function DPSClock:is_active() + return self.active +end + +function DPSClock:reset() + self.active = false + self.clock = 0 + self.prev_time = 0 +end + + +-- Convert integer seconds into a "HhMmSs" string +function DPSClock:to_string() + local seconds = self.clock + + local hours = math.floor(seconds / 3600) + seconds = seconds - hours * 3600 + + local minutes = math.floor(seconds / 60) + seconds = seconds - minutes * 60 + + local hours_str = hours > 0 and hours .. "h" or "" + local minutes_str = minutes > 0 and minutes .. "m" or "" + local seconds_str = seconds and seconds .. "s" or "" + + return hours_str .. minutes_str .. seconds_str +end + +return DPSClock + +--[[ +Copyright (c) 2013, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] + + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/mergedplayer.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/mergedplayer.lua new file mode 100644 index 0000000..8346cfd --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/mergedplayer.lua @@ -0,0 +1,354 @@ +--[[ + The entire mergedplayer file exists to flatten individual stats in the db + into two numbers (per name). So normally the db is: + dps_db.dp[mob_name][player_name] = {stats} + Mergedplayer iterates over mob_name and returns a table that's just: + tab[player_name] = {CalculatedStatA,CalculatedStatB} +]] + +local MergedPlayer = {} + +function MergedPlayer:new (o) + o = o or {} + + assert(o.players and #o.players > 0, + "MergedPlayer constructor requires at least one Player instance.") + + setmetatable(o, self) + self.__index = self + + return o +end + +--[[ + 'wsmin', 'wsmax', 'wsavg' +]] + +function MergedPlayer:mavg() + local hits, hit_dmg = 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + p.m_hits + hit_dmg = hit_dmg + p.m_hits*p.m_avg + end + + if hits > 0 then + return { hit_dmg / hits, hits} + else + return {0, 0} + end +end + + +function MergedPlayer:mrange() + local m_min, m_max = math.huge, 0 + + for _, p in ipairs(self.players) do + m_min = math.min(m_min, p.m_min) + m_max = math.max(m_max, p.m_max) + end + + return {m_min~=math.huge and m_min or m_max, m_max} +end + + +function MergedPlayer:critavg() + local crits, crit_dmg = 0, 0 + + for _, p in ipairs(self.players) do + crits = crits + p.m_crits + crit_dmg = crit_dmg + p.m_crits*p.m_crit_avg + end + + if crits > 0 then + return { crit_dmg / crits, crits} + else + return {0, 0} + end +end + + +function MergedPlayer:critrange() + local m_crit_min, m_crit_max = math.huge, 0 + + for _, p in ipairs(self.players) do + m_crit_min = math.min(m_crit_min, p.m_crit_min) + m_crit_max = math.max(m_crit_max, p.m_crit_max) + end + + return {m_crit_min~=math.huge and m_crit_min or m_crit_max, m_crit_max} +end + + +function MergedPlayer:ravg() + local r_hits, r_hit_dmg = 0, 0 + + for _, p in ipairs(self.players) do + r_hits = r_hits + p.r_hits + r_hit_dmg = r_hit_dmg + p.r_hits*p.r_avg + end + + if r_hits > 0 then + return { r_hit_dmg / r_hits, r_hits} + else + return {0, 0} + end +end + + +function MergedPlayer:rrange() + local r_min, r_max = math.huge, 0 + + for _, p in ipairs(self.players) do + r_min = math.min(r_min, p.r_min) + r_max = math.max(r_max, p.r_max) + end + + return {r_min~=math.huge and r_min or r_max, r_max} +end + + +function MergedPlayer:rcritavg() + local r_crits, r_crit_dmg = 0, 0 + + for _, p in ipairs(self.players) do + r_crits = r_crits + p.r_crits + r_crit_dmg = r_crit_dmg + p.r_crits*p.r_crit_avg + end + + if r_crits > 0 then + return { r_crit_dmg / r_crits, r_crits} + else + return {0, 0} + end +end + + +function MergedPlayer:rcritrange() + local r_crit_min, r_crit_max = math.huge, 0 + + for _, p in ipairs(self.players) do + r_crit_min = math.min(r_crit_min, p.r_crit_min) + r_crit_max = math.max(r_crit_max, p.r_crit_max) + end + + return {r_crit_min~=math.huge and r_crit_min or r_crit_max, r_crit_max} +end + + +function MergedPlayer:acc() + local hits, crits, misses = 0, 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + p.m_hits + crits = crits + p.m_crits + misses = misses + p.m_misses + end + + local total = hits + crits + misses + if total > 0 then + return {(hits + crits) / total, total} + else + return {0, 0} + end +end + + +function MergedPlayer:racc() + local hits, crits, misses = 0, 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + p.r_hits + crits = crits + p.r_crits + misses = misses + p.r_misses + end + + local total = hits + crits + misses + if total > 0 then + return {(hits + crits) / total, total} + else + return {0, 0} + end +end + + +function MergedPlayer:crit() + local hits, crits = 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + p.m_hits + crits = crits + p.m_crits + end + + local total = hits + crits + if total > 0 then + return {crits / total, total} + else + return {0, 0} + end +end + + +function MergedPlayer:rcrit() + local hits, crits = 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + p.r_hits + crits = crits + p.r_crits + end + + local total = hits + crits + if total > 0 then + return {crits / total, total} + else + return {0, 0} + end +end + + +function MergedPlayer:wsavg() + local wsdmg = 0 + local wscount = 0 + --[[ + for _, p in pairs(self.players) do + for _, dmgtable in pairs(p.ws) do + for _, dmg in pairs(dmgtable) do + wsdmg = wsdmg + dmg + wscount = wscount + 1 + end + end + end + ]] + + for _, p in pairs(self.players) do + for _, dmg in pairs(p.ws) do + wsdmg = wsdmg + dmg + wscount = wscount + 1 + end + end + + if wscount > 0 then + return {wsdmg / wscount, wscount} + else + return {0, 0} + end +end + +function MergedPlayer:wsacc() + local hits, misses = 0, 0 + + for _, p in ipairs(self.players) do + hits = hits + table.length(p.ws) + misses = misses + p.ws_misses + end + + local total = hits + misses + if total > 0 then + return {hits / total, total} + else + return {0, 0} + end +end + +-- Unused atm +function MergedPlayer:merge(other) + self.damage = self.damage + other.damage + + for ws_id, values in pairs(other.ws) do + if self.ws[ws_id] then + for _, value in ipairs(values) do + self.ws[ws_id]:append(value) + end + else + self.ws[ws_id] = table.copy(values) + end + end + + self.m_hits = self.m_hits + other.m_hits + self.m_misses = self.m_misses + other.m_misses + self.m_min = math.min(self.m_min, other.m_min) + self.m_max = math.max(self.m_max, other.m_max) + + local total_m_hits = self.m_hits + other.m_hits + if total_m_hits > 0 then + self.m_avg = self.m_avg * self.m_hits/total_m_hits + + other.m_avg * other.m_hits/total_m_hits + else + self.m_avg = 0 + end + + self.m_crits = self.m_crits + other.m_crits + self.m_crit_min = math.min(self.m_crit_min, other.m_crit_min) + self.m_crit_max = math.max(self.m_crit_max, other.m_crit_max) + + local total_m_crits = self.m_crits + other.m_crits + if total_m_crits > 0 then + self.m_crit_avg = self.m_crit_avg * self.m_crits / total_m_crits + + other.m_crit_avg * other.m_crits / total_m_crits + else + self.m_crit_avg = 0 + end + + self.r_hits = self.r_hits + other.r_hits + self.r_misses = self.r_misses + other.r_misses + self.r_min = math.min(self.r_min, other.r_min) + self.r_max = math.max(self.r_max, other.r_max) + + local total_r_hits = self.r_hits + other.r_hits + if total_r_hits > 0 then + self.r_avg = self.r_avg * self.r_hits/total_r_hits + + other.r_avg * other.r_hits/total_r_hits + else + self.r_avg = 0 + end + + self.r_crits = self.r_crits + other.r_crits + self.r_crit_min = math.min(self.r_crit_min, other.r_crit_min) + self.r_crit_max = math.max(self.r_crit_max, other.r_crit_max) + + local total_r_crits = self.r_crits + other.r_crits + if total_r_crits > 0 then + self.r_crit_avg = self.r_crit_avg * self.r_crits / total_r_crits + + other.r_crit_avg * other.r_crits / total_r_crits + else + self.r_crit_avg = 0 + end + + self.jobabils = self.jobabils + other.jobabils + self.spells = self.spells + other.spells +end + + + + +return MergedPlayer + +--[[ +Copyright (c) 2013, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] + + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/player.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/player.lua new file mode 100644 index 0000000..18bcf94 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/player.lua @@ -0,0 +1,178 @@ +--[[ +Object to encapsulate Player battle data + +For each mob fought, a separate player instance will be stored. Therefore +there will be multiple Player instances for each actual player in the game. +This allows for easier mob filtering. +]] + +local Player = {} + +function Player:new (o) + o = o or {} + + assert(o.name, "Must pass a name to player constructor") + -- attrs should be defined in Player above but due to interpreter bug it's here for now + local attrs = { + clock = nil, -- specific DPS clock for this player + damage = 0, -- total damage done by this player + ws = T{}, -- table of all WS and their corresponding damage + ws_misses = 0, -- total ws misses + m_hits = 0, -- total melee hits + m_misses = 0, -- total melee misses + m_min = math.huge, -- minimum melee damage + m_max = 0, -- maximum melee damage + m_avg = 0, -- avg melee damage + m_crits = 0, -- total melee crits + m_crit_min = math.huge, -- minimum melee crit + m_crit_max = 0, -- maximum melee crit + m_crit_avg = 0, -- avg melee crit + r_hits = 0, -- total ranged hits + r_min = math.huge, -- minimum ranged damage + r_max = 0, -- maximum ranged damage + r_avg = 0, -- avg ranged damage + r_misses = 0, -- total ranged misses + r_crits = 0, -- total ranged crits + r_crit_min = math.huge, -- minimum ranged crit + r_crit_max = 0, -- maximum ranged crit + r_crit_avg = 0, -- avg ranged crit + jobabils = 0, -- total damage from JAs + spells = 0, -- total damage from spells + + parries = 0, -- total number of parries + blocks = 0, -- total number of blocks/guards + nonblocks = 0, -- total number of nonblocks + evades = 0, -- total number of evades + damage_taken = 0, -- total damage taken by this player + + } + attrs.name = o.name + o = attrs + if o.name:match('^Skillchain%(') then + o.is_sc = true + else + o.is_sc = false + end + + setmetatable(o, self) + self.__index = self + + return o +end + + +function Player:add_damage(damage) + self.damage = self.damage + damage +end + + +function Player:add_ws_damage(ws_name, damage) + --[[ + if not self.ws[ws_name] then + self.ws[ws_name] = L{} + end + + self.ws[ws_name]:append(damage) + ]] + self.ws:append(damage) + self.damage = self.damage + damage +end + + +function Player:add_m_hit(damage) + -- increment hits + self.m_hits = self.m_hits + 1 + + -- update min/max/avg melee values + self.m_min = math.min(self.m_min, damage) + self.m_max = math.max(self.m_max, damage) + self.m_avg = self.m_avg * (self.m_hits - 1)/self.m_hits + damage/self.m_hits + + -- accumulate damage + self.damage = self.damage + damage +end + + +function Player:add_m_crit(damage) + -- increment crits + self.m_crits = self.m_crits + 1 + + -- update min/max/avg melee values + self.m_crit_min = math.min(self.m_crit_min, damage) + self.m_crit_max = math.max(self.m_crit_max, damage) + self.m_crit_avg = self.m_crit_avg * (self.m_crits - 1)/self.m_crits + damage/self.m_crits + + -- accumulate damage + self.damage = self.damage + damage +end + +function Player:incr_m_misses() self.m_misses = self.m_misses + 1 end + +function Player:incr_ws_misses() self.ws_misses = self.ws_misses + 1 end + +function Player:add_r_hit(damage) + -- increment hits + self.r_hits = self.r_hits + 1 + + -- update min/max/avg melee values + self.r_min = math.min(self.r_min, damage) + self.r_max = math.max(self.r_max, damage) + self.r_avg = self.r_avg * (self.r_hits - 1)/self.r_hits + damage/self.r_hits + + -- accumulate damage + self.damage = self.damage + damage +end + + +function Player:add_r_crit(damage) + -- increment crits + self.r_crits = self.r_crits + 1 + + -- update min/max/avg melee values + self.r_crit_min = math.min(self.r_crit_min, damage) + self.r_crit_max = math.max(self.r_crit_max, damage) + self.r_crit_avg = self.r_crit_avg * (self.r_crits - 1)/self.r_crits + damage/self.r_crits + + -- accumulate damage + self.damage = self.damage + damage +end + + +function Player:incr_r_misses() self.r_misses = self.r_misses + 1 end + +-- Returns the name of this player +function Player:get_name() return self.name end + + + +return Player + +--[[ +Copyright (c) 2013, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] + + diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/readme.txt b/Data/DefaultContent/Libraries/addons/addons/scoreboard/readme.txt new file mode 100644 index 0000000..1e5ff45 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/readme.txt @@ -0,0 +1,120 @@ +Author: Suji +Version: 1.09 +Addon to show alliance DPS and damage in real time. +Abbreviation: //sb + +This addon allows players to see their DPS live while fighting enemies. Party +and alliance member DPS is also dispalyed. In addition to DPS, each player's +total damage and their percent contribution is also displayed. + +Notable features: +* Live DPS +* You can still parse damage even if you enable chat filters. +* Ability to filter only the mobs you want to see damage for. +* 'Report' command for reporting damage back to where you like. + +DPS accumulation is active whenever anyone in your alliance is currently +in battle. + +All in-game commands are prefixed with "//sb" or "//scoreboard", for +example: "//sb help". + +Command list: +* HELP + Displays the help text + +* POS <x> <y> + Positions the scoreboard to the given coordinates + +* RESET + Resets all the data that's been tracked so far. + +* REPORT [<target>] + Reports the damage. With no argument, it will go to whatever you have + your current chatmode set to. You may also pass the standard FFXI chat + abbreviations as arguments. Support arguments are 's', 't', 'p', 'l'. + If you pass 't' (for tell), you must also pass a player name to send + the tell to. Examples: + //sb report Reports to current chatmode + //sb report l Reports to your linkshell + //sb report t suji Reports in tell to Suji + +* REPORTSTAT <stat> [<playerName>] [<target>] + RS <stat> [<playerName>] [<target>] + Reports the given stat. Supported stats are: + mavg, mrange, acc, ravg, rrange, racc, critavg, critrange, crit, + rcritavg, rcritrange, rcrit, wsavg, wsacc + + 'playerName' may be the name of a player if you wish to see only one player. + + For 'target', with no argument, it will go to whatever you have + your current chatmode set to. You may also pass the standard FFXI chat + abbreviations as arguments. Support arguments are 's', 't', 'p', 'l'. + If you pass 't' (for tell), you must also pass a player name to send + the tell to. + + Examples: + //sb reportstat acc -- Sends acc report your default chatmode + //sb rs crit -- Same as above + //sb rs crit p -- Explicitly to party + //sb rs acc tell suji -- Sends acc to Suji + //sb rs acc t suji -- Same as above + //sb rs acc tulia t suji -- Report accuracy for Tulia only and send it in tell to Suji + +* FILTER + This takes one of three sub-commands. + * FILTER SHOW + Shows the current mob filters. + +* FILTER ADD <mob1> <mob2> ... + Adds mob(s) to the filters. These can all be substrings. Legal Lua + patterns are also allowed. + + * FILTER CLEAR + Clears all mobs from the filter. + +* VISIBLE + Toggles the visibility of the scoreboard. Data will continue to + accumulate even while it is hidden. + +* STAT <statname> [<player>] + View specific parser stats. This will respect the current filter settings. + Valid stats are: acc, racc, crit, rcrit + Examples: + //sb stat acc Shows accuracy for everyone + //sb stat crit Flippant Only show crit rate for Flippant + +The settings file, located in addons/scoreboard/data/settings.xml, contains +additional configuration options: +* posX - x coordinate for position +* posY - y coordinate for position +* numPlayers - The maximum number of players to display damage for +* bgTransparency - Transparency level for the background. 0-255 range +* font - The font for the Scoreboard. This defaults to Courier but it + it may be changed to one of the following fonts: + Fixedsys, Lucida Console, Courier, Courier New, MS Mincho, + Consolas, Dejavu Sans Mono. +* fontsize - Size of Scoreboard's font +* sbcolor - Color of scoreboard's chat log output +* showallidps - Set to true to display the alliance DPS, false otherwise. +* resetfilters - Set to true if you want filters reset when you "//sb reset", false otherwise. +* showfellow - Set to true to display your adventuring fellow's DPS, false otherwise. + +Caveats: +* DPS is an approximation, although I tested it manually and found it to + be very accurate. Because DPS accumulation is based on the game's notion + of when you are in battle, if someone else engages before you, your DPS + will suffer. Try to engage fast to get a better approximation. + +* The methods used in here cause some discrepancies with the data reported + by KParser. In some cases, Scoreboard will report more damage, which + generally indicates that KParser is not including something (ie, Scoreboard + will be more accurate). However, there are cases where KParser is reporting + damage that Scoreboard is not, and I'm currently focused on resolving this + issue in particular. + +* This addon is still in development. Please report any issues or feedback to + to me (Suji on Phoenix) on FFXIAH or Guildwork. + +Thanks to Flippant for all of the helpful feedback and comments and to Zumi +for encouraging me to write this in the first place. diff --git a/Data/DefaultContent/Libraries/addons/addons/scoreboard/scoreboard.lua b/Data/DefaultContent/Libraries/addons/addons/scoreboard/scoreboard.lua new file mode 100644 index 0000000..893cefb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/scoreboard/scoreboard.lua @@ -0,0 +1,555 @@ +-- Scoreboard addon for Windower4. See readme.md for a complete description. + +_addon.name = 'Scoreboard' +_addon.author = 'Suji' +_addon.version = '1.14' +_addon.commands = {'sb', 'scoreboard'} + +require('tables') +require('strings') +require('maths') +require('logger') +require('actions') +local file = require('files') +config = require('config') + +local Display = require('display') +local display +dps_clock = require('dpsclock'):new() -- global for now +dps_db = require('damagedb'):new() -- global for now + +------------------------------------------------------- + +-- Conventional settings layout +local default_settings = {} +default_settings.numplayers = 8 +default_settings.sbcolor = 204 +default_settings.showallidps = true +default_settings.resetfilters = true +default_settings.visible = true +default_settings.showfellow = true +default_settings.UpdateFrequency = 0.5 +default_settings.combinepets = true + +default_settings.display = {} +default_settings.display.pos = {} +default_settings.display.pos.x = 500 +default_settings.display.pos.y = 100 + +default_settings.display.bg = {} +default_settings.display.bg.alpha = 200 +default_settings.display.bg.red = 0 +default_settings.display.bg.green = 0 +default_settings.display.bg.blue = 0 + +default_settings.display.text = {} +default_settings.display.text.size = 10 +default_settings.display.text.font = 'Courier New' +default_settings.display.text.fonts = {} +default_settings.display.text.alpha = 255 +default_settings.display.text.red = 255 +default_settings.display.text.green = 255 +default_settings.display.text.blue = 255 + +settings = config.load(default_settings) + +-- Accepts msg as a string or a table +function sb_output(msg) + local prefix = 'SB: ' + local color = settings['sbcolor'] + + if type(msg) == 'table' then + for _, line in ipairs(msg) do + windower.add_to_chat(color, prefix .. line) + end + else + windower.add_to_chat(color, prefix .. msg) + end +end + +-- Handle addon args +windower.register_event('addon command', function() + local chatmodes = S{'s', 'l', 'l2', 'p', 't', 'say', 'linkshell', 'linkshell2', 'party', 'tell', 'echo'} + + return function(command, ...) + if command == 'e' then + assert(loadstring(table.concat({...}, ' ')))() + return + end + + command = (command or 'help'):lower() + local params = {...} + + if command == 'help' then + sb_output('Scoreboard v' .. _addon.version .. '. Author: Suji') + sb_output('sb help : Shows help message') + sb_output('sb pos <x> <y> : Positions the scoreboard') + sb_output('sb reset : Reset damage') + sb_output('sb report [<target>] : Reports damage. Can take standard chatmode target options.') + sb_output('sb reportstat <stat> [<player>] [<target>] : Reports the given stat. Can take standard chatmode target options. Ex: //sb rs acc p') + sb_output('Valid chatmode targets are: ' .. chatmodes:concat(', ')) + sb_output('sb filter show : Shows current filter settings') + sb_output('sb filter add <mob1> <mob2> ... : Add mob patterns to the filter (substrings ok)') + sb_output('sb filter clear : Clears mob filter') + sb_output('sb visible : Toggles scoreboard visibility') + sb_output('sb stat <stat> [<player>]: Shows specific damage stats. Respects filters. If player isn\'t specified, ' .. + 'stats for everyone are displayed. Valid stats are:') + sb_output(dps_db.player_stat_fields:tostring():stripchars('{}"')) + elseif command == 'pos' then + if params[2] then + local posx, posy = tonumber(params[1]), tonumber(params[2]) + settings.display.pos.x = posx + settings.display.pos.y = posy + config.save(settings) + display:set_position(posx, posy) + end + elseif command == 'set' then + if not params[2] then + return + end + + local setting = params[1] + if setting == 'combinepets' then + if params[2] == 'true' then + settings.combinepets = true + elseif params[2] == 'false' then + settings.combinepets = false + else + error("Invalid value for 'combinepets'. Must be true or false.") + return + end + settings:save() + sb_output("Setting 'combinepets' set to " .. tostring(settings.combinepets)) + elseif setting == 'numplayers' then + settings.numplayers = tonumber(params[2]) + settings:save() + display:update() + sb_output("Setting 'numplayers' set to " .. settings.numplayers) + elseif setting == 'bgtransparency' then + settings.display.bg.alpha = tonumber(params[2]) + settings:save() + display:update() + sb_output("Setting 'bgtransparency' set to " .. settings.display.bg.alpha) + elseif setting == 'font' then + settings.display.text.font = params[2] + settings:save() + display:update() + sb_output("Setting 'font' set to " .. settings.display.text.font) + elseif setting == 'sbcolor' then + settings.sbcolor = tonumber(params[2]) + settings:save() + sb_output("Setting 'sbcolor' set to " .. settings.sbcolor) + elseif setting == 'showallidps' then + if params[2] == 'true' then + settings.showallidps = true + elseif params[2] == 'false' then + settings.showallidps = false + else + error("Invalid value for 'showallidps'. Must be true or false.") + return + end + + settings:save() + sb_output("Setting 'showalldps' set to " .. tostring(settings.showallidps)) + elseif setting == 'resetfilters' then + if params[2] == 'true' then + settings.resetfilters = true + elseif params[2] == 'false' then + settings.resetfilters = false + else + error("Invalid value for 'resetfilters'. Must be true or false.") + return + end + + settings:save() + sb_output("Setting 'resetfilters' set to " .. tostring(settings.resetfilters)) + elseif setting == 'showfellow' then + if params[2] == 'true' then + settings.showfellow = true + elseif params[2] == 'false' then + settings.showfellow = false + else + error("Invalid value for 'showfellow'. Must be true or false.") + return + end + + settings:save() + sb_output("Setting 'showfellow' set to " .. tostring(settings.showfellow)) + end + elseif command == 'reset' then + reset() + elseif command == 'report' then + local arg = params[1] + local arg2 = params[2] + + if arg then + if chatmodes:contains(arg) then + if arg == 't' or arg == 'tell' then + if not arg2 then + -- should be a valid player name + error('Invalid argument for report t: Please include player target name.') + return + elseif not arg2:match('^[a-zA-Z]+$') then + error('Invalid argument for report t: ' .. arg2) + end + end + else + error('Invalid parameter passed to report: ' .. arg) + return + end + end + + display:report_summary(arg, arg2) + + elseif command == 'visible' then + display:update() + display:visibility(not settings.visible) + + elseif command == 'filter' then + local subcmd + if params[1] then + subcmd = params[1]:lower() + else + error('Invalid option to //sb filter. See //sb help') + return + end + + if subcmd == 'add' then + for i=2, #params do + dps_db:add_filter(params[i]) + end + display:update() + elseif subcmd == 'clear' then + dps_db:clear_filters() + display:update() + elseif subcmd == 'show' then + display:report_filters() + else + error('Invalid argument to //sb filter') + end + elseif command == 'stat' then + if not params[1] or not dps_db.player_stat_fields:contains(params[1]:lower()) then + error('Must pass a stat specifier to //sb stat. Valid arguments: ' .. + dps_db.player_stat_fields:tostring():stripchars('{}"')) + else + local stat = params[1]:lower() + local player = params[2] + display:show_stat(stat, player) + end + elseif command == 'reportstat' or command == 'rs' then + if not params[1] or not dps_db.player_stat_fields:contains(params[1]:lower()) then + error('Must pass a stat specifier to //sb reportstat. Valid arguments: ' .. + dps_db.player_stat_fields:tostring():stripchars('{}"')) + return + end + + local stat = params[1]:lower() + local arg2 = params[2] -- either a player name or a chatmode + local arg3 = params[3] -- can only be a chatmode + + -- The below logic is obviously bugged if there happens to be a player named "say", + -- "party", "linkshell" etc but I don't care enough to account for those people! + + if chatmodes:contains(arg2) then + -- Arg2 is a chatmode so we assume this is a 3-arg version (no player specified) + display:report_stat(stat, {chatmode = arg2, telltarget = arg3}) + else + -- Arg2 is not a chatmode, so we assume it's a player name and then see + -- if arg3 looks like an optional chatmode. + if arg2 and not arg2:match('^[a-zA-Z]+$') then + -- should be a valid player name + error('Invalid argument for reportstat t ' .. arg2) + return + end + + if arg3 and not chatmodes:contains(arg3) then + error('Invalid argument for reportstat t ' .. arg2 .. ', must be a valid chatmode.') + return + end + + display:report_stat(stat, {player = arg2, chatmode = arg3, telltarget = params[4]}) + end + elseif command == 'fields' then + error("Not implemented yet.") + return + elseif command == 'save' then + if params[1] then + if not params[1]:match('^[a-ZA-Z0-9_-,.:]+$') then + error("Invalid filename: " .. params[1]) + return + end + save(params[1]) + else + save() + end + else + error('Unrecognized command. See //sb help') + end + end +end()) + +local months = { + 'jan', 'feb', 'mar', 'apr', + 'may', 'jun', 'jul', 'aug', + 'sep', 'oct', 'nov', 'dec' +} + + +function save(filename) + if not filename then + local date = os.date("*t", os.time()) + filename = string.format("sb_%s-%d-%d-%d-%d.txt", + months[date.month], + date.day, + date.year, + date.hour, + date.min) + end + local parse = file.new('data/parses/' .. filename) + + if parse:exists() then + local dup_path = file.new(parse.path) + local dup = 0 + + while dup_path:exists() do + dup_path = file.new(parse.path .. '.' .. dup) + dup = dup + 1 + end + parse = dup_path + end + + parse:create() +end + + +-- Resets application state +function reset() + if settings.resetfilters then + dps_db:clear_filters() + end + display:reset() + dps_clock:reset() + dps_db:reset() +end + + +display = Display:new(settings, dps_db) + + +-- Keep updates flowing +local function update_dps_clock() + local player = windower.ffxi.get_player() + local pet + if player ~= nil then + local player_mob = windower.ffxi.get_mob_by_id(player.id) + if player_mob ~= nil then + local pet_index = player_mob.pet_index + if pet_index ~= nil then + pet = windower.ffxi.get_mob_by_index(pet_index) + end + end + end + if player and (player.in_combat or (pet ~= nil and pet.status == 1)) then + dps_clock:advance() + else + dps_clock:pause() + end + + display:update() +end + + +-- Returns all mob IDs for anyone in your alliance, including their pets. +function get_ally_mob_ids() + local allies = T{} + local party = windower.ffxi.get_party() + + for _, member in pairs(party) do + if type(member) == 'table' and member.mob then + allies:append(member.mob.id) + if member.mob.pet_index and member.mob.pet_index> 0 and windower.ffxi.get_mob_by_index(member.mob.pet_index) then + allies:append(windower.ffxi.get_mob_by_index(member.mob.pet_index).id) + end + end + end + + if settings.showfellow then + local fellow = windower.ffxi.get_mob_by_target("ft") + if fellow ~= nil then + allies:append(fellow.id) + end + end + + return allies +end + + +-- Returns true if is someone (or a pet of someone) in your alliance. +function mob_is_ally(mob_id) + -- get zone-local ids of all allies and their pets + return get_ally_mob_ids():contains(mob_id) +end + + +function action_handler(raw_actionpacket) + local actionpacket = ActionPacket.new(raw_actionpacket) + + local category = actionpacket:get_category_string() + + local player = windower.ffxi.get_player() + local pet + if player ~= nil then + local player_mob = windower.ffxi.get_mob_by_id(player.id) + if player_mob ~= nil then + local pet_index = player_mob.pet_index + if pet_index ~= nil then + pet = windower.ffxi.get_mob_by_index(pet_index) + end + end + end + if not player or not (windower.ffxi.get_player().in_combat or (pet ~= nil and pet.status == 1)) then + -- nothing to do + return + end + + for target in actionpacket:get_targets() do + for subactionpacket in target:get_actions() do + if (mob_is_ally(actionpacket.raw.actor_id) and not mob_is_ally(target.raw.id)) then + -- Ignore actions within the alliance, but parse all alliance-outwards or outwards-alliance packets. + local main = subactionpacket:get_basic_info() + local add = subactionpacket:get_add_effect() + local spike = subactionpacket:get_spike_effect() + if main.message_id == 1 then + dps_db:add_m_hit(target:get_name(), create_mob_name(actionpacket), main.param) + elseif main.message_id == 67 then + dps_db:add_m_crit(target:get_name(), create_mob_name(actionpacket), main.param) + elseif main.message_id == 15 or main.message_id == 63 then + dps_db:incr_misses(target:get_name(), create_mob_name(actionpacket)) + elseif main.message_id == 353 then + dps_db:add_r_crit(target:get_name(), create_mob_name(actionpacket), main.param) + elseif T{157, 352, 576, 577}:contains(main.message_id) then + dps_db:add_r_hit(target:get_name(), create_mob_name(actionpacket), main.param) + elseif main.message_id == 353 then + dps_db:add_r_crit(target:get_name(), create_mob_name(actionpacket), main.param) + elseif main.message_id == 354 then + dps_db:incr_r_misses(target:get_name(), create_mob_name(actionpacket)) + elseif main.message_id == 188 then + dps_db:incr_ws_misses(target:get_name(), create_mob_name(actionpacket)) + elseif main.resource and main.resource == 'weapon_skills' and main.conclusion then + dps_db:add_ws_damage(target:get_name(), create_mob_name(actionpacket), main.param, main.spell_id) + -- Siren's Hysteric Assault does HP drain and falls under message_id 802 + elseif main.message_id == 802 then + dps_db:add_damage(target:get_name(), create_mob_name(actionpacket), main.param) + elseif main.conclusion then + if main.conclusion.subject == 'target' and T(main.conclusion.objects):contains('HP') and main.param ~= 0 then + dps_db:add_damage(target:get_name(), create_mob_name(actionpacket), (main.conclusion.verb == 'gains' and -1 or 1)*main.param) + end + end + + if add and add.conclusion then + local actor_name = create_mob_name(actionpacket) + if T{196,223,288,289,290,291,292, + 293,294,295,296,297,298,299, + 300,301,302,385,386,387,388, + 389,390,391,392,393,394,395, + 396,397,398,732,767,768,769,770}:contains(add.message_id) then + actor_name = string.format("Skillchain(%s%s)", actor_name:sub(1, 3), + actor_name:len() > 3 and '.' or '') + end + if add.conclusion.subject == 'target' and T(add.conclusion.objects):contains('HP') and add.param ~= 0 then + dps_db:add_damage(target:get_name(), actor_name, (add.conclusion.verb == 'gains' and -1 or 1)*add.param) + end + end + if spike and spike.conclusion then + if spike.conclusion.subject == 'target' and T(spike.conclusion.objects):contains('HP') and spike.param ~= 0 then + dps_db:add_damage(target:get_name(), create_mob_name(actionpacket), (spike.conclusion.verb == 'gains' and -1 or 1)*spike.param) + end + end + elseif (mob_is_ally(target.raw.id) and not mob_is_ally(actionpacket.raw.actor_id)) then + local spike = subactionpacket:get_spike_effect() + if spike and spike.conclusion then + if spike.conclusion.subject == 'actor' and T(spike.conclusion.objects):contains('HP') and spike.param ~= 0 then + dps_db:add_damage(create_mob_name(actionpacket), target:get_name(), (spike.conclusion.verb == 'loses' and 1 or -1)*spike.param) + end + end + end + end + end +end + +ActionPacket.open_listener(action_handler) + + function find_pet_owner_name(actionpacket) + local pet = windower.ffxi.get_mob_by_id(actionpacket:get_id()) + local party = windower.ffxi.get_party() + + local name = nil + + for _, member in pairs(party) do + if type(member) == 'table' and member.mob then + if member.mob.pet_index and member.mob.pet_index> 0 and pet.index == member.mob.pet_index then + name = member.mob.name + break + end + end + end + return name, pet.name + end + + function create_mob_name(actionpacket) + local actor = actionpacket:get_actor_name() + local result = '' + local owner, pet = find_pet_owner_name(actionpacket) + if owner ~= nil then + if string.len(actor) > 8 then + result = string.sub(actor, 1, 7)..'.' + else + result = actor + end + if settings.combinepets then + result = '' + else + result = actor + end + if pet then + result = '('..owner..')'..' '..pet + end + else + return actor + end + return result + end + +config.register(settings, function(settings) + update_dps_clock:loop(settings.UpdateFrequency) + display:visibility(display.visible and windower.ffxi.get_info().logged_in) +end) + + +--[[ +Copyright � 2013-2014, Jerry Hebert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Scoreboard nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL JERRY HEBERT 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. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/send/readme.md b/Data/DefaultContent/Libraries/addons/addons/send/readme.md new file mode 100644 index 0000000..e77c79f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/send/readme.md @@ -0,0 +1,28 @@ +## Usage + +``` +//send <target> <command> +``` + +The target can be the receiver's name, `@all` to send to all instances, `@other` to send to all instances excluding the sender or `@<job>` to send to a specific job. The command can be any valid FFXI or Windower command. + +Examples: +``` +//send @all /ma "Blizzard" <t> + +//send @whm /ma "Haste" <me> + +//send Mymule //reload timers +``` + +## Sending entity IDs + +Entity IDs can be sent to the receiver by appending `id` to a target specifier, like `<tid>` for the target's ID. This works not just for `<tid>` but for all common targets mentioned [here](https://github.com/Windower/Lua/wiki/FFXI-Functions#windowerffxiget_mob_by_targettarget), i.e. `<laststid>`, `<meid`, etc. To be able to use in-game actions with target IDs like that requires GearSwap to be loaded on the receiver's end. + +Examples: +``` +//send Mymule /ma "Stun" <tid> + +/ta <stnpc> +//send Mymule /ma "Blizzard" <laststid> +``` diff --git a/Data/DefaultContent/Libraries/addons/addons/send/send.lua b/Data/DefaultContent/Libraries/addons/addons/send/send.lua new file mode 100644 index 0000000..8df543b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/send/send.lua @@ -0,0 +1,101 @@ +_addon.version = '1.0' +_addon.name = 'Send' +_addon.command = 'send' +_addon.author = 'Byrth' + +windower.register_event('addon command',function (...) + local term = table.concat({...}, ' ') + + term = term:gsub('<(%a+)id>', function(target_string) + local entity = windower.ffxi.get_mob_by_target(target_string) + return entity and entity.id or '<' .. target_string .. 'id>' + end) + + local broken_init = split(term, ' ') + local qual = table.remove(broken_init,1) + local player = windower.ffxi.get_player() + + if qual:lower()==player['name']:lower() then + if broken_init ~= nil then + relevant_msg(table.concat(broken_init,' ')) + end + elseif qual:lower()=='@all' or qual:lower()=='@'..player.main_job:lower() then + if broken_init ~= nil then + relevant_msg(table.concat(broken_init,' ')) + end + windower.send_ipc_message('send ' .. term) + else + windower.send_ipc_message('send ' .. term) + end +end) + +windower.register_event('ipc message',function (msg) + local broken = split(msg, ' ') + + local command = table.remove(broken, 1) + if command ~= 'send' then + return + end + + if #broken < 2 then return end + + local qual = table.remove(broken,1) + local player = windower.ffxi.get_player() + if qual:lower()==player.name:lower() then + relevant_msg(table.concat(broken,' ')) + end + if string.char(qual:byte(1)) == '@' then + local arg = string.char(qual:byte(2, qual:len())) + if arg:upper() == player.main_job:upper() then + if broken ~= nil then + relevant_msg(table.concat(broken,' ')) + end + elseif arg:upper() == 'ALL' then + if broken ~= nil then + relevant_msg(table.concat(broken,' ')) + end + elseif arg:upper() == 'OTHERS' then + if broken ~= nil then + relevant_msg(table.concat(broken,' ')) + end + end + end +end) + +function split(msg, match) + if msg == nil then return '' end + local length = msg:len() + local splitarr = {} + local u = 1 + while u <= length do + local nextanch = msg:find(match,u) + if nextanch ~= nil then + splitarr[#splitarr+1] = msg:sub(u,nextanch-match:len()) + if nextanch~=length then + u = nextanch+match:len() + else + u = length + end + else + splitarr[#splitarr+1] = msg:sub(u,length) + u = length+1 + end + end + return splitarr +end + +function relevant_msg(msg) + local player = windower.ffxi.get_player() + + if msg:sub(1,2)=='//' then + windower.send_command(msg:sub(3)) + elseif msg:sub(1,1)=='/' then + windower.send_command('input '..msg) + elseif msg:sub(1,3)=='atc' then + windower.add_to_chat(55,msg:sub(5)) + else + windower.send_command(msg) + end + +end + diff --git a/Data/DefaultContent/Libraries/addons/addons/setbgm/README b/Data/DefaultContent/Libraries/addons/addons/setbgm/README new file mode 100644 index 0000000..2eda1cb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/setbgm/README @@ -0,0 +1,17 @@ +This script is an addon to be used with Windower for Final Fantasy XI +that allows you to set various types of music in the game. + +You can report issues, get the latest version, and view the source at: + https://github.com/svanheulen/setbgm-windower-addon + +The available commands are: + setbgm list [music] + Lists all of the available songs in the game. + setbgm list type + Lists the different types of music you can set. + setbgm <song id> + Sets the background music given by the ID. + setbgm <song id> <music type id> + Sets only a given type of music. + setbgm <song id> <song id> <song id> <song id> <song id> <song id> <song id> <song id> + Sets all types of music at once.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/setbgm/setbgm.lua b/Data/DefaultContent/Libraries/addons/addons/setbgm/setbgm.lua new file mode 100644 index 0000000..000c7be --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/setbgm/setbgm.lua @@ -0,0 +1,167 @@ +--[[ +Copyright © 2014, Seth VanHeulen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR 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. +--]] + +_addon.name = 'setbgm' +_addon.version = '1.2.3' +_addon.command = 'setbgm' +_addon.author = 'Seth VanHeulen (Acacia@Odin)' + +require('chat') +require('pack') + +music_types = { + [0]='Idle (Day)', + [1]='Idle (Night)', + [2]='Battle (Solo)', + [3]='Battle (Party)', + [4]='Chocobo', + [5]='Death', + [6]='Mog House', + [7]='Fishing' +} + +songs = { + [25]='Voracious Resurgence Unknown 1', [26]='Voracious Resurgence Unknown 2', [27]='Voracious Resurgence Unknown 3', [28]='Voracious Resurgence Unknown 4', [29]="Devils' Delight", [30]="Odyssey - Bumba", [31]='Voracious Resurgence Unknown 5', [32]='Voracious Resurgence Unknown 6', + [40]='Cloister of Time and Souls', [41]='Royal Wanderlust', [42]='Snowdrift Waltz', [43]='Troubled Shadows', [44]='Where Lords Rule Not', [45]='Summers Lost', [46]='Goddess Divine', [47]='Echoes of Creation', [48]='Main Theme', [49]='Luck of the Mog', + [50]='Feast of the Ladies', [51]='Abyssea - Scarlet Skies, Shadowed Plains', [52]='Melodies Errant', [53]='Shinryu', [54]='Everlasting Bonds', [55]='Provenance Watcher', [56]='Where it All Begins', [57]='Steel Sings, Blades Dance', [58]='A New Direction', [59]='The Pioneers', + [60]='Into Lands Primeval - Ulbuka', [61]="Water's Umbral Knell", [62]='Keepers of the Wild', [63]='The Sacred City of Adoulin', [64]='Breaking Ground', [65]='Hades', [66]='Arciela', [67]='Mog Resort', [68]='Worlds Away', [69]="Distant Worlds (Nanaa Mihgo's version)", + [70]='Monstrosity', [71]="The Pioneers (Nanaa Mihgo's version)", [72]='The Serpentine Labyrinth', [73]='The Divine', [74]='Clouds Over Ulbuka', [75]='The Price', [76]='Forever Today', [77]='Distant Worlds (Instrumental)', [78]='Forever Today (Instrumental)', [79]='Iroha', + [80]='The Boundless Black', [81]='Isle of the Gods', [82]='Wail of the Void', [83]="Rhapsodies of Vana'diel", [84]="Full Speed Ahead!", [85]="Times Grow Tense", [86]="Shadow Lord (Record Keeper Remix)", [87]="For a Friend", [88]="Between Dreams and Reality", [89]="Disjoined One", [90]="Winds of Change", + [101]='Battle Theme', [102]='Battle in the Dungeon #2', [103]='Battle Theme #2', [104]='A Road Once Traveled', [105]='Mhaura', [106]='Voyager', [107]="The Kingdom of San d'Oria", [108]="Vana'diel March", [109]='Ronfaure', + [110]='The Grand Duchy of Jeuno', [111]='Blackout', [112]='Selbina', [113]='Sarutabaruta', [114]='Batallia Downs', [115]='Battle in the Dungeon', [116]='Gustaberg', [117]="Ru'Lude Gardens", [118]='Rolanberry Fields', [119]='Awakening', + [120]="Vana'diel March #2", [121]='Shadow Lord', [122]='One Last Time', [123]='Hopelessness', [124]='Recollection', [125]='Tough Battle', [126]='Mog House', [127]='Anxiety', [128]='Airship', [129]='Hook, Line and Sinker', + [130]='Tarutaru Female', [131]='Elvaan Female', [132]='Elvaan Male', [133]='Hume Male', [134]='Yuhtunga Jungle', [135]='Kazham', [136]='The Big One', [137]='A Realm of Emptiness', [138]="Mercenaries' Delight", [139]='Delve', + [140]='Wings of the Goddess', [141]='The Cosmic Wheel', [142]='Fated Strife -Besieged-', [143]='Hellriders', [144]='Rapid Onslaught -Assault-', [145]='Encampment Dreams', [146]='The Colosseum', [147]='Eastward Bound...', [148]='Forbidden Seal', [149]='Jeweled Boughs', + [150]='Ululations from Beyond', [151]='The Federation of Windurst', [152]='The Republic of Bastok', [153]='Prelude', [154]='Metalworks', [155]='Castle Zvahl', [156]="Chateau d'Oraguille", [157]='Fury', [158]='Sauromugue Champaign', [159]='Sorrow', + [160]='Repression (Memoro de la Stono)', [161]='Despair (Memoro de la Stono)', [162]='Heavens Tower', [163]='Sometime, Somewhere', [164]='Xarcabard', [165]='Galka', [166]='Mithra', [167]='Tarutaru Male', [168]='Hume Female', [169]='Regeneracy', + [170]='Buccaneers', [171]='Altepa Desert', [172]='Black Coffin', [173]='Illusions in the Mist', [174]='Whispers of the Gods', [175]="Bandits' Market", [176]='Circuit de Chocobo', [177]='Run Chocobo, Run!', [178]='Bustle of the Capital', [179]="Vana'diel March #4", + [180]='Thunder of the March', [181]='Dash de Chocobo (Low Quality)', [182]='Stargazing', [183]="A Puppet's Slumber", [184]='Eternal Gravestone', [185]='Ever-Turning Wheels', [186]='Iron Colossus', [187]='Ragnarok', [188]='Choc-a-bye Baby', [189]='An Invisible Crown', + [190]="The Sanctuary of Zi'Tah", [191]='Battle Theme #3', [192]='Battle in the Dungeon #3', [193]='Tough Battle #2', [194]='Bloody Promises', [195]='Belief', [196]='Fighters of the Crystal', [197]='To the Heavens', [198]="Eald'narche", [199]="Grav'iton", + [200]='Hidden Truths', [201]='End Theme', [202]='Moongate (Memoro de la Stono)', [203]='Ancient Verse of Uggalepih', [204]="Ancient Verse of Ro'Maeve", [205]='Ancient Verse of Altepa', [206]='Revenant Maiden', [207]="Ve'Lugannon Palace", [208]='Rabao', [209]='Norg', + [210]="Tu'Lia", [211]="Ro'Maeve", [212]='Dash de Chocobo', [213]='Hall of the Gods', [214]='Eternal Oath', [215]='Clash of Standards', [216]='On this Blade', [217]='Kindred Cry', [218]='Depths of the Soul', [219]='Onslaught', + [220]='Turmoil', [221]='Moblin Menagerie - Movalpolos', [222]='Faded Memories - Promyvion', [223]='Conflict: March of the Hero', [224]='Dusk and Dawn', [225]="Words Unspoken - Pso'Xja", [226]='Conflict: You Want to Live Forever?', [227]='Sunbreeze Shuffle', [228]="Gates of Paradise - The Garden of Ru'Hmet", [229]='Currents of Time', + [230]='A New Horizon - Tavnazian Archipelago', [231]='Celestial Thunder', [232]='The Ruler of the Skies', [233]="The Celestial Capital - Al'Taieu", [234]='Happily Ever After', [235]='First Ode: Nocturne of the Gods', [236]='Fourth Ode: Clouded Dawn', [237]='Third Ode: Memoria de la Stona', [238]='A New Morning', [239]='Jeuno -Starlight Celebration-', + [240]='Second Ode: Distant Promises', [241]='Fifth Ode: A Time for Prayer', [242]='Unity', [243]="Grav'iton", [244]='Revenant Maiden', [245]='The Forgotten City - Tavnazian Safehold', [246]='March of the Allied Forces', [247]='Roar of the Battle Drums', [248]='Young Griffons in Flight', [249]='Run Maggot, Run!', + [250]='Under a Clouded Moon', [251]='Autumn Footfalls', [252]='Flowers on the Battlefield', [253]='Echoes of a Zypher', [254]='Griffons Never Die', + [900]='Distant Worlds' +} + +function set_music(music_type, song) + if music_type then + local m = tonumber(music_type) + if music_types[m] then + local s = tonumber(song) + if songs[s] then + windower.add_to_chat(207, 'Setting %s music: %s':format(music_types[m], songs[s]:color(200))) + windower.packets.inject_incoming(0x05F, 'IHH':pack(0x45F, m, s)) + else + windower.add_to_chat(167, 'Invalid song: %s':format(song)) + end + else + windower.add_to_chat(167, 'Invalid music type: %s':format(music_type)) + end + else + local s = tonumber(song) + if songs[s] then + windower.add_to_chat(207, 'Setting all music: %s':format(songs[s]:color(200))) + for music_type=0,7 do + windower.packets.inject_incoming(0x05F, 'IHH':pack(0x45F, music_type, s)) + end + else + windower.add_to_chat(167, 'Invalid song: %s':format(song)) + end + end +end + +function display_songs() + windower.add_to_chat(207, 'Available songs:') + for id=25,900,5 do + local output = ' ' + for i=0,4 do + if songs[id+i] then + output = output .. ' %s: %s':format(tostring(id+i):color(204), songs[id+i]) + end + end + if output ~= ' ' then + windower.add_to_chat(207, output) + end + end +end + +function display_music_types() + windower.add_to_chat(207, 'Available music types:') + local output = ' ' + for music_type=0,7 do + output = output .. ' %s: %s':format(tostring(music_type):color(204), music_types[music_type]) + end + windower.add_to_chat(207, output) +end + +function display_help() + windower.add_to_chat(167, 'Command usage:') + windower.add_to_chat(167, ' setbgm list [music|type]') + windower.add_to_chat(167, ' setbgm <song id> [<music type id>]') + windower.add_to_chat(167, ' setbgm <song id> <song id> <song id> <song id> <song id> <song id> <song id> <song id>') +end + +function setbgm_command(...) + local arg = {...} + if #arg == 1 and arg[1]:lower() == 'list' then + display_songs() + return + elseif #arg == 2 and arg[1]:lower() == 'list' and arg[2]:lower() == 'music' then + display_songs() + return + elseif #arg == 2 and arg[1]:lower() == 'list' and arg[2]:lower() == 'type' then + display_music_types() + return + elseif #arg == 1 then + set_music(nil, arg[1]) + return + elseif #arg == 2 then + set_music(arg[2], arg[1]) + return + elseif #arg == 8 then + set_music(0, arg[1]) + set_music(1, arg[2]) + set_music(2, arg[3]) + set_music(3, arg[4]) + set_music(4, arg[5]) + set_music(5, arg[6]) + set_music(6, arg[7]) + set_music(7, arg[8]) + return + end + display_help() +end + +windower.register_event('addon command', setbgm_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/shortcuts/helper_functions.lua b/Data/DefaultContent/Libraries/addons/addons/shortcuts/helper_functions.lua new file mode 100644 index 0000000..092f4cf --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shortcuts/helper_functions.lua @@ -0,0 +1,209 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +----------------------------------------------------------------------------------- +--Name: find_san() +--Args: +---- str (string) - string to be sanitized +----------------------------------------------------------------------------------- +--Returns: +---- sanitized string +----------------------------------------------------------------------------------- +function find_san(str) + if #str == 0 then return str end + + str = bracket_closer(str,0x28,0x29) + str = bracket_closer(str,0x5B,0x5D) + + -- strip precentages + local hanging_percent,num = 0,num + while str:byte(#str-hanging_percent) == 37 do + hanging_percent = hanging_percent + 1 + end + str = str:sub(1,#str-hanging_percent%2) + return str +end + +----------------------------------------------------------------------------------- +--Name: bracket_closer() +--Args: +---- str (string) - string to have its brackets closed +---- opener (number) - opening character's ASCII code +---- closer (number) - closing character's ASCII code +----------------------------------------------------------------------------------- +--Returns: +---- string with its opened brackets closed +----------------------------------------------------------------------------------- +function bracket_closer(str,opener,closer) + op,cl,opadd = 0,0,1 + for i=1,#str do + local ch = str:byte(i) + if ch == opener then + op = op +1 + opadd = i + elseif ch == closer then + cl = cl + 1 + end + end + if op > cl then + if opadd ~= #str then + str = str..string.char(closer) + else + str = str..str.char(0x7,closer) + end -- Close captures + end + return str +end + +----------------------------------------------------------------------------------- +--Name: strip() +--Args: +---- name (string): Name to be slugged +----------------------------------------------------------------------------------- +--Returns: +---- string with a gsubbed version of name that removes non-alphanumeric characters, +-------- forces the string to lower-case, and converts numbers to Roman numerals, +-------- which are upper case. +----------------------------------------------------------------------------------- +function strip(name) + return name:gsub('[^%w]',''):lower():gsub('(%d+)',to_roman) +end + + +----------------------------------------------------------------------------------- +--Name: to_roman() +--Args: +---- num (string or number): Number to be converted from Arabic to Roman numerals. +----------------------------------------------------------------------------------- +--Returns: +---- roman numerals that represent the passed number. +-------- This function returns ix for 9 instead of viiii. They are both valid, but +-------- FFXI uses the ix nomenclature so we will use that. +----------------------------------------------------------------------------------- +function to_roman(num) + if type(num) ~= 'number' then + num = tonumber(num) + if num == nil then + print('Debug to_roman') + return '' + end + end + if num>4599 then return tostring(num) end + + local retstr = '' + + if num == 0 then return 'zilch' end + if num == 1 then return '' end + + while num > 0 do + if num >= 1000 then + num = num - 1000 + retstr = retstr..'m' + elseif num >= 900 then + num = num - 900 + retstr = retstr..'cm' + elseif num >= 500 then + num = num - 500 + retstr = retstr..'d' + elseif num >= 400 then + num = num - 400 + retstr = retstr..'cd' + elseif num >= 100 then + num = num - 100 + retstr = retstr..'c' + elseif num >= 90 then + num = num - 90 + retstr = retstr..'xc' + elseif num >= 50 then + num = num - 50 + retstr = retstr..'l' + elseif num >= 40 then + num = num - 40 + retstr = retstr..'xl' + elseif num >= 10 then + num = num - 10 + retstr = retstr..'x' + elseif num == 9 then + num = num - 9 + retstr = retstr..'ix' + elseif num >= 5 then + num = num - 5 + retstr = retstr..'v' + elseif num == 4 then + num = num - 4 + retstr = retstr..'iv' + elseif num >= 1 then + num = num - 1 + retstr = retstr..'i' + end + end + + return retstr +end + + +----------------------------------------------------------------------------------- +--Name: check_usability() +--Args: +---- player (table): get_player() table +---- resource (string): name of the resource to be examined +---- id (num): ID of the spell/ability of interest +----------------------------------------------------------------------------------- +--Returns: +---- boolean : true indicates that the spell/ability is known to you and false indicates that it is not. +----------------------------------------------------------------------------------- +function check_usability(player,resource,id) + if resource == 'spells' and ( (res.spells[id].levels[player.main_job_id] and res.spells[id].levels[player.main_job_id] <= player.main_job_level) or + (res.spells[id].levels[player.sub_job_id] and res.spells[id].levels[player.sub_job_id] <= player.sub_job_level) ) then -- Should check to see if you know the spell + return true + elseif L(windower.ffxi.get_abilities()[resource] or {}):contains(id) then + return true + elseif resource == 'monster_skills' and player.main_job_id == 23 and (res.monstrosity[windower.ffxi.get_mjob_data().species].tp_moves[id] or 0) <= player.main_job_level then + return true + elseif resource == 'mounts' and math.floor((windower.packets.last_incoming(0x0AE):byte(math.floor(id/8)+5)%2^(id%8+1))/2^(id%8)) == 1 then + return true + end +end + + +----------------------------------------------------------------------------------- +--Name: debug_chat() +--Args: +---- str (string): string to be printed to the log +----------------------------------------------------------------------------------- +--Returns: +---- None +----------------------------------------------------------------------------------- +function debug_chat(str) + if not debugging then return end + + if tostring(str) then + windower.add_to_chat(8,str) + else + error('Debug chat is not a string',2) + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/shortcuts/readme.md b/Data/DefaultContent/Libraries/addons/addons/shortcuts/readme.md new file mode 100644 index 0000000..9d1dc0e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shortcuts/readme.md @@ -0,0 +1,40 @@ +#Shortcuts v2.9 +####written by Byrth + +Completes and properly formats commands (prefixed by at least one '/'), +including emotes and checks. This addon is part of a larger "replacing +spellcast" project, and represents the interpretation part of spellcast. + +####Commands/Settings: +None + +####Changelog: +v2.9 - 03/05/16 - Adjusted default target logic when the target is a Trust. +v2.8 - 11/27/15 - Changed how the non-action commands are handled (including secondary arguments). +v2.7 - 11/15/14 - Totally gutted and reworked the ambiguous spell handling system. It is much simpler now. +v2.6 - 9/27/14 - Fixed an error with absolute remapping of ambiguous spells +v2.5 - 8/30/14 - Expanded shortcuts to include single-slash commands. Expanded command prefixes to include double-slash commands. +v2.4 - 8/30/14 - Changed ambiguous case handling to respect specified prefixes. +v2.3 - 6/20/14 - Added new monstrosity skill disambiguation. +v2.2 - 5/19/14 - Accommodated new Resources changes. +v2.1 - 4/18/14 - Added custom aliases to Shortcuts. +v2.0 - 4/ 7/14 - Made Shortcuts calculate available spells/abilities in the case of ambiguous abilities. Moving towards an automated ambiguity-handling framework. +v1.9 - 3/20/14 - Changed Shortcuts over to use the resources library. +v1.8 - 1/ 6/14 - Fix target selection. Interpret the outgoing text as an ability first (instead of combination of ability and target). +v1.7 - 12/31/13 - Fixed st targets for shortcuts. +v1.6 - 12/12/13 - Fixed "target is nil" bug. +v1.5 - 12/7/13 - Various bugfixes and more complete support for pattern matching. +v1.4 - 11/17/13 - Improved ambiguous name handling using windower.ffxi.get_abilities(). +v1.3 - 11/15/13 - Added handling for "Corpse" spells and fixed a minor glitch in target creation. +v1.2 - 11/13/13 - Added abiguous case handling for amorph/bird spells. +v1.1 - 10/23/13 - Reduced "spell1" to "spell" and made some minor adjustments. +v1.0 - 10/16/13 - Fixed some ambiguous name processing issues with monsterskills. +v0.9 - 10/1/13 - Fixed targets.lua's interpretation of the target flags. +v0.8 - 08/14/13 - Fixed split(), which was causing errors when assembling resources. +v0.7 - 08/12/13 - Improved the hashing algorithm (better roman numeral conversion) and improved target creation again. Updated documentation. +v0.6 - 08/11/13 - Fixed autotranslate, eliminated valid commands that aren't prefixed by '/', and made target creation smarter. +v0.5 - 08/05/13 - Added handling for <st..> commands and support for target shorthands (t for <t>, stpc for <stpc>, etc.) +v0.4 - 07/25/13 - Added handling for Monstrosity. Added logging. Modified target handling. +v0.3 - 07/14/13 - Fixed an ambiguous_names error. Retooled some parts in anticipation of the new hook and Monstrosity. Added infinite loop detection. +v0.2 - 06/19/13 - Fixed the addon's capacity for infinite loops. Added safe auto-completion for party commands. Added target commands. +v0.1 - 06/15/13 - Created Addon
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/shortcuts/shortcuts.lua b/Data/DefaultContent/Libraries/addons/addons/shortcuts/shortcuts.lua new file mode 100644 index 0000000..a2f5da5 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shortcuts/shortcuts.lua @@ -0,0 +1,409 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.version = '2.903' +_addon.name = 'Shortcuts' +_addon.author = 'Byrth' +_addon.commands = {'shortcuts'} + + +debugging = false +logging = false +if windower.dir_exists('../addons/shortcuts/data/') and logging then + logfile = io.open('../addons/shortcuts/data/NormalLog'..tostring(os.clock())..'.log','w+') + logfile:write('\n\n','SHORTCUTS LOGGER HEADER: ',tostring(os.clock()),'\n') + logfile:flush() +end + +if windower.file_exists(windower.addon_path..'resources.lua') then + local result = os.remove(windower.addon_path..'resources.lua') + if not result then + os.rename(windower.addon_path..'resources.lua',windower.addon_path..'unnecessary.lua') + end +end + +if not windower.dir_exists(windower.addon_path..'data') then + windower.create_dir(windower.addon_path..'data') +end + +require 'sets' +require 'lists' +require 'helper_functions' +require 'tables' +require 'strings' +res = require 'resources' +config = require 'config' + +default_aliases = { + c1="Cure", + c2="Cure II", + c3="Cure III", + c4="Cure IV", + c5="Cure V", + c6="Cure VI", + r1="Raise", + r2="Raise II", + r3="Raise III", + pro1="Protectra", + pro2="Protectra II", + pro3="Protectra III", + pro4="Protectra IV", + pro5="Protectra V", + sh1="Shellra", + sh2="Shellra II", + sh3="Shellra III", + sh4="Shellra IV", + sh5="Shellra V", + she1="Shellra", + she2="Shellra II", + she3="Shellra III", + she4="Shellra IV", + she5="Shellra V", + bl="Blink", + ss="Stoneskin", + re1="Regen", + re2="Regen II", + re3="Regen III", + re4="Regen IV", + re5="Regen V", + holla="Teleport-Holla", + dem="Teleport-Dem", + mea="Teleport-Mea", + yhoat="Teleport-Yhoat", + altep="Teleport-Altep", + vahzl="Teleport-Vahzl", + jugner="Recall-Jugner", + pashh="Recall-Pashh", + meri="Recall-Meriph", + pash="Recall-Pashh", + meriph="Recall-Meriph", + ichi="Utsusemi: Ichi", + ni="Utsusemi: Ni", + utsu1="Utsusemi: Ichi", + utsu2="Utsusemi: Ni", + ds="Divine Seal", + es="Elemental Seal", + la="Light Arts", + da="Dark Arts", + pen="Penury", + cel="Celerity", + cw1="Curing Waltz", + cw2="Curing Waltz II", + cw3="Curing Waltz III", + cw4="Curing Waltz IV", + cw5="Curing Waltz V", + hw="Healing Waltz" +} + +aliases = config.load('data\\aliases.xml',default_aliases) +config.save(aliases) +setmetatable(aliases,nil) + + +require 'statics' +require 'targets' + +----------------------------------------------------------------------------------- +--Name: event_load() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None, simply a routine that runs once at the load (after the entire document +---- is loaded and treated as a script) +----------------------------------------------------------------------------------- +windower.register_event('load',function() + lastsent = '' +end) + +----------------------------------------------------------------------------------- +--Name: event_unload() +--Args: +---- None +----------------------------------------------------------------------------------- +--Returns: +---- None, simply a routine that runs once at unload. +----------------------------------------------------------------------------------- +windower.register_event('unload',function() + if logging then logfile:close() end +end) + + +----------------------------------------------------------------------------------- +--Name: event_outgoing_text() +--Args: +---- original (string): Original command entered by the player +---- modified (string): Modified command with changes upstream of the addon +----------------------------------------------------------------------------------- +--Returns: +---- string, changed command +----------------------------------------------------------------------------------- +windower.register_event('outgoing text',function(original,modified) + local temp_org = windower.convert_auto_trans(modified) + if modified:sub(1,1) ~= '/' then return modified end + debug_chat('outgoing_text: '..modified..' '..tostring(windower.ffxi.get_mob_by_target('st'))) + temp_org = temp_org:gsub(' <wait %d+>',''):sub(2) + + if logging then + logfile:write('\n\n',tostring(os.clock()),'temp_org: ',temp_org,'\nModified: ',modified) + logfile:flush() + end + + -- If it's the command that was just sent, blank lastsent and pass it through with only the changes applied by other addons + if modified == lastsent then + lastsent = '' + return modified + end + + -- Otherwise, dump the inputs into command_logic() + return command_logic(temp_org,modified) +end) + +----------------------------------------------------------------------------------- +--Name: event_unhandled_command() +--Args: +---- table of strings: //entries split on ' ' +----------------------------------------------------------------------------------- +--Returns: +---- None, but can generate text output through command_logic() +----------------------------------------------------------------------------------- +windower.register_event('unhandled command',function(...) + local combined = windower.convert_auto_trans(table.concat({...},' ')) -- concat it back together... + local cmd,bool = command_logic(combined,combined) -- and then dump it into command_logic() + if cmd and bool and cmd ~= '' then + if cmd:sub(1,1) ~= '/' then cmd = '/'..cmd end + windower.send_command('@input '..cmd) + end +end) + + +----------------------------------------------------------------------------------- +--Name: command_logic(original,modified) +--Args: +---- original (string): Full line entry from event unhandled command/outgoing text +---- modified (string): Modified line if from event_outgoing_text, otherwise the +---- same as original +----------------------------------------------------------------------------------- +--Returns: +---- string (sometimes '') depending what the logic says to do. +----------------------------------------------------------------------------------- +function command_logic(original,modified) + local splitline = alias_replace(string.split(original,' '):filter(-'')) + local command = splitline[1] -- Treat the first word as a command. + local potential_targ = '/nope//' + if splitline.n ~= 1 then + potential_targ = splitline[splitline.n] + end + local a,b,spell = string.find(original,'"(.-)"') + + if unhandled_list[command] then + return modified,true + end + + if spell then + spell = spell:lower() + elseif splitline.n == 3 then + if valid_target(potential_targ) then + spell = splitline[2] + else + spell = splitline[2]..' '..splitline[3] + end + end + + if targ_reps[potential_targ] then + potential_targ = targ_reps[potential_targ] + end + + if ignore_list[command] then -- If the command is legitimate and on the blacklist, return it unaltered. + lastsent = '' + return modified,true + elseif command2_list[command] and not valid_target(potential_targ,true) then + -- If the command is legitimate and requires target completion but not ability interpretation + + if not command2_list[command].args then -- If there are not any secondary commands + local temptarg = valid_target(potential_targ) or target_make(command2_list[command]) -- Complete the target or make one. + if temptarg ~= '<me>' then -- These commands, like emotes, check, etc., don't need to default to <me> + lastsent = '/'..command..' '..temptarg -- Push the command and target together and send it out. + else + lastsent = '/'..command + end + + debug_chat('258: input '..lastsent) + if logging then + logfile:write('\n\n',tostring(os.clock()),'Original: ',original,'\n(162) ',lastsent) + logfile:flush() + end + windower.send_command('@input '..lastsent) + return '',false + else -- If there are secondary commands (like /pcmd add <name>) + local tempcmd = command + local passback + local targs = command2_list[command] + for _,v in ipairs(splitline) do -- Iterate over the potential secondary arguments. + if command2_list[command]['args'] and command2_list[command]['args'][v] then + tempcmd = tempcmd..' '..v + passback = v + targs = command2_list[command]['args'][v] + break + end + end + local temptarg = '' + if targs ~= true then + -- Target is required + if command == potential_targ or passback and passback == potential_targ or potential_targ == '/nope//' then + -- No target is provided + temptarg = target_make(targs) + else + -- A target is provided, which is either corrected or (if not possible) used raw + temptarg = valid_target(potential_targ) or potential_targ + end + end + lastsent = '/'..tempcmd..' '..temptarg + debug_chat('292: input '..lastsent) + if logging then + logfile:write('\n\n',tostring(os.clock()),'Original: ',original,'\n(193) ',lastsent) + logfile:flush() + end + windower.send_command('@input '..lastsent) + return '',false + end + elseif command2_list[command] then + -- If the submitted command does not require ability interpretation and is fine already, send it out. + lastsent = '' + if logging then + logfile:write('\n\n',tostring(os.clock()),'Original: ',original,'\n(146) Legitimate command') + logfile:flush() + end + return modified,true + elseif command_list[command] then + -- If there is a valid command, then pass the text with an offset of 1 to the text interpretation function + return interp_text(splitline,1,modified) + else + -- If there is not a valid command, then pass the text with an offset of 0 to the text interpretation function + return interp_text(splitline,0,modified) + end +end + + +----------------------------------------------------------------------------------- +--Name: alias_replace(tab) +--Args: +---- tab (table of strings): splitline +----------------------------------------------------------------------------------- +--Returns: +---- tab (table of strings): with all the aliased values replaced +----------------------------------------------------------------------------------- +function alias_replace(tab) + for ind,key in ipairs(tab) do + if aliases[key:lower()] then + tab[ind] = aliases[key:lower()] + end + end + return tab +end + + +----------------------------------------------------------------------------------- +--Name: interp_text() +--Args: +---- splitline (table of strings): entire entry, split on spaces. +---- original (string): Full line entry from event unhandled command/outgoing text +---- modified (string): Modified line if from event_outgoing_text, otherwise the +---- same as original +----------------------------------------------------------------------------------- +--Returns: +---- string (sometimes '') depending what the logic says to do. +---- Sends a command if the command needs to be changed. +----------------------------------------------------------------------------------- +function interp_text(splitline,offset,modified) + local temptarg,abil + local no_targ_abil = strip(table.concat(splitline,' ',1+offset,splitline.n)) + + if validabils[no_targ_abil] then + abil = no_targ_abil + elseif splitline.n > 1 then + temptarg = valid_target(targ_reps[splitline[splitline.n]] or splitline[splitline.n]) + end + + if temptarg then abil = _raw.table.concat(splitline,' ',1+offset,splitline.n-1) + elseif not abil then abil = _raw.table.concat(splitline,' ',1+offset,splitline.n) end + + local strippedabil = strip(abil) -- Slug the ability + + if validabils[strippedabil] then + local options,nonoptions,num_opts, r_line = {},{},0 + local player = windower.ffxi.get_player() + for v in validabils[strippedabil]:it() do + if check_usability(player,v.res,v.id) then + options[v.res] = v.id + num_opts = num_opts + 1 + elseif v.res ~= nil then + nonoptions[v.res] = v.id + end + end + if num_opts > 0 then + -- If there are usable options then prioritize: + -- Prefix, if given -> Spells -> Job Abilities -> Weapon Skills -> Monster Skills + r_line = res[(offset == 1 and options[command_list[splitline[1]]] and command_list[splitline[1]]) or (options.spells and 'spells') or (options.job_abilities and 'job_abilities') or (options.weapon_skills and 'weapon_skills') or (options.monster_skills and 'monster_skills') or (options.mounts and 'mounts')][options[command_list[splitline[1]]] or options.spells or options.job_abilities or options.weapon_skills or options.monster_skills or options.mounts] + elseif num_opts == 0 then + r_line = res[(offset == 1 and nonoptions[command_list[splitline[1]]] and command_list[splitline[1]]) or (nonoptions.spells and 'spells') or (nonoptions.weapon_skills and 'weapon_skills') or (nonoptions.job_abilities and 'job_abilities') or (nonoptions.monster_skills and 'monster_skills') or (nonoptions.mounts and 'mounts')][nonoptions[command_list[splitline[1]]] or nonoptions.spells or nonoptions.weapon_skills or nonoptions.job_abilities or nonoptions.monster_skills or nonoptions.mounts] + end + + local targets = table.reassign({},r_line.targets) + + -- Handling for abilities that change potential targets. + if r_line.skill == 40 and r_line.cast_time == 8 and L(player.buffs):contains(409) then + targets.Party = true -- Pianissimo changes the target list of + elseif r_line.skill == 44 and r_line.en:find('Indi-') and L(player.buffs):contains(584) then + targets.Party = true -- Indi- spells can be cast on others when Entrust is up + end + + local abil_name = r_line.english -- Remove spaces at the end of the ability name. + while abil_name:sub(-1) == ' ' do + abil_name = abil_name:sub(1,-2) + end + + local out_tab = {prefix = in_game_res_commands[r_line.prefix:gsub("/","")], name = abil_name, target = temptarg or target_make(targets)} + if not out_tab.prefix then print('Could not find prefix',r_line.prefix) end + lastsent = out_tab.prefix..' "'..out_tab.name..'" '..out_tab.target + if logging then + logfile:write('\n\n',tostring(os.clock()),'Original: ',table.concat(splitline,' '),'\n(180) ',lastsent) + logfile:flush() + end + debug_chat('390 comp '..lastsent:sub(2):gsub('"([^ ]+)"', '%1'):lower()..' || '..table.concat(splitline,' ',1,splitline.n):gsub('"([^ ]+)"', '%1'):lower()) + if offset == 1 and in_game_res_commands[splitline[1]] and in_game_res_commands[splitline[1]] == out_tab.prefix and + ('"'..out_tab.name..'" '..out_tab.target):gsub('"([^ ]+)"', '%1'):lower() == table.concat(splitline,' ',2,splitline.n):gsub('"([^ ]+)"', '%1'):lower() then + debug_chat('400 return '..lastsent) + return lastsent,true + else + debug_chat('403 input '..lastsent) + windower.send_command('@input '..lastsent) + return '',false + end + end + lastsent = '' + return modified,false +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/shortcuts/statics.lua b/Data/DefaultContent/Libraries/addons/addons/shortcuts/statics.lua new file mode 100644 index 0000000..4fd1219 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shortcuts/statics.lua @@ -0,0 +1,212 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation andor other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + +-- Convert the spells and job abilities into a referenceable list of aliases -- +validabils = {} + +--f = io.open('..addonsprdata'..tostring(os.clock())..'.log','w+') +--for i,v in pairs(validabils) do +-- f:write(tostring(i)..' '..tostring(v)..'\n') +--end + +-- Constants used in the rest of the addon. + +-- List of valid prefixes to be interpreted with the resources. The values currently have no use. +command_list = {['ja']='job_abilities',['jobability']='job_abilities',['so']='spells',['song']='spells',['ma']='spells',['magic']='spells',['nin']='spells',['ninjutsu']='spells', + ['ra']='Ranged Attack',['range']='Ranged Attack',['throw']='Ranged Attack',['shoot']='Ranged Attack',['monsterskill']='monster_skills',['ms']='monster_skills', + ['ws']='weapon_skills',['weaponskill']='weapon_skills',['item']='Ability',['pet']='job_abilities',['mo']='mounts',['mount']='mounts'} + +in_game_res_commands = {['ja']='/ja',['jobability']='/ja',['pet']='/ja', + ['so']='/ma',['song']='/ma',['ma']='/ma',['magic']='/ma',['nin']='/ma',['ninjutsu']='/ma', + ['monsterskill']='/ms',['ms']='/ms',['ws']='/ws',['weaponskill']='/ws', + ['ra']='/ra',['range']='/ra',['throw']='/ra',['shoot']='/ra',['mount']='/mo',['mo']='/mo'} + +-- List of other commands that might use name completion. +local No_targets = {['Player']=false,['Enemy']=false,['Party']=false,['Ally']=false,['NPC']=false,['Self']=false,['Corpse']=false} +local All_targets = {['Player']=true,['Enemy']=true,['Party']=true,['Ally']=true,['NPC']=true,['Self']=true,['Corpse']=true} +local PC_targets = {['Player']=true,['Enemy']=false,['Party']=true,['Ally']=true,['NPC']=false,['Self']=true,['Corpse']=true} +local Party_targets = {['Player']=false,['Enemy']=false,['Party']=true,['Ally']=false,['NPC']=false,['Self']=true,['Corpse']=true} +local Alliance_targets = {['Player']=false,['Enemy']=false,['Party']=false,['Ally']=true,['NPC']=false,['Self']=true,['Corpse']=true} +local BST_targets = {['Player']=true,['Enemy']=false,['Party']=false,['Ally']=false,['NPC']=false,['Self']=true,['Corpse']=false} + +local function new_cmd_entry(default_targets,subcommands) + local rettab = table.reassign({},default_targets) + if subcommands then + rettab.args = subcommands + end + return rettab +end + +local emote_table = new_cmd_entry(All_targets,{motion=true}) + +command2_list = { + --['kick']=true, --Is this actually a command? + ['assist']=All_targets, + ['alliancecmd']=new_cmd_entry(No_targets,{ + kick=Alliance_targets, + add=PC_targets, + leader=Alliance_targets, + breakup=true, + leave=true, + looter=Alliance_targets}), + ['partycmd']=new_cmd_entry(No_targets,{ + kick=Party_targets, + add=PC_targets, + leader=Party_targets, + breakup=true, + leave=true, + looter=Party_targets}), + ['acmd']=new_cmd_entry(No_targets,{ + kick=Alliance_targets, + add=PC_targets, + leader=Alliance_targets, + breakup=true, + leave=true, + looter=Alliance_targets}), + ['pcmd']=new_cmd_entry(No_targets,{ + kick=Party_targets, + add=PC_targets, + leader=Party_targets, + breakup=true, + leave=true, + looter=Party_targets}), + ['wave']=emote_table, + ['poke']=emote_table, + ['dance']=emote_table, + ['dance1']=emote_table, + ['dance2']=emote_table, + ['dance3']=emote_table, + ['dance4']=emote_table, + ['amazed']=emote_table, + ['angry']=emote_table, + ['bell']=emote_table, + ['bellsw']=emote_table, + ['blush']=emote_table, + ['bow']=emote_table, + ['cheer']=emote_table, + ['clap']=emote_table, + ['comfort']=emote_table, + ['cry']=emote_table, + ['disgusted']=emote_table, + ['doze']=emote_table, + ['doubt']=emote_table, + ['huh']=emote_table, + ['farewell']=emote_table, + ['goodbye']=emote_table, + ['fume']=emote_table, + ['grin']=emote_table, + ['hurray']=emote_table, + ['joy']=emote_table, + ['kneel']=emote_table, + ['laugh']=emote_table, + ['muted']=emote_table, + ['kneel']=emote_table, + ['laugh']=emote_table, + ['no']=emote_table, + ['nod']=emote_table, + ['yes']=emote_table, + ['panic']=emote_table, + ['point']=emote_table, + ['praise']=emote_table, + ['psych']=emote_table, + ['salute']=emote_table, + ['shocked']=emote_table, + ['sigh']=emote_table, + ['sit']=emote_table, + ['slap']=emote_table, + ['smile']=emote_table, + ['stagger']=emote_table, + ['stare']=emote_table, + ['sulk']=emote_table, + ['surprised']=emote_table, + ['think']=emote_table, + ['toss']=emote_table, + ['upset']=emote_table, + ['welcome']=emote_table, + ['check']=new_cmd_entry(PC_targets), + ['c']=new_cmd_entry(PC_targets), + ['checkparam']=new_cmd_entry({['Player']=false,['Enemy']=false,['Party']=false,['Ally']=false,['NPC']=false,['Self']=true,['Corpse']=false},{}), -- Blank table forces it into the second processing stream, which lets it default to <me> + ['target']=new_cmd_entry(PC_targets), + ['ta']=new_cmd_entry(PC_targets), + ['ra']=new_cmd_entry({['Player']=false,['Enemy']=true,['Party']=false,['Ally']=false,['NPC']=false,['Self']=false,['Corpse']=false}), + ['follow']=new_cmd_entry(All_targets), + ['recruit']=new_cmd_entry(PC_targets), + ['rec']=new_cmd_entry(PC_targets), + ['retr']=new_cmd_entry(Party_targets,{all=No_targets}), + ['returntrust']=new_cmd_entry(Party_targets,{all=No_targets}), + ['refa']=new_cmd_entry(Party_targets,{all=No_targets}), + ['returnfaith']=new_cmd_entry(Party_targets,{all=No_targets}), + ['bstpet']=new_cmd_entry(No_targets,{['1']=BST_targets,['2']=BST_targets,['3']=BST_targets,['4']=BST_targets,['5']=BST_targets,['6']=BST_targets,['7']=BST_targets}), + } + +unhandled_list = {['p']=true,['s']=true,['sh']=true,['yell']=true,['echo']=true,['t']=true,['l']=true,['breaklinkshell']=true} + +-- List of commands to be ignored +ignore_list = {['equip']=true,['raw']=true,['fish']=true,['dig']=true,['range']=true,['map']=true,['hide']=true,['jump']=true,['attackoff']=true,['quest']=true,['recruitlist']=true,['rlist']=true,['statustimer']=true} + +-- Targets to ignore and just pass through +pass_through_targs = T{'<t>','<me>','<ft>','<scan>','<bt>','<lastst>','<r>','<pet>','<p0>','<p1>','<p2>','<p3>','<p4>', + '<p5>','<a10>','<a11>','<a12>','<a13>','<a14>','<a15>','<a20>','<a21>','<a22>','<a23>','<a24>','<a25>','<focust>'} + +st_targs = T{'<st>','<stpc>','<stal>','<stnpc>','<stpt>'} + +targ_reps = {t='<t>',me='<me>',ft='<ft>',scan='<scan>',bt='<bt>',lastst='<lastst>',r='<r>',pet='<pet>',p0='<p0>',p1='<p1>',p2='<p2>',p3='<p3>',p4='<p4>', + p5='<p5>',a10='<a10>',a11='<a11>',a12='<a12>',a13='<a13>',a14='<a14>',a15='<a15>',a20='<a20>',a21='<a21>',a22='<a22>',a23='<a23>',a24='<a24>',a25='<a25>', + st='<st>',stpc='<stpc>',stal='<stal>',stnpc='<stnpc>',stpt='<stpt>',focust='<focust>'} + +language = 'english' -- windower.ffxi.get_info()['language']:lower() + + +----------------------------------------------------------------------------------- +--Name: make_abil() +--Args: +---- ind (string): stripped ability name +---- t (string): type of ability (Magic or Ability) +---- i (number): index id +----------------------------------------------------------------------------------- +--Returns: +---- Nothing, adds a new line to validabils or modifies it. +----------------------------------------------------------------------------------- +function make_abil(ind,res,id) + validabils[ind] = validabils[ind] or L{} + validabils[ind]:append({res=res,id=id}) +end + +-- Iterate through resources and make validabils. +function validabils_it(resource) + for id,v in pairs(res[resource]) do + if (not v.monster_level and v.prefix) or (v.monster_level and v.monster_level ~= -1 and v.ja:sub(1,1) ~= '#' ) then + -- Monster Abilities contains a large number of player-usable moves (but not monstrosity-usable). This excludes them. + make_abil(strip(v.english),resource,id) + end + end +end + +validabils_it('spells') +validabils_it('job_abilities') +validabils_it('weapon_skills') +validabils_it('monster_skills') +validabils_it('mounts')
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/shortcuts/targets.lua b/Data/DefaultContent/Libraries/addons/addons/shortcuts/targets.lua new file mode 100644 index 0000000..25ecabb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shortcuts/targets.lua @@ -0,0 +1,136 @@ +--Copyright (c) 2014, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> 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. + + +-- Target Processing -- + +----------------------------------------------------------------------------------- +--Name: valid_target(targ,flag) +--Args: +---- targ (string): The proposed target +---- flag (boolean): sets a more stringent criteria for target. It has to be a +---- match to a player in the mob_array. +----------------------------------------------------------------------------------- +--Returns: +---- A string or false. +----------------------------------------------------------------------------------- +function valid_target(targ,flag) + local spell_targ + local san_targ = find_san(strip(targ)) + -- If the target is whitelisted, pass it through. + if pass_through_targs:contains(targ:lower()) or st_targs:contains(targ:lower()) or (tonumber(targ:lower()) and windower.ffxi.get_mob_by_id(tonumber(targ:lower()))) then + return targ:lower() + elseif targ and windower.ffxi.get_player() then + -- If the target exists, scan the mob array for it + local current_target = windower.ffxi.get_mob_by_target('t') + local targar = {} + for i,v in pairs(windower.ffxi.get_mob_array()) do + if string.find(strip(v.name),san_targ) and (v.valid_target or v.id == windower.ffxi.get_player().id) then -- Malformed pattern somehow + -- Handling for whether it's a monster or not + if v.is_npc and v.spawn_type ~= 14 and current_target then + if v.id == current_target.id then + targar['<t>'] = math.sqrt(v.distance) + end + elseif not v.is_npc or (v.spawn_type == 14 and v.in_party) then + targar[v.name] = math.sqrt(v.distance) + end + end + end + + -- If flag is set, push out the target only if it is in the targ array. + if targar[targ] then + spell_targ = targ + elseif flag then + spell_targ = false + else + -- If targ starts an element of the monster array, use it. + local priority = 50 + for i,v in pairs(targar) do + if (i:lower()==san_targ:lower()) then + v = 0 + elseif i:lower():find('^'..san_targ:lower()) then + v = v/50 + end + if v < priority then -- Use the highest priority match, with a default priority hierarchy based on distance + priority = v + spell_targ = i + end + end + end + end + return spell_targ +end + + +----------------------------------------------------------------------------------- +--Name: target_make(targarr) +--Args: +---- targets (table of booleans): Keyed to potential targets +----------------------------------------------------------------------------------- +--Returns: +---- Created valid target, defaulting to '<me>' +----------------------------------------------------------------------------------- +function target_make(targets) + local target = windower.ffxi.get_mob_by_target('<t>') + local target_type = '' + if not target then + -- If target doesn't exist, leave it set to ''. This will shortcircuit the + -- rest of the processing and just return <me>. + elseif target.hpp == 0 then + target_type = 'Corpse' + elseif target.is_npc and target.spawn_type ~= 14 then + target_type = 'Enemy' + -- Need to add handling that differentiates 'Enemy' and 'NPC' here. + else + target_type = 'Ally' + local party = windower.ffxi.get_party() + for i,v in pairs(party) do + if type(v) == 'table' and v.name == target.name then + if i:sub(1,1) == 'p' then + if i:sub(1,2) == 'p0' then + target_type = 'Self' + else + target_type = 'Party' + end + end + if target.charmed and not target.is_npc then + target_type = 'Enemy' + end + break + end + end + end + + if targets[target_type] and target_type ~= 'Self' then + return '<t>' + elseif targets.Self then + return '<me>' + elseif targets.Self or targets.Party or targets.Enemy or targets.NPC or targets.Ally or targets.Corpse then + return '<t>' + else + return '' + end +end
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/shoutHelper/Alliance.lua b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/Alliance.lua new file mode 100644 index 0000000..7cb5606 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/Alliance.lua @@ -0,0 +1,498 @@ +--[[ +Copyright (c) 2013, Chiara De Acetis +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +]] + +-- Object Alliance +--[[ +Concept +list = {party1={} party2={} party3={}} +list = {party1={WHM="ppl" PLD="ppl" WAR="ppl" DD="ppl" } party2={} party3={}} +if there's "add war" and only DD slots are left, it has to put the war in the DD slot +]] +local Alliance = {} + +require 'logger' +local files = require 'files' + +local JobList = T{ + 'blm', + 'blu', + 'brd', + 'bst', + 'dnc', + 'drg', + 'drk', + 'whm', + 'rdm', + 'pup', + 'cor', + 'pld', + 'geo', + 'run', + 'sch', + 'mnk', + 'thf', + 'war', + 'sam', + 'nin', + 'smn', + 'rng', + 'dd', + 'support', + 'heal', + 'tank' +} + +local DDlist = T{ + 'blm', + 'blu', + 'bst', + 'dnc', + 'drg', + 'drk', + 'pup', + 'pld', + 'run', + 'mnk', + 'thf', + 'war', + 'sam', + 'nin', + 'smn', + 'rng' +} + +local SupportList = T{ + 'cor', + 'brd', + 'geo' +} + +local HealList = T{ + 'whm', + 'rdm', + 'sch' +} + +--create and empty alliance +function Alliance:new() + local ally = { + party1 = {}, + party2 = {}, + party3 = {} + } + setmetatable(ally, self) + self.__index = self + return ally +end + +--sets one or multiple job in party1 +function Alliance:setParty1(jobs) + local length = 0 + local numJob = #self.party1 + if numJob == 6 then + return + end + if #jobs <= (6 - numJob) then + length = #jobs + else + length = (6 - numJob) + end + j = 1 + for i = 1, length do + job = jobs[i]:lower() + if JobList:contains(job) then + self.party1[j+numJob] = {job} + j = j+1 + end + end +end + +--sets one or multiple job in the party2 +function Alliance:setParty2(jobs) + local length = 0 + local numJob = #self.party2 + if numJob == 6 then + return + end + if #jobs <= (6 - numJob) then + length = #jobs + else + length = (6 - numJob) + end + j = 1 + for i = 1, length do + job = jobs[i]:lower() + if JobList:contains(job) then + self.party2[j+numJob] = {job} + j = j+1 + end + end +end + +--sets one or multiple job in party3 +function Alliance:setParty3(jobs) + local length = 0 + local numJob = #self.party3 + if numJob == 6 then + return + end + if #jobs <= (6 - numJob) then + length = #jobs + else + length = (6 - numJob) + end + j = 1 + for i = 1, length do + job = jobs[i]:lower() + if JobList:contains(job) then + self.party3[j+numJob] = {job} + j = j+1 + end + end +end + +--delete a job inside the ally +function Alliance:deleteJob(job, party) --it rescale the player list too + job = job:lower() + -- party slot not given + if (party == nil ) then + party = self:findJob(job) + if party[1] == 1 then + table.remove(self.party1, party[2]) + elseif party[1] == 2 then + table.remove(self.party2, party[2]) + elseif party[1] == 3 then + table.remove(self.party3, party[2]) + end + else -- party given + if (party == "party1") then + for i=1, #self.party1 do + local slot = self.party1[i] + local v = slot[1] + if(v == job) then + table.remove(self.party1, i) + return + end + end + elseif (party == "party2") then + for i=1, #self.party2 do + local slot = self.party2[i] + local v = slot[1] + if(v == job) then + table.remove(self.party2, i) + return + end + end + elseif (party == "party3") then + for i=1, #self.party3 do + if(v == job) then + table.remove(self.party3, i) + return + end + end + end + end +end + +--returns the first party table that contains the given job +function Alliance:findJob(job) + local party = nil + for i=1, #self.party1 do + local slot = self.party1[i] + local v = slot[1] + if( v == job ) then + party = {1, i} + return party + end + end + if(party == nil) then + for i=1, #self.party2 do + local slot = self.party2[i] + local v = slot[1] + if( v == job ) then + party = {2, i} + return party + end + end + end + if(party == nil) then + for i=1, #self.party3 do + local slot = self.party3[i] + local v = slot[1] + if( v == job ) then + party = {3, i} + return party + end + end + end + if(party == nil ) then + error(job..' not found') + return + end +end + +--returns the string representing the party +function Alliance:printAlly() + local s = '' + if(#self.party1 > 0) then + s = s..'\nPARTY 1\n' + for i=1, #self.party1 do + slot = self.party1[i] + job = slot[1] + name = slot[2] + s = s..'['..job:upper()..']' + if name then + s = s..' '..name + end + s = s..' \n' + end + end + if(#self.party2 > 0) then + s = s..'\nPARTY 2\n' + for i=1, #self.party2 do + slot = self.party2[i] + job = slot[1] + name = slot[2] + s = s..'['..job:upper()..']' + if name then + s = s..' '..name + end + s = s..' \n' + end + end + if(#self.party3 > 0) then + s = s..'\nPARTY 3\n' + for i=1, #self.party3 do + slot = self.party3[i] + job = slot[1] + name = slot[2] + s = s..'['..job:upper()..']' + if name then + s = s..' '..name + end + s = s..' \n' + end + end + return s +end + +--delete the ally +function Alliance:deleteAll() + self.party1 = {} + self.party2 = {} + self.party3 = {} +end + +--delete the party +function Alliance:delete(party) + if party == "party1" then + self.party1 = {} + elseif party == "party2" then + self.party2 = {} + elseif party == "party3" then + self.party3 = {} + end +end + +--sets one player to the first <job> slot free +function Alliance:addPlayer(job, name) + local party = self:findFreeSlot(job) + local rightJob = true + --if party is null the job wasn't found + --it means the there isn't the given job (example: one's looking for mnk and only slot DD are left) + if not party and job then + if DDlist:contains(job:lower()) then + party = self:findFreeSlot('dd') + rightJob = false + elseif SupportList:contains(job:lower())then + party = self:findFreeSlot('support') + rightJob = false + elseif HealList:contains(job:lower()) then + party = self:findFreeSlot('heal') + rightJob = false + end + if not party then + error("Can't find a free slot") + return + end + end + local pos = party[2] + if (party[1] == 1) then + local slot = self.party1[pos] + if rightJob then + self.party1[pos] = {slot[1], name} + else + self.party1[pos] = {job:lower(), name} + end + end + if (party[1] == 2) then + local slot = self.party2[pos] + if rightJob then + self.party2[pos] = {slot[1], name} + else + self.party2[pos] = {job, name} + end + end + if (party[1] == 3) then + local slot = self.party3[pos] + if rightJob then + self.party3[pos] = {slot[1], name} + else + self.party3[pos] = {job, name} + end + end + --if I'm here it means it the given job is missing +end + +--find the first free party slot (job is optional) +function Alliance:findFreeSlot(job) + local party = nil --first position is pt, second is party slot + for i=1, #self.party1 do + local slot = self.party1[i] + local jobName = slot[1] + local name = slot[2] + if ((not job) and name == nil) then + --no job given, I'm looking for the first free slot in party + party = {1, i} + return party + elseif (job and name == nil) then + --the job is given, I'm looking for the first free slot for the given job + job = job:lower() + if(jobName == job)then + party = {1, i} + return party + end + end + end + for i=1, #self.party2 do + local slot = self.party2[i] + local jobName = slot[1] + local name = slot[2] + if ((not job) and name == nil) then + --no job given, I'm looking for the first free slot in party + party = {2, i} + return party + elseif (job and name == nil) then + --the job is given, I'm looking for the first free slot for the given job + job = job:lower() + if(jobName == job)then + party = {2, i} + return party + end + end + end + for i=1, #self.party3 do + local slot = self.party3[i] + local jobName = slot[1] + local name = slot[2] + if ((not job) and name == nil) then + --no job given, I'm looking for the first free slot in party + party = {3, i} + return party + elseif (job and name == nil) then + --the job is given, I'm looking for the first free slot for the given job + job = job:lower() + if(jobName == job)then + party = {3, i} + return party + end + end + end +end + +-- removes the player from alliance list (i remove the player in i-esim position) +function Alliance:removePlayer(name) + name = name:lower() + --looks through the pts + local v = '' + local slot = nil + for k=1, #self.party1 do + slot = self.party1[k] + v = slot[2] + if(v~= nil)then + v = v:lower() + if v == name then + self.party1[k] = {slot[1]} + return + end + end + end + for k=1, #self.party2 do + slot = self.party2[k] + v = slot[2] + if(v~= nil)then + v = v:lower() + if v == name then + self.party2[k] = {slot[1]} + return + end + end + end + for k=1, #self.party3 do + slot = self.party3[k] + v = slot[2] + if(v~= nil)then + v = v:lower() + if v == name then + self.party3[k] = {slot[1]} + return + end + end + end +end + +--save the current ally in an xml file +function Alliance:save() + --TODO (now creates only a string xml) + local a = '<?xml version="1.0" ?>\n' + a = a..'<alliance>\n' + a = a..'\t<party1>\n' + for i=1, #self.party1 do + slot = self.party1[i] + job = slot[1] + a = a..'\t\t<job'..i..'>'..job:upper()..'</job'..i..'>\n' + end + a = a..'\t</party1>\n' + a = a..'\t<party2>\n' + for i=1, #self.party2 do + slot = self.party2[i] + job = slot[1] + a = a..'\t\t<job'..i..'>'..job:upper()..'</job'..i..'>\n' + end + a = a..'\t</party2>\n' + a = a..'\t<party3>\n' + for i=1, #self.party3 do + slot = self.party3[i] + job = slot[1] + a = a..'\t\t<job'..i..'>'..job:upper()..'</job'..i..'>\n' + end + a = a..'\t</party3>\n' + a = a..'</alliance>\n' +end + +return Alliance diff --git a/Data/DefaultContent/Libraries/addons/addons/shoutHelper/blackboard.lua b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/blackboard.lua new file mode 100644 index 0000000..998e7bb --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/blackboard.lua @@ -0,0 +1,150 @@ +--[[ +Copyright (c) 2013, Chiara De Acetis +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> 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. +]] + +-- Blackboard object (wrapper to text box) +-- Manage the textbox alliance list +local Alliance = require 'Alliance' +require 'logger' + +local Blackboard = { +visible = true, +settings = nil, +tb_name = 'shoutHelper', +allyName = 'Alliance' +} + +function Blackboard:new(settings) + local o = {} + self.settings = settings + o = {ally = Alliance:new()} + setmetatable(o, self) + self.__index = self + windower.text.create(self.tb_name) + windower.text.set_bg_color(self.tb_name, self.settings.bgtransparency, 30, 30, 30) + windower.text.set_color(self.tb_name, 255, 225, 225, 225) + windower.text.set_location(self.tb_name, self.settings.posx, self.settings.posy) + windower.text.set_visibility(self.tb_name, self.visible) + windower.text.set_bg_visibility(self.tb_name, 1) + return o +end + +function Blackboard:set_position(posx, posy) + self.settings.posx = posx + self.settings.posy = posy + windower.text.set_location(self.tb_name, posx, posy) +end + +function Blackboard:show() + self.visible = true + windower.text.set_visibility(self.tb_name, true) +end + +function Blackboard:hide() + self.visible = false + windower.text.set_visibility(self.tb_name, false) +end + +function Blackboard:set(party, jobs) + --need to check here party format string + local ptBool = (party == ('party1')) or (party == ('pt1')) or (party == ('1')) + if(ptBool) then + self.ally:setParty1(jobs) + end + ptBool = (party == ('party2')) or (party == ('pt2')) or (party == ('2')) + if(ptBool) then + self.ally:setParty2(jobs) + end + ptBool = (party == ('party3')) or (party == ('pt3')) or (party == ('3')) + if(ptBool) then + self.ally:setParty3(jobs) + end + self:update() +end + +function Blackboard:deleteJob(job, party) + local ptBool = (party == ('party1')) or (party == ('pt1')) or (party == ('1')) + local party = nil + if(ptBool) then + party = 'party1' + end + ptBool = (party == ('party2')) or (party == ('pt2')) or (party == ('2')) + if(ptBool) then + party = 'party2' + end + ptBool = (party == ('party3')) or (party == ('pt3')) or (party == ('3')) + if(ptBool) then + party = 'party3' + end + self.ally:deleteJob(job, party) + self:update() +end + +function Blackboard:addPlayer(job, name) + self.ally:addPlayer(job, name) + self:update() +end + +function Blackboard:rmPlayer(name) + self.ally:removePlayer(name) + self:update() +end + +function Blackboard:update() + local string = self.ally:printAlly() + windower.text.set_text(self.tb_name, self.allyName..'\n'..string) + if (not self.visible) then + self:show() + end +end + +function Blackboard:reset(party) + if not party then + self.ally:deleteAll() + else + local ptBool = (party == ('party1')) or (party == ('pt1')) or (party == ('1')) + if(ptBool) then + party = 'party1' + end + ptBool = (party == ('party2')) or (party == ('pt2')) or (party == ('2')) + if(ptBool) then + party = 'party2' + end + ptBool = (party == ('party3')) or (party == ('pt3')) or (party == ('3')) + if(ptBool) then + party = 'party3' + end + self.ally:delete(party) + end + self:update() +end + +function Blackboard:destroy() +windower.text.delete(self.tb_name) +end + +return Blackboard diff --git a/Data/DefaultContent/Libraries/addons/addons/shoutHelper/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/data/settings.xml new file mode 100644 index 0000000..133f21c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/data/settings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" ?> +<settings> + <!-- + This file controls the settings for the Scoreboard plugin. + Settings in the <global> section apply to all characters + + The available settings are: + posX - x coordinate for position + posY - y coordinate for position + bgTransparency - Transparency level for the background. 0-255 range + --> + <global> + <posX>300</posX> + <posY>140</posY> + <bgTransparency>200</bgTransparency> + </global> + + <!-- + You may also override specific settings on a per-character basis here. + --> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/shoutHelper/readme.md b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/readme.md new file mode 100644 index 0000000..5a6f0b2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/readme.md @@ -0,0 +1,94 @@ +Author: Jandel +Version: 0.2 +Addon to help manage alliance while shouting. +Abbreviation: //sh + +This addon allows you to create a virtual alliance list in game. Why using the old piece of paper +or switching to a file .txt to manage your alliance shout? +This addon will allow you to create a job list and assign a player to the wanted job +Example: +Alliance +PARTY 1 +[JOB] +[JOB] +[JOB] +... + +PARTY 2 +[JOB] +[JOB] +[JOB] +... + +PARTY 3 +[JOB] +[JOB] +[JOB] +... + +All in-game commands are prefixed with "//sh", for example: "//sh help". + +Command list: +* HELP + Displays the help text + +* POS <x> <y> + Positions the alliance list to the given coordinates + +* Clear [<party>] + Clears the alliance list if no <party> is given. It will clear all the jobs + only on <party> list if <party> is given. + <party> formats are 'party1', 'pt1', '1', 'party2', 'pt2', '2', + 'party3', 'pt3', '3' + +* SET <party> <job1> <job2> ... + Insert the <job> into the <party> list. + Both <party> and <job> are required. + Support <party> formats are 'party1', 'pt1', '1', 'party2', 'pt2', '2', + 'party3', 'pt3', '3' + Support <job> formats are all FFXI short jobs name ('whm', 'rdm', 'mnk', ...) + and 'healer', 'support', 'dd' + Examples: + //sh set pt1 mnk Adds a mnk to the party1 job list + //sh set party2 mnk healer dd Adds a mnk, an healer and a dd to the party2 job list + //sh set 3 healer support brd dd dd dd Adds the six jobs to the party3 job list + +* DEL [<party>] <job> + removes a job from the job list. <party> is optional and if is not given, the first + occurrence of the given job will be deleted + +* VISIBLE + Toggles the visibility of the scoreboard. Data will continue to + accumulate even while it is hidden. + +* ADD [<job>] <player> + Adds the <player> to the first free slot. + If <job> is given then the <player> will be added to the first free slot of + that corresponding <job> + Examples: + //sh add mnk Grievesk Put the name Grievesk near the first free MNK slot in + the three party + //sh add Jandel Put the name Jandel near the first job slot that is free + +* RM <player> + Removes the <player> from the party list + +* SAVE <name> + This function is not implemented yet + +* LOAD <name> + This function is not implemented yet + +The settings file, located in addons/shoutHelper/data/settings.xml, contains +additional configuration options: +* posX - x coordinate for position +* posY - y coordinate for position +* bgTransparency - Transparency level for the background. 0-255 range + +Caveats: + +* This addon is still in development. Please report any issues or feedback to + to me (Jandel on Ragnarok) on FFXIAH or Guildwork. + +Thanks to Grievesk for encouraging me to write this addon :) + diff --git a/Data/DefaultContent/Libraries/addons/addons/shoutHelper/shoutHelper.lua b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/shoutHelper.lua new file mode 100644 index 0000000..c4ccc3e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/shoutHelper/shoutHelper.lua @@ -0,0 +1,172 @@ +--[[ +Copyright (c) 2013, Chiara De Acetis +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'shoutHelper' +_addon.version = '0.2' +_addon.commands = {'shouthelper','sh'} +_addon.author = 'Jandel' + +require 'tables' +require 'strings' +require 'logger' +--local file = require 'files' +local Blackboard = require 'blackboard' +local lavagna = nil +local config = require 'config' + +-- Memo: //lua load shoutHelper + +-- Constructor +windower.register_event('load',function () + settings = config.load({ + posx = 300, + posy = 140, + bgtransparency = 200, + font = 'courier', + fontsize = 10 + }) + lavagna = Blackboard:new(settings) +end) + +-- Handle addon args +windower.register_event('addon command',function (...) + local params = {...}; + + if #params < 1 then + return + end + if params[1] then + if params[1]:lower() == "help" then + --Idea of helper + local color = '204' -- !!there is a function in scoreboard for add_to_chat + windower.add_to_chat(color, 'SH: ShoutHelper v' .. _addon.version .. '. Author: Jandel') + windower.add_to_chat(color, 'SH: sh help : Shows help message') + windower.add_to_chat(color, 'SH: sh pos <x> <y> : Positions the list') + windower.add_to_chat(color, 'SH: sh clear [<party>]: Reset list (if no party is given, it will reset all alliance).') + --the following two line are commented because there's no function implemented + --windower.add_to_chat(color, 'SH: sh save <filename> : Save alliance settings. If the file already exists it will overwrite it.') + --windower.add_to_chat(color, 'SH: sh load <filename> : Load the <filename> alliance settings.') + windower.add_to_chat(color, "SH: sh set <party> <job1> <job2> ... : Add a job to the party. pt1 is for first party, pt2 and pt3 for second and third party. ".."Won\'t add jobs if the party list is full") + windower.add_to_chat(color, 'SH: sh add [<job>] <player> : assign the name of that player to the corrisponding job.') + windower.add_to_chat(color, 'SH: sh del [<party>] <job> : deletes the job from the alliance list. Party from wich delet it is optional') + windower.add_to_chat(color, 'SH: sh rm <player>: removes the player from the alliance list') + windower.add_to_chat(color, 'SH: sh visible : shows/hide the current alliance list') + elseif params[1]:lower() == "pos" then + if params[3] then + local posx, posy = tonumber(params[2]), tonumber(params[3]) + lavagna:set_position(posx, posy) + --TODO check this if to save settings + if posx ~= settings.posx or posy ~= settings.posy then + settings.posx = posx + settings.posy = posy + settings:save() + end + end + elseif params[1]:lower() == "clear" then + lavagna:reset(params[2]) + --elseif params[1]:lower() == "save" then + --if --[[the filename isn't legit(emplty string too)]] --then + --error('Invalid name') + --return + --end + -- TODO function that create&save xml + --log('This function needs to be implemented') + --elseif params[1]:lower() == "load" then + --if --[[the filename isn't legit(emplty string too)] --then + --error('Invalid name') + --return + --end + -- TODO function that load xml + --log('This function needs to be implemented') + elseif params[1]:lower() == "set" then --add jobs to party list + local party = params[2] + if not party then + error('No input given') + return + end + if not params[3] then + error('no jobs given') + return + end + local jobs = {} + local j = 1 + for i=3, #params do + jobs[j] = params[i] + j = j + 1 + end + lavagna:set(party, jobs) + elseif params[1]:lower() == "add" then --add playername to party + local job = params[2] + if not job then + error('No input given') + return + end + local name = params[3] + if not name then + name = job + job = nil + end + lavagna:addPlayer(job, name) + elseif params[1]:lower() == "del" then --delete job + local party = params[2] + if not party then + error('No input given') + return + end + local job = nil + if (party and params[3]) then + job = params[3] + else + job = party + end + lavagna:deleteJob(job, party) + elseif params[1]:lower() == "rm" then --remove player + if not params[2] then + error('Missing player name') + return + end + lavagna:rmPlayer(params[2]) + elseif params[1]:lower() == "visible" then + if(lavagna.visible) then + lavagna:hide() + else + lavagna:show() + end + else --I don't know if leave the error message or "do nothing" (deleting else) in case the command isn't legit + error('Invalid command') + end + end +end) + + + +-- Destructor +windower.register_event('unload',function () + lavagna:destroy() +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/stna/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/stna/ReadMe.md new file mode 100644 index 0000000..b0cbd2c --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/stna/ReadMe.md @@ -0,0 +1,20 @@ +**Author:** Ricky Gall +**Version:** 1.07 +**Description:** +Addon to enable 1 macro status removal with the help of send. + +**Abbreviation:** //stna + +**Commands:** + 1. //stna + +**Removal Priority:** + 1. Doom + 2. Curse + 3. Petrification + 4. Paralysis + 5. Plague + 6. Silence + 7. Blindness + 8. Poison + 9. Diseased diff --git a/Data/DefaultContent/Libraries/addons/addons/stna/stna.lua b/Data/DefaultContent/Libraries/addons/addons/stna/stna.lua new file mode 100644 index 0000000..8401376 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/stna/stna.lua @@ -0,0 +1,95 @@ +--[[ +Copyright (c) 2013, Ricky Gall +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of <addon name> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + + +_addon.name = 'STNA' +_addon.version = '1.08' +_addon.author = 'Nitrous (Shiva)' +_addon.command = 'stna' + +require('tables') +require('sets') +res = require('resources') + +windower.register_event('load', function() + statSpell = { + paralysis='Paralyna', + curse='Cursna', + doom='Cursna', + silence='Silena', + plague='Viruna', + diseased='Viruna', + petrification='Stona', + poison='Poisona', + blindness='Blindna' + } + --You may change this priority as you see fit this is my personal preference + priority = T{} + priority[1] = 'doom' + priority[2] = 'curse' + priority[3] = 'petrification' + priority[4] = 'paralysis' + priority[5] = 'plague' + priority[6] = 'silence' + priority[7] = 'blindness' + priority[8] = 'poison' + priority[9] = 'diseased' + + statusTable = S{} +end) + +windower.register_event('addon command', function(...) + if statusTable ~= nil then + local player = windower.ffxi.get_player() + for i = 1, 9 do + if statusTable:contains(priority[i]) then + windower.send_command('send @others /ma "'..statSpell[priority[i]]..'" '..player['name']) + if priority[i] == 'doom' then + windower.send_command('input /item "Holy Water" '..player['name']) --Auto Holy water for doom + end + return + end + end + windower.add_to_chat(55,"You are not afflicted by a status with a -na spell.") + end +end) + +windower.register_event('gain buff', function(id) + local name = res.buffs[id].english + if priority:contains(name) and not statusTable:contains(name) then + statusTable:add(name) + end +end) + + +windower.register_event('lose buff', function(id) + local name = res.buffs[id].english + if statusTable:contains(name) then + statusTable:remove(name) + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/stopwatch/readme.md b/Data/DefaultContent/Libraries/addons/addons/stopwatch/readme.md new file mode 100644 index 0000000..6e33c27 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/stopwatch/readme.md @@ -0,0 +1,13 @@ +**Commands:** + +sw start + to start the stopwatch + +sw stop + to pause the stopwatch + +sw reset + to reset the stopwatch cumulative time to 0 + + +Addon by Patrick Finnigan (Puhfyn@Ragnarok) - https://github.com/finnigantime. diff --git a/Data/DefaultContent/Libraries/addons/addons/stopwatch/stopwatch.lua b/Data/DefaultContent/Libraries/addons/addons/stopwatch/stopwatch.lua new file mode 100644 index 0000000..c121266 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/stopwatch/stopwatch.lua @@ -0,0 +1,89 @@ +_addon.name = 'stopwatch' +_addon.author = 'Patrick Finnigan (Puhfyn@Ragnarok)' +_addon.version = '1.0' +_addon.commands = {'sw', 'stopwatch'} + +require('logger') +config = require('config') +texts = require('texts') +timeit = require('timeit') + +defaults = {} +defaults.pos = {} +defaults.pos.x = 450 +defaults.pos.y = 0 +defaults.text = {} +defaults.text.font = 'Arial' +defaults.text.size = 12 + +settings = config.load(defaults) +times = texts.new(settings) +is_active = false +cumulative_time = 0 +timer = timeit.new() + +windower.register_event('prerender', function() + local info = windower.ffxi.get_info() + if not info.logged_in then + times:hide() + return + end + + total_time = cumulative_time + if is_active == true then + total_time = total_time + timer:check() + end + times:text(string.format('%0.2d:%0.2d:%0.2d', math.floor(total_time / 3600), math.floor(total_time / 60) % 60, math.floor(total_time) % 60)) + times:visible(true) +end) + +windower.register_event('addon command', function(command) + command = command:lower() + + if command == 'start' then + if is_active == false then + is_active = true + timer:start() + end + elseif command == 'stop' then + if is_active == true then + is_active = false + cumulative_time = cumulative_time + timer:stop() + end + elseif command == 'reset' then + cumulative_time = 0 + timer:next() + else + log("'sw start' to start the stopwatch" ) + log("'sw stop' to stop the stopwatch") + log("'sw reset' to reset the stopwatch cumulative time to 0") + end +end) + +--[[ +Copyright © 2015, Patrick Finnigan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of stopwatch nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Patrick Finnigan 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. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/targetinfo/readme.md b/Data/DefaultContent/Libraries/addons/addons/targetinfo/readme.md new file mode 100644 index 0000000..2f8c7e9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/targetinfo/readme.md @@ -0,0 +1,12 @@ +# TargetInfo # + +This addon shows some memory information of your current target, provided the target is an NPC. This information is otherwise not available in the game. + +Per settings file adjustable settings: + +`ShowHexID` + This ID is usually used when specifying mob IDs for NM placeholders. +`ShowFullID` + The full ID, containing zone information and mob category as well. +`ShowSpeed` + Shows percentual deviations from regular movement speed (+x% and -x%). diff --git a/Data/DefaultContent/Libraries/addons/addons/targetinfo/targetinfo.lua b/Data/DefaultContent/Libraries/addons/addons/targetinfo/targetinfo.lua new file mode 100644 index 0000000..e8bbe23 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/targetinfo/targetinfo.lua @@ -0,0 +1,115 @@ +_addon.name = 'TargetInfo' +_addon.author = 'Arcon' +_addon.version = '1.0.1.2' +_addon.language = 'English' + +require('luau') +texts = require('texts') + +-- Config + +defaults = {} +defaults.ShowHexID = true +defaults.ShowFullID = true +defaults.ShowSpeed = true +defaults.ShowTargetName = false +defaults.display = {} +defaults.display.pos = {} +defaults.display.pos.x = 0 +defaults.display.pos.y = 0 +defaults.display.bg = {} +defaults.display.bg.red = 0 +defaults.display.bg.green = 0 +defaults.display.bg.blue = 0 +defaults.display.bg.alpha = 102 +defaults.display.text = {} +defaults.display.text.font = 'Consolas' +defaults.display.text.red = 255 +defaults.display.text.green = 255 +defaults.display.text.blue = 255 +defaults.display.text.alpha = 255 +defaults.display.text.size = 12 + +settings = config.load(defaults) +settings:save() + +text_box = texts.new(settings.display, settings) + +-- Constructor + +initialize = function(text, settings) + local properties = L{} + if settings.ShowFullID then + properties:append('ID: ${full|-|%08s}') + end + if settings.ShowHexID then + properties:append('Hex ID: ${hex|-|%.3X}') + end + if settings.ShowSpeed then + properties:append('Speed: ${speed|-}') + end + if settings.ShowTargetName then + properties:append('${target_label} ${target_name||%15s}') + end + + text:clear() + text:append(properties:concat('\n')) +end + +text_box:register_event('reload', initialize) + +-- Events + +windower.register_event('prerender', function() + local remove = S{} + local mob = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') + if mob and mob.id > 0 then + local player = windower.ffxi.get_player() + local mobclaim = windower.ffxi.get_mob_by_id(mob.claim_id) + local target = windower.ffxi.get_mob_by_index(mob.target_index) + local info = {} + info.hex = mob.index + info.full = mob.id + local speed = (mob.status == 5 or mob.status == 85) and (100 * (mob.movement_speed / 4)):round(2) or (100 * (mob.movement_speed / 5 - 1)):round(2) + info.speed = ( + speed > 0 and + '\\cs(0,255,0)' .. ('+' .. speed):lpad(' ', 5) + or speed < 0 and + '\\cs(255,0,0)' .. speed:string():lpad(' ', 5) + or + '\\cs(102,102,102)' .. ('+' .. speed):lpad(' ', 5)) .. '%\\cr' + if mob.id == player.id then + info.target_label = 'Target:' + info.target_name = mob.name + elseif mobclaim and mobclaim.id > 0 then + info.target_label = 'Claim: ' + info.target_name = mobclaim and mobclaim.name or nil + elseif target and target.id > 0 then + info.target_label = 'Target:' + info.target_name = target and target.name or nil + else + remove:add('target_label') + remove:add('target_name') + end + text_box:update(info) + text_box:show() + for entry in remove:it() do + text_box[entry] = nil + end + else + text_box:hide() + end +end) + +--[[ +Copyright © 2013-2017, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/temps/ReadMe.md b/Data/DefaultContent/Libraries/addons/addons/temps/ReadMe.md new file mode 100644 index 0000000..9443371 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/temps/ReadMe.md @@ -0,0 +1,27 @@ +**Author:** Snaps<br> +**Version:** 1.0<br> +**Date:** April 19th, 2017<br> + +# temps # + +* Makes buying temporary items and key items from escha npcs much easier. + +#### Commands: #### +1. help - Shows a menu of commands in game +2. buy [override] - Buys all temporary items in an escha zone. +- buy - Buys all temporary items. +- buy radialens - Buys all temporary items and also attempts to buy a radialens, even if it is on your blacklist or if you already have one. +3. blacklist [add|a|remove|r] [item] - Add/Remove items from your blacklist. +- blacklist add radialens - Adds radialens to your blacklist. +- blacklist a "lucid potion i" - Adds Lucid Potion I to your blacklist. +- blacklist remove radialens - Removes radialens from your blacklist. +- blacklist r "lucid potion i" - Removes Lucid Potion I from your blacklist. +4. turbo - Toggle the turbo feature. +- turbo - Enables or disables the turbo feature. + +#### Notes: #### + +* You must be near an escha npc that sells temporary items. +* A buy radialens command will refresh your Radialens duration in the event that you already have one. +* The blacklist comes preloaded with Mollifier, Primeval Brew, and Radialens. + diff --git a/Data/DefaultContent/Libraries/addons/addons/temps/items.lua b/Data/DefaultContent/Libraries/addons/addons/temps/items.lua new file mode 100644 index 0000000..06175f3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/temps/items.lua @@ -0,0 +1,52 @@ +return { + [2894] = {name="Tribulens", option=4, key_item=true, offset=0}, + [3031] = {name="Radialens", option=2308, key_item=true, offset=9}, + [3032] = {name="Mollifier", option=2564, key_item=true, offset=10}, + [4182] = {name="Instant Reraise", option=7, key_item=false, offset=4}, + [4202] = {name="Daedalus Wing", option=263, key_item=false, offset=26}, + [4206] = {name="Catholicon", option=519, key_item=false, offset=14}, + [4254] = {name="Megalixir", option=775, key_item=false, offset=12}, + [5385] = {name="Barbarian's Drink", option=1031, key_item=false, offset=21}, + [5386] = {name="Fighter's Drink", option=1287, key_item=false, offset=6}, + [5387] = {name="Oracle's Drink", option=1543, key_item=false, offset=22}, + [5388] = {name="Assassin's Drink", option=1799, key_item=false, offset=5}, + [5389] = {name="Spy's Drink", option=2055, key_item=false, offset=8}, + [5390] = {name="Braver's Drink", option=2311, key_item=false, offset=15}, + [5391] = {name="Soldier's Drink", option=2567, key_item=false, offset=17}, + [5392] = {name="Champion's Drink", option=2823, key_item=false, offset=9}, + [5393] = {name="Monarch's Drink", option=3079, key_item=false, offset=20}, + [5394] = {name="Gnostic's Drink", option=3335, key_item=false, offset=7}, + [5395] = {name="Cleric's Drink", option=3591, key_item=false, offset=30}, + [5397] = {name="Sprinter's Drink", option=3847, key_item=false, offset=16}, + [5824] = {name="Lucid Potion I", option=4103, key_item=false, offset=0}, + [5825] = {name="Lucid Potion II", option=4359, key_item=false, offset=10}, + [5826] = {name="Lucid Potion III", option=4615, key_item=false, offset=23}, + [5827] = {name="Lucid Ether I", option=4871, key_item=false, offset=1}, + [5828] = {name="Lucid Ether II", option=5127, key_item=false, offset=11}, + [5829] = {name="Lucid Ether III", option=5383, key_item=false, offset=24}, + [5830] = {name="Lucid Elixir I", option=5639, key_item=false, offset=25}, + [5834] = {name="Lucid Wings I", option=5895, key_item=false, offset=13}, + [5835] = {name="Healing Salve I", option=6151, key_item=false, offset=2}, + [5836] = {name="Healing Salve II", option=6407, key_item=false, offset=18}, + [5837] = {name="Clear Salve I", option=6663, key_item=false, offset=3}, + [5838] = {name="Clear Salve II", option=6919, key_item=false, offset=19}, + [6399] = {name="Savior's Tonic", option=7175, key_item=false, offset=27}, + [6400] = {name="Mirror's Tonic", option=7431, key_item=false, offset=28}, + [6401] = {name="Moneta's Tonic", option=7687, key_item=false, offset=29}, + [6402] = {name="Steadfast Tonic", option=7943, key_item=false, offset=31}, + [5322] = {name="Healing Powder", option=8199, key_item=false, offset=32}, + [4255] = {name="Mana Powder", option=8455, key_item=false, offset=33}, + [4208] = {name="Catholicon +1", option=8711, key_item=false, offset=34}, + [6474] = {name="Poison Buffer", option=8967, key_item=false, offset=35}, + [5832] = {name="Healing Mist", option=9223, key_item=false, offset=36}, + [5833] = {name="Mana Mist", option=9479, key_item=false, offset=37}, + [5439] = {name="Vicar's Drink", option=9735, key_item=false, offset=38}, + [5831] = {name="Lucid Elixir II", option=9991, key_item=false, offset=39}, + [5436] = {name="Dusty Reraise", option=10247, key_item=false, offset=40}, + [5853] = {name="Primeval Brew", option=10503, key_item=false, offset=41}, + [6475] = {name="Lucid Wings II", option=10759, key_item=false, offset=42}, + [6473] = {name="Super Revitalizer", option=11015, key_item=false, offset=43}, + [6476] = {name="Virus Buffer", option=11271, key_item=false, offset=44}, + [6477] = {name="Charm Buffer", option=11527, key_item=false, offset=45}, + [6478] = {name="Curse Buffer", option=11783, key_item=false, offset=46}, +} diff --git a/Data/DefaultContent/Libraries/addons/addons/temps/temps.lua b/Data/DefaultContent/Libraries/addons/addons/temps/temps.lua new file mode 100644 index 0000000..1ec0f89 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/temps/temps.lua @@ -0,0 +1,345 @@ +--[[ +temps v1.0 + +Copyright © 2017, Mojo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of temps nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mojo BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'temps' +_addon.author = 'Mojo' +_addon.version = '1.0' +_addon.command = 'temps' + +require('chat') +require('logger') +require('pack') + +config = require('config') +items = require('items') +packets = require('packets') + +local help_text = [[ +temps - Command List: +1. help - Displays this help menu. +2. buy - Buys all temporary items in an escha zone. +* buy - Buys all temporary items. +* buy radialens - Buys all temporary items and also + attempts to buy a radialens, even if it is on your + blacklist or if you already have one. +3. blacklist - Add(a)/Remove(r) items from your blacklist. +* blacklist add radialens - Adds radialens to your + blacklist. +* blacklist a radialens - Adds radialens to your blacklist. +* blacklist remove radialens - Removes radialens from + your blacklist. +* blacklist r radialens - Removes radialens from your + blacklist. +4. turbo - Toggle the turbo feature. +* turbo - Enables or disables the turbo feature. + +Notes: + You must be near an escha NPC that sells temporary + items. A buy radialens command will refresh your + radialens duration in the event that you already have + one. +]] + +local defaults = { + turbo = false, + blacklist = S{ + 'mollifier', + 'primeval brew', + 'radialens', + } +} + +local settings = config.load('settings.xml', defaults) +local handlers = {} +local state = 'idle' +local zone = nil +local force = nil +local silt = 0 +local key_ids = {} +local key_items = 0 +local outstanding = 0 +local inflight = {} + +local temp_items = { + [1] = 0, + [2] = 0, +} + +local conditions = { + temps = false, + busy = false, +} + +local zones = { + [288] = {npc = "Affi", menu = 9701}, + [289] = {npc = "Dremi", menu = 9701}, + [291] = {npc = "Shiftrix", menu = 9701}, +} + +local function busy_wait(block, timeout, message) + local start = os.time() + while conditions[block] and ((os.time() - start) < timeout) do + coroutine.sleep(.1) + end + if os.time() - start >= timeout then + conditions[block] = false + return "timed out - %s":format(message) + end +end + +local function validate(constrain) + zone = windower.ffxi.get_info()['zone'] + if zones[zone] then + local npc = windower.ffxi.get_mob_by_name(zones[zone].npc) + if npc and ((math.sqrt(npc.distance) < 6) or (not constrain)) then + return npc + else + error("Too far from %s.":format(zones[zone].npc)) + end + else + error("Not in an Escha zone.") + end +end + +local function has_item(item) + if item.key_item then + return (math.floor(key_items/math.pow(2, item.offset)) % 2) == 1 + else + return (math.floor(temp_items[math.floor(item.offset/32) + 1]/math.pow(2, item.offset % 32)) % 2) == 1 + end +end + +local function force_purchase(item) + return (item.name == force) +end + +local function ignore_item(item) + return settings.blacklist:contains(item.name:lower()) +end + +local function purchase_items() + local npc = validate() + local count = 0 + for item_id, item in pairs(items) do + if force_purchase(item) or (not has_item(item)) then + if ignore_item(item) and (not force_purchase(item)) then + windower.add_to_chat(100, string.format("Ignoring \30\2%s\30\43.", item.name)) + else + local p = packets.new('outgoing', 0x5b, { + ["Target"] = npc.id, + ["Option Index"] = item.option, + ["Target Index"] = npc.index, + ["Automated Message"] = true, + ["Zone"] = zone, + ["Menu ID"] = zones[zone].menu, + }) + windower.add_to_chat(100, string.format("Purchasing \30\2%s\30\43.", item.name)) + packets.inject(p) + state = 'purchase' + inflight[item_id] = true + count = count + 1 + if not settings.turbo then + coroutine.sleep(1) + end + end + end + end + if count == 0 then + windower.add_to_chat(100, "No temporary items to buy.") + else + outstanding = outstanding + count + if outstanding == 0 then + windower.add_to_chat(100, "Finished purchasing all temporary items.") + end + end +end + +local function exit_menu(id, data, modified, injected, blocked) + if (id == 0x5b) and ((state == 'init') or (state == 'purchase')) then + local p = packets.parse('outgoing', data) + local npc = validate() + if (p['Target'] == npc.id) and not p['Automated Message'] then + outstanding = 0 + state = 'idle' + end + end +end + +local function observe_temps(id, data, modified, injected, blocked) + if (id == 0x5c) and conditions['temps'] then + local p = packets.parse('incoming', data) + temp_items[1] = p['Menu Parameters']:unpack('I', 1) + temp_items[2] = p['Menu Parameters']:unpack('I', 5) + conditions['temps'] = false + end +end + +local function process_dialogue_event(id, data, modified, injected, blocked) + if (id == 0x34) and (state == 'init') then + local p = packets.parse('incoming', data) + state = 'purchase' + silt = p['Menu Parameters']:unpack('I', 5) + key_items = p['Menu Parameters']:unpack('I', 9) + conditions['temps'] = true + busy_wait('temps', 10, 'observe temp items') + purchase_items() + end +end + +local function check_inflight(iid) + if (inflight[iid]) then + inflight[iid] = nil + outstanding = outstanding - 1 + if outstanding == 0 then + windower.add_to_chat(100, "Finished purchasing all temporary items.") + end + end +end + +local function receive_item(id, data, modified, injected, blocked) + if (id == 0x20) and (state == 'purchase') then + local p = packets.parse('incoming', data) + check_inflight(p['Item']) + end +end + +local function obtained_key_item(p, item_id) + if (math.floor(item_id/512) == p['Type']) then + local bit = item_id % 512 + local n = bit % 8 + local character = math.floor(bit/8) + 1 + return ((math.floor(p['Key item available']:byte(character)/math.pow(2, n)) % 2) == 1) + else + return false + end +end + +local function receive_key_items(id, data, modified, injected, blocked) + if (id == 0x55) and (state == 'purchase') then + local p = packets.parse('incoming', data) + for k, v in pairs(inflight) do + if obtained_key_item(p, k) then + check_inflight(k) + end + end + end +end + +local function validate_item(item) + for k, v in pairs(items) do + if item:lower() == v.name:lower() then + return v.name + end + end + error("%s not found in items list.":format(item)) +end + +local function start(override) + if not (state == 'idle') then + return error("Addon is busy.") + elseif override then + force = validate_item(override) + if not force then + return + else + notice("Override provided for %s.":format(force)) + end + else + force = nil + end + local npc = validate(true) + if npc then + local p = packets.new('outgoing', 0x01a, { + ["Target"] = npc.id, + ["Target Index"] = npc.index, + }) + packets.inject(p) + state = 'init' + else + state = 'idle' + end +end + +local function help() + windower.add_to_chat(100, help_text) +end + +local function blacklist(cmd, name) + if not cmd then + return error("No blacklist command provided.") + elseif not S{'add', 'a', 'remove', 'r'}:contains(cmd) then + return error("Unknown blacklist command %s.":format(cmd)) + elseif not name then + return error("No blacklist command parameter provided.") + end + local item = validate_item(name) + if not item then + return + elseif S{'add', 'a'}:contains(cmd) then + notice("Adding %s to your blacklist.":format(item)) + settings.blacklist:add(item:lower()) + else + notice("Removing %s from your blacklist.":format(item)) + settings.blacklist:remove(item:lower()) + end + settings:save() +end + +local function turbo() + if settings.turbo then + notice("Disabling turbo.") + settings.turbo = false + else + notice("Enabling turbo.") + settings.turbo = true + end + settings:save() +end + +local function handle_command(cmd, ...) + local cmd = cmd and cmd:lower() or 'help' + if handlers[cmd] then + handlers[cmd](...) + else + error("Unknown command %s.":format(cmd)) + end +end + +handlers['buy'] = start +handlers['help'] = help +handlers['blacklist'] = blacklist +handlers['turbo'] = turbo + +windower.register_event('incoming chunk', process_dialogue_event) +windower.register_event('incoming chunk', receive_item) +windower.register_event('incoming chunk', receive_key_items) +windower.register_event('incoming chunk', observe_temps) +windower.register_event('outgoing chunk', exit_menu) +windower.register_event('addon command', handle_command) diff --git a/Data/DefaultContent/Libraries/addons/addons/thtracker/readme.md b/Data/DefaultContent/Libraries/addons/addons/thtracker/readme.md new file mode 100644 index 0000000..c5ff3d2 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/thtracker/readme.md @@ -0,0 +1,20 @@ +Authors: Krizz + +Version: 1.0 + +Date: 20170509 + +TH Tracker + +Abbreviation: //th + +Commands: +* help - Shows a menu of commands in game +* pos x y - Positions the TH box. Default location is 100,400. +* hide - Hides the box +* show - Shows the box + + + + + diff --git a/Data/DefaultContent/Libraries/addons/addons/thtracker/thtracker.lua b/Data/DefaultContent/Libraries/addons/addons/thtracker/thtracker.lua new file mode 100644 index 0000000..2f05e04 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/thtracker/thtracker.lua @@ -0,0 +1,125 @@ +--Copyright © 2017, Krizz +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of thtracker nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL KRIZZ BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +_addon.name = 'THTracker' +_addon.author = 'Krizz' +_addon.version = 1.2 +_addon.commands = {'thtracker', 'th'} + +config = require ('config') +texts = require ('texts') +packets = require('packets') +require('logger') + +defaults = {} +defaults.pos = {} +defaults.pos.x = 1000 +defaults.pos.y = 200 +defaults.color = {} +defaults.color.alpha = 200 +defaults.color.red = 200 +defaults.color.green = 200 +defaults.color.blue = 200 +defaults.bg = {} +defaults.bg.alpha = 200 +defaults.bg.red = 30 +defaults.bg.green = 30 +defaults.bg.blue = 30 + +settings = config.load(defaults) + +th = texts.new('${th_string}', settings) + +local th_table = {} + +windower.register_event('addon command', function(command, ...) + command = command and command:lower() + local params = {...} + + if command == 'pos' then + local posx, posy = tonumber(params[2]), tonumber(params[3]) + if posx and posy then + th:pos(posx, posy) + end + elseif command == "hide" then + th:hide() + elseif command == 'show' then + th:show() + else + print('th help : Shows help message') + print('th pos <x> <y> : Positions the list') + print('th hide : Hides the box') + print('th show : Shows the box') + end +end) + +windower.register_event('incoming chunk', function(id, data) + if id == 0x028 then + local packet = packets.parse('incoming', data) + if packet.Category == 1 and packet['Target 1 Action 1 Has Added Effect'] and packet['Target 1 Action 1 Added Effect Message'] == 603 then + th_table[packet['Target 1 ID']] = 'TH: '..packet['Target 1 Action 1 Added Effect Param'] + update_text() + elseif packet.Category == 3 and packet['Target 1 Action 1 Message'] == 608 then + th_table[packet['Target 1 ID']] = 'TH: '..packet['Target 1 Action 1 Param'] + update_text() + end + elseif id == 0x038 then + local packet = packets.parse('incoming', data) + if th_table[packet['Mob']] and packet['Type'] == 'kesu' then + th_table[packet['Mob']] = nil + update_text() + end + elseif id == 0x00E then + local packet = packets.parse('incoming', data) + if th_table[packet['NPC']] and packet['Status'] == 0 and packet['HP %'] == 100 then + th_table[packet['NPC']] = nil + update_text() + end + end +end) + +windower.register_event('zone change', function() + th_table = {} + update_text() +end) + +windower.register_event('target change', function() + update_text() +end) + +function update_text() + local current_string + local target = windower.ffxi.get_mob_by_target('st') or windower.ffxi.get_mob_by_target('t') + if target and th_table[target.id] then + current_string = target.name..'\n '..th_table[target.id] + th:show() + else + current_string = '' + th:hide() + end + th.th_string = current_string +end diff --git a/Data/DefaultContent/Libraries/addons/addons/timestamp/README.md b/Data/DefaultContent/Libraries/addons/addons/timestamp/README.md new file mode 100644 index 0000000..e109cd8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/timestamp/README.md @@ -0,0 +1,46 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20131102 + +# Timestamp # +This addon prefixes any chat message with a timestamp. +Based on Timestamp plugin. + +## Commands ## +### help ### +Shows the help text + +``` +timestamp [<command>] help +``` +* **_command_:** defines the name of the command you need help with + +### format ### +Sets the timestamp's format. + +``` +timestamp format [help|<color>] +``` +* **help:** shows the help text. +* **_format_:** defines the timestamp's format. The available constants are: **${year}**, **${y}**, **${year_short}**, **${month}**, **${m}**, **${month_short}**, **${month_long}**, **${day}**, **${d}**, **${day_short}**, **${day_long}**, **${hour}**, **${h}**, **${hour24}**, **${hour12}**, **${minute}**, **${min}**, **${second}**, **${s}**, **${sec}**, **${ampm}**, **${timezone}**, **${tz}**, **${timezone_sep}**, **${tz_sep}**, **${time}**, **${date}**, **${datetime}**, **${iso8601}**, **${rfc2822}**, **${rfc822}**, **${rfc1036}**, **${rfc1123}**, **${rfc3339}** + +### color ### +Sets the timestamp's color. + +``` +timestamp color [help|<color>] +``` +* **help:** shows the help text. +* **_color_:** defines the timestamp's color. The value must be between 0 and 511, inclusive. + +---- + +## Changelog ## + +### v1.20130616 ### +* **fix:** Fixed some indentation issues. + +### v1.20130607 ### +* **fix:** Fixed custom timestamp formatting. + +### v1.20130529 ### +* first release. diff --git a/Data/DefaultContent/Libraries/addons/addons/timestamp/data/settings.xml b/Data/DefaultContent/Libraries/addons/addons/timestamp/data/settings.xml new file mode 100644 index 0000000..d694dcc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/timestamp/data/settings.xml @@ -0,0 +1,7 @@ +<?xml version="1.1" ?> +<settings> + <global> + <color>508</color> + <format>[${time}]</format> + </global> +</settings> diff --git a/Data/DefaultContent/Libraries/addons/addons/timestamp/timestamp.lua b/Data/DefaultContent/Libraries/addons/addons/timestamp/timestamp.lua new file mode 100644 index 0000000..0d59d78 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/timestamp/timestamp.lua @@ -0,0 +1,203 @@ +--[[ +timestamp v1.20131102 + +Copyright © 2013-2014, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of timestamp nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'timestamp' +_addon.author = 'Zohno' +_addon.version = '1.20131102' +_addon.commands = {'timestamp', 'ts'} + +chars = require('chat.chars') +require('logger') +require('tables') +require('sets') +require('lists') + +config = require('config') + +do + local now = os.time() + local h, m = math.modf(os.difftime(now, os.time(os.date('!*t', now))) / 3600) + + tz = '%+.4d':format(100 * h + 60 * m) + tz_sep = '%+.2d:%.2d':format(h, 60 * m) +end + +constants = { + ['year'] = '%Y', + ['y'] = '%Y', + ['year_short'] = '%y', + ['month'] = '%m', + ['m'] = '%m', + ['month_short'] = '%b', + ['month_long'] = '%B', + ['day'] = '%d', + ['d'] = '%d', + ['day_short'] = '%a', + ['day_long'] = '%A', + ['hour'] = '%H', + ['h'] = '%H', + ['hour24'] = '%H', + ['hour12'] = '%I', + ['minute'] = '%M', + ['min'] = '%M', + ['second'] = '%S', + ['s'] = '%S', + ['sec'] = '%S', + ['ampm'] = '%p', + ['timezone'] = tz, + ['tz'] = tz, + ['timezone_sep'] = tz_sep, + ['tz_sep'] = tz_sep, + ['time'] = '%H:%M:%S', + ['date'] = '%Y-%m-%d', + ['datetime'] = '%Y:%m:%d %H:%M:%S', + ['iso8601'] = '%Y-%m-%dT%H:%M:%S' .. tz_sep, + ['rfc2822'] = '%a, %d %b %Y %H:%M:%S ' .. tz, + ['rfc822'] = '%a, %d %b %y %H:%M:%S ' .. tz, + ['rfc1036'] = '%a, %d %b %y %H:%M:%S ' .. tz, + ['rfc1123'] = '%a, %d %b %Y %H:%M:%S ' .. tz, + ['rfc3339'] = '%Y-%m-%dT%H:%M:%S' .. tz_sep, +} + +lead_bytes = S{0x1E, 0x1F, 0xF7, 0xEF, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x7F} +newline_pattern = '[' .. string.char(0x07, 0x0A) .. ']' + +defaults = {} +defaults.color = 201 +defaults.format = '[${time}]' + +settings = config.load(defaults) + +function make_timestamp(format) + return os.date((format:gsub('%${([%l%d_]+)}', constants))) +end + +windower.register_event('incoming text', function(original, modified, mode, newmode, blocked) + if blocked then + return + end + + if mode == 151 or mode == 150 then + newmode = 151 + else + local lines = L{} + + -- Split by newline, if applicable + if modified:match(newline_pattern) then + local split = modified:split(newline_pattern, 0, true, false) + lines:append(split[1]) + + for i = 2, split.n, 2 do + local last = lines:last()[-1] + if last and lead_bytes:contains(last:byte()) then + lines[-1] = '%s%s%s':format(lines[-1], split[i], split[i+1]) + else + lines:append(split[i + 1]) + end + end + + if lines:last() == '' then + lines:remove(lines.n) + end + + -- Insert spaces in NPC text + if mode == 190 then + for i = 2, lines.n do + lines[i] = string.char(0x81, 0x40) .. lines[i] + end + end + else + lines:append(modified) + end + + -- Append the colored timestamp before every line and concatenate them again by a newline + modified = lines:map(function(str) + return make_timestamp(settings.format):color(settings.color)..' '..str + end):concat(string.char(0x0A)) + end + + return modified, newmode +end) + +windower.register_event('addon command', function(cmd, ...) + cmd = cmd and cmd:lower() or 'help' + local args = {...} + + if cmd == 'format' then + if not args[1] then + error('Please specify the new timestamp format.') + elseif args[1] == 'help' then + log('Sets the timestamp format.') + log('Usage: timestamp format [help|<format>]') + log('Positional arguments:') + log(chars.wsquare..' help: shows the help text.') + log(chars.wsquare..' <format>: defines the timestamp format. The available constants are:') + + for key in constants:keyset():sort():it() do + log(' ${'..key..'}: '..make_timestamp('${'..key..'}')) + end + else + settings.format = args[1] + + settings:save() + log('The new timestamp format has been saved ('..make_timestamp(settings.format)..').') + end + + elseif cmd == 'color' then + if not args[1] then + error('Please specify the new timestamp color.') + elseif args[1] == 'help' then + log('Sets the timestamp color.') + log('Usage: timestamp color [help|<color>]') + log('Positional arguments:') + log(chars.wsquare..' help: shows the help text.') + log(windower.to_shift_jis(chars.wsquare..' <color>: defines the timestamp color. The value must be between 0 and 511, inclusive.')) + else + local color = tonumber(args[1]) + + if not color or color < 0x00 or color > 0xFF then + error('Please specify a valid color.') + else + settings.color = color + + settings:save() + log('The new timestamp color has been saved ('..color..').') + end + end + + elseif cmd == 'save' then + settings:save('all') + + else + log(chars.wsquare..' timestamp [<command>] help -- shows the help text.') + log(chars.wsquare..' timestamp color <color> -- sets the timestamp color.') + log(chars.wsquare..' timestamp format <format> -- sets the timestamp format.') + end +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/translate/ReadMe.txt b/Data/DefaultContent/Libraries/addons/addons/translate/ReadMe.txt new file mode 100644 index 0000000..d6eab5e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/translate/ReadMe.txt @@ -0,0 +1,13 @@ +Last Updated: 01/02/15 + +Description: +Translate is an addon designed to automatically translate chat text and search comments from JP to EN. It creates a dictionary using the windower resources and custom-made dictionaries (../windower/addons/translate/dicts/), and replaces incoming chat lines with the matching translation in order of longest JP phrase to shortest. + +This approach is not without its problems, and occasionally phrases will be translated that do not mean anything obvious. Short job names are particularly common offenders, and here is a brief guide to interpreting them: +* WAR - Also used to indicate the number of fights that will be done. +* ka - Also the abbreviation for PUP +* GEO - Can also mean winds. + +Commands: //trans + +* show original - Displays the original text. Known to double-up autotranslated phrases sometimes in search comments. Reason unclear.
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/translate/dicts/bgthread_guide.lua b/Data/DefaultContent/Libraries/addons/addons/translate/dicts/bgthread_guide.lua new file mode 100644 index 0000000..f49fa12 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/translate/dicts/bgthread_guide.lua @@ -0,0 +1,169 @@ +-- Dictionary of terms collected from the BG translate addon thread: +-- http://www.bluegartr.com/threads/123229-Translate-Addon + +local dict = { + {en="Normal",ja="ふつう"}, + {en="Normal",ja="普通"}, + {en="Difficult",ja="むず"}, + {en="Very Difficult",ja="とてもむず"}, + + {en="Menace",ja="メナス"}, + {en="Voidwatch",ja="ヴォッチ"}, + {en="Burrows",ja="バローズ"}, + {en="Alluvion",ja="アルビオン"}, + {en="Ra'Kaznar Skirmish",ja="カザスカ"}, + {en="Ra'Kaznar Skirmish",ja="スカカザ"}, + {en="Ra'Kaznar Skirmish",ja="かざすか"}, + + {en="Leveling",ja="レベリング"}, + {en="Aby",ja="アビ"}, + {en="Altepa",ja="アルテパ"}, + {en="Misareaux",ja="セア ミザレオ"}, + {en="Doll",ja="ド-ル"}, + + {en="Recruiting",ja="募集"}, + {en="Check seacom for jobs",ja="サチコ参照ジョブを添えてテルください"}, + {en="Search Comment",ja="サチコ"}, + {en="Job doesn't matter",ja="ジョブ不問"}, + {en="currently",ja="現在"}, + + + {en="Hennetiel",ja="NTL"}, + {en="Morimar",ja="モリマ"}, + {en="Kamihr",ja="カミール"}, + {en="Marjami",ja="マリアミ"}, + {en="Rala",ja="ララ"}, + {en="Yorcia",ja="ヨル"}, + {en="Foret",ja="水林"}, + {en="Ceizak",ja="ケイザ"}, + {en="Cirdas",ja="シルダス"}, + + {en="Trial by Fire",ja="火の試練"}, + {en="Trial by Lightning",ja="雷の試練"}, + {en="Trial by Earth",ja="土の試練"}, + {en="Trial by Water",ja="水の試練"}, + {en="Trial by Ice",ja="氷の試練"}, + {en="Trial by Wind",ja="風の試練"}, + {en="Leviathan",ja="リヴァイアサン"}, + {en="Levi",ja="リヴァ"}, + + {en="Tenzen",ja="てんぜん"}, + {en="Tenzen",ja="テンゼン"}, + {en="Warrior's Path",ja="武士道とは"}, + + {en="Return to Delkfutt's Tower",ja="デルクフの塔 再び"}, + {en="Return to Delkfutt's Tower",ja="テルクフの塔再び"}, + {en="Eald'narche",ja="エルド"}, + {en="Kam'lanaut",ja="カムラ"}, + {en="Stellar Fulcrum",ja="天輪"}, + {en="Celestial Nexus",ja="宿星"}, + + {en="BRD",ja="吟"}, + {en="GEO",ja="風"}, + + {en="Wanted (Battle)",ja="ウォンテッド"}, + {en="Dragonfly",ja="トンボ"}, + {en="Cost",ja="コスト"}, + + {en="Incursion",ja="インカ"}, + {en="Tryl",ja="トリル"}, + + {en="*waves*",ja="ノシ"}, + {en="Full Alliance",ja="フルアラ"}, + + {en="EIN",ja="エイン"}, + {en="Ampuole",ja="アンプル"}, + + -- These are from wiki.ffo.jp: + {en="Hugemaw Harold",ja="ひゅーじまうへらるど"}, + {en="Bouncing Bertha",ja="ばうんしんぐばーさ"}, + {en="Prickly Pitriv",ja="ぷりっくりーぴとりう゛"}, + {en="Ironhorn Balduro",ja="あいあんほーんばるだーの"}, + {en="Sleepy Mabel",ja="すりーぴーめーぶる"}, + {en="Valkurm Imperiator",ja="ばるくるむいんぴらとあ"}, + {en="Serpopard Ninlil",ja="さーぽぱーどにんりる"}, + {en="Abyssdiver",ja="あびすだいばー"}, + {en="Intuila",ja="いんつりあ"}, + {en="Emperor Arthro",ja="えんぺらーあーすろ"}, + {en="Orcfeltrap",ja="おーくふぇるとらっぷ"}, + {en="Lumber Jill",ja="らんばーじる"}, + {en="Joyous Green",ja="じょいあすぐりーん"}, + {en="Strix",ja="すとりくす"}, + {en="Warblade Beak",ja="うぉーぶれいどびーく"}, + {en="Arke",ja="あるけ"}, + {en="Largantua",ja="らがんちゅあ"}, + {en="Largantua",ja="ラガンチュア"}, + {en="Beist",ja="ばいすと"}, + {en="Jester Malatrix",ja="じぇすたーまらとりくす"}, + {en="Cactrot Veloz",ja="かくとろっとべろす"}, + {en="Woodland Mender",ja="うっどらんどめんだー"}, + {en="Sybaritic Samantha",ja="しばりちっくさまんさ"}, + {en="Keeper of Heiligtum",ja="きーぱーおぶはいりぐたむ"}, + {en="Douma Weapon",ja="どうまうぇぽん"}, + {en="King Ugoprygid",ja="きんぐうるぴじっど"}, + {en="Vedrfolnir",ja="う゛ぇどるふぉるにる"}, + + + {en="Ark Angel",ja="あーくえんじぇる"}, + {en="Hume",ja="ひゅーむ"}, + {en="Tarutaru",ja="たるたる"}, + {en="Mithra",ja="みすら"}, + {en="Elvaan",ja="えるう゛ぁーん"}, + {en="Galka",ja="がるか"}, + {en="Eald'narche",ja="えるどなーしゅ"}, + {en="Kam'lanaut",ja="かむらなーと"}, + {en="Ouryu",ja="おうりゅう"}, + {en="Lancelord Gaheel Ja",ja="らんすろーど・がひーじゃ"}, + {en="Gessho",ja="げっしょー"}, + {en="Shadow Lord",ja="しゃどうろーど"}, + {en="Head Wind",ja="むかいかぜ"}, + {en="Shadow Lord Fight",ja="らんくごみっしょん"}, + {en="Legacy of the Lost",ja="ぼうこくのいさん"}, + {en="Legacy of the Lost",ja="亡国の遺産"}, + {en="Warrior's Path",ja="ぶしどうとは"}, + {en="Puppet in Peril",ja="少女の傀儡"}, + {en="Puppet in Peril",ja="しょうじょのくぐつ"}, + {en="Puppet in Peril",ja="しょうじょのかいらい"}, + {en="The Savage",ja="たけきものたちよ"}, + {en="The Savage",ja="猛き者たちよ"}, + {en="The Celestial Nexus",ja="しゅくせいのざ"}, + {en="The Celestial Nexus",ja="宿星の座"}, + {en="Return to Delfutt's Tower",ja="デルクフの塔再び"}, + {en="Divine Might",ja="しんい"}, + {en="Divine Might",ja="神威"}, + {en="Trial by Fire",ja="ひのしれん"}, + {en="Trial by Lightning",ja="かみなりのしれん"}, + {en="Trial by Earth",ja="つちのしれん"}, + {en="Trial by Water",ja="みずのしれん"}, + {en="Trial by Ice",ja="こおりのしれん"}, + {en="Trial by Wind",ja="かぜのしれん"}, + {en="Leviathan",ja="りう゛ぁいあさん"}, + {en="Ramuh",ja="らむう"}, + {en="Titan",ja="たいたん"}, + {en="Garuda",ja="がるーだ"}, + {en="Shiva",ja="しう゛ぁ"}, + {en="Ifrit",ja="いふりーと"}, + {en="Fenrir",ja="ふぇんりる"}, + {en="Carbuncle",ja="かーばんくる"}, + {en="Diabolos",ja="でぃあぼろす"}, + {en="Cait Sith",ja="けっとしー"}, + {en="Atomos",ja="あともす"}, + + + {en="Vagary",ja="ベガリ"}, + {en="Vagary",ja="ベガリーインスペクター"}, + {en="Balamor",ja="バラモア"}, + {en="Dhokmak",ja="ドクマク"}, + {en="Ashrakk",ja="アシュラック"}, + {en="Plouton",ja="上位 ハデス"}, + {en="Perfidien",ja="下位 ハデス"}, + {en="Plouton",ja="上位ハデス"}, + {en="Perfidien",ja="下位ハデス"}, + {en="Hades",ja="ハデス"}, + {en="upper",ja="上位"}, + {en="lower",ja="下位"}, + + {en="Sinister Reign",ja="シニスターレイン"}, +} + +return dict
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/translate/dicts/captainhowdy_guide.lua b/Data/DefaultContent/Libraries/addons/addons/translate/dicts/captainhowdy_guide.lua new file mode 100644 index 0000000..60d3ca8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/translate/dicts/captainhowdy_guide.lua @@ -0,0 +1,352 @@ +-- Default custom dictionary, taken from: +-- http://wipe.guildwork.com/forum/threads/5039eb67205cb26e261f6988-japaneseenglish-guide-ffxi + +local dict = { + -- Party Chat section + {en="BRB",ja="りせき"}, + {en="Congratulations",ja="おめでとうー"}, + --Excuse Me, WC for 2 Minutes || sumimasen, 2 pun hodo toile ittekimasu || すみません、2分ほどトイレ行ってきます + {en="Gather for buffs",ja="プロテアとシェルかけるのであつまってください"}, + {en="I messed up",ja="しまった"}, + {en="Sorry, I'm back",ja="ごめんただいまですー"}, + {en="job okay?",ja="でいいかね"}, + {en="It can't be helped",ja="仕方がない "}, + {en="Let's go",ja="行きましょ"}, + --"Mary", Please go to "John's" Party || Mary wa John no patei ni itte kudasai || MaryはJohnのパーティーに行って下さい + {en="Nice to meet you",ja="よろしくですー"}, + {en="Oh, that's good!",ja="それはいいですね"}, + {en="One moment please",ja="ちょっと待って下さい"}, + {en="Relogging, BRB",ja="コンピューターをさいきどうして、すぐもどります"}, + {en="Sorry, can you invite me again?",ja="ごめんね、またさそって下さい"}, + {en="Sorry, I disconnected",ja="私は接続を失いましたごめんなさい"}, + {en="Thank you",ja="ありがとうですー"}, + {en="They are already there",ja="もおむこおにいっています"}, + {en="Welcome back",ja="お帰りー"}, + {en="What job?",ja="すみません、私のジョブなんですか?"}, + + -- During an event section + {en="pop",ja="わかす"}, + {en="Go ahead",ja="先にどうぞ"}, + {en="How many left?",ja="あとなんかいでおわりますか"}, + {en="Hurry up",ja="急いで"}, + {en="I have to go",ja="行かなくてはなりません"}, + {en="I understand",ja="わかった"}, -- Informal + {en="I understand",ja="わかりました"}, -- Formal + {en="I will be right back",ja="すぐ戻ります"}, + {en="I will take it",ja="それ下さい"}, + {en="If we trigger weakness, defeat it quickly",ja="弱点ついたら早めに倒しましょう"}, + {en="Ignore weaknesses and defeat it quickly",ja="弱点を無視して早めに倒しましょう "}, + {en="Mission accomplished, see you!",ja="またあとで会いましょう"}, + {en="I'm going to use my merits",ja="メリポ振ってきます"}, + {en="next",ja="次"}, + {en="Roger that",ja="りょうかい"}, + {en="Run/Fight",ja="にげて"}, + {en="See you later",ja="またあとで会いましょう"}, + {en="Take it easy",ja="むりしないで"}, + {en="Thanks for the hard work",ja="お疲れ様でしたー"}, + {en="That was fun",ja="たのしい。。"}, + {en="Puller",ja="つりやく"}, + --We'll Be Done In About 2 More Pulls || Ato 2 webu kurai de owarimasuka || あと2ウェーブくらいで終わりますか + {en="What an idiot",ja="ばかやろう。。"}, + {en="When is the ending?",ja="おわりますか"}, + {en="You can pop next",ja="つぎわかせていいよ"}, + --(You Find Nothing In The Riftworn Pyxis) || Tanakasan wa daikirai desuyo! || (ノ ゜Д゜)ノ三┸┸ + + -- Coming and Going + {en="Bad",ja="だめ"}, + {en="Cheap",ja="やすい"}, + {en="Come with me",ja="私といっしょに来て下さい。"}, + {en="Done",ja="それできまった"}, + {en="Don't touch me",ja="らないで"}, + --Excuse Me, Mind if I Join You? || sumimasen, akiwa arimasu ka? ikitai desu || すみません、あきはありますか?いきたいですー + --Go Straight, Then Turn Left/ Right! || massugu itte kudasai, Soshite, hidari / migi ni magatte kudasai || まっすぐ行って下さい。そして、 左/右にまがって下さい。 + {en="Good luck",ja="がんばってね"}, + {en="I almost gave up",ja="あきらめてました"}, + {en="I feel sick",ja="調子が悪いです"}, + {en="I'm fine",ja="大丈夫です"}, + {en="I'm leaving",ja="行ってきます"}, + {en="I'm locked out",ja="しめだされました"}, + {en="I'm lost",ja="迷ってしまいました"}, + {en="I'm not going",ja="行きません"}, + {en="Shall we go?",ja="行きませんか"}, + {en="I need your help",ja="たすけて下さい"}, + {en="It's an emergency",ja="きんきゅです"}, + {en="Just a little",ja="少しだけ"}, + {en="Look out",ja="あぶない"}, + {en="No problem",ja="大丈夫です"}, + {en="Nothing",ja="べつに"}, + {en="Nothing much",ja="変わりないです"}, + {en="Please",ja="どぞ"}, -- Offer + {en="Please",ja="お願します"}, -- Request + {en="Please write it",ja="かいて下さい"}, + {en="Really sorry",ja="もうしわけございません"}, + {en="Listen",ja="あのね"}, + {en="Stop fooling around",ja="もうやめたら"}, + {en="Take care of yourself",ja="おだいじに"}, + {en="Take your time",ja="ゆっくりしなさい"}, + {en="Team up?",ja="一緒にやりませんか"}, + {en="This sucks",ja="サイアクだ"}, + {en="This way, please",ja="こちらえどうぞ"}, + {en="Time is up",ja="もう時間よ"}, + {en="Troublesome",ja="めんどくさい。。。"}, + {en="What happened?",ja="何かあった"}, + {en="Yes, I'm coming",ja="はい行きます"}, + {en="Yes, that's right",ja="はいそうです"}, + + {en="Brb, I'm going to the store",ja="りせき、コンビニで行きます"}, + {en="Dont be late",ja="ちこくしないで"}, + {en="Don't lie",ja="うそつかないで"}, + {en="Don't worry",ja="ご心配なく"}, + {en="Excuse me",ja="すみません"}, + {en="Good night",ja="おやすみなさい"}, + {en="Have fun",ja="たのしんで"}, + {en="I agree",ja="まったくだ"}, + {en="I don't know",ja="知りません"}, + {en="I don't speak Japanese, but I'm using an online translator",ja="私は日本語を話しませんでもオンラ イン訳者を使用しています"}, + {en="I guess",ja="そうおもいます"}, + {en="I'm bored",ja="たいくつしてる"}, + {en="I'm confused",ja="まよってる、こんんわくしてる"}, + {en="I'm hungry",ja="おなかがすいた"}, + {en="I'm sleepy",ja="ねむたい"}, + {en="I'm sorry",ja="ごめんなさい"}, + {en="I'm sorry (Sympathy)",ja="ざんねんです"}, + {en="I'm sorry, I'm still studying Japanese",ja="ごめん、日本語勉強しますー"}, + {en="I'm sorry I understand now",ja="ごめんわかった"}, + {en="I really like it!",ja="ほんとに好きです"}, + {en="It's freezing",ja="こごえそうにさむいです"}, + {en="It's not fair",ja="ふこうへいだ"}, + {en="Just kidding",ja="冗談だよ"}, + {en="lol",ja="www"}, + {en="Leave me alone",ja="ほっといて"}, + {en="Me too",ja="私も"}, + {en="No thanks",ja="いいえけっこです"}, + {en="Not bad",ja="まあ々"}, + {en="Okay",ja="いいよ"}, + {en="Of course",ja="もちろん"}, + {en="Oh, I see",ja="なるほんど"}, + {en="One moment please",ja="ちょっと待って下さい"}, + {en="Probably",ja="たぶん"}, + {en="Sleeping",ja="ねている"}, + {en="Wake up",ja="おきて"}, + {en="What's up?",ja="最近どうですか"}, + {en="Whoops",ja="あら々"}, + {en="Yes",ja="はいー"}, + {en="You're welcome",ja="どういたしまして"}, + + {en="Any ideas?",ja="あなたがどんな考えを持っていますか"}, + {en="Are you okay?",ja="大丈夫ですか"}, + {en="Can I help you?",ja="お手伝いしましょうか"}, + {en="Can you help me?",ja="手伝ってくれますか"}, + {en="Can you say it again?",ja="もういちど言ってくれますか"}, + {en="Can you type Roman letters?",ja="ロマジでうってもらえますか"}, + {en="Do you need it?",ja="いりませんか"}, + {en="Do you understand?",ja="わかりますか"}, + {en="Do you want to party?",ja="パーティーをくみませんか"}, + {en="Does anyone speak English?",ja="だれが英語を話しますか"}, + {en="Don’t be surprised if mistakes are made",ja="まちがいごあってもおどろかないで下さい"}, + {en="Give me a minute to look that up",ja="どういういみかしらべるからちょっとまって下さい"}, + {en="How?",ja="ほのように"}, + {en="How are you?",ja="お元気ですか"}, +--How Do You Say ___ in JP || ___wa nihongo de nan to iimasuka || __ は日本語で何と言いますか"}, + {en="How much is this",ja="これはいくらですか"}, + {en="How soon?",ja="どのくらいすぐに"}, + {en="I don’t understand Japanese characters",ja="日本語のもじはわかりません"}, + {en="I need to practice my Japanese",ja="日本語を練習する必要があります"}, + {en="Linkshell",ja="リンクシェル"}, + {en="Look For a replacement?",ja="ぬけたいのでほじゅうをさがして下さい"}, + {en="My Japanese is not good",ja="私の日本語はよくないです"}, + {en="My Japanese is bad",ja="私の日本語はへたです"}, + {en="Shall we go?",ja="行きましょか"}, + {en="What?",ja="何"}, +-- {en="What is ___? || ___ wa nandesu ka? || ___はなんですか"}, + {en="What's next?",ja="つぎわなんですか"}, + {en="What's that called in Japanese?",ja="あれは日本語で何といいますか"}, + {en="What's your job?",ja="どんなジョブをしているの"}, + {en="Where?",ja="どこ"}, + {en="Where are you?",ja="どこにいますか"}, + {en="When?",ja="いつ"}, + {en="Which?",ja="どんな"}, + {en="Who?",ja="だれ"}, + {en="Why?",ja="なぜ"}, + +--Common Areas/NMs & Events: + {en="Promathia",ja="プロマシア"}, + {en="Ronfaure",ja="ロンフォール"}, + {en="Gustaberg",ja="グスタベルグ"}, + {en="Sarutabaruta",ja="サルタバルタ"}, + {en="Hahava",ja="ハハヴァ"}, + {en="Celaeno",ja="セラエノ"}, + {en="Voidwrought",ja="ヴォイドロート"}, + {en="Kaggen",ja="カッゲン"}, + {en="Akvan",ja="アクヴァン"}, + {en="Pil",ja="フィル"}, + {en="Qilin",ja="ちーりん"}, + {en="Uptala",ja="ウプタラ"}, + {en="Aello",ja="アイエロ"}, + {en="Gaunab",ja="ガウナブ"}, + {en="Ocythoe",ja="オシトエ"}, + {en="Kalasutrax",ja="カラストラクス"}, + {en="Ig-Alima",ja="イッグアリマ"}, + {en="Botulus Rex",ja="ボチュルス・レックス"}, + {en="Morta",ja="モルタ"}, + {en="Bismarck",ja="ビスマルク"}, + {en="Provenance Fights",ja="真界"}, + {en="Provenance Watcher",ja="水晶龍"}, + {en="Colkhab",ja="コルカブ"}, + {en="Muyingwa",ja="ムイングワ"}, + {en="Tchakka",ja="チャッカ"}, + {en="Dakuwaqa",ja="ダクワカ"}, + {en="Achuka",ja="アチュカ"}, + {en="Tojil",ja="トヒル"}, + {en="Hurkan",ja="フルカン"}, + {en="Yumcax",ja="ユムカクス"}, + {en="Kumhau",ja="クムハウ"}, +-- Kazanaru Palace – カザナル宮外郭 --? + {en="Abyssea",ja="アビセア"}, + {en="Caturae",ja="カトゥラエ"}, + {en="Legion",ja="レギオン"}, + {en="Nyzul",ja="ナイズル"}, + {en="Maze Mongers",ja="モブリンズメイズモンガー"}, + {en="Dynamis",ja="デュナミス"}, + {en="Einherjar",ja="エインヘリヤル"}, + {en="Salvage",ja="サルベージ"}, + {en="Walk of Echoes",ja="ウォークオブエコーズ"}, + {en="Meeble Burrows",ja="ミーブルバローズ"}, + {en="Assaults",ja="アサルト"}, + {en="Limbus",ja="リンバス"}, + {en="Garrison",ja="ガリスン"}, + {en="Grounds of Valor",ja="グラウンドオブヴァラー"}, + {en="Fields of Valor",ja="フィールドオブヴァラー"}, + {en="Brenner",ja="ブレンナー"}, + {en="Ballista",ja="バリスタ"}, + {en="Colonization",ja="コロナイズ"}, + {en="Lair Reive",ja="レイアレイヴ"}, + {en="Wildskeeper Reive",ja="ワイルドキーパーレイヴ"}, + {en="Skirmish",ja="スカーム"}, + {en="Delve",ja="メナス"}, + {en="Ark Angel",ja="アークガーディアン"}, + {en="Divine Might",ja="神威"}, + {en="North",ja="北"}, + {en="East",ja="東"}, + {en="South",ja="南"}, + {en="West",ja="西"}, + + -- Things you should know + {en="Polearm",ja="やり"}, + {en="Automaton",ja="オートマトン"}, + {en="Instrument",ja="楽器"}, + {en="Ability",ja="アビリティ"}, + {en="Wyvern",ja="飛竜"}, + {en="Pet",ja="ペット"}, + {en="Pet",ja="よぶだす"}, + + + -- Useful words + {en="Synergy",ja="相乗効果"}, + {en="Carbuncle",ja="カーバンクル"}, + {en="Fenrir",ja="フェンリル"}, + {en="Ifrit",ja="イフリート"}, + {en="Titan",ja="タイタン"}, + {en="Leviathan",ja="リヴァイアサン"}, + {en="Garuda",ja="ガルーダ"}, + {en="Shiva",ja="シヴァ"}, + {en="Ramuh",ja="ラムウ"}, + {en="Diabolos",ja="ディアボロス"}, + {en="Odin",ja="オーディン"}, + {en="Alexander",ja="アレキサンダー"}, + {en="Cait Sith",ja="ケット・シー"}, + + + {en="Weakness",ja="弱点"}, + {en="Trigger",ja="狙って"}, + {en="Blue",ja="青"}, + {en="Red",ja="赤"}, + {en="Yellow",ja="黄"}, + {en="White",ja="白"}, + {en="Black",ja="黒"}, + {en="Silver",ja="銀"}, + {en="Gold",ja="金"}, + {en="Armor",ja="装束"}, + {en="Chest",ja="宝箱"}, + {en="Body",ja="胴"}, + {en="Hands",ja="両手"}, + {en="Legs",ja="両足"}, + {en="Head",ja="頭"}, + {en="Feet",ja="両足"}, + {en="Ring",ja="指"}, + {en="Ear",ja="耳"}, + {en="Back",ja="背"}, + {en="Waist",ja="腰"}, + {en="Lucky Roll",ja="ラッキーロール"}, + {en="Wide Scan",ja="広域スキャン"}, + {en="Charm",ja="あやつる"}, + {en="Snapshot",ja="スナップショット"}, + {en="Double Attack",ja="ダブルアタック"}, + {en="Artifact",ja="アーティファクト"}, + {en="Records of Eminence",ja="エミネンスレコード"}, + {en="Monstrosity",ja="モンストロス"}, + {en="Trust",ja="フェイス"}, + {en="Very Easy",ja="とてもやさしい"}, + {en="Easy",ja="やさしい"}, + {en="Normal",ja="ふつう"}, + {en="Difficult",ja="むずかしい"}, + {en="Very Difficult",ja="とてもむずかしい"}, + {en="Hume",ja="ヒュム"}, + {en="Tarutaru",ja="タルタル"}, + {en="Elvaan",ja="エルヴァーン"}, + {en="Teleport",ja="テレポ"}, + {en="Byne Bill",ja="バイン紙幣"}, + {en="Bronzepiece",ja="オルデール銅貨"}, + {en="Whiteshell",ja="トゥクク白貝貨"}, + {en="Alexandrite",ja="アレキサンドライト"}, + {en="Ancient Beastcoins",ja="獣人古銭"}, + {en="Relic",ja="レリック"}, + {en="Mythic",ja="ミシック"}, + {en="Empyrean",ja="エンピリアン"}, + {en="Sneak and Invisible",ja="インスニ"}, + {en="Aegis and Ochain",ja="イーハン"}, + {en="Overkilled",ja="オーバーキル"}, + {en="Overfished",ja="乱獲"}, + {en="Carby pull",ja="カーバンクルマラソン"}, + {en="set up a Magic Burst",ja="マジックバースト"}, + {en="Skill-up party",ja="スキル上げパーティ"}, + {en="zombie it",ja="ゾンビアタック"}, + {en="Plasm farming",ja="メナポ"}, + {en="Falling asleep",ja="寝落ち"}, + {en="losing claim",ja="横取り"}, + + + -- 10/4/14 update + {en="Dismemberment Brigade",ja="八つ裂き旅団"}, + {en="The Worm's Turn",ja="地竜大王"}, + {en="Grimshell Shocktroopers",ja="鉄甲突撃隊"}, + {en="Steamed Sprouts",ja="居候妖精"}, + {en="Divine Punishers",ja="天誅六人衆"}, + {en="Brothers D'Aurphe",ja="ドーフェ兄弟"}, + {en="Legion XI Comitatensis",ja="第11軍団独立支隊"}, + {en="Amphibian Assault",ja="潜行特務隊"}, + {en="Jungle Boogymen",ja="特命介錯人"}, + {en="Shadow Lord",ja="闇王"}, + {en="Kindred Spirits",ja="蒼の血族"}, + {en="Kam'lanaut",ja="カムラナート"}, + {en="Eald'narche",ja="エルドナーシュ"}, + {en="Ouryu",ja="オウリュウ"}, + {en="Tenzen",ja="テンゼン"}, + {en="Shikaree",ja="シカレー"}, + {en="Puppet in Peril",ja="ランスロード"}, + {en="Gessho",ja="ゲッショー"}, + {en="Incursion",ja="インカージョン"}, + {en="Ymmr",ja="イムル"}, + {en="Ignor",ja="イグノア"}, + {en="Durs",ja="ダルス"}, + {en="Tryl",ja="ダルス"}, + {en="Liij",ja="リッジ"}, + {en="Gramk",ja="グラムク"}, + {en="Utkux",ja="アットクックス"}, + {en="Wopket",ja="ウォップケット"}, + {en="Cailimh",ja="カイルイム"}, + {en="Surge",ja="サージ"}, + {en="Endowed",ja="エンダウ"}, +} + +return dict
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/translate/katakana_to_romanji.lua b/Data/DefaultContent/Libraries/addons/addons/translate/katakana_to_romanji.lua new file mode 100644 index 0000000..40fcd83 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/translate/katakana_to_romanji.lua @@ -0,0 +1,116 @@ + +katakana_to_romanji = { + {en="a",ja="ア"}, + {en="i",ja="イ"}, + {en="u",ja="ウ"}, + {en="e",ja="エ"}, + {en="o",ja="オ"}, + {en="ka",ja="カ"}, + {en="ki",ja="キ"}, + {en="ku",ja="ク"}, + {en="ke",ja="ケ"}, + {en="ko",ja="コ"}, + {en="sa",ja="サ"}, + {en="si",ja="シ"}, + {en="su",ja="ス"}, + {en="se",ja="セ"}, + {en="so",ja="ソ"}, + {en="ta",ja="タ"}, + {en="ti",ja="チ"}, + {en="tu",ja="ツ"}, + {en="te",ja="テ"}, + {en="to",ja="ト"}, + {en="na",ja="ナ"}, + {en="ni",ja="ニ"}, + {en="nu",ja="ヌ"}, + {en="ne",ja="ネ"}, + {en="no",ja="ノ"}, + {en="ha",ja="ハ"}, + {en="hi",ja="ヒ"}, + {en="hu",ja="フ"}, + {en="he",ja="ヘ"}, + {en="ho",ja="ホ"}, + {en="ma",ja="マ"}, + {en="mi",ja="ミ"}, + {en="mu",ja="ム"}, + {en="me",ja="メ"}, + {en="mo",ja="モ"}, + {en="ya",ja="ヤ"}, + {en="yu",ja="ユ"}, + {en="yo",ja="ヨ"}, + {en="ra",ja="ラ"}, + {en="ri",ja="リ"}, + {en="ru",ja="ル"}, + {en="re",ja="レ"}, + {en="ro",ja="ロ"}, + {en="wa",ja="ワ"}, + {en="wi",ja="ヰ"}, + {en="we",ja="ヱ"}, + {en="wo",ja="ヲ"}, + + {en="ga",ja="ガ"}, + {en="gi",ja="ギ"}, + {en="gu",ja="グ"}, + {en="ge",ja="ゲ"}, + {en="go",ja="ゴ"}, + {en="za",ja="ザ"}, + {en="ji",ja="ジ"}, + {en="zu",ja="ズ"}, + {en="ze",ja="ゼ"}, + {en="zo",ja="ゾ"}, + {en="da",ja="ダ"}, + {en="ji",ja="ヂ"}, + {en="zu",ja="ヅ"}, + {en="de",ja="デ"}, + {en="do",ja="ド"}, + {en="ba",ja="バ"}, + {en="bi",ja="ビ"}, + {en="bu",ja="ブ"}, + {en="be",ja="ベ"}, + {en="bo",ja="ボ"}, + {en="pa",ja="パ"}, + {en="pi",ja="ピ"}, + {en="pu",ja="プ"}, + {en="pe",ja="ペ"}, + {en="po",ja="ポ"}, + + {en="n",ja="ン"}, + + {en="kya",ja="キャ"}, + {en="kyu",ja="キュ"}, + {en="kyo",ja="キョ"}, + {en="sha",ja="シャ"}, + {en="shu",ja="シュ"}, + {en="sho",ja="ショ"}, + {en="cha",ja="チャ"}, + {en="chu",ja="チュ"}, + {en="cho",ja="チョ"}, + {en="nya",ja="ニャ"}, + {en="nyu",ja="ニュ"}, + {en="nyo",ja="ニョ"}, + {en="hya",ja="ヒャ"}, + {en="hyu",ja="ヒュ"}, + {en="hyo",ja="ヒョ"}, + {en="mya",ja="ミャ"}, + {en="myu",ja="ミュ"}, + {en="myo",ja="ミョ"}, + {en="rya",ja="リャ"}, + {en="ryu",ja="リュ"}, + {en="ryo",ja="リョ"}, + + {en="gya",ja="ギャ"}, + {en="gyu",ja="ギュ"}, + {en="gyo",ja="ギョ"}, + {en="ja",ja="ジャ"}, + {en="ju",ja="ジュ"}, + {en="jo",ja="ジョ"}, + {en="ja",ja="ヂャ"}, + {en="ju",ja="ヂュ"}, + {en="jo",ja="ヂョ"}, + {en="bya",ja="ビャ"}, + {en="byu",ja="ビュ"}, + {en="byo",ja="ビョ"}, + {en="pya",ja="ピャ"}, + {en="pyu",ja="ピュ"}, + {en="pyo",ja="ピョ"}, +}
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/translate/translate.lua b/Data/DefaultContent/Libraries/addons/addons/translate/translate.lua new file mode 100644 index 0000000..a33bbda --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/translate/translate.lua @@ -0,0 +1,398 @@ +--Copyright (c) 2014~2020, Byrthnoth +--All rights reserved. + +--Redistribution and use in source and binary forms, with or without +--modification, are permitted provided that the following conditions are met: + +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- * Neither the name of <addon name> nor the +-- names of its contributors may be used to endorse or promote products +-- derived from this software without specific prior written permission. + +--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +--ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +--WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +--DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY +--DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +--(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +--LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +--ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +--(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_addon.name = 'Translate' +_addon.version = '2.0.0.0' +_addon.author = 'Byrth' +_addon.commands = {'trans','translate'} + + +language = 'english' +trans_list = {} +res = require 'resources' +require 'sets' +require 'lists' +require 'pack' +require 'strings' +require 'katakana_to_romanji' +search_comment = {ts = 0, reg = L{}, translated = false} + +handled_resources = S{ + 'ability_recasts', + 'auto_translates', + 'buffs', + 'days', + 'elements', + 'items', + 'job_abilities', + 'job_traits', + 'jobs', + 'key_items', + 'monster_abilities', + 'monstrosity', + 'moon_phases', + 'races', + 'regions', + 'skills', + 'spells', + 'titles', + 'weapon_skills', + 'weather', + 'zones' + } + +green_open = string.char(0xEF,0x27) +red_close = string.char(0xEF,0x28) + +green_col = ''--string.char(0x1E,2) +rcol = ''--string.char(0x1E,1) + +local temp_str + +function to_a_code(num) + local first_byte,second_byte = math.floor(num/256),num%256 + if first_byte == 0 or second_byte == 0 then return nil end + return string.char(0xFD,2,2,first_byte,second_byte,0xFD):escape() + -- 0xFD,2,2,8,37,0xFD :: 37 = % +end + +function to_item_code(id) + local first_byte,second_byte = math.floor(id/256),id%256 + local t = 0x07 + if first_byte == 0 then + t = 0x09 + first_byte = 0xFF + elseif second_byte == 0 then + t = 0x0A + second_byte = 0xFF + end + return string.char(0xFD,t,2,first_byte,second_byte,0xFD):escape() +end + +function to_ki_code(id) + local first_byte,second_byte = math.floor(id/256),id%256 + local t = 0x13 + if first_byte == 0 then + t = 0x15 + first_byte = 0xFF + elseif second_byte == 0 then + t = 0x16 + second_byte = 0xFF + end + return string.char(0xFD,t,2,first_byte,second_byte,0xFD):escape() +end + +function sanity_check(ja) + return (ja and string.len(ja) > 0 and ja ~= '%.') +end + +function load_dict(dict) + for _,res_line in pairs(dict) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + local jps = windower.to_shift_jis(res_line.jas or ''):escape() + if sanity_check(jp) and sanity_check(res_line.en) and jp~= res_line.en:escape() then + trans_list[jp] = green_col..res_line.en..rcol + end + if sanity_check(jps) and sanity_check(res_line.ens) and jps ~= res_line.ens:escape() then + trans_list[jps] = green_col..res_line.ens..rcol + end + end +end + +load_dict(katakana_to_romanji) + +for res_name in pairs(handled_resources) do + local resource = res[res_name] or {} + if res_name == 'auto_translates' then + for autotranslate_code,res_line in pairs(resource) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + if sanity_check(jp) and not trans_list[jp] and jp ~= res_line.en:escape() then + trans_list[jp] = to_a_code(autotranslate_code) + end + end + elseif res_name == 'items' then + for id,res_line in pairs(resource) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + if sanity_check(jp) and not trans_list[jp] and jp ~= res_line.en:escape() then + trans_list[jp] = to_item_code(id) + end + end + elseif res_name == 'key_items' then + for id,res_line in pairs(resource) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + if sanity_check(jp) and not trans_list[jp] and jp ~= res_line.en:escape() then + trans_list[jp] = to_ki_code(id) + end + end + elseif res_name == 'jobs' then + for _,res_line in pairs(resource) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + local jps = windower.to_shift_jis(res_line.jas or ''):escape() + if sanity_check(jp) and sanity_check(res_line.en) and jp ~= res_line.en:escape() then + trans_list[jp] = green_col..res_line.en..rcol:escape() + end + if sanity_check(jps) and sanity_check(res_line.ens) and jp ~= res_line.en:escape() and jps ~= res_line.ens:escape() and res_line.ens ~= 'PUP' then + trans_list[jps] = green_col..res_line.ens..rcol:escape() + end + end + else + for _,res_line in pairs(resource) do + local jp = windower.to_shift_jis(res_line.ja or ''):escape() + local jps = windower.to_shift_jis(res_line.jas or ''):escape() + if sanity_check(jp) and not trans_list[jp] and sanity_check(res_line.en) and jp ~= res_line.en:escape() then + trans_list[jp] = green_col..res_line.en..rcol:escape() + end + if sanity_check(jps) and not trans_list[jps] and sanity_check(res_line.ens) and jp ~= res_line.en:escape() and jps ~= res_line.ens:escape() then + trans_list[jps] = green_col..res_line.ens..rcol:escape() + end + end + end +end + +local custom_dict_names = S(windower.get_dir(windower.addon_path..'dicts/')):filter(string.endswith-{'.lua'}):map(string.sub-{1, -5}) +for dict_name in pairs(custom_dict_names) do + local dict = dofile(windower.addon_path..'dicts/'..dict_name..'.lua') + if dict then + load_dict(dict) + end +end + +function print_bytes(str) + local c = '' + local i = 1 + while i <= #str do + c = c..' '..str:byte(i) + i = i + 1 + end + return c +end + +trans_list[string.char(0x46)] = nil +trans_list['\.'] = nil + + +windower.register_event('incoming chunk',function(id,orgi,modi,is_injected,is_blocked) + if id == 0x17 and not is_injected and not is_blocked then + local out_text = modi:unpack('z',0x18) + + out_text = translate_phrase(out_text) + + if not out_text then return end + + if show_original then windower.add_to_chat(8,modi:sub(9,0x17):unpack('z',1)..'[Original]: '..modi:unpack('z',0x18)) end + while #out_text > 0 do + local boundary = get_boundary_length(out_text,151) + local len = math.ceil((boundary+1+23)/2) -- Make sure there is at least one nul after the string + local out_pack = string.char(0x17,len)..modi:sub(3,0x17)..out_text:sub(1,boundary) + + -- zero pad it + while #out_pack < len*2 do + out_pack = out_pack..string.char(0) + end + windower.packets.inject_incoming(0x17,out_pack) + out_text = out_text:sub(boundary+1) + end + return true + end +end) + +function translate_phrase(out_text) + local matches,match_bool = {},false + local function make_matches(catch) + -- build a table of matches indexed by their length + local esc = catch:escape() + if not sanity_check(esc) then return end + + + + if not matches[#catch] then + matches[#catch] = {} + end + matches[#catch][#matches[#catch]+1] = esc + match_bool = true + end + for jp,en in pairs(trans_list) do + out_text:gsub(jp,make_matches) + end + + if not match_bool then return end + + local order = {} + for len,_ in pairs(matches) do + local c,found = 1,false + while c <= #order do + if len > order[c] then + table.insert(order,c,len) + found = true + break + end + c = c + 1 + end + if c > #order then order[c] = len end + end + + for _,ind in ipairs(order) do + for _,option in ipairs(matches[ind]) do + out_text = sjis_gsub(out_text,unescape(option),unescape(trans_list[option])) + end + end + return out_text +end + +function get_boundary_length(str,limit) + -- If it is already short enough, return it + if #str <= limit then return #str end + + local lim = 0 + for i= 1,#str do + local c_byte = str:byte(i) + if c_byte == 0xFD then + i = i + 6 + elseif ( (c_byte > 0x7F and c_byte <= 0xA0) or c_byte >= 0xE0) then + i = i + 2 + else + i = i + 1 + end + if i > limit then + return lim + else + lim = i + end + end + + -- Otherwise, try to pick a spot to split that will not interfere with command codes and such +--[[ local boundary = limit + for i=limit-5,limit do + local c_byte = str:byte(i) + if c_byte ==0xFD then + -- 0xFD: Autotranslate code, 6 bytes + boundary = i-1 + break + elseif c_byte == 0xEF and str:byte(i+1) == 0x27 then + -- Opening green ( + boundary = i-1 + break + elseif i == limit and ( (c_byte > 0x7F and c_byte <= 0xA0) or c_byte >= 0xE0) then + -- Double-byte shift_JIS character + boundary = i-1 + break + end + end + return boundary]] +end + +windower.register_event('addon command', function(...) + local commands = {...} + if not commands[1] then return end + if commands[1]:lower() == 'show' then + if commands[2] and commands[2]:lower() == 'original' then + show_original=not show_original + if show_original then + print('Translate: Showing the original text line.') + else + print('Translate: Hiding the original text line.') + end + end + elseif commands[1]:lower() == 'eval' and commands[2] then + table.remove(commands,1) + assert(loadstring(table.concat(commands, ' ')))() + end +end) + +windower.register_event('incoming text',function(org,mod,ocol,mcol,blk) + if not blk and ocol == 204 then + local ret = translate_phrase(mod) + temp_str = ret or org + if ret then + if show_original then + windower.add_to_chat:schedule(0.3, 8,'[Original]: '..org) + end + mod = ret + if org == temp_str then + blk = true + temp_str = '' + end + return blk and blk or mod + end + end +end) + +function unescape(str) + return (str:gsub('%%([%%%%^%$%*%(%)%.%+%?%-%]%[])','%1')) +end + + +-- Two problems with how I currently do this: +-- 1: It is possible to have something like 0x94, (0x92, 0x8B,) 0xE1, which are two JP characters that contain a third. +-- 2: It is possible to have a gsub replace something with an autotranslate code, which then causes a later dictionary +-- option to match part of the replacement. +-- If I solve #1, will #2 be an issue? No, it should not be. + +function sjis_gsub(str,pattern,rep) + if not (type(rep) == 'function' or type(rep) == 'string') then return str end + local str_len,pat_len,ret_str = string.len(str),string.len(pattern),str + local i = 1 + while i<=str_len-pat_len+1 do + local c_byte = str:byte(i) + if str:sub(i,i+pat_len-1) == pattern then + if type(rep) == 'function' then + ret_str = rep(pattern) or str + -- No recursion for functions at the moment, because this addon doesn't need it + return + elseif type(rep) == 'string' then + if i ~= 1 then + -- Not the beginning + ret_str = str:sub(1,i-1)..rep + else + -- The beginning + ret_str = rep + end + if i+pat_len <= str_len-pat_len+1 then + -- i == 13, pat_len == 2, str_len == 16 + -- Match is characters 13 and 14. Could conceivably match again to characters 15 and 16. + + -- Send the remainder of the string back through recursively. + return ret_str..sjis_gsub(str:sub(i+pat_len),pattern,rep) + elseif i+pat_len <= str_len then + -- i == 14, pat_len == 2, str_len == 16 + -- Match is characters 14 and 15, so 16 can't possibly be a match but needs to be stuck on there + return ret_str..str:sub(i+pat_len) + else + -- i == 15, pat_len == 2, str_len == 16 + -- Match is characters 15 and 16, so no further addition is necessary + return ret_str + end + end + elseif c_byte == 0xFD then + i = i + 6 + elseif ( (c_byte > 0x7F and c_byte <= 0xA0) or c_byte >= 0xE0) then + i = i + 2 + else + i = i + 1 + end + end + return ret_str +end diff --git a/Data/DefaultContent/Libraries/addons/addons/update/readme.md b/Data/DefaultContent/Libraries/addons/addons/update/readme.md new file mode 100644 index 0000000..3a1adb3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/update/readme.md @@ -0,0 +1,39 @@ +# Update + +Once loaded can update all addons and plugins to the newest version using `//update`. This can also be done automatically by configuring the settings file or by typing `//update auto`. + +### Commands + +#### Update + +``` +update +``` + +Will send an update command. + +**Note:** This interferes with Spellcast XMLs using the custom `update` trigger command. To resolve that problem stop using Spellcast. + +#### Toggle automatic updates + +``` +update auto [on|off] +``` + +Sets automatic updating to on or off (toggles if no argument specified). Note that automatic updates will not check for updates while you're currently in combat. + +#### Set update interval + +``` +update interval <time> +``` + +Sets the automatic update interval to the provided time. Allows unit conversion, defaults to seconds. Example: + +``` +update interval 5min +update interval 2h +update interval 30 +``` + +These will set the interval to 5 minutes, 2 hours and 30 seconds respectively. diff --git a/Data/DefaultContent/Libraries/addons/addons/update/update.lua b/Data/DefaultContent/Libraries/addons/addons/update/update.lua new file mode 100644 index 0000000..d9e546e --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/update/update.lua @@ -0,0 +1,113 @@ +_addon.name = 'Update' +_addon.author = 'Arcon' +_addon.version = '1.1.0.0' +_addon.command = 'update' + +require('luau') + +defaults = {} +defaults.AutoUpdate = false +defaults.CheckInterval = 300 + +settings = config.load(defaults) + +debug.setmetatable(nil, {__index = {}, __call = functions.empty}) + +units = {[''] = 1} +units.s = units[''] + +units.min = 60*units.s +units.h = 60*units.min +units.d = 24*units.h + +units.ms = units.s/1000 +units.us = units.ms/1000 +units.ns = units.us/1000 + +math.randomseed(os.time() + os.clock()) +handle = (0x7FFFFFFF):random():hex():zfill(8) + +tick = function(msg) + last = os.clock() + if not msg then + windower.send_ipc_message('update ' .. handle) + end +end + +update = tick .. windower.execute:prepare(windower.windower_path .. 'Windower.exe', {'--update'}) + +windower.register_event('ipc message', tick:cond(function(str) + local args = str:split(' ') + return args[1] == 'update' and args[2] ~= handle +end)) + +windower.register_event('time change', update:cond(function() + return settings.AutoUpdate and os.clock() - last > settings.CheckInterval and not windower.ffxi.get_player().in_combat +end)) + +windower.register_event('addon command', function(command, param, ...) + command = command and command:lower() or nil + param = param and param:lower() or nil + + if not command then + update() + + elseif command == 'auto' then + if not param then + settings.AutoUpdate = not settings.AutoUpdate + elseif param == 'on' then + settings.AutoUpdate = true + elseif param == 'off' then + settings.AutoUpdate = false + else + error('Invalid syntax: //update auto [on|off]') + return + end + + config.save(settings) + log('Automatic updates turned ' .. (settings.AutoUpdate and 'on' or 'off') .. '.') + + elseif command == 'interval' then + if not param then + error('Invalid syntax: //update interval <time>') + return + end + + param = param .. L{...}:concat(' ') + local number = param:gsub('[^%d]', ''):number() + local unit = param:gsub('[%d]', '') + if not number or unit ~= '' and not units[unit] then + error('Invalid syntax: //update interval <time>') + return + end + + settings.CheckInterval = number * units[unit] + config.save(settings) + log('Interval set to ' .. settings.CheckInterval:string() .. ' seconds.') + + elseif command == 'save' then + config.save(settings, 'all') + + else + print(_addon.name .. ' v' .. _addon.version) + print(' auto [on|off] - Set automatic updates to on or off or toggle.') + print(' interval <time> - Set automatic update interval to the provided time (in seconds if no units provided).') + print(' save - Saves the current settings for all characters.') + + end +end) + +windower.register_event('load', tick) + +--[[ +Copyright (c) 2013, Windower +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Windower nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Windower BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] diff --git a/Data/DefaultContent/Libraries/addons/addons/vwhl/README.md b/Data/DefaultContent/Libraries/addons/addons/vwhl/README.md new file mode 100644 index 0000000..b586a1d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/vwhl/README.md @@ -0,0 +1,24 @@ +**Author:** Giuliano Riccio +**Version:** v 1.20131102 + +# VWHL # +This addon redirects the nm's weaknesses (VW or Abyssea) to the "tell" stream so that they can be held in the chat log using the chat filters' hold function (Config > Chat Filters > "Enter" on "Tell" until you get the green "hold" icon). +Moreover, the important informations are highlighted and a number is added to quickly understand the vulnerability level (1, 3, 5). + +## Commands ## +### test ### +Fills the chat log with some messages to show how the plugin will work. + +``` +vwhl test +``` + +---- + +## Changelog ## + +### v1.20130529### +* **change**: Aligned to Windower's addon development guidelines. + +### v1.20130407### +* First release. diff --git a/Data/DefaultContent/Libraries/addons/addons/vwhl/vwhl.lua b/Data/DefaultContent/Libraries/addons/addons/vwhl/vwhl.lua new file mode 100644 index 0000000..6504cf9 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/vwhl/vwhl.lua @@ -0,0 +1,250 @@ +--[[ +vwhl - voidwatch highlighter v1.20131102 + +Copyright (c) 2013, Giuliano Riccio +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of vwhl nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Giuliano Riccio 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. +]] + +require 'chat' + + +_addon.name = 'vwhl' +_addon.author = 'Zohno' +_addon.version = '1.20131102' +_addon.command = 'vwhl' + +windower.register_event('incoming text', function(original, modified, mode) + if mode ~= 148 then + return modified, mode + end + + if original:match('The fiend appears') then + if original:match('extremely vulnerable') then + original = original:gsub('extremely vulnerable', ('extremely vulnerable (5)'):color(258)) + elseif original:match('highly vulnerable') then + original = original:gsub('highly vulnerable', ('highly vulnerable (3)'):color(258)) + else + original = original:gsub('vulnerable', ('vulnerable (1)'):color(258)) + end + + if original:match('great sword') then + original = original:gsub('great sword', ('great sword'):color(258)) + elseif original:match('sword') then + original = original:gsub('sword', ('sword'):color(258)) + elseif original:match('great axe') then + original = original:gsub('great axe', ('great axe'):color(258)) + elseif original:match('axe') then + original = original:gsub('axe', ('axe'):color(258)) + end + + original = original + :gsub('(%w+) elemental', ('%1'):color(258)..' elemental') + :gsub('white magic', ('white magic'):color(258)) + :gsub('black magic', ('black magic'):color(258)) + :gsub('ninjutsu', ('ninjutsu'):color(258)) + :gsub('bard songs', ('bard songs'):color(258)) + :gsub('blue magic', ('blue magic'):color(258)) + :gsub('abilities', ('abilities'):color(258)) + :gsub('warrior', ('warrior'):color(258)) + :gsub('monk', ('monk'):color(258)) + :gsub('white mage', ('white mage'):color(258)) + :gsub('black mage', ('black mage'):color(258)) + :gsub('red mage', ('red mage'):color(258)) + :gsub('thief', ('thief'):color(258)) + :gsub('paladin', ('paladin'):color(258)) + :gsub('dark knight', ('dark knight'):color(258)) + :gsub('beastmaster', ('beastmaster'):color(258)) + :gsub('bard', ('bard'):color(258)) + :gsub('ranger', ('ranger'):color(258)) + :gsub('samurai', ('samurai'):color(258)) + :gsub('ninja', ('ninja'):color(258)) + :gsub('dragoon', ('dragoon'):color(258)) + :gsub('summoner', ('summoner'):color(258)) + :gsub('blue mage', ('blue mage'):color(258)) + :gsub('corsair', ('corsair'):color(258)) + :gsub('puppetmaster', ('puppetmaster'):color(258)) + :gsub('dancer', ('dancer'):color(258)) + :gsub('scholar', ('scholar'):color(258)) + :gsub('hand%-to%-hand', ('hand-to-hand'):color(258)) + :gsub('dagger', ('dagger'):color(258)) + :gsub('scythe', ('scythe'):color(258)) + :gsub('polearm', ('polearm'):color(258)) + :gsub('katana', ('katana'):color(258)) + :gsub('great katana', ('great katana'):color(258)) + :gsub('club', ('club'):color(258)) + :gsub('staff', ('staff'):color(258)) + :gsub('archery', ('archery'):color(258)) + :gsub('marksmanship', ('marksmanship'):color(258)) + :gsub('pet', ('pet'):color(258)) + :gsub('automaton', ('automaton'):color(258)) + :gsub('avatar', ('avatar'):color(258)) + :gsub('wyvern', ('wyvern'):color(258)) + :gsub('weapon skills', ('weapon skills'):color(258)) + :gsub('special attacks', ('special attacks'):color(258)) + :gsub('blood pacts', ('blood pacts'):color(258)) + + windower.add_to_chat(12, '>>> '..original) + elseif original:match('L\'un des points faibles') then + if original:match('points faibles critiques') then + original = original:gsub('points faibles critiques', ('points faibles critiques (5)'):color(258)) + elseif original:match('points faibles majeurs') then + original = original:gsub('points faibles majeurs', ('points faibles majeurs (3)'):color(258)) + else + original = original:gsub('points faibles', ('points faibles (1)'):color(258)) + end + + if original:match('grande épée') then + original = original:gsub('grande épée', ('grande épée'):color(258)) + elseif original:match('épée') then + original = original:gsub('épée', ('épée'):color(258)) + elseif original:match('grande hache') then + original = original:gsub('grande hache', ('grande hache'):color(258)) + elseif original:match('hache') then + original = original:gsub('hache', ('hache'):color(258)) + end + + original = original + :gsub('feu', ('feu'):color(258)) + :gsub('glace', ('glace'):color(258)) + :gsub('vent', ('vent'):color(258)) + :gsub('terre', ('terre'):color(258)) + :gsub('foudre', ('foudre'):color(258)) + :gsub('eau', ('eau'):color(258)) + :gsub('lumière', ('lumière'):color(258)) + :gsub('ténèbres', ('ténèbres'):color(258)) + :gsub('magie blanche', ('magie blanche'):color(258)) + :gsub('magie noire', ('magie noire'):color(258)) + :gsub('ninjutsu', ('ninjutsu'):color(258)) + :gsub('chant', ('chant'):color(258)) + :gsub('magie bleue', ('magie bleue'):color(258)) + :gsub('souffle', ('souffle'):color(258)) + + :gsub('guerrier', ('guerrier'):color(258)) + :gsub('moine', ('moine'):color(258)) + :gsub('mage blanc', ('mage blanc'):color(258)) + :gsub('mage noir', ('mage noir'):color(258)) + :gsub('mage rouge', ('mage rouge'):color(258)) + :gsub('voleur', ('voleur'):color(258)) + :gsub('paladin', ('paladin'):color(258)) + :gsub('chevalier noir', ('chevalier noir'):color(258)) + :gsub('dresseur', ('dresseur'):color(258)) + :gsub('barde', ('barde'):color(258)) + :gsub('chasseur', ('chasseur'):color(258)) + :gsub('samouraï', ('samouraï'):color(258)) + :gsub('ninja', ('ninja'):color(258)) + :gsub('chevalier dragon', ('chevalier dragon'):color(258)) + :gsub('invocateur', ('invocateur'):color(258)) + :gsub('mage bleu', ('mage bleu'):color(258)) + :gsub('corsaire', ('corsaire'):color(258)) + :gsub('marionnettiste', ('marionnettiste'):color(258)) + :gsub('danseur', ('danseur'):color(258)) + :gsub('érudit', ('érudit'):color(258)) + + :gsub('corps à corps', ('corps à corps'):color(258)) + :gsub('dague', ('dague'):color(258)) + :gsub('faux', ('faux'):color(258)) + :gsub('arme d\'hast', ('arme d\'hast'):color(258)) + :gsub('katana', ('katana'):color(258)) + :gsub('grand katana', ('grand katana'):color(258)) + :gsub('massue', ('massue'):color(258)) + :gsub('crosse', ('crosse'):color(258)) + :gsub('archerie', ('archerie'):color(258)) + :gsub('artillerie', ('artillerie'):color(258)) + :gsub('familier', ('familier'):color(258)) + :gsub('automate', ('automate'):color(258)) + :gsub('avatar', ('avatar'):color(258)) + :gsub('wyvern', ('wyvern'):color(258)) + :gsub('compétence arme', ('compétence arme'):color(258)) + :gsub('attaque spéciale', ('attaque spéciale'):color(258)) + :gsub('pacte de sang', ('pacte de sang'):color(258)) + + windower.add_to_chat(12, '>>> '..original) + elseif original:match('Das Monster ist nun') then + if original:match('ganz besonders anfällig') then + original = original:gsub('ganz besonders anfällig', ('ganz besonders anfällig (5)'):color(258)) + elseif original:match('besonders anfällig') then + original = original:gsub('besonders anfällig', ('besonders anfällig (3)'):color(258)) + else + original = original:gsub('anfällig', ('anfällig (1)'):color(258)) + end + + original = original + :gsub('(%w+)%-Elementarschaden', ('%1'):color(258)..'-Elementarschaden') + :gsub('(%w+)%-Magie', ('%1'):color(258)..'-Magie') + :gsub('Weißmagie', ('Weißmagie'):color(258)) + :gsub('Schwarzmagie', ('Schwarzmagie'):color(258)) + :gsub('Ninjutsu', ('Ninjutsu'):color(258)) + :gsub('Gesang', ('Gesang'):color(258)) + :gsub('Blaumagie', ('Blaumagie'):color(258)) + + :gsub('Kriegern', ('Kriegern'):color(258)) + :gsub('Mönchen', ('Mönchen'):color(258)) + :gsub('Weißmagiern', ('Weißmagiern'):color(258)) + :gsub('Schwarzmagiern', ('Schwarzmagiern'):color(258)) + :gsub('Rotmagiern', ('Rotmagiern'):color(258)) + :gsub('Dieben', ('Dieben'):color(258)) + :gsub('Paladinen', ('Paladinen'):color(258)) + :gsub('Dunkelrittern', ('Dunkelrittern'):color(258)) + :gsub('Bestienbändigern', ('Bestienbändigern'):color(258)) + :gsub('Barden', ('Barden'):color(258)) + :gsub('Jägern', ('Jägern'):color(258)) + :gsub('Samurai', ('Samurai'):color(258)) + :gsub('Ninja', ('Ninja'):color(258)) + :gsub('Dragoons', ('Dragoons'):color(258)) + :gsub('Beschwörern', ('Beschwörern'):color(258)) + :gsub('Blaumagiern', ('Blaumagiern'):color(258)) + :gsub('Freibeutern', ('Freibeutern'):color(258)) + :gsub('Puppenmeistern', ('Puppenmeistern'):color(258)) + :gsub('Tänzern', ('Tänzern'):color(258)) + :gsub('Gelehrten', ('Gelehrten'):color(258)) + + :gsub('Fäusten', ('Fäusten'):color(258)) + :gsub('Dolchen', ('Dolchen'):color(258)) + :gsub('Schwertern', ('Schwertern'):color(258)) + :gsub('Großschwertern', ('Großschwertern'):color(258)) + :gsub('Äxten', ('Äxten'):color(258)) + :gsub('Großäxten', ('Großäxten'):color(258)) + :gsub('Sensen', ('Sensen'):color(258)) + :gsub('Lanzen', ('Lanzen'):color(258)) + :gsub('Katanas', ('Katanas'):color(258)) + :gsub('Großkatanas', ('Großkatanas'):color(258)) + :gsub('Keulen', ('Keulen'):color(258)) + :gsub('Kampfstöcken', ('Kampfstöcken'):color(258)) + :gsub('Bögen', ('Bögen'):color(258)) + :gsub('Schusswaffen', ('Schusswaffen'):color(258)) + :gsub('Haustieren', ('Haustieren'):color(258)) + :gsub('Automaten', ('Automaten'):color(258)) + :gsub('Avataren', ('Avataren'):color(258)) + :gsub('(Wyverns?)', ('%1'):color(258)) + :gsub('Waffenfertigkeiten', ('Waffenfertigkeiten'):color(258)) + :gsub('Spezialattacken', ('Spezialattacken'):color(258)) + :gsub('Blutsbünde', ('Blutsbünde'):color(258)) + + windower.add_to_chat(12, '>>> '..original) + end + + return modified, mode +end) diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/README.md b/Data/DefaultContent/Libraries/addons/addons/xivbar/README.md new file mode 100644 index 0000000..ec44b0b --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/README.md @@ -0,0 +1,44 @@ +# xivbar +This addon displays vital bars for easy tracking + + + +You can choose from 3 different styles 'ffxiv', 'ffxi' and 'ffxiv-legacy'. + + + +and you can use a compact version for a smaller resolution: + + +## Available Settings +##### Bars +* **Offset X** - moves the entire addon left (negative number) or right (positive number) the given number of pixels +* **Offset Y** - moves the entire addon up (negative number) or down (positive number) the given number of pixels + +##### Theme +* **Name** - Name of the theme to use - 'ffxi', 'ffxiv', 'ffxiv-legacy', or your own custom one +* **Compact** - Enables or disables compact mode +* **Bar** - Values for bar width, spacing, offset and compact mode. Useful for creating a custom theme. + +##### Texts +* **Color** - The font color for the HP, MP and TP numbers +* **Font** - The font for the HP, MP and TP numbers +* **Offset** - moves the HP, MP and TP numbers left (negative number) or right (positive number) the given number of pixels +* **Size** - The font size for the HP, MP and TP numbers +* **Stroke** - The font stroke the HP, MP and TP numbers +* **FullTpColor** - The font color for the TP numbers when the bar is full +* **DimTpBar** - dim the TP bar when not full + +## How to edit the settings +1. Login to your character in FFXI +2. Edit the addon's settings file: **_Windower4\addons\xivbar\data\settings.xml_** +3. Save the file +4. Press Insert in FFXI to access the windower console +5. Type ``` lua r xivbar ``` to reload the addon +6. Press Insert in FFXI again to close the windower console + +## How to create my own custom theme +1. Create a folder inside the *theme* directory of the addon: **_Windower4\addons\xivbar\themes\MY_CUSTOM_THEME_** +2. Create the necessary images. A theme is composed of 5 images: a background for the bars (*bar_bg.png*), a background for the compact mode (*bar_compact.png*), and one image for each bar (*hp_fg.png, mp_fg.png and tp_fg.png*). You can take a look at the default themes. +3. Edit the name of the theme in the settings to yours. This setting must match the name of the folder you just created. +4. Adjust the bar width, spacing and offset for your custom theme in the settings. diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/defaults.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/defaults.lua new file mode 100644 index 0000000..641f9ad --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/defaults.lua @@ -0,0 +1,68 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX 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. +]] + +local defaults = {} + +defaults.Bars = {} +defaults.Bars.OffsetX = 0 +defaults.Bars.OffsetY = 0 + +defaults.Theme = {} +defaults.Theme.Name = 'ffxiv' +defaults.Theme.Compact = false +defaults.Theme.Bar = {} +defaults.Theme.Bar.Width = 132 +defaults.Theme.Bar.Spacing = 18 +defaults.Theme.Bar.Offset = 0 +defaults.Theme.Bar.Compact = {} +defaults.Theme.Bar.Compact.Width = 116 +defaults.Theme.Bar.Compact.Spacing = 16 +defaults.Theme.Bar.Compact.Offset = 0 +defaults.Theme.DimTpBar = true + +defaults.Texts = {} +defaults.Texts.Font = 'sans-serif' +defaults.Texts.Size = 14 +defaults.Texts.Offset = 0 +defaults.Texts.Color = {} +defaults.Texts.Color.Alpha = 255 +defaults.Texts.Color.Red = 253 +defaults.Texts.Color.Green = 252 +defaults.Texts.Color.Blue = 250 +defaults.Texts.Stroke = {} +defaults.Texts.Stroke.Width = 2 +defaults.Texts.Stroke.Alpha = 200 +defaults.Texts.Stroke.Red = 50 +defaults.Texts.Stroke.Green = 50 +defaults.Texts.Stroke.Blue = 50 +defaults.Texts.FullTpColor = {} +defaults.Texts.FullTpColor.Red = 80 +defaults.Texts.FullTpColor.Green = 180 +defaults.Texts.FullTpColor.Blue = 250 + +return defaults
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/player.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/player.lua new file mode 100644 index 0000000..e28a1a0 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/player.lua @@ -0,0 +1,42 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX 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. +]] + +local player = {} + +player.hpp = 0 +player.mpp = 0 +player.tpp = 0 +player.current_hp = 0 +player.current_mp = 0 +player.current_tp = 0 + +function player:calculate_tpp() + self.tpp = math.min(self.current_tp / 10, 100) +end + +return player
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/theme.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/theme.lua new file mode 100644 index 0000000..4ef66ab --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/theme.lua @@ -0,0 +1,84 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX 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. +]] + +local theme = {} + +theme.apply = function (settings) + local options = {} + + options.total_height = 8 + options.total_width = 472 + options.offset_x = settings.Bars.OffsetX + options.offset_y = settings.Bars.OffsetY + + options.bar_background = windower.addon_path .. 'themes/' .. settings.Theme.Name .. '/bar_bg.png' + options.bar_hp = windower.addon_path .. 'themes/' .. settings.Theme.Name .. '/hp_fg.png' + options.bar_mp = windower.addon_path .. 'themes/' .. settings.Theme.Name .. '/mp_fg.png' + options.bar_tp = windower.addon_path .. 'themes/' .. settings.Theme.Name .. '/tp_fg.png' + + options.font = settings.Texts.Font + options.font_size = settings.Texts.Size + options.font_alpha = settings.Texts.Color.Alpha + options.font_color_red = settings.Texts.Color.Red + options.font_color_green = settings.Texts.Color.Green + options.font_color_blue = settings.Texts.Color.Blue + options.font_stroke_width = settings.Texts.Stroke.Width + options.font_stroke_alpha = settings.Texts.Stroke.Alpha + options.font_stroke_color_red = settings.Texts.Stroke.Red + options.font_stroke_color_green = settings.Texts.Stroke.Green + options.font_stroke_color_blue = settings.Texts.Stroke.Blue + options.full_tp_color_red = settings.Texts.FullTpColor.Red + options.full_tp_color_green = settings.Texts.FullTpColor.Green + options.full_tp_color_blue = settings.Texts.FullTpColor.Blue + options.text_offset = settings.Texts.Offset + + options.bar_width = settings.Theme.Bar.Width + options.bar_spacing = settings.Theme.Bar.Spacing + options.bar_offset = settings.Theme.Bar.Offset + + options.dim_tp_bar = settings.Theme.DimTpBar + + if settings.Theme.Compact then + options.bar_background = windower.addon_path .. 'themes/' .. settings.Theme.Name .. '/bar_compact.png' + options.total_width = 422 + options.bar_width = settings.Theme.Bar.Compact.Width + options.bar_spacing = settings.Theme.Bar.Compact.Spacing + options.bar_offset = settings.Theme.Bar.Compact.Offset + end + + if settings.Theme.Name == 'ffxiv' then + options.font_stroke_alpha = 150 + options.font_stroke_color_red = 80 + options.font_stroke_color_green = 70 + options.font_stroke_color_blue = 30 + end + + return options +end + +return theme
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_bg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_bg.png Binary files differnew file mode 100644 index 0000000..99f4acc --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_bg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_compact.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_compact.png Binary files differnew file mode 100644 index 0000000..0941f24 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/bar_compact.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/hp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/hp_fg.png Binary files differnew file mode 100644 index 0000000..14e014d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/hp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/mp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/mp_fg.png Binary files differnew file mode 100644 index 0000000..f130838 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/mp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/tp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/tp_fg.png Binary files differnew file mode 100644 index 0000000..5d8c7fa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxi/tp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_bg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_bg.png Binary files differnew file mode 100644 index 0000000..c09f586 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_bg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_compact.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_compact.png Binary files differnew file mode 100644 index 0000000..c5b03b3 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/bar_compact.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/hp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/hp_fg.png Binary files differnew file mode 100644 index 0000000..543d608 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/hp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/mp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/mp_fg.png Binary files differnew file mode 100644 index 0000000..c49ceda --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/mp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/tp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/tp_fg.png Binary files differnew file mode 100644 index 0000000..ccc24d5 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv-legacy/tp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_bg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_bg.png Binary files differnew file mode 100644 index 0000000..37c619f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_bg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_compact.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_compact.png Binary files differnew file mode 100644 index 0000000..8180072 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/bar_compact.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/hp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/hp_fg.png Binary files differnew file mode 100644 index 0000000..d3abc61 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/hp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/mp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/mp_fg.png Binary files differnew file mode 100644 index 0000000..b1f7bfa --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/mp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/tp_fg.png b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/tp_fg.png Binary files differnew file mode 100644 index 0000000..49fd63f --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/themes/ffxiv/tp_fg.png diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/ui.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/ui.lua new file mode 100644 index 0000000..6410885 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/ui.lua @@ -0,0 +1,130 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX 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. +]] + + +local ui = {} + +local text_setup = { + flags = { + draggable = false + } +} + +local images_setup = { + draggable = false +} + +-- ui variables +ui.background = images.new(images_setup) + +ui.hp_bar = images.new(images_setup) +ui.mp_bar = images.new(images_setup) +ui.tp_bar = images.new(images_setup) + +ui.hp_text = texts.new(text_setup) +ui.mp_text = texts.new(text_setup) +ui.tp_text = texts.new(text_setup) + +-- setup images +function setup_image(image, path) + image:path(path) + image:repeat_xy(1, 1) + image:draggable(false) + image:fit(true) + image:show() +end + +-- setup text +function setup_text(text, theme_options) + text:bg_alpha(0) + text:bg_visible(false) + text:font(theme_options.font) + text:size(theme_options.font_size) + text:color(theme_options.font_color_red, theme_options.font_color_green, theme_options.font_color_blue) + text:stroke_transparency(theme_options.font_stroke_alpha) + text:stroke_color(theme_options.font_stroke_color_red, theme_options.font_stroke_color_green, theme_options.font_stroke_color_blue) + text:stroke_width(theme_options.font_stroke_width) + text:right_justified() + text:show() +end + +-- load the images and text +function ui:load(theme_options) + setup_image(self.background, theme_options.bar_background) + setup_image(self.hp_bar, theme_options.bar_hp) + setup_image(self.mp_bar, theme_options.bar_mp) + setup_image(self.tp_bar, theme_options.bar_tp) + setup_text(self.hp_text, theme_options) + setup_text(self.mp_text, theme_options) + setup_text(self.tp_text, theme_options) + + self:position(theme_options) +end + +-- position the images and text +function ui:position(theme_options) + local x = windower.get_windower_settings().x_res / 2 - (theme_options.total_width / 2) + theme_options.offset_x + local y = windower.get_windower_settings().y_res - 60 + theme_options.offset_y + + self.background:pos(x, y) + + self.hp_bar:pos(x + 15 + theme_options.bar_offset, y + 2) + self.mp_bar:pos(x + 25 + theme_options.bar_offset + theme_options.bar_width + theme_options.bar_spacing, y + 2) + self.tp_bar:pos(x + 35 + theme_options.bar_offset + (theme_options.bar_width*2) + (theme_options.bar_spacing*2), y + 2) + self.hp_bar:width(0) + self.mp_bar:width(0) + self.tp_bar:width(0) + + self.hp_text:pos(x + 65 + theme_options.text_offset, self.background:pos_y() + 2) + self.mp_text:pos(x + 80 + theme_options.text_offset + theme_options.bar_width + theme_options.bar_spacing, self.background:pos_y() + 2) + self.tp_text:pos(x + 90 + theme_options.text_offset + (theme_options.bar_width*2) + (theme_options.bar_spacing*2), self.background:pos_y() + 2) +end + +-- hide ui +function ui:hide() + self.background:hide() + self.hp_bar:hide() + self.hp_text:hide() + self.mp_bar:hide() + self.mp_text:hide() + self.tp_bar:hide() + self.tp_text:hide() +end + +-- show ui +function ui:show() + self.background:show() + self.hp_bar:show() + self.hp_text:show() + self.mp_bar:show() + self.mp_text:show() + self.tp_bar:show() + self.tp_text:show() +end + +return ui
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/variables.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/variables.lua new file mode 100644 index 0000000..bd6ff74 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/variables.lua @@ -0,0 +1,42 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX 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. +]] + +local control = {} + +control.initialized = false +control.ready = false +control.hide_bars = false + +control.update_hp = false +control.update_mp = false +control.update_tp = false +control.hp_bar_width = 0 +control.mp_bar_width = 0 +control.tp_bar_width = 0 + +return control
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/xivbar/xivbar.lua b/Data/DefaultContent/Libraries/addons/addons/xivbar/xivbar.lua new file mode 100644 index 0000000..066dc72 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/xivbar/xivbar.lua @@ -0,0 +1,220 @@ +--[[ + Copyright © 2017, SirEdeonX + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of xivbar nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL SirEdeonX BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +-- Addon description +_addon.name = 'XIV Bar' +_addon.author = 'Edeon' +_addon.version = '1.0' +_addon.language = 'english' + +-- Libs +config = require('config') +texts = require('texts') +images = require('images') + +-- User settings +local defaults = require('defaults') +local settings = config.load(defaults) +config.save(settings) + +-- Load theme options according to settings +local theme = require('theme') +local theme_options = theme.apply(settings) + +-- Addon Dependencies +local ui = require('ui') +local player = require('player') +local xivbar = require('variables') + +-- initialize addon +function initialize() + ui:load(theme_options) + + local windower_player = windower.ffxi.get_player() + + if windower_player ~= nil then + player.hpp = windower_player.vitals.hpp + player.mpp = windower_player.vitals.mpp + player.current_hp = windower_player.vitals.hp + player.current_mp = windower_player.vitals.mp + player.current_tp = windower_player.vitals.tp + + player:calculate_tpp() + end + + xivbar.initialized = true +end + +-- update a bar +function update_bar(bar, text, width, current, pp, flag) + local old_width = width + local new_width = math.floor((pp / 100) * theme_options.bar_width) + + if new_width ~= nil and new_width >= 0 then + if old_width == new_width then + if new_width == 0 then + bar:hide() + end + + if flag == 1 then + xivbar.hp_update = false + elseif flag == 2 then + xivbar.update_mp = false + elseif flag == 3 then + xivbar.update_tp = false + end + else + local x = old_width + + if old_width < new_width then + x = old_width + math.ceil((new_width - old_width) * 0.1) + + x = math.min(x, theme_options.bar_width) + elseif old_width > new_width then + x = old_width - math.ceil((old_width - new_width) * 0.1) + + x = math.max(x, 0) + end + + if flag == 1 then + xivbar.hp_bar_width = x + elseif flag == 2 then + xivbar.mp_bar_width = x + elseif flag == 3 then + xivbar.tp_bar_width = x + end + + bar:size(x, theme_options.total_height) + bar:show() + end + end + + if flag == 3 and current >= 1000 then + text:color(theme_options.full_tp_color_red, theme_options.full_tp_color_green, theme_options.full_tp_color_blue) + if theme_options.dim_tp_bar then bar:alpha(255) end + else + text:color(theme_options.font_color_red, theme_options.font_color_green, theme_options.font_color_blue) + if theme_options.dim_tp_bar then bar:alpha(180) end + end + + text:text(tostring(current)) +end + +-- hide the addon +function hide() + ui:hide() + xivbar.ready = false +end + +-- show the addon +function show() + if xivbar.initialized == false then + initialize() + end + + ui:show() + xivbar.ready = true + xivbar.update_hp = true + xivbar.update_mp = true + xivbar.update_tp = true +end + + +-- Bind Events +-- ON LOAD +windower.register_event('load', function() + if windower.ffxi.get_info().logged_in then + initialize() + show() + end +end) + +-- ON LOGIN +windower.register_event('login', function() + show() +end) + +-- ON LOGOUT +windower.register_event('logout', function() + hide() +end) + +-- BIND EVENTS +windower.register_event('hp change', function(new, old) + player.current_hp = new + xivbar.update_hp = true +end) + +windower.register_event('hpp change', function(new, old) + player.hpp = new + xivbar.update_hp = true +end) + +windower.register_event('mp change', function(new, old) + player.current_mp = new + xivbar.update_mp = true +end) + +windower.register_event('mpp change', function(new, old) + player.mpp = new + xivbar.update_mp = true +end) + +windower.register_event('tp change', function(new, old) + player.current_tp = new + player:calculate_tpp() + xivbar.update_tp = true +end) + +windower.register_event('prerender', function() + if xivbar.ready == false then + return + end + + if xivbar.update_hp then + update_bar(ui.hp_bar, ui.hp_text, xivbar.hp_bar_width, player.current_hp, player.hpp, 1) + end + + if xivbar.update_mp then + update_bar(ui.mp_bar, ui.mp_text, xivbar.mp_bar_width, player.current_mp, player.mpp, 2) + end + + if xivbar.update_tp then + update_bar(ui.tp_bar, ui.tp_text, xivbar.tp_bar_width, player.current_tp, player.tpp, 3) + end +end) + +windower.register_event('status change', function(new_status_id) + if xivbar.hide_bars == false and (new_status_id == 4) then + xivbar.hide_bars = true + hide() + elseif xivbar.hide_bars and new_status_id ~= 4 then + xivbar.hide_bars = false + show() + end +end)
\ No newline at end of file diff --git a/Data/DefaultContent/Libraries/addons/addons/zonetimer/readme.md b/Data/DefaultContent/Libraries/addons/addons/zonetimer/readme.md new file mode 100644 index 0000000..30e7362 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/zonetimer/readme.md @@ -0,0 +1,15 @@ +Commands: + +zonetimer posX # + change the x position + +zonetimer posY # + change the y position + +zonetimer fontsize # + change the fontsize + +zoentimer help + print these instructions in the log + +Original plugin by Krellion. diff --git a/Data/DefaultContent/Libraries/addons/addons/zonetimer/zonetimer.lua b/Data/DefaultContent/Libraries/addons/addons/zonetimer/zonetimer.lua new file mode 100644 index 0000000..e2f851d --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/addons/zonetimer/zonetimer.lua @@ -0,0 +1,93 @@ +_addon.name = 'zonetimer' +_addon.author = 'Ihina' +_addon.version = '1.0.1.0' +_addon.command = 'zonetimer' + +require('logger') +config = require('config') +texts = require('texts') + +defaults = {} +defaults.pos = {} +defaults.pos.x = 400 +defaults.pos.y = 0 +defaults.text = {} +defaults.text.font = 'Arial' +defaults.text.size = 12 + +settings = config.load(defaults) +times = texts.new(settings) +start_time = os.time() + +windower.register_event('zone change', function(new_zone, old_zone) + start_time = os.time() +end) + +windower.register_event('prerender', function() + local info = windower.ffxi.get_info() + if not info.logged_in then + times:hide() + return + end + + seconds = os.time() - start_time + times:text(os.date('!%H:%M:%S', seconds)) + times:visible(true) +end) + +windower.register_event('addon command', function(...) + local param = L{...} + local command = param[1] + if command == 'help' then + log("'zonetimer fontsize #' to change the font size" ) + log("'zonetimer posX #' to change the x position") + log("'zonetimer posY #' to change the y position") + + elseif command == 'fontsize' or command == 'posX' or command == 'posY' then + + if command == 'fontsize' then + settings.text.size = param[2] + elseif command == 'posX' then + settings.pos.x = param[2] + elseif command == 'posY' then + settings.pos.y = param[2] + end + + config.save(settings, 'all') + times:visible(false) + times = texts.new(settings) + elseif command == 'print' then + print(start_time .. " " .. os.time()) + elseif command == 'reset' then + start_time = os.time() + + end +end) + +--[[ +Copyright (c) 2013, Ihina +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of zonetimer nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL IHINA 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. +]] diff --git a/Data/DefaultContent/Libraries/addons/scripts/README.txt b/Data/DefaultContent/Libraries/addons/scripts/README.txt new file mode 100644 index 0000000..32844b4 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/scripts/README.txt @@ -0,0 +1,8 @@ +Lua scripts are similar to the files that are currently run via the //exec windower command. Instead of just executing commands, though, Lua scripts are capable of their own logic and decision making. + +Once a Lua script finishes running, it is unloaded. + +You can run lua scripts via the //lua exec command. For example, if you have a Lua script named test.lua, you would run it by typing: +//lua exec test +or +//lua e test diff --git a/Data/DefaultContent/Libraries/addons/scripts/checkstorage.lua b/Data/DefaultContent/Libraries/addons/scripts/checkstorage.lua new file mode 100644 index 0000000..c3946c8 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/scripts/checkstorage.lua @@ -0,0 +1,310 @@ +--[[ +Copyright (c) 2014, Mujihina +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of checkstorage.lua nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mujihina 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. +]] + +-- Short script to generate a list of what items you have can be stored in a slip + +require('luau') + +local slips = require('slips') +local res = require ('resources').items +local items = windower.ffxi.get_items() + +--[[ +EVENT ITEMS +List of items currently storable with event storage NPC: + +Furnishings 1 +1. 86 San d'Orian holiday tree +2. 115 Bastokan holiday tree +3. 116 Windurstian holiday tree +4. 87 Kadomatsu +5. 117 Wing egg +6. 118 Lamp egg +7. 119 Flower egg +8. 193 Adventuring certificate +9. 88 Timepiece +10. 154 Miniature airship +11. 204 Pumpkin lantern +12. 203 Bomb lantern +13. 205 Mandragora lantern +14. 140 Dream platter +15. 141 Dream coffer +16. 155 Dream stocking +17. 192 Copy of "Hoary Spire" +18. 179 Jeweled egg +19. 323 Sprig of red bamboo grass +20. 324 Sprig of blue bamboo grass +21. 325 Sprig of green bamboo grass +22. 176 Snowman knight +23. 177 Snowman miner +24. 178 Snowman mage +25. 180 Bonbori +26. 215 set of festival dolls +27. 196 Melodious egg +28. 197 Clockwork egg +29. 199 Hatchling egg +30. 320 Harpsichord +31. 415 Aldebaran horn + +Furnishings 2 +1. 264 Stuffed chocobo +2. 455 Egg buffet +3. 265 Adamantoise statue +4. 266 Behemoth statue +5. 267 Fafnir Statue +6. 456 Pepo lantern +7. 457 Cushaw lantern +8. 458 Calabazilla lantern +9. 138 Jeunoan tree +10. 269 Shadow Lord statue +11. 3641 Kabuto-kazari +12. 3642 Katana-kazari +13. 270 Odin statue +14. 271 Alexander statue +15. 3643 Carillon vermeil +16. 3644 Aeolsglocke +17. 3645 Leafbell +18. 181 San d'Orian flag +19. 182 Bastokan flag +20. 183 Windurstian flag +21. 3622 Jack-o'-pricket +22. 3623 Djinn pricket +23. 3624 Korrigan pricket +24. 3646 Mandragora pricket + +Weapons and shields +1. 17074 Chocobo wand +2. 17565 Trick staff +3. 17566 Treat staff +4. 17588 Treat staff II +5. 17830 Wooden katana +6. 17831 Hardwood katana +7. 18102 Pitchfork +8. 18103 Pitchfork +1 +9. 18399 Charm wand +1 +10. 18436 Lotus katana +11. 18842 Moogle rod +12. 18846 Battledore +13. 18844 Miracle wand +1 +14. 18441 Shinai +15. 17748 Ibushi shinai +16. 17749 Ibushi shinai +1 +17. 16182 Town moogle shield +18. 16183 Nomad moogle shield +19. 18863 Dream bell +20. 18864 Dream bell +1 + +Armor - Head +1. 13916 Pumpkin head +2. 13917 Horror head +3. 15176 Pumpkin head II +4. 15177 Horror head II +5. 15178 Dream hat +6. 15179 Dream hat +1 +7. 15198 Sprout beret +8. 15199 Guide beret +9. 15204 Mandragora beret +10. 16075 Witch hat +11. 16076 Coven hat +12. 16109 Egg helm +13. 16118 Moogle cap +14. 16119 Nomad cap +15. 16120 (Pairs of) Redeyes +16. 16144 Sol cap +17. 16145 Lunar cap +18. 11491 Snow bunny hat +1 +19. 11500 Chocobo beret + +Armor - Body, Legs, Feet +1. 13819 Onoko Yukata / 13820 Omina yukata +2. 13821 Lord's Yukata / 13822 Lady's yukata +3. 14450 Hume Gilet / 14451 Hume Top + 14452 Elvaan Gilet / 14453 Elvaan Top + 14454 Tarutaru Maillot / 14471 Tarutaru Top + 14455 Mithra Top + 14456 Galka Gilet +4. 14457 Hume Gilet +1 / 14458 Hume Top +1 + 14459 Elvaan Gilet +1 / 14460 Elvaan Top +1 + 14461 Tarutaru Maillot +1 / 14472 Tarutaru Top +1 + 14462 Mithra Top +1 + 14463 Galka Gilet +1 +5. 15408 Hume Trunks / 15409 Hume Shorts + 15410 Elvaan Trunks / 15411 Elvaan Shorts + 15412 Tarutaru Trunks / 15423 Tarutaru Shorts + 15413 Mithra Shorts + 15414 Galka Trunks +6. 15415 Hume Trunks +1 / 15416 Hume Shorts +1 + 15417 Elvaan Trunks +1 / 15418 Elvaan Shorts +1 + 15419 Tarutaru Trunks +1 / 15424 Tarutaru Shorts +1 + 15420 Mithra Shorts +1 + 15421 Galka Trunks +1 +7. 14519 Dream robe +8. 14520 Dream robe +1 +9. 14532 Otoko Yukata / 14533 Onago yukata +10. 14534 Otokogimi Yukata / 14535 Onnagimi yukata +11. 15752 (Pair of) dream boots +12. 15753 (Pair of) dream boots +1 +13. 11265 Custom Gilet / 11266 Custom Top + 11267 Magna Gilet / 11268 Magna Top + 11269 Wonder Maillot / 11270 Wonder Top + 11271 Savage Top + 11272 Elder Gilet +14. 11273 Custom Gilet +1 / 11274 Custom Top +1 + 11275 Magna Gilet +1 / 11276 Magna Top +1 + 11277 Wonder Maillot +1 / 11278 Wonder Top +1 + 11279 Savage Top +1 + 11280 Elder Gilet +1 +15. 16321 Custom Trunks / 16322 Custom Shorts + 16323 Magna Trunks / 16324 Magna Shorts + 16325 Wonder Trunks / 16326 Wonder Shorts + 16327 Savage Shorts + 16328 Elder Trunks +16. 16329 Custom Trunks +1 / 16330 Custom Shorts +1 + 16331 Magna Trunks +1 / 16332 Magna Shorts +1 + 16333 Wonder Trunks +1 / 16334 Wonder Shorts +1 + 16335 Savage Shorts +1 + 16336 Elder Trunks +1 +17. 11300 Eerie cloak +18. 11301 Eerie cloak +1 +19. 11290 Tidal talisman +20. 11316 Otokogusa yukata / 11317 Onnagusa yukata +21. 11318 Otokoeshi Yukata / 11319 Ominaeshi yukata +22. 11355 Dinner jacket +23. 16378 Dinner hose + + +SEALS +List of items currently storable with Shami: +1126 Beastmen's Seal +1127 Kindred's Seal +2955 Kindred's Crest +2956 High Kindred's Crest +2957 Sacred Kindred's Crest + + +SKIRMISH STONES +List of items currently stoable with Divainy-Gamainy: +3954 Ghastly Stone +3955 Ghastly Stone +1 +3955 Ghastly Stone +2 +4033 Verdigris Stone +4034 Verdigris Stone +1 +4035 Verdigris Stone +2 +3951 Wailing Stone +3952 Wailing Stone +1 +3953 Wailing Stone +2 +8930 Snowslit Stone +8931 Snowslit Stone +1 +8932 Snowslit Stone +2 +8933 Leafslit Stone +8934 Leafslit Stone +1 +8935 Leafslit Stone +2 +8939 Snowtip Stone +8940 Snowtip Stone +1 +8941 Snowtip Stone +2 +8942 Leaftip Stone +8943 Leaftip Stone +1 +8944 Leaftip Stone +2 +8948 Snowdim Stone +8949 Snowdim Stone +1 +8950 Snowdim Stone +2 +8951 Leafdim Stone +8952 Leafdim Stone +1 +8953 Leafdim Stone +2 +8954 Leadorb Stone +8955 Leadorb Stone +1 +8956 Leadorb Stone +2 + + +SKIRMISH WINGS +List of items storable with Lola: +3950 Pulchridopt Wing +4036 Lebondopt Wing + +LIMBUS +List of items storable with Sagheera: +1875 Ancient Beastcoins + +ROE +List of items storable with RoE NPCs: +8711 Copper Voucher +]] + +require('luau') + +local slips = require('slips') +local res = require ('resources').items +local items = windower.ffxi.get_items() + +local storables = T{ + ['event_items'] = { + NPC = 'Event Item NPC', + items = S{ 86, 115, 116, 87, 117, 118, 119, 193, 88, 154, 204, 203, 205, 140, 141, 155, 192, 179, 323, 324, 325, 176, 177, 178, 180, 215, 196, 197, 199, 320, 415, 264, 455, 265, 266, 267, 456, 457, 458, 138, 269, 3641, 3642, 270, 271, 3643, 3644, 3645, 181, 182, 183, 3622, 3623, 3624, 3646, 17074, 17565, 17566, 17588, 17830, 17831, 18102, 18103, 18399, 18436, 18842, 18846, 18844, 18441, 17748, 17749, 16182, 16183, 18863, 18864, 13916, 13917, 15176, 15177, 15178, 15179, 15198, 15199, 15204, 16075, 16076, 16109, 16118, 16119, 16120, 16144, 16145, 11491, 11500, 13819, 13820, 13821, 13822, 14450, 14451, 14452, 14453, 14454, 14471, 14455, 14456, 14457, 14458, 14459, 14460, 14461, 14472, 14462, 14463, 15408, 15409, 15410, 15411, 15412, 15423, 15413, 15414, 15415, 15416, 15417, 15418, 15419, 15424, 15420, 15421, 14519, 14520, 14532, 14533, 14534, 14535, 15752, 15753, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, 11279, 11280, 16321, 16322, 16323, 16324, 16325, 16326, 16327, 16328, 16329, 16330, 16331, 16332, 16333, 16334, 16335, 16336, 11300, 11301, 11290, 11316, 11317, 11318, 11319, 11355, 16378 }, -- 179 + }, + ['seals'] = { + NPC = 'Shami', + items = S{ 1126, 1127, 2955, 2956, 2957}, -- 5 + }, + ['skirmish_stones'] = { + NPC = 'Divainy-Gamainy', + items = S{ 3954, 3955, 3956, 3951, 3952, 3953, 4033, 4034, 4035, 8930, 8931, 8932, 8933, 8934, 8935, 8939, 8940, 8941, 8942, 8943, 8944, 8948, 8949, 8950, 8951, 8952, 8953, 8957, 8958, 8959, 8960, 8961, 8962, }, -- 33 + }, + ['skirmish_wings'] = { + NPC = 'Lola', + items = S{ 3950, 4036}, -- 2 + }, + ['limbus'] = { + NPC = 'Sagheera', + items = S{ 1875, }, -- 1 + }, + ['roe'] = { + NPC = 'RoE NPCs', + items = S{ 8711, }, -- 1 + }, +} + +for _,container in pairs (slips.default_storages) do + for _,item in ipairs (items[container]) do + if (item.id > 0) then + -- check storables + for item_type, item_list in pairs(storables) do + if (item_list['items']:contains(item.id)) then + log ("%s/%s can be stored with %s":format(container:color(259), res[item.id].name:color(258), storables[item_type].NPC:color(261))) + end + end + -- check slips + for slip_id,slip_table in pairs (slips.items) do + for _,j in ipairs (slip_table) do + if (j == item.id) then + log ("%s/%s can be stored in %s":format(container:color(259), res[item.id].name:color(258), res[slip_id].name:color(240))) + end + end + end + end + end +end diff --git a/Data/DefaultContent/Libraries/addons/scripts/colortest.lua b/Data/DefaultContent/Libraries/addons/scripts/colortest.lua new file mode 100644 index 0000000..793ce02 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/scripts/colortest.lua @@ -0,0 +1,32 @@ +colors = {} +colors[1] = 'Menu > Font Colors > Chat > Immediate vicinity ("Say")' +colors[2] = 'Menu > Font Colors > Chat > Wide area ("Shout")' +colors[4] = 'Menu > Font Colors > Chat > Tell target only ("Tell")' +colors[5] = 'Menu > Font Colors > Chat > All party members ("Party")' +colors[6] = 'Menu > Font Colors > Chat > Linkshell group ("Linkshell")' +colors[7] = 'Menu > Font Colors > Chat > Emotes' +colors[17] = 'Menu > Font Colors > Chat > Messages ("Message")' +colors[142] = 'Menu > Font Colors > Chat > NPC Conversations' +colors[20] = 'Menu > Font Colors > For Others > HP/MP others loose' +colors[21] = 'Menu > Font Colors > For Others > Actions others evade' +colors[22] = 'Menu > Font Colors > For Others > HP/MP others recover' +colors[60] = 'Menu > Font Colors > For Others > Beneficial effects others are granted' +colors[61] = 'Menu > Font Colors > For Others > Detrimental effects others receive' +colors[63] = 'Menu > Font Colors > For Others > Effects others resist' +colors[28] = 'Menu > Font Colors > For Self > HP/MP you loose' +colors[29] = 'Menu > Font Colors > For Self > Actions you evade' +colors[30] = 'Menu > Font Colors > For Self > HP/MP you recover' +colors[56] = 'Menu > Font Colors > For Self > Beneficial effects you are granted' +colors[57] = 'Menu > Font Colors > For Self > Detrimental effects you receive' +colors[59] = 'Menu > Font Colors > For Self > Effects you resist' +colors[8] = 'Menu > Font Colors > System > Calls for help' +colors[50] = 'Menu > Font Colors > System > Standard battle messages' +colors[121] = 'Menu > Font Colors > System > Basic system messages' + +for v = 0, 255, 1 do + if(colors[v] ~= nil) then + windower.add_to_chat(v, "Color "..v..": "..colors[v]) + else + windower.add_to_chat(v, "Color "..v..": This is some random text to display the color.") + end +end diff --git a/Data/DefaultContent/Libraries/addons/scripts/strange.lua b/Data/DefaultContent/Libraries/addons/scripts/strange.lua new file mode 100644 index 0000000..2c47b45 --- /dev/null +++ b/Data/DefaultContent/Libraries/addons/scripts/strange.lua @@ -0,0 +1,66 @@ +--[[ +Copyright © 2014, Mujihina +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of strange.lua nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mujihina 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. +]] + +-- Short script to generate password for doctor status when using a strange apparatus + +require('luau') + +if (not windower.ffxi.get_info().logged_in) then + log ("You must be logged in order to use this script") + return +end + +local zone_values = T{ + [191] = {val = 0, element = "Fire", chip = "Red"}, -- Dangruf Wadi + [196] = {val = 1, element = "Earth", chip = "Yellow"}, -- Gusgen Mines + [197] = {val = 2, element = "Water", chip = "Blue"}, -- Crawlers' Nest + [193] = {val = 3, element = "Wind", chip = "Green"}, -- Ordelle's Caves + [195] = {val = 4, element = "Ice", chip = "Clear"}, -- Eldieme Necropolis + [194] = {val = 5, element = "Lightning", chip = "Purple"}, -- Outer Horutoto Ruins + [200] = {val = 6, element = "Light", chip = "White"}, -- Garlaige Citadel + [198] = {val = 7, element = "Dark", chip = "Black"}, -- Maze of Shakrami +} + +local name = windower.ffxi.get_player().name:lower():sub(1,3) -- First 3 chars of name +local area = windower.ffxi.get_info().zone + + +if (not zone_values[area]) then + log ("This is not an area with a strange apparatus") + return +end + +local values = T{} +values[0] = name:byte(1) - 97 + zone_values[area].val +values[1] = name:byte(2) - 97 + zone_values[area].val +values[2] = name:byte(3) - 97 + zone_values[area].val +values[3] = values[0] + values[1] + values[2] + zone_values[area].val + +log ("Password: %02d%02d%02d%02d":format(values[0], values[1], values[2], values[3])) +log ("Chip: %s (%s)":format(zone_values[area].chip, zone_values[area].element)) + diff --git a/Runtime/Scripting/Events/Events.bind.cpp b/Runtime/Scripting/Events/Events.bind.cpp index 40ea544..32e8a2b 100644 --- a/Runtime/Scripting/Events/Events.bind.cpp +++ b/Runtime/Scripting/Events/Events.bind.cpp @@ -31,33 +31,33 @@ int luaopen_GameLab_Events(lua_State* L) state.RegisterMethods(funcs); LUA_BIND_REGISTER_ENUM(state, "EEventType", - { "MouseDown", InputEvent_MouseDown },
- { "MouseUp", InputEvent_MouseUp },
- { "MouseMove", InputEvent_MouseMove },
- { "MouseDrag", InputEvent_MouseDrag },
- { "KeyDown", InputEvent_KeyDown },
- { "KeyUp", InputEvent_KeyUp },
- { "ScrollWheel", InputEvent_ScrollWheel },
- { "Repaint", InputEvent_Repaint },
- { "Layout", InputEvent_Layout },
- { "DragUpdated", InputEvent_DragUpdated },
- { "DragPerform", InputEvent_DragPerform },
- { "DragExited", InputEvent_DragExited },
- { "Ignore", InputEvent_Ignore },
- { "Used", InputEvent_Used },
- { "ValidateCommand", InputEvent_ValidateCommand },
- { "ExecuteCommand", InputEvent_ExecuteCommand },
- { "ContextClick", InputEvent_ContextClick },
- { "MouseEnterWindow", InputEvent_MouseEnterWindow },
- { "MouseLeaveWindow", InputEvent_MouseLeaveWindow },
- { "MagnifyGesture", InputEvent_MagnifyGesture },
- { "SwipeGesture", InputEvent_SwipeGesture },
- { "RotateGesture", InputEvent_RotateGesture } + { "MouseDown", InputEvent_MouseDown },
+ { "MouseUp", InputEvent_MouseUp },
+ { "MouseMove", InputEvent_MouseMove },
+ { "MouseDrag", InputEvent_MouseDrag },
+ { "KeyDown", InputEvent_KeyDown },
+ { "KeyUp", InputEvent_KeyUp },
+ { "ScrollWheel", InputEvent_ScrollWheel },
+ { "Repaint", InputEvent_Repaint },
+ { "Layout", InputEvent_Layout },
+ { "DragUpdated", InputEvent_DragUpdated },
+ { "DragPerform", InputEvent_DragPerform },
+ { "DragExited", InputEvent_DragExited },
+ { "Ignore", InputEvent_Ignore },
+ { "Used", InputEvent_Used },
+ { "ValidateCommand", InputEvent_ValidateCommand },
+ { "ExecuteCommand", InputEvent_ExecuteCommand },
+ { "ContextClick", InputEvent_ContextClick },
+ { "MouseEnterWindow", InputEvent_MouseEnterWindow },
+ { "MouseLeaveWindow", InputEvent_MouseLeaveWindow },
+ { "MagnifyGesture", InputEvent_MagnifyGesture },
+ { "SwipeGesture", InputEvent_SwipeGesture },
+ { "RotateGesture", InputEvent_RotateGesture } ); LUA_BIND_REGISTER_ENUM(state, "EMouseButton", - { "LeftButton", Mouse_LeftButton }, - { "RightButton", Mouse_RightButton }, + { "LeftButton", Mouse_LeftButton }, + { "RightButton", Mouse_RightButton }, { "MiddleButton", Mouse_MiddleButton } ); |