summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua')
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua814
1 files changed, 814 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua b/Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua
new file mode 100644
index 0000000..9751dd7
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/craft/craft.lua
@@ -0,0 +1,814 @@
+--[[
+Craft v1.1.3
+
+Copyright © 2017 Mojo
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+* Neither the name of craft nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Mojo BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+]]
+
+_addon.name = 'craft'
+_addon.author = 'Mojo, Recipes provided by BG-Wiki.com'
+_addon.version = '1.1.3'
+_addon.commands = {'craft'}
+
+require('chat')
+require('lists')
+require('coroutine')
+require('queues')
+require('logger')
+require('tables')
+require('sets')
+require('strings')
+
+local packets = require('packets')
+local res = require('resources')
+local recipes = require('recipes')
+local queue = Q{}
+local handlers = {}
+local delay = 24
+local synth = 0
+local skip_delay = false
+local busy = false
+local paused = false
+local display = false
+local jiggle = false
+local support = false
+local zone = nil
+local hqsynth = false
+
+local conditions = {
+ move = false,
+ sort = false,
+ crystal = false,
+ support = false,
+}
+
+local food = false
+local supported = false
+local appropriated = {}
+local inventory = {}
+
+local function filter_bag(v)
+ return not (v.name:match("Inventory") or
+ v.name:match("Temporary") or
+ v.name:match("Wardrobe"))
+end
+
+local function get_bag_command(k)
+ return res.bags[k].command
+end
+
+local function get_bag_id(bag)
+ return bag.id
+end
+
+local bags = res.bags:filter(filter_bag):key_map(get_bag_command):map(get_bag_id)
+
+local support_npcs = {
+ {name = "Orechiniel", zone = 230, menu = 650, buff = 240},
+ {name = "Greubaque", zone = 231, menu = 628, buff = 237},
+ {name = "Ulycille", zone = 231, menu = 623, buff = 236},
+ {name = "Azima", zone = 234, menu = 122, buff = 242},
+ {name = "Fatimah", zone = 235, menu = 302, buff = 238},
+ {name = "Wise Owl", zone = 237, menu = 103, buff = 237},
+ {name = "Kipo-Opo", zone = 238, menu = 10015, buff = 243},
+ {name = "Lih Pituu", zone = 241, menu = 10018, buff = 241},
+ {name = "Terude-Harude", zone = 241, menu = 10013, buff = 239},
+ {name = "Fleuricette", zone = 256, menu = 1201, buff = 512},
+ {name = "Quiri-Aliri", zone = 257, menu = 1201, buff = 512},
+}
+
+local exceptions = {
+ ['Geo Crystal'] = 6509,
+ ['Fire Card'] = 9764,
+ ['Ice Card'] = 9765,
+ ['Wind Card'] = 9766,
+ ['Earth Card'] = 9767,
+ ['Lightning Card'] = 9768,
+ ['Water Card'] = 9769,
+ ['Light Card'] = 9770,
+ ['Dark Card'] = 9771,
+}
+
+local clusters = {
+ ['Fire Crystal'] = 'Fire Cluster',
+ ['Ice Crystal'] = 'Ice Cluster',
+ ['Wind Crystal'] = 'Wind Cluster',
+ ['Earth Crystal'] = 'Earth Cluster',
+ ['Lightng. Crystal'] = 'Lightning Cluster',
+ ['Water Crystal'] = 'Water Cluster',
+ ['Light Crystal'] = 'Light Cluster',
+ ['Dark Crystal'] = 'Dark Cluster',
+}
+
+local hqcrystal = {
+ ['Fire Crystal'] = 'Inferno Crystal',
+ ['Ice Crystal'] = 'Glacier Crystal',
+ ['Wind Crystal'] = 'Cyclone Crystal',
+ ['Earth Crystal'] = 'Terra Crystal',
+ ['Lightng. Crystal'] = 'Plasma Crystal',
+ ['Water Crystal'] = 'Torrent Crystal',
+ ['Light Crystal'] = 'Aurora Crystal',
+ ['Dark Crystal'] = 'Twilight Crystal',
+}
+
+local help_commands = [[
+craft - Command List:
+1. help - Displays this message.
+2. repeat - Repeats synthesis (default 1) using the
+ lastsynth command.
+* repeat - Repeats 1 synthesis
+* repeat 13 - Repeats 13 synthesis
+3. make - Issue a synthesis command using a recipe name
+* make "Sheep Leather" - Makes 1 Sheep Leather
+* make "Sheep Leather" 5 - Makes 5 Sheep Leather
+4. put - Moves all copies of an item into available bags.
+* put "Dragon Mask" - Moves all Dragon Masks in inventory
+ to any available bags.
+* put "Dragon Mask" satchel - Moves all Dragon Masks in
+ inventory to Mog Satchel.
+* put "Dragon Mask" safe2 - Moves all Dragon Masks to Mog
+ Safe 2 (if available).
+5. delay - Sets the delay between crafting attempts
+ (default 24, minimum 17)
+* delay 30 - Sets the delay between crafting to 30
+ seconds.]]
+
+local help_commands_2 = [[
+6. food - Sets a food item that will automatically
+ be consumed while crafting.
+* food - Sets the auto food to None.
+* food "Kitron Macaron" - Sets the auto food
+ to Kitron Macaron.
+7. pause - Pauses the addon.
+8. resume - Resumes the addon.
+9. clear - Clears all items in the queue.
+10. jiggle - Set a key that will be pressed between every
+ queue item (default disabled.)
+* jiggle - Disables the jiggle feature.
+* jiggle escape - Sets the jiggle key to escape.
+11. support - Toggles auto support/ionis (default off)
+* Must be near an NPC that offers Ionis or advanced
+ imagery support to work.
+* Determines whether items will be sold instantly or slowly.
+12. status - Display some information about the
+ addon's current state.
+13. find - Search for a recipe fromt the recipes list using
+ a string.
+* find "Pizza" - Finds and displays all recipes containing
+ the string "Pizza".
+* find "Pizza" details - Finds and displays all recipes
+ containing the string "Pizza" (ingredients/crystal are
+ also displayed.)
+14. display - Toggles whether outgoing crafting
+ packets are displayed in the chat log.
+15. hqcrystal - Toggle whether to use HQ Crystal
+]]
+
+local help_notes = [[
+Notes:
+ Make commands will automatically pull items from
+ any available bags if they are not present in your
+ inventory. This includes all recipe ingredients
+ and the crystal. If a crystal cannot be found,
+ it will search for a cluster from your inventory
+ and available bags and use the cluster. These
+ features are not supported with the repeat command.
+
+ The available recipes are stored in recipes.lua.
+ The order in which ingredients are entered matter.
+ To add new recipes, enable the display (//craft
+ display) and manually synthesis an item. The
+ packet will be printed to your chat log. Create
+ an entry similar to the recipes that are already
+ provided. Only add the actual ingredients and
+ crystal. Then save recipes.lua and reload the addon.
+
+ Ingredients and food are case sensitive and use
+ the short english name. These are the ones
+ displayed on FFXIAH.
+]]
+
+local function validate(npcs)
+ zone = windower.ffxi.get_info()['zone']
+ local valid = false
+ for _, npc in pairs(npcs) do
+ if zone == npc.zone then
+ valid = true
+ local mob = windower.ffxi.get_mob_by_name(npc.name)
+ if mob then
+ if (math.sqrt(mob.distance) < 6) then
+ return mob, npc
+ end
+ end
+ end
+ end
+ if valid then
+ warning("Too far from away from NPC")
+ end
+end
+
+local function get_support(id, data)
+ if (id == 0x34) and conditions['support'] then
+ local mob, npc = validate(support_npcs)
+ local p = packets.new('outgoing', 0x5b, {
+ ["Target"] = mob.id,
+ ["Option Index"] = 1,
+ ["Target Index"] = mob.index,
+ ["Automated Message"] = false,
+ ["Zone"] = zone,
+ ["Menu ID"] = npc.menu,
+ })
+ packets.inject(p)
+ conditions['support'] = false
+ return true
+ end
+end
+
+local function check_bag(bag, id)
+ if not inventory['enabled_%s':format(bag)] then
+ return false
+ end
+ local contents = inventory[bag]
+ for index = 1, inventory['max_%s':format(bag)] do
+ if contents[index].id == id then
+ conditions['sort'] = true
+ conditions['move'] = true
+ windower.ffxi.get_item(bags[bag], index, contents[index].count)
+ return true
+ end
+ end
+ return false
+end
+
+local function check_bags(id)
+ if inventory['count_inventory'] == inventory['max_inventory'] then
+ return false
+ end
+ for bag, bag_id in pairs(bags) do
+ if check_bag(bag, id) then
+ return true
+ end
+ end
+ return false
+end
+
+local function block_sort(id, data)
+ if (id == 0x3a) and conditions['sort'] then
+ return true
+ end
+end
+
+local function busy_wait(block, timeout, message)
+ local start = os.time()
+ while conditions[block] and ((os.time() - start) < timeout) do
+ coroutine.sleep(.1)
+ end
+ if os.time() - start >= timeout then
+ conditions[block] = false
+ return "Timed out - %s":format(message)
+ else
+ inventory = windower.ffxi.get_items()
+ end
+end
+
+local function poke_npc()
+ local mob, npc = validate(support_npcs)
+ if npc then
+ local player = windower.ffxi.get_player()
+ if S(player.buffs):contains(npc.buff) then
+ return
+ end
+ conditions['support'] = true
+ local p = packets.new('outgoing', 0x01a, {
+ ["Target"] = mob.id,
+ ["Target Index"] = mob.index,
+ ["Category"] = 0,
+ ["Param"] = 0,
+ ["_unknown1"] = 0,
+ })
+ packets.inject(p)
+ return busy_wait('support', 10, "getting crafting buff")
+ end
+end
+
+local function unblock_sort(id, data)
+ if id == 0x1d then
+ conditions['move'] = false
+ end
+end
+
+local function unblock_item(id, data)
+ if (id == 0x20) then
+ p = packets.parse('incoming', data)
+ if p['Item'] == conditions['item'] then
+ conditions['item'] = false
+ end
+ end
+end
+
+local function commence_jigglin()
+ windower.send_command('setkey %s down':format(jiggle))
+ coroutine.sleep(.25)
+ windower.send_command('setkey %s up':format(jiggle))
+end
+
+local function consume_item(item)
+ windower.chat.input('/item \"%s\" <me>':format(item))
+ coroutine.sleep(3.5)
+ inventory = windower.ffxi.get_items()
+end
+
+local function fetch_ingredient(ingredient)
+
+ local id, name
+ if exceptions[ingredient] then
+ id = exceptions[ingredient]
+ else
+ item = res.items:name(ingredient)
+ id, name = next(item, nil)
+ end
+ if id then
+ local contents = inventory['inventory']
+ for index = 1, inventory['max_inventory'] do
+ if appropriated[index] == nil then
+ appropriated[index] = 0
+ end
+ if (contents[index].id == id) and
+ (contents[index].count > appropriated[index]) then
+ appropriated[index] = appropriated[index] + 1
+ return id, index
+ end
+ end
+ if check_bags(id) then
+ local status = busy_wait('move', 10, 'moving %s':format(ingredient))
+ if status then
+ return status
+ else
+ return fetch_ingredient(ingredient)
+ end
+ end
+ if clusters[ingredient] then
+ local cluster = clusters[ingredient]
+ local cluster_id, cluster_index = fetch_ingredient(cluster)
+ if cluster_index then
+ conditions['sort'] = true
+ conditions['item'] = id
+ local start = os.time()
+ windower.chat.input('/item \"%s\" <me>':format(cluster))
+ local status = busy_wait('item', 10, 'using %s':format(cluster))
+ if status then
+ error(status)
+ end
+ coroutine.sleep(4 - (os.time() - start))
+ inventory = windower.ffxi.get_items()
+ return fetch_ingredient(ingredient)
+ end
+ end
+ return "Unable to locate %s":format(ingredient)
+ else
+ return "Unknown item %s":format(ingredient)
+ end
+end
+
+local function consume_food()
+ local player = windower.ffxi.get_player()
+ if S(player.buffs):contains(251) then
+ return
+ end
+ inventory = windower.ffxi.get_items()
+ local id, index = fetch_ingredient(food)
+ if index then
+ windower.chat.input('/item \"%s\" <me>':format(food))
+ coroutine.sleep(3.5)
+ else
+ warning("Unable to consume %s":format(food))
+ end
+end
+
+local function fetch_recipe(item)
+ local item = item:lower()
+ for name, recipe in pairs(recipes) do
+ if item == name:lower() then
+ return recipe
+ end
+ end
+end
+
+local function hash(crystal, item, count)
+ local c = ((crystal % 6506) % 4238) % 4096
+ local m = (c + 1) * 6 + 77
+ local b = (c + 1) * 42 + 31
+ local m2 = (8 * c + 26) + (item - 1) * (c + 35)
+ return (m * item + b + m2 * (count - 1)) % 127
+end
+
+local function build_recipe(item)
+ if windower.ffxi.get_player().status ~= 0 then
+ return "You can't craft at the moment"
+ end
+
+ local recipe = fetch_recipe(item)
+
+ if recipe then
+ inventory = windower.ffxi.get_items()
+ appropriated = {}
+ local p = packets.new('outgoing', 0x096)
+ local crystal = recipe['crystal']
+ if hqsynth then
+ crystal = hqcrystal[crystal]
+ end
+ local id, index = fetch_ingredient(crystal)
+ if not index then return id end
+ p['Crystal'] = id
+ p['Crystal Index'] = index
+ p['Ingredient count'] = #recipe['ingredients']
+ for i, ingredient in pairs(recipe['ingredients']) do
+ id, index = fetch_ingredient(ingredient)
+ if not index then return id end
+ p["Ingredient %i":format(i)] = id
+ p["Ingredient Index %i":format(i)] = index
+ end
+ p['_unknown1'] = hash(p['Crystal'], p['Ingredient 1'], p['Ingredient count'])
+ return p
+ else
+ return "No recipe for %s":format(item)
+ end
+end
+
+local function issue_synthesis(item)
+ local p = build_recipe(item)
+ if type(p) == 'string' then
+ skip_delay = true
+ conditions['sort'] = false
+ return "%s - %s":format(item, p)
+ else
+ packets.inject(p)
+ conditions['sort'] = false
+ end
+end
+
+local function repeat_synthesis()
+ windower.chat.input('/lastsynth')
+end
+
+local function put_items(bag, id)
+ local src = inventory['inventory']
+ local dst = inventory[bag]
+ local empty = {}
+ for index = 1, inventory['max_%s':format(bag)] do
+ if dst[index].count == 0 then
+ empty[index] = true
+ end
+ end
+ local idx, status = next(empty, nil)
+ for index = 1, inventory['max_inventory'] do
+ if (src[index].id == id) and idx then
+ windower.ffxi.put_item(bags[bag], index, src[index].count)
+ dst[idx].id = id
+ dst[idx].count = src[index].count
+ src[index].id = 0
+ src[index].count = 0
+ idx, status = next(empty, idx)
+ delta = true
+ end
+ end
+end
+
+local function put(args)
+ conditions['sort'] = true
+ delta = false
+ inventory = windower.ffxi.get_items()
+ if args['bag'] then
+ local bag = args['bag']
+ if not inventory['enabled_%s':format(bag)] then
+ block = false
+ return "bag %s disabled":format(bag)
+ end
+ put_items(bag, args['id'])
+ else
+ for bag, bag_id in pairs(bags) do
+ if inventory['enabled_%s':format(bag)] then
+ put_items(bag, args['id'])
+ end
+ end
+ end
+ if delta then
+ delta = false
+ busy_wait('move', 10, 'moving %s':format(args['name']))
+ end
+ conditions['sort'] = false
+ coroutine.sleep(3.5)
+ skip_delay = true
+end
+
+local function check_queue()
+ if not queue:empty() then
+ if not paused then
+ if jiggle then
+ commence_jigglin()
+ end
+ if support then
+ poke_npc()
+ end
+ if food then
+ consume_food()
+ end
+ local fn, arg = unpack(queue:pop())
+ local msg = fn(arg)
+ if msg then
+ error(msg)
+ end
+ if skip_delay then
+ coroutine.schedule(check_queue, 0)
+ skip_delay = false
+ else
+ coroutine.schedule(check_queue, delay)
+ end
+ end
+ else
+ busy = false
+ end
+end
+
+local function process_queue()
+ if not busy then
+ busy = true
+ coroutine.schedule(check_queue, 0)
+ end
+end
+
+local function handle_help()
+ windower.add_to_chat(100, help_commands)
+ windower.add_to_chat(100, help_commands_2)
+ windower.add_to_chat(100, help_notes)
+end
+
+local function handle_status()
+ notice("delay", delay)
+ notice("paused", paused)
+ notice("display", display)
+ notice("auto food", food)
+ notice("auto support", support)
+ notice("jiggle", jiggle)
+ notice("queue size", queue:length())
+ notice("hq crystal", hqsynth)
+end
+
+local function handle_delay(seconds)
+ local n = tonumber(seconds)
+ if n == nil then
+ return "Invalid delay %s":format(seconds)
+ else
+ n = math.max(17, n)
+ notice("Setting delay to %d":format(n))
+ delay = n
+ end
+end
+
+local function handle_clear()
+ notice("Clearing queue")
+ queue = Q{}
+end
+
+local function handle_pause()
+ notice("Pausing")
+ paused = true
+end
+
+local function handle_resume()
+ notice("Resuming")
+ if paused then
+ paused = false
+ busy = false
+ process_queue()
+ end
+end
+
+local function handle_jiggle(key)
+ if key then
+ notice("Setting jiggle to %s key":format(key))
+ jiggle = key
+ else
+ notice("Removing jiggle")
+ jiggle = false
+ end
+end
+
+local function handle_repeat(count)
+ local count = count or 1
+ local n = tonumber(count)
+ if n == nil then
+ return "Invalid count %s":format(count)
+ end
+ notice("Adding %d repeat commands to the queue":format(count))
+ for i = 1, count do
+ local item = {repeat_synthesis, nil}
+ queue:push(item)
+ end
+ process_queue()
+end
+
+local function handle_make(item, count)
+ local count = count or 1
+ local n = tonumber(count)
+ if n == nil then
+ return "Invalid count %s":format(count)
+ end
+ local recipe = fetch_recipe(item)
+ if not recipe then
+ return "No recipe for %s":format(item)
+ end
+ notice("Adding %d make %s commands to the queue":format(count, item))
+ for i = 1, count do
+ local item = {issue_synthesis, item}
+ queue:push(item)
+ end
+ process_queue()
+end
+
+local function handle_food(item)
+ if not item then
+ notice("Setting auto food to None")
+ food = false
+ else
+ local search = res.items:name(item)
+ local id, name = next(search, nil)
+ if id then
+ notice("Setting auto food to %s":format(name.en))
+ food = name.en
+ else
+ return "Invalid food %s":format(item)
+ end
+ end
+end
+
+local function handle_put(ingredient, bag)
+ if bag then
+ bag = bag:lower()
+ if not bags[bag] then
+ return "Unknown bag %s":format(bag)
+ end
+ end
+ local search = res.items:name(ingredient)
+ local id, name = next(search, nil)
+ if id then
+ local msg = nil
+ local args = {
+ ['id'] = id,
+ ['bag'] = bag,
+ ['name'] = name.english,
+ }
+ local item = {put, args}
+ if bag then
+ msg = "%s %s":format(ingredient, bag)
+ else
+ msg = ingredient
+ end
+ notice("Adding a put %s command to the queue":format(msg))
+ queue:push(item)
+ process_queue()
+ else
+ return "Unknown item %s":format(ingredient)
+ end
+end
+
+local function display_crafting_packet(id, data)
+ if id == 0x096 and display then
+ local p = packets.parse('outgoing', data)
+ log(p)
+ end
+end
+
+local function handle_display()
+ if display then
+ notice("Disabling display")
+ display = false
+ else
+ notice("Enabling display")
+ display = true
+ end
+end
+
+local function handle_support()
+ if support then
+ notice("Disabling support")
+ support = false
+ else
+ notice("Enabling support")
+ support = true
+ end
+end
+
+local function handle_find(query, details)
+ local query = query:lower()
+ notice("Searching for recipes containing %s":format(query))
+ for name, recipe in pairs(recipes) do
+ if string.find(name:lower(), query) then
+ notice("Found recipe - \"%s\"":format(name))
+ if details then
+ notice(" %s":format(recipe['crystal']))
+ for _, ingredient in pairs(recipe['ingredients']) do
+ notice(" %s":format(ingredient))
+ end
+ end
+ end
+ end
+end
+
+local function handle_hqsynth()
+ if hqsynth then
+ notice("Disabling HQ Crystal")
+ hqsynth = false
+ else
+ notice("Enabling HQ Crystal")
+ hqsynth = true
+ end
+end
+
+
+handlers['clear'] = handle_clear
+handlers['repeat'] = handle_repeat
+handlers['r'] = handle_repeat
+handlers['delay'] = handle_delay
+handlers['pause'] = handle_pause
+handlers['resume'] = handle_resume
+handlers['make'] = handle_make
+handlers['m'] = handle_make
+handlers['display'] = handle_display
+handlers['put'] = handle_put
+handlers['food'] = handle_food
+handlers['status'] = handle_status
+handlers['help'] = handle_help
+handlers['jiggle'] = handle_jiggle
+handlers['support'] = handle_support
+handlers['find'] = handle_find
+handlers['hqcrystal'] = handle_hqsynth
+
+local function handle_command(cmd, ...)
+ local cmd = cmd or 'help'
+ if handlers[cmd] then
+ local msg = handlers[cmd](unpack({...}))
+ if msg then
+ error(msg)
+ end
+ else
+ error("Unknown command %s":format(cmd))
+ end
+end
+
+-- This is here so if a player does a legitimate synth the result is not displayed twice, since results are only hidden on injected synthesis.
+windower.register_event('outgoing chunk', function(id, original, modified, injected, blocked)
+ if id == 0x096 and injected then
+ injected_synth = true
+ end
+end)
+
+windower.register_event('incoming chunk', function(id, original, modified, injected, blocked)
+ if id == 0x06F and injected_synth then
+ local p = packets.parse('incoming',original)
+ if p['Result'] == 0 or p['Result'] == 2 then
+ local item = res.items[p['Item']].english
+ windower.add_to_chat(121, 'You synthesized: \30\02%s\30\01.':format(item))
+ injected_synth = false
+ end
+ if p['Result'] == 1 or p['Result'] == 5 then
+ windower.add_to_chat(121,'Your synthesis has failed and your crystal is lost.')
+ for i=1, 8 do
+ if p['Lost Item '..i] ~= 0 then
+ windower.add_to_chat(121, 'You lost: \30\02%s\30\01.':format(res.items[p['Lost Item '..i]].english))
+ end
+ end
+ injected_synth = false
+ end
+ end
+end)
+
+windower.register_event('addon command', handle_command)
+windower.register_event('outgoing chunk', display_crafting_packet)
+windower.register_event('outgoing chunk', block_sort)
+windower.register_event('incoming chunk', unblock_sort)
+windower.register_event('incoming chunk', unblock_item)
+windower.register_event('incoming chunk', get_support)