summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/libs
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/libs')
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/actions.lua625
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/chat.lua118
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/chars.lua361
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/colors.lua27
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/controls.lua5
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/icons.lua19
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/config.lua513
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/dialog.lua227
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/extdata.lua2246
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/files.lua250
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/functions.lua508
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/images.lua461
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/json.lua297
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/lists.lua455
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/logger.lua281
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/logger.xml11
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/ltn12.lua309
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/luau.lua28
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/maths.lua129
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/matrices.lua266
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/mime.lua90
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/packets.lua456
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/packets/data.lua253
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/packets/fields.lua3959
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/queues.lua165
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/readme.md1
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/resources.lua226
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/sets.lua343
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/slips.lua187
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket.lua149
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/ftp.lua329
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/headers.lua104
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/http.lua382
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/smtp.lua256
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/tp.lua134
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/url.lua309
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/strings.lua529
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/tables.lua621
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/texts.lua694
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/timeit.lua121
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/vectors.lua192
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/libs/xml.lua596
42 files changed, 17232 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/libs/actions.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/actions.lua
new file mode 100644
index 0000000..14254ce
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/chat.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/chat.lua
new file mode 100644
index 0000000..89b10cd
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/chat/chars.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/chars.lua
new file mode 100644
index 0000000..1389356
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/chat/colors.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/colors.lua
new file mode 100644
index 0000000..8ab54c0
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/chat/controls.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/controls.lua
new file mode 100644
index 0000000..dc68aa3
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/chat/icons.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/chat/icons.lua
new file mode 100644
index 0000000..6e7c571
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/config.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/config.lua
new file mode 100644
index 0000000..7829c7f
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/dialog.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/dialog.lua
new file mode 100644
index 0000000..5598175
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/extdata.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/extdata.lua
new file mode 100644
index 0000000..0537a43
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/files.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/files.lua
new file mode 100644
index 0000000..52a9909
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/functions.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/functions.lua
new file mode 100644
index 0000000..5dc15f3
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/images.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/images.lua
new file mode 100644
index 0000000..b540210
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/json.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/json.lua
new file mode 100644
index 0000000..ff8e314
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/lists.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/lists.lua
new file mode 100644
index 0000000..2ea86e1
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/logger.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/logger.lua
new file mode 100644
index 0000000..b1fb360
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/logger.xml b/Data/BuiltIn/Libraries/lua-addons/addons/libs/logger.xml
new file mode 100644
index 0000000..b9c3c9b
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/ltn12.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/ltn12.lua
new file mode 100644
index 0000000..575c5a7
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/luau.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/luau.lua
new file mode 100644
index 0000000..4b8e68b
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/maths.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/maths.lua
new file mode 100644
index 0000000..732f8bd
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/matrices.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/matrices.lua
new file mode 100644
index 0000000..5931aad
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/mime.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/mime.lua
new file mode 100644
index 0000000..642cd9c
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/packets.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/packets.lua
new file mode 100644
index 0000000..f3ab5b0
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/packets/data.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/packets/data.lua
new file mode 100644
index 0000000..31df307
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/packets/fields.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/packets/fields.lua
new file mode 100644
index 0000000..bdc9b78
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/queues.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/queues.lua
new file mode 100644
index 0000000..4b92370
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/readme.md b/Data/BuiltIn/Libraries/lua-addons/addons/libs/readme.md
new file mode 100644
index 0000000..318bbc2
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/resources.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/resources.lua
new file mode 100644
index 0000000..a33b9cf
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/sets.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/sets.lua
new file mode 100644
index 0000000..0165689
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/slips.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/slips.lua
new file mode 100644
index 0000000..3fd7063
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket.lua
new file mode 100644
index 0000000..d1c0b16
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/ftp.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/ftp.lua
new file mode 100644
index 0000000..bd528ca
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/headers.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/headers.lua
new file mode 100644
index 0000000..1eb8223
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/http.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/http.lua
new file mode 100644
index 0000000..a386165
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/smtp.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/smtp.lua
new file mode 100644
index 0000000..b113d00
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/tp.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/tp.lua
new file mode 100644
index 0000000..b8ebc56
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/socket/url.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/socket/url.lua
new file mode 100644
index 0000000..d61111e
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/strings.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/strings.lua
new file mode 100644
index 0000000..cd7ac1e
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/tables.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/tables.lua
new file mode 100644
index 0000000..24417a2
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/texts.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/texts.lua
new file mode 100644
index 0000000..2213e06
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/timeit.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/timeit.lua
new file mode 100644
index 0000000..096510a
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/vectors.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/vectors.lua
new file mode 100644
index 0000000..db19dcb
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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/BuiltIn/Libraries/lua-addons/addons/libs/xml.lua b/Data/BuiltIn/Libraries/lua-addons/addons/libs/xml.lua
new file mode 100644
index 0000000..fc5c9bc
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-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.
+]]