summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-addons/addons/SpellBook')
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/SpellBook.lua447
-rw-r--r--Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/readme.md37
2 files changed, 484 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/SpellBook.lua b/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/SpellBook.lua
new file mode 100644
index 0000000..311f287
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/SpellBook.lua
@@ -0,0 +1,447 @@
+_addon.name = 'SpellBook'
+_addon.author = 'SigilBaram'
+_addon.version = '1.0.1'
+_addon.commands = {'spellbook','spbk'}
+
+require('tables')
+res = require('resources')
+
+spell_types = {
+ whitemagic = { type = 'WhiteMagic', readable = 'White Magic spells' },
+ blackmagic = { type = 'BlackMagic', readable = 'Black Magic spells' },
+ songs = { type = 'BardSong', readable = 'Bard songs' },
+ ninjutsu = { type = 'Ninjutsu', readable = 'Ninjutsu' },
+ summoning = { type = 'SummonerPact', readable = 'Summoning spells' },
+ bluemagic = { type = 'BlueMagic', readable = 'Blue Magic spells' },
+ geomancy = { type = 'Geomancy', readable = 'Geomancy spells' },
+ trusts = { type = 'Trust', readable = 'Trusts'},
+ all = { type = 'all', readable = 'spells of all types'}
+}
+
+windower.register_event('addon command', function (command, ...)
+ local args = L{...}
+ local jobs = build_job_list()
+
+ command = command and command:lower() or 'current'
+
+ if command == 'help' then
+ display_help()
+ elseif command == 'current' then
+ local player = windower.ffxi.get_player()
+ spells_by_current(player)
+ elseif command == 'main' then
+ local player = windower.ffxi.get_player()
+ local level = player.main_job_level
+ local job_points = player.job_points[player.main_job:lower()].jp_spent
+ if job_points > 99 then
+ level = job_points
+ end
+ level = args[1] or level
+ if level == 'all' then
+ level = 1500
+ end
+ level = tonumber(level)
+ if level then
+ spells_by_job(player.main_job_id, level)
+ else
+ invalid_input()
+ end
+ elseif command == 'sub' then
+ local player = windower.ffxi.get_player()
+ if not player.sub_job then
+ windower.add_to_chat(7, "You don't have a subjob equipped.")
+ return
+ end
+ local level = args[1] or player.sub_job_level
+ if level == 'all' then
+ level = 1500
+ end
+ level = tonumber(level)
+ if level then
+ spells_by_job(player.sub_job_id, level)
+ else
+ invalid_input()
+ end
+ elseif spell_types[command] then
+ if args[1] == 'all' then
+ local player = windower.ffxi.get_player()
+ spells_by_type(player, spell_types[command], false)
+ elseif args[1] == nil then
+ local player = windower.ffxi.get_player()
+ spells_by_type(player, spell_types[command], true)
+ else
+ invalid_input()
+ end
+ elseif jobs[command] then
+ local job = jobs[command]
+ local player = windower.ffxi.get_player()
+ local level = args[1] or player.jobs[res.jobs[job].english_short]
+ if level == 'all' then
+ level = 1500
+ end
+ level = tonumber(level)
+ if level then
+ spells_by_job(job, level)
+ else
+ invalid_input()
+ end
+ else
+ invalid_input()
+ end
+end)
+
+--------------------------------------------------------------------------------
+--Name: build_job_list
+--------------------------------------------------------------------------------
+--Returns:
+---- (table) list of jobs with short name as the key and id as the value
+--------------------------------------------------------------------------------
+function build_job_list()
+ local jobs = {}
+ for id,val in pairs(res.jobs) do
+ jobs[val.english_short:lower()] = id
+ end
+ return jobs
+end
+
+--------------------------------------------------------------------------------
+--Name: invalid_input
+---- Display an error message for invalid input.
+--------------------------------------------------------------------------------
+function invalid_input()
+ windower.add_to_chat(7, 'Invalid input. See //spbk help.')
+end
+
+--------------------------------------------------------------------------------
+--Name: display_help
+-- Display help text for the addon.
+--------------------------------------------------------------------------------
+function display_help()
+ windower.add_to_chat(7, _addon.name .. ' version ' .. _addon.version)
+ windower.add_to_chat(7, 'Spent jp can be specified for spells learned from Gifts by entering a value of 100-1500.')
+ windower.add_to_chat(7, 'Spells are never given as Gifts for less than 100 jp and values under 100 are treated as level.')
+ windower.add_to_chat(7, '//spbk help -- Show this help text.')
+ windower.add_to_chat(7, '//spbk [current] -- Show learnable spells based on current main and sub job and level/jp.')
+ windower.add_to_chat(7, '//spbk <main|sub> [<level|spent jp|all>] -- Show missing spells for current main or sub job. Defaults to the job\'s current level/jp.')
+ windower.add_to_chat(7, '//spbk <job> [<level|spent jp|all>] -- Show missings spells for specified job and level. Defaults to the job\'s level/jp.')
+ windower.add_to_chat(7, '//spbk <category> [all] -- Show learnable spells by category. Limited to spells which are learnable, unless all is added after the category.')
+ windower.add_to_chat(7, 'Categories: whitemagic, blackmagic, songs, ninjustu, summoning, bluemagic, geomancy, trusts, all (Trusts are not included in all)')
+end
+
+--------------------------------------------------------------------------------
+--Name: is_learnable
+--Args:
+---- player (table): player object from windower.ffxi.get_player()
+---- spell (table): a spell from resources.spells
+--------------------------------------------------------------------------------
+--Returns:
+---- (bool) true if player has a job that is high enough level to learn spell
+--------------------------------------------------------------------------------
+function is_learnable(player, spell)
+ local player_levels = player.jobs
+ for job,level in pairs(spell.levels) do
+ if player_levels[res.jobs[job].english_short] >= level then
+ return true
+ end
+ end
+ return false
+end
+
+--------------------------------------------------------------------------------
+--Name: format_spell
+-- Formats a spell as the spell's name followed by a list of jobs and levels
+-- which would qualify to learn that spell.
+--Args:
+---- spell (table): a spell from resources.spells
+--------------------------------------------------------------------------------
+--Returns:
+---- (string) the formatted string
+--------------------------------------------------------------------------------
+function format_spell(spell)
+ local format
+
+ if spell.type ~= 'Trust' then
+ local jobs = T{}
+ local levels = T{}
+ for job_id,_ in pairs(spell.levels) do
+ jobs:append(job_id)
+ end
+ jobs:sort()
+ for _,job_id in ipairs(jobs) do
+ if spell.levels[job_id] <= 99 then
+ levels:append(res.jobs[job_id].english_short .. ' Lv.' ..
+ tostring(spell.levels[job_id]))
+ else
+ levels:append(res.jobs[job_id].english_short .. ' Jp.' ..
+ tostring(spell.levels[job_id]))
+ end
+ end
+ format = levels:concat(' / ')
+ else
+ format = ' ( Trust )'
+ end
+ return string.format('%-20s %s', spell.english, format)
+end
+
+--------------------------------------------------------------------------------
+--Name: spells_by_type
+-- List spells of a given type, i.e. white magic.
+--Args:
+---- player (T): player object from windower.ffxi.get_player()
+---- spell_type (table): one of the types from spell_types global
+---- learnable_only (bool): if true then the output is limited to spell for
+---- which the player has a job that is high enough level
+--------------------------------------------------------------------------------
+function spells_by_type(player, spell_type, learnable_only)
+ local missing_spells = T{}
+ local player_spells = windower.ffxi.get_spells()
+ local spell_count = 0
+
+ for spell_id,spell in pairs(res.spells) do
+ if ((spell_type.type == 'all' and spell.type ~= 'Trust') or
+ spell.type == spell_type.type) and
+ not table.empty(spell.levels) and
+ not player_spells[spell_id] and
+ (is_learnable(player, spell) or not learnable_only) and
+ not (spell_type.type == 'Trust' and spell.name:match('.*%(UC%)')) and
+ not spell.unlearnable then
+
+ missing_spells:append(format_spell(spell))
+ spell_count = spell_count + 1
+ end
+ end
+
+ if learnable_only then
+ windower.add_to_chat(7, string.format('Showing learnable %s.',
+ spell_type.readable))
+ else
+ windower.add_to_chat(7, string.format('Showing all missing %s.',
+ spell_type.readable))
+ end
+
+ if not missing_spells:empty() then
+ missing_spells:sort()
+ for _,spell in ipairs(missing_spells) do
+ windower.add_to_chat(7, spell)
+ end
+ if learnable_only then
+ windower.add_to_chat(7,string.format(
+ 'List Complete. You are missing %d learnable %s.',
+ spell_count, spell_type.readable))
+ else
+ windower.add_to_chat(7,string.format(
+ 'List Complete. You are missing %d %s.',
+ spell_count, spell_type.readable))
+ end
+ else
+ if learnable_only then
+ windower.add_to_chat(7,string.format(
+ 'Congratulations! You know all currently learnable %s.',
+ spell_type.readable))
+ else
+ windower.add_to_chat(7,string.format(
+ 'Congratulations! You know all %s.',
+ spell_type.readable))
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+--Name: spells_by_job
+-- List unknown spells by job.
+--Args:
+---- job (int): the job's id
+---- level_cap (int): the max level/jp required by listed spells
+--------------------------------------------------------------------------------
+function spells_by_job(job, level_cap)
+ local missing_spells = T{}
+ local player_spells = windower.ffxi.get_spells()
+ local spell_count = 0
+
+ for spell_id,spell in pairs(res.spells) do
+ local spell_level = spell.levels[job]
+ if spell_level and spell_level <= level_cap and
+ spell.type ~= 'Trust' and not player_spells[spell_id] and
+ not spell.unlearnable then
+
+ missing_spells[spell_level] = missing_spells[spell_level] or T{}
+ missing_spells[spell_level]:append(spell.english)
+ spell_count = spell_count + 1
+ end
+ end
+
+ if not missing_spells:empty() then
+ if level_cap > 99 then
+ windower.add_to_chat(7, string.format(
+ 'Showing missing spells for %s up to %d spent job points.',
+ res.jobs[job].en, level_cap))
+ else
+ windower.add_to_chat(7, string.format(
+ 'Showing missing spells for %s up to level %d.',
+ res.jobs[job].en, level_cap))
+ end
+
+ for level=1,level_cap do
+ if missing_spells[level] then
+ missing_spells[level]:sort()
+ if level > 99 then
+ windower.add_to_chat(7,
+ level .. 'jp: ' .. missing_spells[level]:concat(', '))
+ else
+ windower.add_to_chat(7,
+ level .. ': ' .. missing_spells[level]:concat(', '))
+ end
+ end
+ end
+ if level_cap > 99 then
+ windower.add_to_chat(7, string.format(
+ 'List Complete. You are missing %d %s spells up to %d spent job points.',
+ spell_count, res.jobs[job].en, level_cap))
+ else
+ windower.add_to_chat(7, string.format(
+ 'List Complete. You are missing %d %s spells up to level %d.',
+ spell_count, res.jobs[job].en, level_cap))
+ end
+ else
+ if level_cap >= 1500 then
+ windower.add_to_chat(7,string.format(
+ 'Congratulations! You know all spells for %s.',
+ res.jobs[job].en))
+ elseif level_cap > 99 then
+ windower.add_to_chat(7,string.format(
+ 'Congratulations! You know all spells for %s up to %d spent job points!',
+ res.jobs[job].en, level_cap))
+ else
+ windower.add_to_chat(7,string.format(
+ 'Congratulations! You know all spells for %s up to level %d!',
+ res.jobs[job].en, level_cap))
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+--Name: spells_by_current
+-- Show missing spells for the current main and sub jobs.
+--Args:
+---- player (T): player object from windower.ffxi.get_player()
+--------------------------------------------------------------------------------
+function spells_by_current(player)
+ local missing_spells = T{}
+ local player_spells = windower.ffxi.get_spells()
+ local spell_count = 0
+
+
+ local main_job_level = player.main_job_level
+ local main_job_jp = player.job_points[player.main_job:lower()].jp_spent
+
+ local main_level
+ local sub_level
+
+ -- If the player has over 99 spend jp, then switch to treating JP as level.
+ if main_job_jp > 99 then
+ main_job_level = main_job_jp
+ end
+
+ for spell_id,spell in pairs(res.spells) do
+ local main_level = spell.levels[player.main_job_id]
+ local sub_level = spell.levels[player.sub_job_id]
+ local spell_level = nil
+
+
+ if main_level and main_level > main_job_level then
+ main_level = nil
+ end
+ if sub_level and sub_level > player.sub_job_level then
+ sub_level = nil
+ end
+
+ if main_level and sub_level then
+ spell_level = math.min(main_level, sub_level)
+ else
+ spell_level = spell_level or main_level or sub_level
+ end
+
+ if spell_level and spell.type ~= 'Trust' and
+ not player_spells[spell_id] and not spell.unlearnable then
+
+ missing_spells:append(format_spell(spell))
+ spell_count = spell_count + 1
+ end
+ end
+
+ if not missing_spells:empty() then
+ if main_job_jp > 0 then
+ if player.sub_job then
+ windower.add_to_chat(7, string.format('Showing learnable spells for %s%d with %d spent job points and %s%d.',
+ player.main_job, player.main_job_level,
+ player.job_points[player.main_job:lower()].jp_spent,
+ player.sub_job, player.sub_job_level))
+ else
+ windower.add_to_chat(7, string.format('Showing learnable spells for %s%d with %d spent job points.',
+ player.main_job, player.main_job_level,
+ player.job_points[player.main_job:lower()].jp_spent))
+ end
+ else
+ if player.sub_job then
+ windower.add_to_chat(7, string.format('Showing learnable spells for %s%d/%s%d.',
+ player.main_job, player.main_job_level,
+ player.sub_job, player.sub_job_level))
+ else
+ windower.add_to_chat(7, string.format('Showing learnable spells for %s%d.',
+ player.main_job, player.main_job_level))
+ end
+ end
+
+ missing_spells:sort()
+ for _,spell in ipairs(missing_spells) do
+ windower.add_to_chat(7, spell)
+ end
+ if player.sub_job then
+ windower.add_to_chat(7, string.format('List Complete. You are missing %d learnable spells for %s%d/%s%d.',
+ spell_count,
+ player.main_job, player.main_job_level,
+ player.sub_job, player.sub_job_level))
+ else
+ windower.add_to_chat(7, string.format('List Complete. You are missing %d learnable spells for %s%d.',
+ spell_count,
+ player.main_job, player.main_job_level))
+ end
+ else
+ if player.sub_job then
+ windower.add_to_chat(7, string.format('Congratulations! You know all learnable spells for %s%d/%s%d!',
+ player.main_job, player.main_job_level,
+ player.sub_job, player.sub_job_level))
+ else
+ windower.add_to_chat(7, string.format('Congratulations! You know all learnable spells for %s%d!',
+ player.main_job, player.main_job_level))
+ end
+ end
+end
+
+--[[
+Copyright © 2018, John S Hobart
+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 SpellBook 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 John S Hobart 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/SpellBook/readme.md b/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/readme.md
new file mode 100644
index 0000000..aef96a2
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-addons/addons/SpellBook/readme.md
@@ -0,0 +1,37 @@
+# SpellBook
+
+This addon helps you find missing spells. You can search by job and level,
+or by category.
+
+## Usage
+Spent jp can be specified for spells learned from Gifts by entering a value of 100-1500.
+Spells are never given as Gifts for less than 100 jp and values under 100 are treated as level.
+
+
+```
+//spbk help
+```
+Show help text.
+```
+//spbk [current]
+```
+Show learnable spells based on current main and sub job and level/jp.
+```
+//spbk <main|sub> [<level|spent jp|all>]
+```
+Show missing spells for current main or sub job. Defaults to the job\'s current level/jp.
+```
+//spbk <job> [<level|spent jp|all>]
+```
+Show missings spells for specified job and level. Defaults to the job\'s level/jp.
+```
+//spbk <category> [all]
+```
+Show learnable spells by category. Limited to spells which are learnable, unless all is added after the category.
+
+Categories: whitemagic, blackmagic, songs, ninjustu, summoning, bluemagic, geomancy, trusts, all (Trusts are not included in all)
+
+
+## Credits
+
+Inspired by the SpellCheck addon by Zubis