Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --Copyright (c) 2015, Byrthnoth and Rooks
- --All rights reserved.
- --Redistribution and use in source and binary forms, with or without
- --modification, are permitted provided that the following conditions are met:
- -- * Redistributions of source code must retain the above copyright
- -- notice, this list of conditions and the following disclaimer.
- -- * Redistributions in binary form must reproduce the above copyright
- -- notice, this list of conditions and the following disclaimer in the
- -- documentation and/or other materials provided with the distribution.
- -- * Neither the name of <addon name> nor the
- -- names of its contributors may be used to endorse or promote products
- -- derived from this software without specific prior written permission.
- --THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- --ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- --WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- --DISCLAIMED. IN NO EVENT SHALL <your name> BE LIABLE FOR ANY
- --DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- --(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- --LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- --ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- --(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- --SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- res = require 'resources'
- files = require 'files'
- require 'pack'
- Items = require 'items'
- extdata = require 'extdata'
- logger = require 'logger'
- require 'tables'
- require 'lists'
- require 'functions'
- config = require 'config'
- slips = require 'slips'
- _addon.name = 'Organizer'
- _addon.author = 'Byrth, maintainer: Rooks'
- _addon.version = 0.20210516
- _addon.commands = {'organizer','org'}
- _static = {
- bag_ids = {
- inventory=0,
- safe=1,
- storage=2,
- temporary=3,
- locker=4,
- satchel=5,
- sack=6,
- case=7,
- wardrobe=8,
- safe2=9,
- wardrobe2=10,
- wardrobe3=11,
- wardrobe4=12,
- },
- wardrobe_ids = {[8]=true,[10]=true,[11]=true,[12]=true},
- usable_bags = {1,9,4,2,5,6,7,8,10,11,12}
- }
- _global = {
- language = 'english',
- language_log = 'english_log',
- }
- _ignore_list = {}
- _retain = {}
- _valid_pull = {}
- _valid_dump = {}
- default_settings = {
- dump_bags = {['Safe']=1,['Safe2']=2,['Locker']=3,['Storage']=4},
- bag_priority = {['Safe']=1,['Safe2']=2,['Locker']=3,['Storage']=4,['Satchel']=5,['Sack']=6,['Case']=7,['Inventory']=8,['Wardrobe']=9,['Wardrobe2']=10,['Wardrobe3']=11,['Wardrobe4']=12,},
- item_delay = 0,
- ignore = {},
- retain = {
- ["moogle_slip_gear"]=false,
- ["seals"]=false,
- ["items"]=false,
- ["slips"]=false,
- },
- auto_heal = false,
- default_file='default.lua',
- verbose=false,
- }
- _debugging = {
- debug = {
- ['contains']=true,
- ['command']=true,
- ['find']=true,
- ['find_all']=true,
- ['items']=true,
- ['move']=true,
- ['settings']=true,
- ['stacks']=true
- },
- debug_log = 'data\\organizer-debug.log',
- enabled = false,
- warnings = false, -- This mode gives warnings about impossible item movements and crash conditions.
- }
- debug_log = files.new(_debugging.debug_log)
- function s_to_bag(str)
- if not str and tostring(str) then return end
- for i,v in pairs(res.bags) do
- if v.en:lower():gsub(' ', '') == str:lower() then
- return v.id
- end
- end
- end
- windower.register_event('load',function()
- debug_log:write('Organizer loaded at '..os.date()..'\n')
- if debugging then windower.debug('load') end
- options_load()
- end)
- function options_load( )
- if not windower.dir_exists(windower.addon_path..'data\\') then
- org_debug("settings", "Creating data directory")
- windower.create_dir(windower.addon_path..'data\\')
- if not windower.dir_exists(windower.addon_path..'data\\') then
- org_error("unable to create data directory!")
- end
- end
- for bag_name, bag_id in pairs(_static.bag_ids) do
- if not windower.dir_exists(windower.addon_path..'data\\'..bag_name) then
- org_debug("settings", "Creating data directory for "..bag_name)
- windower.create_dir(windower.addon_path..'data\\'..bag_name)
- if not windower.dir_exists(windower.addon_path..'data\\'..bag_name) then
- org_error("unable to create"..bag_name.."directory!")
- end
- end
- end
- -- We can't just do a:
- --
- -- settings = config.load('data\\settings.xml', default_settings)
- --
- -- because the config library will try to merge them, and it will
- -- add back anything a user has removed (like items in bag_priority)
- if windower.file_exists(windower.addon_path..'data\\settings.xml') then
- org_debug("settings", "Loading settings from file")
- settings = config.load('data\\settings.xml')
- else
- org_debug("settings", "Saving default settings to file")
- settings = config.load('data\\settings.xml', default_settings)
- end
- -- Build the ignore list
- if(settings.ignore) then
- for bn,i_list in pairs(settings.ignore) do
- bag_name = bn:lower()
- _ignore_list[bag_name] = {}
- for _,ignore_name in pairs(i_list) do
- org_verbose("Adding "..ignore_name.." in the "..bag_name.." to the ignore list")
- _ignore_list[bag_name][ignore_name] = 1
- end
- end
- end
- -- Build a hard-wired pull list
- for bag_name,_ in pairs(settings.bag_priority) do
- org_verbose("Adding "..bag_name.." to the pull list")
- _valid_pull[s_to_bag(bag_name)] = 1
- end
- -- Build a hard-wired dump list
- for bag_name,_ in pairs(settings.dump_bags) do
- org_verbose("Adding "..bag_name.." to the push list")
- _valid_dump[s_to_bag(bag_name)] = 1
- end
- -- Build the retain lists
- if(settings.retain) then
- if(settings.retain.moogle_slip_gear == true) then
- org_verbose("Moogle slip gear set to retain")
- slip_lists = require('slips')
- for slip_id,slip_list in pairs(slip_lists.items) do
- for item_id in slip_list:it() do
- if item_id ~= 0 then
- _retain[item_id] = "moogle slip"
- org_debug("settings", "Adding ("..res.items[item_id].english..') to slip retain list')
- end
- end
- end
- end
- if(settings.retain.seals == true) then
- org_verbose("Seals set to retain")
- seals = {1126,1127,2955,2956,2957}
- for _,seal_id in pairs(seals) do
- _retain[seal_id] = "seal"
- org_debug("settings", "Adding ("..res.items[seal_id].english..') to slip retain list')
- end
- end
- if(settings.retain.items == true) then
- org_verbose("Non-equipment items set to retain")
- end
- if(settings.retain.slips == true) then
- org_verbose("Slips set to retain")
- for _,slips_id in pairs(slips.storages) do
- _retain[slips_id] = "slips"
- org_debug("settings", "Adding ("..res.items[slips_id].english..') to slip retain list')
- end
- end
- end
- -- Always allow inventory and wardrobe, obviously
- _valid_dump[0] = 1
- _valid_pull[0] = 1
- _valid_dump[8] = 1
- _valid_pull[8] = 1
- _valid_dump[10] = 1
- _valid_pull[10] = 1
- _valid_dump[11] = 1
- _valid_pull[11] = 1
- _valid_dump[12] = 1
- _valid_pull[12] = 1
- end
- windower.register_event('addon command',function(...)
- local inp = {...}
- -- get (g) = Take the passed file and move everything to its defined location.
- -- tidy (t) = Take the passed file and move everything that isn't in it out of my active inventory.
- -- organize (o) = get followed by tidy.
- local command = table.remove(inp,1):lower()
- if command == 'eval' then
- assert(loadstring(table.concat(inp,' ')))()
- return
- end
- local bag = 'all'
- if inp[1] and (_static.bag_ids[inp[1]:lower()] or inp[1]:lower() == 'all') then
- bag = table.remove(inp,1):lower()
- end
- org_debug("command", "Using '"..bag.."' as the bag target")
- file_name = table.concat(inp,' ')
- if string.length(file_name) == 0 then
- file_name = default_file_name()
- end
- if file_name:sub(-4) ~= '.lua' then
- file_name = file_name..'.lua'
- end
- org_debug("command", "Using '"..file_name.."' as the file name")
- if (command == 'g' or command == 'get') then
- org_debug("command", "Calling get with file_name '"..file_name.."' and bag '"..bag.."'")
- get(thaw(file_name, bag))
- elseif (command == 't' or command == 'tidy') then
- org_debug("command", "Calling tidy with file_name '"..file_name.."' and bag '"..bag.."'")
- tidy(thaw(file_name, bag))
- elseif (command == 'f' or command == 'freeze') then
- org_debug("command", "Calling freeze command")
- local items = Items.new(windower.ffxi.get_items(),true)
- local frozen = {}
- items[3] = nil -- Don't export temporary items
- if _static.bag_ids[bag] then
- org_debug("command", "Bag: "..bag)
- freeze(file_name,bag,items)
- else
- for bag_id,item_list in items:it() do
- org_debug("command", "Bag ID: "..bag_id)
- -- infinite loop protection
- if(frozen[bag_id]) then
- org_warning("Tried to freeze ID #"..bag_id.." twice, aborting")
- return
- end
- frozen[bag_id] = 1
- freeze(file_name,res.bags[bag_id].english:lower():gsub(' ', ''),items)
- end
- end
- elseif (command == 'o' or command == 'organize') then
- org_debug("command", "Calling organize command")
- organize(thaw(file_name, bag))
- end
- if settings.auto_heal and tostring(settings.auto_heal):lower() ~= 'false' then
- org_debug("command", "Automatically healing")
- windower.send_command('input /heal')
- end
- org_debug("command", "Organizer complete")
- end)
- function get(goal_items,current_items)
- org_verbose('Getting!')
- if goal_items then
- count = 0
- failed = 0
- current_items = current_items or Items.new()
- goal_items, current_items = clean_goal(goal_items,current_items)
- for bag_id,inv in goal_items:it() do
- for ind,item in inv:it() do
- if not item:annihilated() then
- local start_bag, start_ind = current_items:find(item)
- -- Table contains a list of {bag, pos, count}
- if start_bag then
- if not current_items:route(start_bag,start_ind,bag_id) then
- org_warning('Unable to move item.')
- failed = failed + 1
- else
- count = count + 1
- end
- simulate_item_delay()
- else
- -- Need to adapt this for stacking items somehow.
- org_warning(res.items[item.id].english..' not found.')
- end
- end
- end
- end
- org_verbose("Got "..count.." item(s), and failed getting "..failed.." item(s)")
- end
- return goal_items, current_items
- end
- function freeze(file_name,bag,items)
- org_debug("command", "Entering freeze function with bag '"..bag.."'")
- local lua_export = T{}
- local counter = 0
- for _,item_table in items[_static.bag_ids[bag]]:it() do
- counter = counter + 1
- if(counter > 80) then
- org_warning("We hit an infinite loop in freeze()! ABORT.")
- return
- end
- org_debug("command", "In freeze loop for bag '"..bag.."'")
- org_debug("command", "Processing '"..item_table.log_name.."'")
- local temp_ext,augments = extdata.decode(item_table)
- if temp_ext.augments then
- org_debug("command", "Got augments for '"..item_table.log_name.."'")
- augments = table.filter(temp_ext.augments,-functions.equals('none'))
- end
- lua_export:append({name = item_table.name,log_name=item_table.log_name,
- id=item_table.id,extdata=item_table.extdata:hex(),augments = augments,count=item_table.count})
- end
- -- Make sure we have something in the bag at all
- if lua_export[1] then
- org_verbose("Freezing "..tostring(bag)..".")
- local export_file = files.new('/data/'..bag..'/'..file_name,true)
- export_file:write('return '..lua_export:tovstring({'augments','log_name','name','id','count','extdata'}))
- else
- org_debug("command", "Got nothing, skipping '"..bag.."'")
- end
- end
- function tidy(goal_items,current_items,usable_bags)
- org_debug("command", "Entering tidy()")
- usable_bags = usable_bags or get_dump_bags()
- -- Move everything out of items[0] and into other inventories (defined by the passed table)
- if goal_items and goal_items[0] and goal_items[0]._info.n > 0 then
- current_items = current_items or Items.new()
- goal_items, current_items = clean_goal(goal_items,current_items)
- for index,item in current_items[0]:it() do
- if not goal_items[0]:contains(item,true) then
- org_debug("command", "Putting away "..item.log_name)
- current_items[0][index]:put_away(usable_bags)
- simulate_item_delay()
- end
- end
- end
- return goal_items, current_items
- end
- function organize(goal_items)
- org_message('Starting...')
- local current_items = Items.new()
- local dump_bags = get_dump_bags()
- local inventory_max = windower.ffxi.get_bag_info(0).max
- if current_items[0].n == inventory_max then
- tidy(goal_items,current_items,dump_bags)
- end
- if current_items[0].n == inventory_max then
- org_error('Unable to make space, aborting!')
- return
- end
- local remainder = math.huge
- while remainder do
- goal_items, current_items = get(goal_items,current_items)
- goal_items, current_items = clean_goal(goal_items,current_items)
- goal_items, current_items = tidy(goal_items,current_items,dump_bags)
- remainder = incompletion_check(goal_items,remainder)
- if(remainder) then
- org_verbose("Remainder: "..tostring(remainder)..' Current: '..current_items[0]._info.n,1)
- else
- org_verbose("No remainder, so we found everything we were looking for!")
- end
- end
- goal_items, current_items = tidy(goal_items,current_items,dump_bags)
- local count,failures = 0,T{}
- for bag_id,bag in goal_items:it() do
- for ind,item in bag:it() do
- if item:annihilated() then
- count = count + 1
- else
- item.bag_id = bag_id
- failures:append(item)
- end
- end
- end
- org_message('Done! - '..count..' items matched and '..table.length(failures)..' items missing!')
- if table.length(failures) > 0 then
- for i,v in failures:it() do
- org_verbose('Item Missing: '..i.name..' '..(i.augments and tostring(T(i.augments)) or ''))
- end
- end
- end
- function clean_goal(goal_items,current_items)
- for i,inv in goal_items:it() do
- for ind,item in inv:it() do
- local potential_ind = current_items[i]:contains(item)
- if potential_ind then
- -- If it is already in the right spot, delete it from the goal items and annihilate it.
- local count = math.min(goal_items[i][ind].count,current_items[i][potential_ind].count)
- goal_items[i][ind]:annihilate(goal_items[i][ind].count)
- current_items[i][potential_ind]:annihilate(current_items[i][potential_ind].count)
- end
- end
- end
- return goal_items, current_items
- end
- function incompletion_check(goal_items,remainder)
- -- Does not work. On cycle 1, you fill up your inventory without purging unnecessary stuff out.
- -- On cycle 2, your inventory is full. A gentler version of tidy needs to be in the loop somehow.
- local remaining = 0
- for i,v in goal_items:it() do
- for n,m in v:it() do
- if not m:annihilated() then
- remaining = remaining + 1
- end
- end
- end
- return remaining ~= 0 and remaining < remainder and remaining
- end
- function thaw(file_name,bag)
- local bags = _static.bag_ids[bag] and {[bag]=file_name} or table.reassign({},_static.bag_ids) -- One bag name or all of them if no bag is specified
- if settings.default_file:sub(-4) ~= '.lua' then
- settings.default_file = settings.default_file..'.lua'
- end
- for i,v in pairs(_static.bag_ids) do
- bags[i] = bags[i] and windower.file_exists(windower.addon_path..'data/'..i..'/'..file_name) and file_name or default_file_name()
- end
- bags.temporary = nil
- local inv_structure = {}
- for cur_bag,file in pairs(bags) do
- local f,err = loadfile(windower.addon_path..'data/'..cur_bag..'/'..file)
- if f and not err then
- local success = false
- success, inv_structure[cur_bag] = pcall(f)
- if not success then
- org_warning('User File Error (Syntax) - '..inv_structure[cur_bag])
- inv_structure[cur_bag] = nil
- end
- elseif bag and cur_bag:lower() == bag:lower() then
- org_warning('User File Error (Loading) - '..err)
- end
- end
- -- Convert all the extdata back to a normal string
- for i,v in pairs(inv_structure) do
- for n,m in pairs(v) do
- if m.extdata then
- inv_structure[i][n].extdata = string.parse_hex(m.extdata)
- end
- end
- end
- return Items.new(inv_structure)
- end
- function org_message(msg,col)
- windower.add_to_chat(col or 8,'Organizer: '..msg)
- flog(_debugging.debug_log, 'Organizer [MSG] '..msg)
- end
- function org_warning(msg)
- if _debugging.warnings then
- windower.add_to_chat(123,'Organizer: '..msg)
- end
- flog(_debugging.debug_log, 'Organizer [WARN] '..msg)
- end
- function org_debug(level, msg)
- if(_debugging.enabled) then
- if (_debugging.debug[level]) then
- flog(_debugging.debug_log, 'Organizer [DEBUG] ['..level..']: '..msg)
- end
- end
- end
- function org_error(msg)
- error('Organizer: '..msg)
- flog(_debugging.debug_log, 'Organizer [ERROR] '..msg)
- end
- function org_verbose(msg,col)
- if tostring(settings.verbose):lower() ~= 'false' then
- windower.add_to_chat(col or 8,'Organizer: '..msg)
- end
- flog(_debugging.debug_log, 'Organizer [VERBOSE] '..msg)
- end
- function default_file_name()
- player = windower.ffxi.get_player()
- job_name = res.jobs[player.main_job_id]['english_short']
- return player.name..'_'..job_name..'.lua'
- end
- function simulate_item_delay()
- if settings.item_delay and settings.item_delay > 0 then
- coroutine.sleep(settings.item_delay)
- end
- end
- function get_dump_bags()
- local dump_bags = {}
- for i,v in pairs(settings.dump_bags) do
- if i and s_to_bag(i) then
- dump_bags[tonumber(v)] = s_to_bag(i)
- elseif i then
- org_error('The bag name ("'..tostring(i)..'") in dump_bags entry #'..tostring(v)..' in the ../addons/organizer/data/settings.xml file is not valid.\nValid options are '..tostring(res.bags))
- return
- end
- end
- return dump_bags
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement