summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer')
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/README12
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/boxdestroyer.lua466
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/make_messages.py77
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/settings.py190
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/messages.lua63
5 files changed, 808 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/README b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/README
new file mode 100644
index 0000000..a34f5b9
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/README
@@ -0,0 +1,12 @@
+This script is a treasure casket helper addon to be used with Windower
+for Final Fantasy XI.
+
+You can report issues, get the latest version, and view the source at:
+ https://github.com/svanheulen/boxdestroyer-windower-addon
+
+Donations are welcome in the form of Alexandrite:
+ Acacia on Odin (http://www.ffxiah.com/player/Odin/Acacia)
+
+With this addon loaded, just click on any brown treasure casket and it
+will display info about possible combinations, the best number to guess
+and the worst case chance of guessing correctly.
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/boxdestroyer.lua b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/boxdestroyer.lua
new file mode 100644
index 0000000..af70616
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/boxdestroyer.lua
@@ -0,0 +1,466 @@
+--[[
+Copyright (c) 2014, Seth VanHeulen
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--]]
+
+-- addon information
+
+_addon.name = 'boxdestroyer'
+_addon.version = '1.0.5'
+_addon.command = 'boxdestroyer'
+_addon.author = 'Seth VanHeulen (Acacia@Odin)'
+
+-- modules
+
+require('pack')
+require('tables')
+require('chat')
+
+-- load message constants
+
+require('messages')
+
+-- config
+
+config = require('config')
+defaults = {
+ HighlightResult = false,
+ HighlightColor = 36,
+}
+settings = config.load(defaults)
+-- global constants
+
+default = {
+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
+ 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
+}
+
+observed_default = {
+ ['second_even_odd'] = false,
+ ['first_even_odd'] = false,
+ ['range'] = false,
+ ['equal'] = false,
+ ['second_multiple'] = false,
+ ['first_multiple'] = false,
+ ['thief_tools_active'] = false
+}
+
+thief_tools = {[1022] = true}
+
+-- global variables
+
+box = {}
+observed = {}
+zone_id = windower.ffxi.get_info().zone
+
+-- filter helper functions
+
+function greater_less(id, greater, num)
+ local new = {}
+ for _,v in pairs(box[id]) do
+ if greater and v > num or not greater and v < num then
+ table.insert(new, v)
+ end
+ end
+ return new
+end
+
+function even_odd(id, div, rem)
+ local new = {}
+ for _,v in pairs(box[id]) do
+ if (math.floor(v / div) % 2) == rem then
+ table.insert(new, v)
+ end
+ end
+ return new
+end
+
+function equal(id, first, num)
+ local new = {}
+ for _,v in pairs(box[id]) do
+ if first and math.floor(v / 10) == num or not first and (v % 10) == num then
+ table.insert(new, v)
+ end
+ end
+ return new
+end
+
+function exclusive_mean(counts)
+ total = 0
+ for _,v in pairs(counts) do
+ total = total + v
+ end
+ weighted_mean = 0
+ for _,v in pairs(counts) do
+ weighted_mean = weighted_mean + (total - v) * v / total
+ end
+ return weighted_mean
+end
+
+function calculate_odds(id,chances)
+ local reductions = {}
+ if not observed[id].first_even_odd then
+ local counter = {0}
+ counter[0] = 0
+ for _,v in pairs(box[id]) do
+ counter[math.floor(v / 10) % 2] = counter[math.floor(v / 10) % 2] + 1
+ end
+ reductions[#reductions+1] = exclusive_mean(counter)
+ end
+ if not observed[id].second_even_odd then
+ local counter = {0}
+ counter[0] = 0
+ for _,v in pairs(box[id]) do
+ counter[v % 2] = counter[v % 2] + 1
+ end
+ reductions[#reductions+1] = exclusive_mean(counter)
+ end
+ if not observed[id].range then
+ local new = {}
+ local reduction = 0
+ for i,v in pairs(box[id]) do
+ new[i] = 0
+ for _,m in pairs(box[id]) do
+ if m-v > 16 then break end
+ new[i] = new[i] + math.max(16-math.abs(m - v),0)^2/256
+ end
+ reduction = reduction + (#box[id] - new[i])/#box[id]
+ end
+
+ reductions[#reductions+1] = reduction
+ end
+ if not observed[id].equal then
+ local counter = {0,0,0,0,0,0,0,0,0}
+ counter[0] = 0
+ local eliminated = {0,0,0,0,0,0,0,0,0}
+ eliminated[0] = 0
+ for _,v in pairs(box[id]) do
+ counter[math.floor(v / 10)] = counter[math.floor(v / 10)] + 1/2
+ counter[v % 10] = counter[v % 10] + 1/2
+ for i = 0,9 do
+ if i ~= v % 10 and i ~= math.floor(v / 10) then
+ eliminated[i] = eliminated[i] + 1
+ end
+ end
+ end
+
+ reduction = 0
+ for i,v in pairs(counter) do
+ reduction = reduction + eliminated[i] * v / #box[id]
+ end
+
+ reductions[#reductions+1] = reduction
+ end
+ if not observed[id].second_multiple then
+ local counter = {0,0,0,0,0,0,0,0,0}
+ counter[0] = 0
+ for _,v in pairs(box[id]) do
+ counter[v % 10] = counter[v % 10] + 1
+ end
+
+ local weights = {
+ counter[0] + counter[1]/2 + counter[2]/3,
+ counter[1]/2 + counter[2]/3 + counter[3]/3,
+ counter[2]/3 + counter[3]/3 + counter[4]/3,
+ counter[3]/3 + counter[4]/3 + counter[5]/3,
+ counter[4]/3 + counter[5]/3 + counter[6]/3,
+ counter[5]/3 + counter[6]/3 + counter[7]/3,
+ counter[6]/3 + counter[7]/3 + counter[8]/2,
+ counter[7]/3 + counter[8]/2 + counter[9]
+ }
+
+ local eliminated = {
+ counter[3] + counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[0] + counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[0] + counter[1] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[0] + counter[1] + counter[2] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[0] + counter[1] + counter[2] + counter[3] + counter[7] + counter[8] + counter[9],
+ counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[8] + counter[9],
+ counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[9],
+ counter[0] + counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[6]
+ }
+
+ local reduction = 0
+ for i,v in pairs(weights) do
+ reduction = reduction + eliminated[i] * v / #box[id]
+ end
+
+ reductions[#reductions + 1] = reduction
+ end
+ if not observed[id].first_multiple then
+ local counter = {0,0,0,0,0,0,0,0,0}
+ for _,v in pairs(box[id]) do
+ counter[math.floor(v / 10)] = counter[math.floor(v / 10)] + 1
+ end
+
+ local weights = {
+ counter[1] + counter[2]/2 + counter[3]/3,
+ counter[2]/2 + counter[3]/3 + counter[4]/3,
+ counter[3]/3 + counter[4]/3 + counter[5]/3,
+ counter[4]/3 + counter[5]/3 + counter[6]/3,
+ counter[5]/3 + counter[6]/3 + counter[7]/3,
+ counter[6]/3 + counter[7]/3 + counter[8]/2,
+ counter[7]/3 + counter[8]/2 + counter[9]
+ }
+
+ local eliminated = {
+ counter[4] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[1] + counter[5] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[1] + counter[2] + counter[6] + counter[7] + counter[8] + counter[9],
+ counter[1] + counter[2] + counter[3] + counter[7] + counter[8] + counter[9],
+ counter[1] + counter[2] + counter[3] + counter[4] + counter[8] + counter[9],
+ counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[9],
+ counter[1] + counter[2] + counter[3] + counter[4] + counter[5] + counter[6]
+ }
+
+ local reduction = 0
+ for i,v in pairs(weights) do
+ reduction = reduction + eliminated[i] * v / #box[id]
+ end
+
+ reductions[#reductions + 1] = reduction
+ end
+
+ local expected_examine_value = 0
+ for _,v in pairs(reductions) do
+ expected_examine_value = expected_examine_value + v/#reductions
+ end
+
+ local optimal_guess = math.ceil(#box[id] / 2)
+
+ local expected_guess_value = 2*optimal_guess - 2*optimal_guess^2 / #box[id] + 2*optimal_guess/#box[id] - 1 / #box[id]
+
+ return expected_examine_value, expected_guess_value
+end
+
+-- display helper function
+
+function display(id, chances)
+ if #box[id] == 90 then
+ windower.add_to_chat(207, 'Possible combinations: 10~99')
+ else
+ windower.add_to_chat(207, 'Possible combinations: ' .. table.concat(box[id], ' '))
+ end
+ local remaining = math.floor(#box[id] / math.pow(2, (chances - 1)))
+ if remaining == 0 then
+ remaining = 1
+ end
+
+ if chances == 1 and observed[id].equal then
+ -- The "equal" message (== "X") for X in 1..9 gives an unequal probability to the remaining options
+ -- because "XX" is twice as likely to be indicated by the "equal" message.
+ -- This is too annoying to propagate to the rest of the addon, although it should be some day.
+ local printed = false
+ for _,v in pairs(box[id]) do
+ if math.floor(v/10) == v%10 then
+ windower.add_to_chat(207, 'Best guess: %d (%d%%)':format(v, 1 / remaining * 100))
+ printed = true
+ break
+ end
+ end
+ if not printed then
+ windower.add_to_chat(207, 'Best guess: %d (%d%%)':format(box[id][math.ceil(#box[id] / 2)], 1 / remaining * 100))
+ end
+ else
+ windower.add_to_chat(207, 'best guess: %d (%d%%)':format(box[id][math.ceil(#box[id] / 2)], 1 / remaining * 100))
+ local clue_value,guess_value = calculate_odds(id,chances)
+ local result = clue_value > guess_value and remaining ~= 1 and 'examining the chest' or 'guessing ' .. '%d':format(box[id][math.ceil(#box[id] / 2)])
+ local formatted_result = settings.HighlightResult and result:color(settings.HighlightColor) or result
+ windower.add_to_chat(207, 'boxdestroyer recommends ' .. formatted_result .. '.')
+ end
+
+end
+
+-- ID obtaining helper function
+function get_id(zone_id,str)
+ return messages[zone_id] + offsets[str]
+end
+
+-- event callback functions
+
+function check_incoming_chunk(id, original, modified, injected, blocked)
+ if id == 0x0A then
+ zone_id = original:unpack('H', 49)
+ elseif messages[zone_id] then
+ if id == 0x0B then
+ box = {}
+ observed = {}
+ elseif id == 0x2A then
+ local box_id = original:unpack('I', 5)
+ local param0 = original:unpack('I', 9)
+ local param1 = original:unpack('I', 13)
+ local param2 = original:unpack('I', 17)
+ local message_id = original:unpack('H', 27) % 0x8000
+
+ if box[box_id] == nil then
+ box[box_id] = table.copy(default)
+ end
+ if observed[box_id] == nil then
+ observed[box_id] = table.copy(observed_default)
+ end
+
+ if get_id(zone_id,'greater_less') == message_id then
+ box[box_id] = greater_less(box_id, param1 == 0, param0)
+ elseif get_id(zone_id,'second_even_odd') == message_id then
+ -- tells whether the second digit is even or odd
+ box[box_id] = even_odd(box_id, 1, param0)
+ observed[box_id].second_even_odd = true
+ elseif get_id(zone_id,'first_even_odd') == message_id then
+ -- tells whether the first digit is even or odd
+ box[box_id] = even_odd(box_id, 10, param0)
+ observed[box_id].first_even_odd = true
+ elseif get_id(zone_id,'range') == message_id then
+ if observed[box_id].thief_tools_active then
+ -- Thief tools are the same as normal ranges but with larger bounds.
+ -- lower bound (param0) = solution - RANDINT(8,32)
+ -- upper bound (param1) = solution + RANDINT(8,32)
+ -- param0 + 33 > solution > param0 + 7
+ -- param1 - 7 > solution > param1 - 33
+ -- if the bound is less than 11 or greater than 98, the message changes to "greater" or "less" respectively
+ box[box_id] = greater_less(box_id, true, math.max(param1-33,param0+7) )
+ box[box_id] = greater_less(box_id, false, math.min(param0+33,param1-7) )
+ observed[box_id].thief_tools_active = false
+ else
+ -- lower bound (param0) = solution - RANDINT(5,20)
+ -- upper bound (param1) = solution + RANDINT(5,20)
+ -- param0 + 21 > solution > param0 + 4
+ -- param1 - 4 > solution > param1 - 21
+ -- if the bound is less than 11 or greater than 98, the message changes to "greater" or "less" respectively
+ box[box_id] = greater_less(box_id, true, math.max(param1-21,param0+4) )
+ box[box_id] = greater_less(box_id, false, math.min(param0+21,param1-4) )
+ observed[box_id].range = true
+ end
+ elseif get_id(zone_id,'less') == message_id then
+ -- Less is a range with 9 as the lower bound
+ if observed[box_id].thief_tools_active then
+ box[box_id] = greater_less(box_id, true, math.max(9, param0-33) )
+ box[box_id] = greater_less(box_id, false, math.min(10+33,param0-7) )
+ observed[box_id].thief_tools_active = false
+ else
+ box[box_id] = greater_less(box_id, true, math.max(9, param0-21) )
+ box[box_id] = greater_less(box_id, false, math.min(10+21,param0-4) )
+ observed[box_id].range = true
+ end
+ elseif get_id(zone_id,'greater') == message_id then
+ -- Greater is a range with 100 as the upper bound
+ if observed[box_id].thief_tools_active then
+ box[box_id] = greater_less(box_id, true, math.max(99-33,param0+7) )
+ box[box_id] = greater_less(box_id, false, math.min(100,param0+33) )
+ observed[box_id].thief_tools_active = false
+ else
+ box[box_id] = greater_less(box_id, true, math.max(99-21,param0+4) )
+ box[box_id] = greater_less(box_id, false, math.min(100,param0+21) )
+ observed[box_id].range = true
+ end
+ elseif get_id(zone_id,'equal') == message_id then
+ -- single number that is either the first or second digit of the solution
+ local new = equal(box_id, true, param0)
+ local duplicate = param0 * 10 + param0
+ for k,v in pairs(new) do
+ if v == duplicate then
+ table.remove(new, k)
+ end
+ end
+ for _,v in pairs(equal(box_id, false, param0)) do table.insert(new, v) end
+ table.sort(new)
+ box[box_id] = new
+ observed[box_id].equal = true
+ elseif get_id(zone_id,'second_multiple') == message_id then
+ -- three digit range including the second digit of the solution
+ local new = equal(box_id, false, param0)
+ for _,v in pairs(equal(box_id, false, param1)) do table.insert(new, v) end
+ for _,v in pairs(equal(box_id, false, param2)) do table.insert(new, v) end
+ table.sort(new)
+ box[box_id] = new
+ observed[box_id].second_multiple = true
+ elseif get_id(zone_id,'first_multiple') == message_id then
+ -- three digit range including the first digit of the solution
+ local new = equal(box_id, true, param0)
+ for _,v in pairs(equal(box_id, true, param1)) do table.insert(new, v) end
+ for _,v in pairs(equal(box_id, true, param2)) do table.insert(new, v) end
+ table.sort(new)
+ box[box_id] = new
+ observed[box_id].first_multiple = true
+ elseif get_id(zone_id,'success') == message_id or get_id(zone_id,'failure') == message_id then
+ box[box_id] = nil
+ elseif get_id(zone_id,'tool_failure') == message_id then
+ observed[box_id].thief_tools_active = false
+ end
+ elseif id == 0x34 then
+ local box_id = original:unpack('I', 5)
+ if windower.ffxi.get_mob_by_id(box_id).name == 'Treasure Casket' then
+ local chances = original:byte(9)
+ if box[box_id] == nil then
+ box[box_id] = table.copy(default)
+ observed[box_id] = table.copy(observed_default)
+ end
+ if chances > 0 and chances < 7 then
+ display(box_id, chances)
+ end
+ end
+ elseif id == 0x5B then
+ box[original:unpack('I', 17)] = nil
+ observed[original:unpack('I', 17)] = nil
+ end
+ end
+end
+
+function watch_for_keys(id, original, modified, injected, blocked)
+ if blocked then
+ elseif (id == 0x036 or id == 0x037) and
+ windower.ffxi.get_mob_by_id(modified:unpack('I',0x05)).name == 'Treasure Casket' and
+ (windower.ffxi.get_player().main_job == 'THF' or windower.ffxi.get_player().sub_job == 'THF') then
+
+ local box_id = modified:unpack('I',0x05)
+ if not box[box_id] then
+ box[box_id] = table.copy(default)
+ observed[box_id] = table.copy(observed_default)
+ end
+
+ if id == 0x037 and thief_tools[windower.ffxi.get_items(modified:byte(0x11))[modified:byte(0xF)].id] then
+ observed[box_id].thief_tools_active = true
+ elseif id == 0x036 then
+ for i = 1,9 do
+ if thief_tools[windower.ffxi.get_items(0)[modified:byte(0x30+i)].id] then
+ observed[box_id].thief_tools_active = true
+ break
+ end
+ end
+ end
+ end
+end
+
+-- register event callbacks
+
+windower.register_event('incoming chunk', check_incoming_chunk)
+
+windower.register_event('outgoing chunk', watch_for_keys)
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/make_messages.py b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/make_messages.py
new file mode 100644
index 0000000..932ea3b
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/make_messages.py
@@ -0,0 +1,77 @@
+# Python 3
+import array
+import os
+import struct
+import winreg
+
+from settings import search, zones
+
+
+def find_dat(dat_id):
+ key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\WOW6432Node\\PlayOnlineUS\\InstallFolder')
+ ffxi_path = winreg.QueryValueEx(key, '0001')[0]
+ key.Close()
+ for i in range(1, 10):
+ vtable = None
+ if i == 1:
+ vtable = open(os.path.join(ffxi_path, 'VTABLE.DAT'), 'rb')
+ else:
+ vtable = open(os.path.join(ffxi_path, 'ROM{}'.format(i), 'VTABLE{}.DAT'.format(i)), 'rb')
+ vtable.seek(dat_id)
+ temp = vtable.read(1)[0]
+ vtable.close()
+ if temp != i:
+ continue
+ ftable = None
+ if i == 1:
+ ftable = open(os.path.join(ffxi_path, 'FTABLE.DAT'), 'rb')
+ else:
+ ftable = open(os.path.join(ffxi_path, 'ROM{}'.format(i), 'FTABLE{}.DAT'.format(i)), 'rb')
+ ftable.seek(dat_id * 2)
+ path = struct.unpack('H', ftable.read(2))[0]
+ ftable.close()
+ if i == 1:
+ return os.path.join(ffxi_path, 'ROM', '{}'.format(path >> 7), '{}.DAT'.format(path & 0x7f))
+ else:
+ return os.path.join(ffxi_path, 'ROM{}'.format(i), '{}'.format(path >> 7), '{}.DAT'.format(path & 0x7f))
+ return None
+
+def decipher_dialog(dat_file):
+ dat = open(dat_file, 'rb')
+ dat_size, first_entry = struct.unpack('II', dat.read(8))
+ dat_size -= 0x10000000
+ first_entry ^= 0x80808080
+ dat.seek(4)
+ data = bytearray(dat.read())
+ dat.close()
+ for i in range(len(data)):
+ data[i] ^= 0x80
+ offsets = array.array('I', data[:first_entry])
+ offsets.append(dat_size)
+ for i in range(len(offsets)):
+ offsets[i] -= first_entry
+ return offsets, bytes(data[first_entry:])
+
+def search_dialog(zones, search):
+ messages = {}
+ for zone_id, dat_id in zones.items():
+ offsets, data = decipher_dialog(find_dat(dat_id))
+ for i in range(len(offsets) - 1):
+ message = data[offsets[i]:offsets[i+1]]
+ if message == search:
+ messages[zone_id] = str(i)
+ return messages
+
+def write_lua(messages):
+ o = open('messages.lua', 'w')
+ print('messages = { -- These dialogue IDs match "You were unable to enter a combination" for the associated zone IDs', file=o)
+ zone_ids = list(messages.keys())
+ zone_ids.sort()
+ for zone_id in zone_ids:
+ line = messages[zone_id]+','
+ print(" [{}] = {}".format(zone_id, line), file=o)
+ print('}',file=o)
+ print('offsets = {greater_less=1, failure=2, success=4, second_even_odd=5, first_even_odd=6, range=7, less=8, greater=9, equal=10, second_multiple=11, first_multiple=12, tool_failure=13}',file=o)
+ o.close()
+
+write_lua(search_dialog(zones, search))
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/settings.py b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/settings.py
new file mode 100644
index 0000000..ccc6b1b
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/make_messages/settings.py
@@ -0,0 +1,190 @@
+search = b'You were unable to enter a combination.\x7f1\x00\x07'
+
+zones = {
+ #1: 6421, # phanauet channel
+ #2: 6422, # carpenters' landing
+ #3: 6423, # manaclipper
+ #4: 6424, # bibiki bay
+ #11: 6431, # oldton movalpolos
+ #15: 6435, # abyssea - konschtat
+ #24: 6444, # lufaise meadows
+ #25: 6445, # misareaux coast
+ #26: 6446, # tavnazian safehold
+ #27: 6447, # phomiuna aquaducts
+ #33: 6453, # al'taieu
+ #39: 6459, # dynamis - valkurm
+ #40: 6460, # dynamis - buburimu
+ #41: 6461, # dynamis - qufim
+ #42: 6462, # dynamis - tavnazia
+ #43: 6463, # diorama abdhaljs-ghelsba
+ #44: 6464, # abdhaljs isle-purgonorgo
+ #45: 6465, # abyssea - tahrongi
+ #46: 6466, # open sea route to al zahbi
+ #47: 6467, # open sea route to mhaura
+ #48: 6468, # al zahbi
+ #50: 6470, # aht urhgan whitegate
+ #51: 6471, # wajaom woodlands
+ #52: 6472, # bhaflau thickets
+ #53: 6473, # nashmau
+ #54: 6474, # arrapago reef
+ #55: 6475, # ilrusi atoll
+ #56: 6476, # periqia
+ #57: 6477, # talacca cove
+ #58: 6478, # silver sea route to nashmau
+ #59: 6479, # silver sea route to al zahbi
+ #60: 6480, # the ashu talif
+ #61: 6481, # mount zhayolm
+ #65: 6485, # mamook
+ #66: 6486, # mamool ja training grounds
+ #67: 6487, # jade sepulcher
+ #68: 6488, # aydeewa subterrane
+ #69: 6489, # leujaoam sanctum
+ #79: 6499, # caedarva mire
+ #81: 6501, # east ronfaure [s]
+ #82: 6502, # jugner forest [s]
+ #83: 6503, # vunkerl inlet [s]
+ #84: 6504, # batallia downs [s]
+ #85: 6505, # la vaule [s]
+ #86: 6506, # everbloom hollow
+ #87: 6507, # bastok markets [s]
+ #88: 6508, # north gustaberg [s]
+ #89: 6509, # grauberg [s]
+ #90: 6510, # pashhow marshlands [s]
+ #91: 6511, # rolanberry fields [s]
+ #93: 6513, # ruhotz silvermines
+ #94: 6514, # windurst waters [s]
+ #95: 6515, # west sarutabaruta [a]
+ #96: 6516, # fort karugo-narugo [s]
+ #99: 6519, # castle oztroja [s]
+ 100: 6520, # west ronfaure
+ 101: 6521, # east ronfaure
+ 102: 6522, # la theine plateau
+ 103: 6523, # valkurm dunes
+ 104: 6524, # jugner forest
+ 105: 6525, # batallia downs
+ 106: 6526, # north gustaberg
+ 107: 6527, # south gustaberg
+ 108: 6528, # konschtat highlands
+ 109: 6529, # pashhow marshlands
+ 110: 6530, # rolanberry fields
+ 111: 6531, # beaucedine glacier
+ 112: 6532, # xarcabard
+ 113: 6533, # cape teriggan
+ 114: 6534, # eastern altepa desert
+ 115: 6535, # west sarutabaruta
+ 116: 6536, # east sarutabaruta
+ 117: 6537, # tahrongi canyon
+ 118: 6538, # buburimu peninsula
+ 119: 6539, # meriphataud mountains
+ 120: 6540, # sauromugue champaign
+ 121: 6541, # the sanctuary of zi'tah
+ 122: 6542, # ro'maeve
+ 123: 6543, # yuhtunga jungle
+ 124: 6544, # yhoator jungle
+ 125: 6545, # western altepa desert
+ 126: 6546, # qufim island
+ 127: 6547, # behemoth's dominion
+ 128: 6548, # valley of sorrows
+ 130: 6550, # ru'aun gardens
+ #132: 6552, # abyssea - la theine
+ #134: 6554, # dynamis - beaucedine
+ #136: 6556, # beaucedine glacier [s]
+ #139: 6559, # horlais peak
+ #140: 6560, # ghelsba outpost
+ #141: 6561, # fort ghelsba
+ #142: 6562, # yughott grotto
+ #143: 6563, # palborough mines
+ #145: 6565, # giddeus
+ #148: 6568, # qulun dome
+ #149: 6569, # davoi
+ #151: 6571, # castle oztroja
+ 153: 6573, # the boyahda tree
+ #154: 6574, # dragon's aery
+ 157: 6577, # middle delkfutt's tower
+ 158: 6578, # upper delkfutt's tower
+ 159: 6579, # temple of uggalepih
+ 160: 6580, # den of rancor
+ 166: 6586, # ranguemont pass
+ 167: 6587, # bostaunieux oubliette
+ 169: 6589, # torimarai canal
+ 172: 6592, # zeruhn mines
+ 173: 6593, # korroloka tunnel
+ 174: 6594, # kuftal tunnel
+ 176: 6596, # sea serpent grotto
+ 177: 6597, # ve'lugannon palace
+ 178: 6598, # the shrine of ru'avitau
+ 184: 6604, # lower delkfutt's tower
+ #186: 6606, # dynamis - bastok
+ #187: 6607, # dynamis - windurst
+ 190: 6610, # king ranpere's tomb
+ 191: 6611, # dangruf wadi
+ 192: 6612, # inner horutoto ruins
+ 193: 6613, # ordelle's caves
+ 194: 6614, # outer horutoto ruins
+ 195: 6615, # eldieme necropolis
+ 196: 6616, # gusgan mines
+ 197: 6617, # crawler's nest
+ 198: 6618, # maze of shakrami
+ 200: 6620, # garlaige citadel
+ 204: 6624, # fei'yin
+ 205: 6625, # ifrit's cauldron
+ 208: 6628, # quicksand caves
+ 212: 6632, # gustav tunnel
+ 213: 6633, # labyrinth of onzozo
+ #216: 6636, # abyssea - misareaux
+ #217: 6637, # abyssea - vunkerl
+ #218: 6638, # abyssea - altepa
+ #220: 6640, # ship bound for selbina
+ #221: 6641, # ship bound for mhaura
+ #222: 6642, # provenance
+ #227: 6647, # ship bound for selbina
+ #228: 6648, # ship bound for mhaura
+ #231: 6651, # northern san d'oria
+ #232: 6652, # port san d'oria
+ #234: 6654, # bastok mines
+ #235: 6655, # bastok markets
+ #236: 6656, # port bastok
+ #237: 6657, # metalworks
+ #238: 6658, # windurst waters
+ #239: 6659, # windurst walls
+ #240: 6660, # port windurst
+ #241: 6661, # windurst woods
+ #242: 6662, # heavens tower
+ #245: 6665, # lower jeuno
+ #246: 6666, # port jeuno
+ #247: 6667, # rabao
+ #248: 6668, # selbina
+ #249: 6669, # mhaura
+ #250: 6670, # kazham
+ #251: 6671, # hall of the gods
+ #252: 6672, # norg
+ #253: 6473, # abyssea - uleguerand
+ #254: 6674, # abyssea - grauberg
+ #256: 85591, # western adoulin
+ #257: 85592, # eastern adoulin
+ #258: 85593, # rala waterways
+ #259: 85594, # rala waterways [u]
+ #260: 85595, # yahse hunting grounds
+ #261: 85596, # ceizak battlegrounds
+ #262: 85597, # foret de hennetiel
+ #263: 85598, # yorcia weald
+ #264: 85599, # yorcia weald [u]
+ #265: 85600, # morimar basalt fields
+ #266: 85601, # marjami ravine
+ #267: 85602, # kamihr drifts
+ #268: 85603, # sih gates
+ #269: 85604, # moh gates
+ #270: 85605, # cirdas caverns
+ #271: 85606, # cirdas caverns [u]
+ #272: 85607, # dho gates
+ #273: 85608, # woh gates
+ #274: 85609, # outer ra'kaznar
+ #275: 85610, # outer ra'kaznar [u]
+ #276: 85611, # ra'kaznar inner court
+ #277: 85612, # ra'kaznar turris
+ #280: 85615, # mog garden
+ #281: 85616, # leafallia
+ #288: 85623, # escha - zi'tah
+ #289: 85624, # escha - ru'aun
+ #291: 85626, # reisenjima
+}
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/messages.lua b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/messages.lua
new file mode 100644
index 0000000..6624a95
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/boxdestroyer/messages.lua
@@ -0,0 +1,63 @@
+messages = { -- These dialogue IDs match "You were unable to enter a combination" for the associated zone IDs
+ [100] = 8078,
+ [101] = 7509,
+ [102] = 7929,
+ [103] = 8104,
+ [104] = 8669,
+ [105] = 7720,
+ [106] = 8094,
+ [107] = 7555,
+ [108] = 7624,
+ [109] = 8487,
+ [110] = 7611,
+ [111] = 8588,
+ [112] = 8190,
+ [113] = 7932,
+ [114] = 7789,
+ [115] = 7885,
+ [116] = 7581,
+ [117] = 7588,
+ [118] = 8130,
+ [119] = 8367,
+ [120] = 7527,
+ [121] = 8107,
+ [122] = 7438,
+ [123] = 7890,
+ [124] = 7841,
+ [125] = 7651,
+ [126] = 8074,
+ [127] = 7355,
+ [128] = 7510,
+ [130] = 7574,
+ [153] = 11400,
+ [157] = 7380,
+ [158] = 7386,
+ [159] = 8449,
+ [160] = 7413,
+ [166] = 10582,
+ [167] = 10596,
+ [169] = 7543,
+ [172] = 7416,
+ [173] = 10521,
+ [174] = 11399,
+ [176] = 7608,
+ [177] = 11223,
+ [178] = 11403,
+ [184] = 8633,
+ [190] = 8257,
+ [191] = 8377,
+ [192] = 7413,
+ [193] = 8389,
+ [194] = 8269,
+ [195] = 7600,
+ [196] = 8309,
+ [197] = 7354,
+ [198] = 8275,
+ [200] = 7531,
+ [204] = 7519,
+ [205] = 11486,
+ [208] = 8288,
+ [212] = 10642,
+ [213] = 10452,
+}
+offsets = {greater_less=1, failure=2, success=4, second_even_odd=5, first_even_odd=6, range=7, less=8, greater=9, equal=10, second_multiple=11, first_multiple=12, tool_failure=13}