diff options
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/temps')
3 files changed, 424 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/temps/ReadMe.md b/Data/BuiltIn/Libraries/lua-addons/addons/temps/ReadMe.md new file mode 100644 index 0000000..9443371 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/temps/ReadMe.md @@ -0,0 +1,27 @@ +**Author:** Snaps<br> +**Version:** 1.0<br> +**Date:** April 19th, 2017<br> + +# temps # + +* Makes buying temporary items and key items from escha npcs much easier. + +#### Commands: #### +1. help - Shows a menu of commands in game +2. buy [override] - Buys all temporary items in an escha zone. +- buy - Buys all temporary items. +- buy radialens - Buys all temporary items and also attempts to buy a radialens, even if it is on your blacklist or if you already have one. +3. blacklist [add|a|remove|r] [item] - Add/Remove items from your blacklist. +- blacklist add radialens - Adds radialens to your blacklist. +- blacklist a "lucid potion i" - Adds Lucid Potion I to your blacklist. +- blacklist remove radialens - Removes radialens from your blacklist. +- blacklist r "lucid potion i" - Removes Lucid Potion I from your blacklist. +4. turbo - Toggle the turbo feature. +- turbo - Enables or disables the turbo feature. + +#### Notes: #### + +* You must be near an escha npc that sells temporary items. +* A buy radialens command will refresh your Radialens duration in the event that you already have one. +* The blacklist comes preloaded with Mollifier, Primeval Brew, and Radialens. + diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/temps/items.lua b/Data/BuiltIn/Libraries/lua-addons/addons/temps/items.lua new file mode 100644 index 0000000..06175f3 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/temps/items.lua @@ -0,0 +1,52 @@ +return { + [2894] = {name="Tribulens", option=4, key_item=true, offset=0}, + [3031] = {name="Radialens", option=2308, key_item=true, offset=9}, + [3032] = {name="Mollifier", option=2564, key_item=true, offset=10}, + [4182] = {name="Instant Reraise", option=7, key_item=false, offset=4}, + [4202] = {name="Daedalus Wing", option=263, key_item=false, offset=26}, + [4206] = {name="Catholicon", option=519, key_item=false, offset=14}, + [4254] = {name="Megalixir", option=775, key_item=false, offset=12}, + [5385] = {name="Barbarian's Drink", option=1031, key_item=false, offset=21}, + [5386] = {name="Fighter's Drink", option=1287, key_item=false, offset=6}, + [5387] = {name="Oracle's Drink", option=1543, key_item=false, offset=22}, + [5388] = {name="Assassin's Drink", option=1799, key_item=false, offset=5}, + [5389] = {name="Spy's Drink", option=2055, key_item=false, offset=8}, + [5390] = {name="Braver's Drink", option=2311, key_item=false, offset=15}, + [5391] = {name="Soldier's Drink", option=2567, key_item=false, offset=17}, + [5392] = {name="Champion's Drink", option=2823, key_item=false, offset=9}, + [5393] = {name="Monarch's Drink", option=3079, key_item=false, offset=20}, + [5394] = {name="Gnostic's Drink", option=3335, key_item=false, offset=7}, + [5395] = {name="Cleric's Drink", option=3591, key_item=false, offset=30}, + [5397] = {name="Sprinter's Drink", option=3847, key_item=false, offset=16}, + [5824] = {name="Lucid Potion I", option=4103, key_item=false, offset=0}, + [5825] = {name="Lucid Potion II", option=4359, key_item=false, offset=10}, + [5826] = {name="Lucid Potion III", option=4615, key_item=false, offset=23}, + [5827] = {name="Lucid Ether I", option=4871, key_item=false, offset=1}, + [5828] = {name="Lucid Ether II", option=5127, key_item=false, offset=11}, + [5829] = {name="Lucid Ether III", option=5383, key_item=false, offset=24}, + [5830] = {name="Lucid Elixir I", option=5639, key_item=false, offset=25}, + [5834] = {name="Lucid Wings I", option=5895, key_item=false, offset=13}, + [5835] = {name="Healing Salve I", option=6151, key_item=false, offset=2}, + [5836] = {name="Healing Salve II", option=6407, key_item=false, offset=18}, + [5837] = {name="Clear Salve I", option=6663, key_item=false, offset=3}, + [5838] = {name="Clear Salve II", option=6919, key_item=false, offset=19}, + [6399] = {name="Savior's Tonic", option=7175, key_item=false, offset=27}, + [6400] = {name="Mirror's Tonic", option=7431, key_item=false, offset=28}, + [6401] = {name="Moneta's Tonic", option=7687, key_item=false, offset=29}, + [6402] = {name="Steadfast Tonic", option=7943, key_item=false, offset=31}, + [5322] = {name="Healing Powder", option=8199, key_item=false, offset=32}, + [4255] = {name="Mana Powder", option=8455, key_item=false, offset=33}, + [4208] = {name="Catholicon +1", option=8711, key_item=false, offset=34}, + [6474] = {name="Poison Buffer", option=8967, key_item=false, offset=35}, + [5832] = {name="Healing Mist", option=9223, key_item=false, offset=36}, + [5833] = {name="Mana Mist", option=9479, key_item=false, offset=37}, + [5439] = {name="Vicar's Drink", option=9735, key_item=false, offset=38}, + [5831] = {name="Lucid Elixir II", option=9991, key_item=false, offset=39}, + [5436] = {name="Dusty Reraise", option=10247, key_item=false, offset=40}, + [5853] = {name="Primeval Brew", option=10503, key_item=false, offset=41}, + [6475] = {name="Lucid Wings II", option=10759, key_item=false, offset=42}, + [6473] = {name="Super Revitalizer", option=11015, key_item=false, offset=43}, + [6476] = {name="Virus Buffer", option=11271, key_item=false, offset=44}, + [6477] = {name="Charm Buffer", option=11527, key_item=false, offset=45}, + [6478] = {name="Curse Buffer", option=11783, key_item=false, offset=46}, +} diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/temps/temps.lua b/Data/BuiltIn/Libraries/lua-addons/addons/temps/temps.lua new file mode 100644 index 0000000..1ec0f89 --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-addons/addons/temps/temps.lua @@ -0,0 +1,345 @@ +--[[ +temps v1.0 + +Copyright © 2017, Mojo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of temps nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Mojo BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +]] + +_addon.name = 'temps' +_addon.author = 'Mojo' +_addon.version = '1.0' +_addon.command = 'temps' + +require('chat') +require('logger') +require('pack') + +config = require('config') +items = require('items') +packets = require('packets') + +local help_text = [[ +temps - Command List: +1. help - Displays this help menu. +2. buy - Buys all temporary items in an escha zone. +* buy - Buys all temporary items. +* buy radialens - Buys all temporary items and also + attempts to buy a radialens, even if it is on your + blacklist or if you already have one. +3. blacklist - Add(a)/Remove(r) items from your blacklist. +* blacklist add radialens - Adds radialens to your + blacklist. +* blacklist a radialens - Adds radialens to your blacklist. +* blacklist remove radialens - Removes radialens from + your blacklist. +* blacklist r radialens - Removes radialens from your + blacklist. +4. turbo - Toggle the turbo feature. +* turbo - Enables or disables the turbo feature. + +Notes: + You must be near an escha NPC that sells temporary + items. A buy radialens command will refresh your + radialens duration in the event that you already have + one. +]] + +local defaults = { + turbo = false, + blacklist = S{ + 'mollifier', + 'primeval brew', + 'radialens', + } +} + +local settings = config.load('settings.xml', defaults) +local handlers = {} +local state = 'idle' +local zone = nil +local force = nil +local silt = 0 +local key_ids = {} +local key_items = 0 +local outstanding = 0 +local inflight = {} + +local temp_items = { + [1] = 0, + [2] = 0, +} + +local conditions = { + temps = false, + busy = false, +} + +local zones = { + [288] = {npc = "Affi", menu = 9701}, + [289] = {npc = "Dremi", menu = 9701}, + [291] = {npc = "Shiftrix", menu = 9701}, +} + +local function busy_wait(block, timeout, message) + local start = os.time() + while conditions[block] and ((os.time() - start) < timeout) do + coroutine.sleep(.1) + end + if os.time() - start >= timeout then + conditions[block] = false + return "timed out - %s":format(message) + end +end + +local function validate(constrain) + zone = windower.ffxi.get_info()['zone'] + if zones[zone] then + local npc = windower.ffxi.get_mob_by_name(zones[zone].npc) + if npc and ((math.sqrt(npc.distance) < 6) or (not constrain)) then + return npc + else + error("Too far from %s.":format(zones[zone].npc)) + end + else + error("Not in an Escha zone.") + end +end + +local function has_item(item) + if item.key_item then + return (math.floor(key_items/math.pow(2, item.offset)) % 2) == 1 + else + return (math.floor(temp_items[math.floor(item.offset/32) + 1]/math.pow(2, item.offset % 32)) % 2) == 1 + end +end + +local function force_purchase(item) + return (item.name == force) +end + +local function ignore_item(item) + return settings.blacklist:contains(item.name:lower()) +end + +local function purchase_items() + local npc = validate() + local count = 0 + for item_id, item in pairs(items) do + if force_purchase(item) or (not has_item(item)) then + if ignore_item(item) and (not force_purchase(item)) then + windower.add_to_chat(100, string.format("Ignoring \30\2%s\30\43.", item.name)) + else + local p = packets.new('outgoing', 0x5b, { + ["Target"] = npc.id, + ["Option Index"] = item.option, + ["Target Index"] = npc.index, + ["Automated Message"] = true, + ["Zone"] = zone, + ["Menu ID"] = zones[zone].menu, + }) + windower.add_to_chat(100, string.format("Purchasing \30\2%s\30\43.", item.name)) + packets.inject(p) + state = 'purchase' + inflight[item_id] = true + count = count + 1 + if not settings.turbo then + coroutine.sleep(1) + end + end + end + end + if count == 0 then + windower.add_to_chat(100, "No temporary items to buy.") + else + outstanding = outstanding + count + if outstanding == 0 then + windower.add_to_chat(100, "Finished purchasing all temporary items.") + end + end +end + +local function exit_menu(id, data, modified, injected, blocked) + if (id == 0x5b) and ((state == 'init') or (state == 'purchase')) then + local p = packets.parse('outgoing', data) + local npc = validate() + if (p['Target'] == npc.id) and not p['Automated Message'] then + outstanding = 0 + state = 'idle' + end + end +end + +local function observe_temps(id, data, modified, injected, blocked) + if (id == 0x5c) and conditions['temps'] then + local p = packets.parse('incoming', data) + temp_items[1] = p['Menu Parameters']:unpack('I', 1) + temp_items[2] = p['Menu Parameters']:unpack('I', 5) + conditions['temps'] = false + end +end + +local function process_dialogue_event(id, data, modified, injected, blocked) + if (id == 0x34) and (state == 'init') then + local p = packets.parse('incoming', data) + state = 'purchase' + silt = p['Menu Parameters']:unpack('I', 5) + key_items = p['Menu Parameters']:unpack('I', 9) + conditions['temps'] = true + busy_wait('temps', 10, 'observe temp items') + purchase_items() + end +end + +local function check_inflight(iid) + if (inflight[iid]) then + inflight[iid] = nil + outstanding = outstanding - 1 + if outstanding == 0 then + windower.add_to_chat(100, "Finished purchasing all temporary items.") + end + end +end + +local function receive_item(id, data, modified, injected, blocked) + if (id == 0x20) and (state == 'purchase') then + local p = packets.parse('incoming', data) + check_inflight(p['Item']) + end +end + +local function obtained_key_item(p, item_id) + if (math.floor(item_id/512) == p['Type']) then + local bit = item_id % 512 + local n = bit % 8 + local character = math.floor(bit/8) + 1 + return ((math.floor(p['Key item available']:byte(character)/math.pow(2, n)) % 2) == 1) + else + return false + end +end + +local function receive_key_items(id, data, modified, injected, blocked) + if (id == 0x55) and (state == 'purchase') then + local p = packets.parse('incoming', data) + for k, v in pairs(inflight) do + if obtained_key_item(p, k) then + check_inflight(k) + end + end + end +end + +local function validate_item(item) + for k, v in pairs(items) do + if item:lower() == v.name:lower() then + return v.name + end + end + error("%s not found in items list.":format(item)) +end + +local function start(override) + if not (state == 'idle') then + return error("Addon is busy.") + elseif override then + force = validate_item(override) + if not force then + return + else + notice("Override provided for %s.":format(force)) + end + else + force = nil + end + local npc = validate(true) + if npc then + local p = packets.new('outgoing', 0x01a, { + ["Target"] = npc.id, + ["Target Index"] = npc.index, + }) + packets.inject(p) + state = 'init' + else + state = 'idle' + end +end + +local function help() + windower.add_to_chat(100, help_text) +end + +local function blacklist(cmd, name) + if not cmd then + return error("No blacklist command provided.") + elseif not S{'add', 'a', 'remove', 'r'}:contains(cmd) then + return error("Unknown blacklist command %s.":format(cmd)) + elseif not name then + return error("No blacklist command parameter provided.") + end + local item = validate_item(name) + if not item then + return + elseif S{'add', 'a'}:contains(cmd) then + notice("Adding %s to your blacklist.":format(item)) + settings.blacklist:add(item:lower()) + else + notice("Removing %s from your blacklist.":format(item)) + settings.blacklist:remove(item:lower()) + end + settings:save() +end + +local function turbo() + if settings.turbo then + notice("Disabling turbo.") + settings.turbo = false + else + notice("Enabling turbo.") + settings.turbo = true + end + settings:save() +end + +local function handle_command(cmd, ...) + local cmd = cmd and cmd:lower() or 'help' + if handlers[cmd] then + handlers[cmd](...) + else + error("Unknown command %s.":format(cmd)) + end +end + +handlers['buy'] = start +handlers['help'] = help +handlers['blacklist'] = blacklist +handlers['turbo'] = turbo + +windower.register_event('incoming chunk', process_dialogue_event) +windower.register_event('incoming chunk', receive_item) +windower.register_event('incoming chunk', receive_key_items) +windower.register_event('incoming chunk', observe_temps) +windower.register_event('outgoing chunk', exit_menu) +windower.register_event('addon command', handle_command) |