Module:Navboxes: Difference between revisions

From Melvor Idle
(Use tabs instead of spaces for indentation)
(Changed Ores & Bars nav to show all ores and bars and sorted by (abyssalLevel+120 or level) to push ItA items to the end)
 
(24 intermediate revisions by 3 users not shown)
Line 2: Line 2:


local p = {}
local p = {}
local SkillData = mw.loadData('Module:Skills/data')
local MagicData = mw.loadData('Module:Magic/data')
local ItemData = mw.loadData('Module:Items/data')


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Magic = require('Module:Magic')
local Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local Pets = require('Module:Pets')
local Prayer = require('Module:Prayer')
-- Generates a table with a single category which has an Item and it's Icon
-- array is the list of items that will be added to the table
-- iconType is the item's icon type
-- headerData is the text displayed at the top of the generated table
-- productID is the proper capitalization of the productID property -_-
function p.buildSingleNavboxTable(array, iconType, headerData, productID)
-- Generate Table contents
table.sort(array, function(a, b) return a.level < b.level end)
local iconArray = {}
for i, item in ipairs(array) do
if productID ~= nil then
item = Items.getItemByID(item[productID])
end
table.insert(iconArray, Icons.Icon({item.name, type=iconType, expicon=Icons.getExpansionIcon(item.id)}))
end
-- Generate navbox table
local resultTable = mw.html.create('table')
-- Table classes & styles
resultTable
:addClass('wikitable')
:addClass('navigation-not-searchable')
:css('text-align', 'center')
:css('clear', 'both')
:css('width', '100%')
-- Header row
:tag('tr')
:tag('th')
:css('background-color', '#275C87')
:css('color', '#FFFFFF')
:wikitext(Icons.Icon(headerData))
:done()
:done()
-- Content, list of logs
:tag('tr')
:tag('td')
:wikitext(table.concat(iconArray, ' • '))
:done()
:done()
:done()
return tostring(resultTable)
end
function p.getLogNavbox(frame)
local trees = Shared.shallowClone(SkillData.Woodcutting.trees)
return p.buildSingleNavboxTable(trees, 'item', {'Woodcutting', 'Logs', type='skill', section='Logs'}, 'productId')
end
function p.getFamiliarNavbox(frame)
local familiars = Shared.shallowClone(SkillData.Summoning.recipes)
return p.buildSingleNavboxTable(familiars, 'item', {'Summoning', 'Summoning Familiars', type='skill', section='Summoning_Tablets'}, 'productID')
end
function p.getThievingNavbox(frame)
local npcs = Shared.shallowClone(SkillData.Thieving.npcs)
return p.buildSingleNavboxTable(npcs, 'thieving', {'Thieving', 'Thieving Targets', type='skill', section='Thieving_Targets'})
end


function p.getFarmingNavbox(frame)
function p.getFarmingNavbox(frame)
Line 17: Line 76:
local produceTable = {}
local produceTable = {}


for i, item in ipairs(ItemData.Items) do
for i, recipe in ipairs(SkillData.Farming.recipes) do
if item.farmingLevel ~= nil then
local seed = Items.getItemByID(recipe.seedCost.id)
local tier = item.tier
local product = Items.getItemByID(recipe.productId)
if seedsTable[tier] == nil then
local tier = recipe.categoryID
-- Initialise tier tables
if seedsTable[tier] == nil then
seedsTable[tier] = {}
-- Initialize tier tables
produceTable[tier] = {}
seedsTable[tier] = {}
end
produceTable[tier] = {}
end


if item.grownItemID ~= nil then
table.insert(seedsTable[tier], { ["name"] = seed.name, ["level"] = recipe.level })
local grownItem = Items.getItemByID(item.grownItemID)
table.insert(produceTable[tier], { ["name"] = product.name, ["level"] = recipe.level })
if grownItem ~= nil then
table.insert(produceTable[tier], { ["name"] = grownItem.name, ["level"] = item.farmingLevel })
end
end
table.insert(seedsTable[tier], { ["name"] = item.name, ["level"] = item.farmingLevel })
end
end
end


-- Generate output table
-- Generate output table
table.insert(resultPart, '{| class="wikitable mw-collapsible" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!colspan="2" style="padding-left:64px;"|' .. Icons.Icon({'Farming', type='skill'}))
table.insert(resultPart, '\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"|' .. Icons.Icon({'Farming', type='skill'}))


local getItemList = function(itemTable)
local getItemList = function(itemTable)
Line 48: Line 102:
end
end
local sortFunc = function(a, b) return (a.level == b.level and a.name < b.name) or a.level < b.level end
local sortFunc = function(a, b) return (a.level == b.level and a.name < b.name) or a.level < b.level end
-- Determine tier list & order in which tiers will be listed in output
local tierList = {}
for tier, seeds in pairs(seedsTable) do
table.insert(tierList, tier)
end
table.sort(tierList, function(a, b) return a < b end)


-- Generate table section for each tier
-- Generate table section for each tier
for i, tier in pairs(tierList) do
for i, category in ipairs(SkillData.Farming.categories) do
local tier = category.id
-- Sort tables by Farming level order
-- Sort tables by Farming level order
table.sort(seedsTable[tier], sortFunc)
table.sort(seedsTable[tier], sortFunc)
table.sort(produceTable[tier], sortFunc)
table.sort(produceTable[tier], sortFunc)


table.insert(resultPart, '\r\n|-\r\n!colspan="2"| ' .. tier .. 's')
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| ' .. category.name)
table.insert(resultPart, '\r\n|-\r\n!scope="row"| Seeds')
table.insert(resultPart, '\r\n|-\r\n!scope="row"| Seeds')
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(seedsTable[tier]))
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(seedsTable[tier]))
Line 75: Line 123:
function p.getFoodNavbox(frame)
function p.getFoodNavbox(frame)
local foundIDs, cookedFood, harvestedFood, otherFood = {}, {}, {}, {}
local foundIDs, cookedFood, harvestedFood, otherFood = {}, {}, {}, {}
-- Exclude unobtainable lemonade
foundIDs['melvorTotH:Lemonade_Full'] = true
-- Cooked food
for i, recipe in ipairs(SkillData.Cooking.recipes) do
if not foundIDs[recipe.productID] then
foundIDs[recipe.productID] = true
if recipe.perfectCookID ~= nil then
foundIDs[recipe.perfectCookID] = true
end
if recipe.noMastery == nil then
local cookedItem = Items.getItemByID(recipe.productID)
if cookedItem ~= nil then
local catIdx = recipe.categoryID
-- Initialize category if it doesn't already exist
if cookedFood[catIdx] == nil then
cookedFood[catIdx] = {}
end
local perfectName = nil
if recipe.perfectCookID ~= nil then
local perfectItem = Items.getItemByID(recipe.perfectCookID)
if perfectItem ~= nil then
perfectName = perfectItem.name
end
end
table.insert(cookedFood[catIdx], { ["name"] = cookedItem.name, ["order"] = recipe.level, ["perfectName"] = perfectName })
end
end
end
end


-- Hide Lemon cake
-- Harvested foods
foundIDs[1029] = true
for i, recipe in ipairs(SkillData.Farming.recipes) do
foundIDs[1061] = true
if not foundIDs[recipe.productID] then
 
local product = Items.getItemByID(recipe.productId)
-- Harvested food first
if product.healsFor ~= nil then
for i, item in ipairs(ItemData.Items) do
table.insert(harvestedFood, { ["name"] = product.name, ["order"] = recipe.level })
if item.grownItemID ~= nil then
foundIDs[product.id] = true
local grownItem = Items.getItemByID(item.grownItemID)
if grownItem ~= nil and grownItem.canEat then
table.insert(harvestedFood, { ["name"] = grownItem.name, ["order"] = item.farmingLevel })
foundIDs[grownItem.id] = true
end
end
end
end
end
end


-- Any cooked & other food
-- Other foods, must be checked after cooked & harvested foods have been identified
for i, item in ipairs(ItemData.Items) do
for i, item in ipairs(GameData.rawData.items) do
-- If an item can be eaten then it must be food
if item.healsFor ~= nil and not foundIDs[item.id] then
if foundIDs[i - 1] == nil and item.canEat then
-- Item can be eaten but isn't cooked nor harvested
if item.cookingCategory ~= nil then
table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
-- Item is cooked, such food items are split by category
foundIDs[item.id] = true
if cookedFood[item.cookingCategory + 1] == nil then
cookedFood[item.cookingCategory + 1] = {}
end
 
local perfectName = nil
if item.perfectItem ~= nil then
local perfectItem = Items.getItemByID(item.perfectItem)
if perfectItem ~= nil then
perfectName = perfectItem.name
foundIDs[item.perfectItem] = true
end
end
table.insert(cookedFood[item.cookingCategory + 1], { ["name"] = item.name, ["order"] = item.cookingLevel, ["perfectName"] = perfectName })
else
-- Item cannot be cooked or grown, but can be eaten
table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
end
foundIDs[i - 1] = true
end
end
end
end
Line 128: Line 186:
-- Generate food lists for final output
-- Generate food lists for final output
local cookingCatHeader = {
local cookingCatHeader = {
Icons.Icon({'Normal Cooking Fire', 'Cooking Fire', type='upgrade', nolink=true}),
{ id = 'melvorD:Fire', header = Icons.Icon({'Normal Cooking Fire', 'Cooking Fire', type='upgrade', nolink=true}) },
Icons.Icon({'Basic Furnace', 'Furnace', type='upgrade', nolink=true}),
{ id = 'melvorD:Furnace', header = Icons.Icon({'Basic Furnace', 'Furnace', type='upgrade', nolink=true}) },
Icons.Icon({'Basic Pot', 'Pot', type='upgrade', nolink=true})
{ id = 'melvorD:Pot', header = Icons.Icon({'Basic Pot', 'Pot', type='upgrade', nolink=true}) }
}
}
local getFoodList = function(foodTable)
local getFoodList = function(foodTable)
local listPart = {}
local listPart = {}
for i, food in ipairs(foodTable) do
if type(foodTable) == 'table' then
local foodText = Icons.Icon({food.name, type='item'})
for i, food in ipairs(foodTable) do
if food.perfectName ~= nil then
local foodText = Icons.Icon({food.name, type='item'})
foodText = Icons.Icon({food.perfectName, type='item', notext=true}) .. ' ' .. foodText
if food.perfectName ~= nil then
foodText = Icons.Icon({food.perfectName, type='item', notext=true}) .. ' ' .. foodText
end
table.insert(listPart, foodText)
end
end
table.insert(listPart, foodText)
end
end
return table.concat(listPart, ' • ')
return table.concat(listPart, ' • ')
Line 145: Line 205:


local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable mw-collapsible" style="margin:0 auto 10px; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:0 auto 10px; clear:both; width: 100%"')
table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"| [[File:Crab_(item).svg|25px|link=Food]] [[Food]]')
table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"| ')
table.insert(resultPart, Icons.Icon({'Food', type='item', img='Crab'}))
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Cooked')
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Cooked')
for catID, foodTable in ipairs(cookedFood) do
for i, cat in ipairs(cookingCatHeader) do
table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. cookingCatHeader[catID])
table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. cat.header)
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getFoodList(foodTable))
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getFoodList(cookedFood[cat.id]))
end
end
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Harvested')
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Harvested')
Line 170: Line 231:
-- Compile list of potions to be included
-- Compile list of potions to be included
local potList = {}
local potList = {}
for i, potData in ipairs(SkillData.Herblore.ItemData) do
for i, potData in ipairs(SkillData.Herblore.recipes) do
if potList[potData.category] == nil then
if potList[potData.categoryID] == nil then
potList[potData.category] = {}
potList[potData.categoryID] = {}
end
end
local potFirstItem = Items.getItemByID(potData.itemID[1])
local potFirstItem = Items.getItemByID(potData.potionIDs[1])
local potName = string.gsub(potFirstItem.name, ' Potion [IV]+$', '')
local potName = string.gsub(potFirstItem.name, ' Potion [IV]+$', '')
table.insert(potList[potData.category], { ["name"] = potName, ["order"] = potData.level, ["img"] = potFirstItem.name })
table.insert(potList[potData.categoryID], { ["name"] = potName, ["order"] = potData.level, ["img"] = potFirstItem.name })
end
end


local resultPart = {}
local resultPart = {}
-- Generate table header
-- Generate table header
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({ 'Herblore', 'Potions', type='skill' }))
table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({ 'Herblore', 'Potions', type='skill' }))
-- Generate section for each category of potions
-- Generate section for each category of potions
for i, catData in ipairs(catList) do
for i, catData in ipairs(SkillData.Herblore.categories) do
-- Compile list of potions
-- Compile list of potions
local potListText = {}
local potListText = {}
table.sort(potList[catData.categoryID], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
table.sort(potList[catData.id], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
for j, potData in ipairs(potList[catData.categoryID]) do
for j, potData in ipairs(potList[catData.id]) do
table.insert(potListText, Icons.Icon({ potData.name .. ' Potion', potData.name, img=potData.img, type='item' }))
table.insert(potListText, Icons.Icon({ potData.name .. ' Potion', potData.name, img=potData.img, type='item' }))
end
end
table.insert(resultPart, '\r\n|-\r\n! ' .. catData.name .. ' Potions')
table.insert(resultPart, '\r\n|-\r\n! ' .. catData.name)
table.insert(resultPart, '\r\n|class="center" style="vertical-align:middle;"| ' .. table.concat(potListText, ' • '))
table.insert(resultPart, '\r\n|class="center" style="vertical-align:middle;"| ' .. table.concat(potListText, ' • '))
end
end
Line 200: Line 261:


function p.getPrayerNavbox(frame)
function p.getPrayerNavbox(frame)
local prayerList = {}
local prayerList = {
for i, prayer in Shared.skpairs(SkillData.Prayer) do
["Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isUnholy == nil and prayer.isAbyssal == nil end),
table.insert(prayerList, { ["name"] = prayer.name, ["order"] = prayer.prayerLevel })
["Unholy Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isUnholy end),
end
["Abyssal Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isAbyssal end)
table.sort(prayerList, function(a, b)
}
if a.order == b.order then
 
return a.name < b.name
local resultPart = {}
else
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
return a.order < b.order
table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Prayer', 'Prayers', type='skill'}))
for catName, subList in pairs(prayerList) do
table.sort(subList, function(a, b)
if a.level == b.level then
return a.name < b.name
else
return a.level < b.level
end
end)
local prayerText = {}
 
-- get expansion icon for prayer categories other than base/TotH
expicon = ''
if catName ~= "Prayers" then
for i, prayer in ipairs(subList) do
expicon = Icons.getExpansionIcon(prayer.id)
break
end
end
end
end)


local prayerListText = {}
table.insert(resultPart, '\r\n|-\r\n!style="text-align:center;"| ' .. expicon .. catName)
for i, prayer in ipairs(prayerList) do
for i, prayer in ipairs(subList) do
table.insert(prayerListText, Icons.Icon({ prayer.name, type='prayer' }))
-- Only show expansion in the base/TotH subtable
expicon = (catName == "Prayers") and Icons.getExpansionIcon(prayer.id)
table.insert(prayerText, Icons.Icon({prayer.name, type='prayer', expicon = expicon}))
end
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. table.concat(prayerText, ' • '))
end
end
table.insert(resultPart, '\r\n|}')


local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!'..Icons.Icon({'Prayer', 'Prayers', type='skill'}))
table.insert(resultPart, '\r\n|-\r\n|style="text-align:center;"| ' .. table.concat(prayerListText, ' • '))
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
return table.concat(resultPart)
end
end
Line 227: Line 304:
function p.getRuneNavbox(frame)
function p.getRuneNavbox(frame)
-- Assumes all runes are from Runecrafting, which may need revising in future updates
-- Assumes all runes are from Runecrafting, which may need revising in future updates
local runeList = { ["Standard"] = {}, ["Combination"] = {} }
local categoryIDs = {
for i, item in ipairs(ItemData.Items) do
'melvorF:StandardRunes',
if item.category == 'Runecrafting' and item.type ~= nil and item.type == 'Rune' and item.runecraftingLevel ~= nil then
'melvorF:CombinationRunes',
local runeType = (type(item.providesRune) == 'table' and Shared.tableCount(item.providesRune) > 1 and 'Combination') or 'Standard'
'melvorItA:AbyssalRunes',
table.insert(runeList[runeType], { ["name"] = item.name, ["order"] = item.runecraftingLevel })
'melvorItA:AbyssalComboRunes'
}
local runeList = {}
for i, recipe in ipairs(SkillData.Runecrafting.recipes) do
local catID = recipe.categoryID
if Shared.contains(categoryIDs, catID) then
if runeList[catID] == nil then
runeList[catID] = {}
end
local product = Items.getItemByID(recipe.productID)
if product ~= nil then
table.insert(runeList[catID], { ["name"] = product.name, ["order"] = recipe.level })
end
end
end
end
end


local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!colspan="2"|[[File:Air_Rune_(item).svg|25px|link=Runes]] [[Runes]]')
table.insert(resultPart, '\r\n!colspan="2"|' .. Icons.Icon({'Runes', type='item', img='Air Rune'}))
for i, cat in ipairs({'Standard', 'Combination'}) do
for i, catID in ipairs(categoryIDs) do
table.sort(runeList[cat], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
local category = GameData.getEntityByID(SkillData.Runecrafting.categories, catID)
table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. cat .. ' Runes')
if category ~= nil then
table.sort(runeList[catID], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. category.name)


local listPart = {}
local listPart = {}
for j, rune in ipairs(runeList[cat]) do
for j, rune in ipairs(runeList[catID]) do
table.insert(listPart, Icons.Icon({rune.name, type='item'}))
table.insert(listPart, Icons.Icon({rune.name, type='item'}))
end
table.insert(resultPart, '\r\n|style="text-align:center;"|'..table.concat(listPart, ' • '))
end
end
table.insert(resultPart, '\r\n|style="text-align:center;"|'..table.concat(listPart, ' • '))
end
end
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
Line 254: Line 346:


function p.getSkillcapeNavbox(frame)
function p.getSkillcapeNavbox(frame)
local capeList = Shop.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
local capeList = {
table.sort(capeList, function(a, b)
["Normal"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, false, nil) end),
if a.cost.gp == b.cost.gp then
["Superior"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, true, nil) end)
return a.name < b.name
}
else
 
return a.cost.gp < b.cost.gp
local capeNames = {}
for catName, subList in pairs(capeList) do
for i, cape in ipairs(subList) do
capeNames[cape.id] = Shop._getPurchaseName(cape)
end
end
end)
table.sort(capeList[catName], function(a, b)
local costA, costB = Shop._getPurchaseSortValue(a), Shop._getPurchaseSortValue(b)
if costA == costB then
return capeNames[a.id] < capeNames[b.id]
else
return costA < costB
end
end)
end


local capeText = {}
local capeText = {}
for i, purch in ipairs(capeList) do
for i, purch in ipairs(capeList) do
if purch.contains ~= nil and purch.contains.items ~= nil then
table.insert(capeText, Icons.Icon({capeNames[purch.id], type='item'}))
local item = Items.getItemByID(purch.contains.items[1][1])
if item ~= nil then
table.insert(capeText, Icons.Icon({item.name, type='item'}))
end
end
end
end


local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n![[File:Cape_of_Completion_(item).svg|25px|link=Skillcapes]] [[Skillcapes]]')
table.insert(resultPart, '\r\n!colspan="2"| ' .. Icons.Icon({'Skillcapes', type='item', img='Cape of Completion'}))
table.insert(resultPart, '\r\n|-\r\n|style="text-align:center;"|'..table.concat(capeText, ' • '))
for catName, subList in pairs(capeList) do
local capeText = {}
table.insert(resultPart, '\r\n|-\r\n!style="text-align:center;"| ' .. catName)
for i, cape in ipairs(subList) do
table.insert(capeText, Icons.Icon({capeNames[cape.id], type='item'}))
end
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. table.concat(capeText, ' • '))
end
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')


Line 283: Line 389:


function p.getSpellNavbox(frame)
function p.getSpellNavbox(frame)
local spellTable = { ["standard"] = {}, ["curse"] = {}, ["aurora"] = {}, ["ancient"] = {}, ["alt"] = {} }
local spellTable = {}
local catData = {
for i, spellBook in ipairs(Magic.spellBooks) do
{ ["name"] = 'standard', ["header"] = '[[Magic#Standard_Magic|Standard Spells]]', ["imgType"] = 'spell' },
spellTable[spellBook.id] = {}
{ ["name"] = 'curse', ["header"] = '[[Magic#Curses|Curses]]', ["imgType"] = 'curse' },
local spells = Magic.getSpellsBySpellBook(spellBook.id)
{ ["name"] = 'aurora', ["header"] = '[[Magic#Auroras|Auroras]]', ["imgType"] = 'aurora' },
for j, spell in ipairs(spells) do
{ ["name"] = 'ancient', ["header"] = '[[Magic#Ancient_Magicks|Ancient Magicks]]', ["imgType"] = 'spell' },
table.insert(spellTable[spellBook.id], { ["name"] = spell.name, ["order"] = spell.level })
{ ["name"] = 'alt', ["header"] = '[[Alternative_Magic|Alt Magic]]', ["imgType"] = 'spell' },
end
}
 
for i, spell in ipairs(MagicData.Spells) do
table.insert(spellTable['standard'], { ["name"] = spell.name, ["order"] = spell.level })
end
for i, spell in ipairs(MagicData.Curses) do
table.insert(spellTable['curse'], { ["name"] = spell.name, ["order"] = spell.level })
end
for i, spell in ipairs(MagicData.Auroras) do
table.insert(spellTable['aurora'], { ["name"] = spell.name, ["order"] = spell.level })
end
for i, spell in ipairs(MagicData.Ancient) do
table.insert(spellTable['ancient'], { ["name"] = spell.name, ["order"] = spell.level })
end
for i, spell in ipairs(MagicData.AltMagic) do
table.insert(spellTable['alt'], { ["name"] = spell.name, ["order"] = spell.level })
end
end


Line 317: Line 407:


local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!colspan=2|[[File:Magic_(skill).svg|25px|link=Spells]] [[Spells]]')
table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Spells', type='skill', img='Magic'}))
for i, catDefn in ipairs(catData) do
for i, spellBook in ipairs(Magic.spellBooks) do
table.sort(spellTable[catDefn.name], function(a, b)
table.sort(spellTable[spellBook.id], function(a, b)
if a.order == b.order then
if a.order == b.order then
return a.name < b.name
return a.name < b.name
Line 327: Line 417:
end
end
end)
end)
table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. catDefn.header)
table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. Magic.getSpellTypeLink(spellBook.id))
table.insert(resultPart, '\r\n|style="text-align:center;| ' .. getSpellList(spellTable[catDefn.name], catDefn.imgType))
table.insert(resultPart, '\r\n|style="text-align:center;| ' .. getSpellList(spellTable[spellBook.id], spellBook.imgType))
end
end
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
Line 335: Line 425:
end
end


function p.getFamiliarNavbox(frame)
function p.getFishingNavbox(frame)
local familiars = Items.getItems(function(item) return item.type == 'Familiar' end)
table.sort(familiars, function(a, b) return a.summoningLevel < b.summoningLevel end)
 
local result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"'
result = result..'\r\n!colspan=2|[[File:Summoning_(skill).svg|25px|link=Summoning]] [[Summoning|Summoning Familiars]]'
local iconArray = {}
for i, fam in Shared.skpairs(familiars) do
table.insert(iconArray, Icons.Icon({fam.name, type='item'}))
end
result = result..'\r\n|-\r\n|style="text-align:center;"|'..table.concat(iconArray, ' • ')
result = result..'\r\n|}'
return result
end
 
function p.getThievingNavbox()
local returnPart = {}
 
-- Create table header
table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]')
table.insert(returnPart, '|-\r\n|')
 
local npcData = {}
for i, npc in ipairs(SkillData.Thieving.NPCs) do
table.insert(npcData, {["level"] = npc.level, ["name"] = npc.name})
end
table.sort(npcData, function(a, b) return a.level < b.level end)
 
local npcList = {}
-- Create row for each NPC
for i, npc in ipairs(npcData) do
table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
end
table.insert(returnPart, table.concat(npcList, ' • '))
table.insert(returnPart, '|}')
 
return table.concat(returnPart, '\r\n')
end
 
function p.getFishingNavbox()
local categoryHeader = {}
local categoryHeader = {}
local categoryItems = {}
local categoryItems = {}
local addCatData = function(cat, catLink, itemName, itemOrder)
local addCatData = function(cat, catLink, itemName, itemOrder)
if categoryItems[cat] == nil then
if categoryItems[cat] == nil then
-- Initialise category
-- Initialize category
table.insert(categoryHeader, { ["name"] = cat, ["link"] = catLink })
table.insert(categoryHeader, { ["name"] = cat, ["link"] = catLink })
categoryItems[cat] = {}
categoryItems[cat] = {}
Line 387: Line 437:
end
end


-- Identify fishing catchable items
local fishingToItemID = {}
local junkItems = {}
local specialItems = {}
for i, item in ipairs(ItemData.Items) do
if item.fishingID ~= nil then
-- Create FishingID to item map
fishingToItemID[item.fishingID] = item
elseif item.category == 'Fishing' and item.type == 'Junk' then
table.insert(junkItems, item)
elseif item.fishingCatchWeight ~= nil then
table.insert(specialItems, item)
end
end
-- Fishing areas
-- Fishing areas
-- Iterate through all fishing areas, identifying fish within each
-- Iterate through all fishing areas, identifying fish within each
for i, area in ipairs(SkillData.Fishing.Areas) do
for i, area in ipairs(SkillData.Fishing.areas) do
for j, fishID in ipairs(area.fish) do
for j, fishID in ipairs(area.fishIDs) do
local fishItem = fishingToItemID[fishID]
local fishItem = Items.getItemByID(fishID)
if fishItem ~= nil then
local recipe = GameData.getEntityByID(SkillData.Fishing.fish, fishID)
addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, fishItem.fishingLevel)
if fishItem ~= nil and recipe ~= nil then
addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, recipe.level)
end
end
end
end
end
end
-- Junk items
-- Junk items
for i, item in ipairs(junkItems) do
for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do
addCatData('Junk', 'Fishing#Junk', item.name, 1)
local item = Items.getItemByID(itemID)
if item ~= nil then
addCatData('Junk', 'Fishing#Junk', item.name, 1)
end
end
end
-- Special items
-- Special items
for i, item in ipairs(specialItems) do
for i, itemDef in ipairs(SkillData.Fishing.specialItems) do
addCatData('Special Items', 'Fishing#Special', item.name, 1 / (item.fishingCatchWeight or 1))
local item = Items.getItemByID(itemDef.itemID)
if item ~= nil then
local weight = itemDef.weight or 1
addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
end
end
end


local resultPart = {}
local resultPart = {}
-- Generate navbox header
-- Generate navbox header
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n|-\r\n!colspan="2" | ' .. Icons.Icon({'Fishing', type='skill'}))
table.insert(resultPart, '\r\n|-\r\n!colspan="2" | ' .. Icons.Icon({'Fishing', type='skill'}))
-- Generate section for each fishing area, junk, and special
-- Generate section for each fishing area, junk, and special
Line 439: Line 483:
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')


return table.concat(resultPart)
end
function p.getOreBarNavbox(frame)
local categoryList = { 'Ores', 'Bars' }
local categoryItems = { ["Ores"] = {}, ["Bars"] = {} }
local barOreIDs = {}
-- Compile list of bars
for i, recipe in ipairs(SkillData.Smithing.recipes) do
if Shared.contains({'melvorD:Bars','melvorItA:AbyssalBars'}, recipe.categoryID) then
local item = Items.getItemByID(recipe.productID)
if item ~= nil then
local displayName = string.gsub(item.name, ' Bar$', '')
local level = recipe.level
if recipe.abyssalLevel then
level = recipe.abyssalLevel + 120
end
table.insert(categoryItems['Bars'], { ["name"] = item.name, ["display"] = displayName, ["order"] = level })
end
end
end
-- Compile list of ores
for i, recipe in ipairs(SkillData.Mining.rockData) do
local item = Items.getItemByID(recipe.productId)
if item ~= nil then
local level = recipe.level
if recipe.abyssalLevel then
level = recipe.abyssalLevel + 120
end
table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = level })
end
end
local resultPart = {}
-- Generate navbox header
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2" | ' .. '<b>Ores & Bars</b>')
-- Generate navbox content
for i, cat in ipairs(categoryList) do
table.sort(categoryItems[cat], function(a, b) return (a.order == b.order and a.display < b.display) or a.order < b.order end)
local listPart = {}
for j, listItem in ipairs(categoryItems[cat]) do
table.insert(listPart, Icons.Icon({ listItem.name, listItem.display, type = 'item' }))
end
table.insert(resultPart, '\r\n|-\r\n! ' .. cat .. '\r\n|style="text-align:center;"| ' .. table.concat(listPart, ' • '))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.getPetNavbox(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; text-align:center; clear:both; width: 100%"')
table.insert(resultPart, '\r\n|-\r\n!colspan="2"|[[Pets]]')
local petListOrder = { 'skill', 'boss', 'other' }
local petList = {
["skill"] = {},
["boss"] = {},
["other"] = {}
}
for i, petData in ipairs(GameData.rawData.pets) do
local source = Pets._getPetSource(petData)
local listCat = 'other'
if type(source) == 'table' and source.type ~= nil then
if source.type == 'skill' then
listCat = 'skill'
elseif Shared.contains({'dungeon', 'abyssDepth', 'stronghold'}, source.type)  then
listCat = 'boss'
else
listCat = 'other'
end
end
table.insert(petList[listCat], petData.name)
end
local getIconList =
function(pets)
local result = {}
for i, pet in ipairs(pets) do
table.insert(result, Icons.Icon({pet, type='pet'}))
end
return table.concat(result, ' • ')
end
for i, catName in ipairs(petListOrder) do
local catData = petList[catName]
table.sort(catData, function(a, b) return a < b end)
table.insert(resultPart, '\r\n|-\r\n!' .. Shared.titleCase(catName) .. ' Pets\r\n|' .. getIconList(catData))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
return table.concat(resultPart)
end
end


return p
return p

Latest revision as of 19:37, 5 August 2024

Documentation for this module may be created at Module:Navboxes/doc

-- New module to stop navbox generators cluttering other modules

local p = {}

local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Magic = require('Module:Magic')
local Shop = require('Module:Shop')
local Pets = require('Module:Pets')
local Prayer = require('Module:Prayer')

-- Generates a table with a single category which has an Item and it's Icon
-- array is the list of items that will be added to the table
-- iconType is the item's icon type
-- headerData is the text displayed at the top of the generated table
-- productID is the proper capitalization of the productID property -_-
function p.buildSingleNavboxTable(array, iconType, headerData, productID)
	-- Generate Table contents
	table.sort(array, function(a, b) return a.level < b.level end)
	local iconArray = {}
	for i, item in ipairs(array) do
		if productID ~= nil then
			item = Items.getItemByID(item[productID])
		end
		table.insert(iconArray, Icons.Icon({item.name, type=iconType, expicon=Icons.getExpansionIcon(item.id)}))
	end
	-- Generate navbox table
	local resultTable = mw.html.create('table')
	-- Table classes & styles
	resultTable
		:addClass('wikitable')
		:addClass('navigation-not-searchable')
		:css('text-align', 'center')
		:css('clear', 'both')
		:css('width', '100%')
	-- Header row
		:tag('tr')
			:tag('th')
				:css('background-color', '#275C87')
				:css('color', '#FFFFFF')
				:wikitext(Icons.Icon(headerData))
			:done()
		:done()
	-- Content, list of logs
		:tag('tr')
			:tag('td')
				:wikitext(table.concat(iconArray, ' • '))
			:done()
		:done()
	:done()

	return tostring(resultTable)
end

function p.getLogNavbox(frame)
	local trees = Shared.shallowClone(SkillData.Woodcutting.trees)
	return p.buildSingleNavboxTable(trees, 'item', {'Woodcutting', 'Logs', type='skill', section='Logs'}, 'productId')
end

function p.getFamiliarNavbox(frame)
	local familiars = Shared.shallowClone(SkillData.Summoning.recipes)
	return p.buildSingleNavboxTable(familiars, 'item', {'Summoning', 'Summoning Familiars', type='skill', section='Summoning_Tablets'}, 'productID')
end

function p.getThievingNavbox(frame)
	local npcs = Shared.shallowClone(SkillData.Thieving.npcs)
	return p.buildSingleNavboxTable(npcs, 'thieving', {'Thieving', 'Thieving Targets', type='skill', section='Thieving_Targets'})
end

function p.getFarmingNavbox(frame)
	local resultPart = {}
	local seedsTable = {}
	local produceTable = {}

	for i, recipe in ipairs(SkillData.Farming.recipes) do
		local seed = Items.getItemByID(recipe.seedCost.id)
		local product = Items.getItemByID(recipe.productId)
		local tier = recipe.categoryID
		if seedsTable[tier] == nil then
			-- Initialize tier tables
			seedsTable[tier] = {}
			produceTable[tier] = {}
		end

		table.insert(seedsTable[tier], { ["name"] = seed.name, ["level"] = recipe.level })
		table.insert(produceTable[tier], { ["name"] = product.name, ["level"] = recipe.level })
	end

	-- Generate output table
	table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"|' .. Icons.Icon({'Farming', type='skill'}))

	local getItemList = function(itemTable)
		local listPart = {}
		for i, item in ipairs(itemTable) do
			table.insert(listPart, Icons.Icon({item.name, type='item'}))
		end
		return table.concat(listPart, ' • ')
	end
	local sortFunc = function(a, b) return (a.level == b.level and a.name < b.name) or a.level < b.level end

	-- Generate table section for each tier
	for i, category in ipairs(SkillData.Farming.categories) do
		local tier = category.id
		-- Sort tables by Farming level order
		table.sort(seedsTable[tier], sortFunc)
		table.sort(produceTable[tier], sortFunc)

		table.insert(resultPart, '\r\n|-\r\n!colspan="2"| ' .. category.name)
		table.insert(resultPart, '\r\n|-\r\n!scope="row"| Seeds')
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(seedsTable[tier]))
		table.insert(resultPart, '\r\n|-\r\n!scope="row"| Produce')
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(produceTable[tier]))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getFoodNavbox(frame)
	local foundIDs, cookedFood, harvestedFood, otherFood = {}, {}, {}, {}
	
	-- Exclude unobtainable lemonade
	foundIDs['melvorTotH:Lemonade_Full'] = true
	
	-- Cooked food
	for i, recipe in ipairs(SkillData.Cooking.recipes) do
		if not foundIDs[recipe.productID] then
			foundIDs[recipe.productID] = true
			if recipe.perfectCookID ~= nil then
				foundIDs[recipe.perfectCookID] = true
			end
			if recipe.noMastery == nil then
				local cookedItem = Items.getItemByID(recipe.productID)
				if cookedItem ~= nil then
					local catIdx = recipe.categoryID
					-- Initialize category if it doesn't already exist
					if cookedFood[catIdx] == nil then
						cookedFood[catIdx] = {}
					end
		
					local perfectName = nil
					if recipe.perfectCookID ~= nil then
						local perfectItem = Items.getItemByID(recipe.perfectCookID)
						if perfectItem ~= nil then
							perfectName = perfectItem.name
						end
					end
					table.insert(cookedFood[catIdx], { ["name"] = cookedItem.name, ["order"] = recipe.level, ["perfectName"] = perfectName })
				end
			end
		end
	end

	-- Harvested foods
	for i, recipe in ipairs(SkillData.Farming.recipes) do
		if not foundIDs[recipe.productID] then
			local product = Items.getItemByID(recipe.productId)
			if product.healsFor ~= nil then
				table.insert(harvestedFood, { ["name"] = product.name, ["order"] = recipe.level })
				foundIDs[product.id] = true
			end
		end
	end

	-- Other foods, must be checked after cooked & harvested foods have been identified
	for i, item in ipairs(GameData.rawData.items) do
		if item.healsFor ~= nil and not foundIDs[item.id] then
			-- Item can be eaten but isn't cooked nor harvested
			table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
			foundIDs[item.id] = true
		end
	end

	-- Sort food lists
	local sortFunc = function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end
	for i, items in pairs(cookedFood) do
		table.sort(cookedFood[i], sortFunc)
	end
	table.sort(harvestedFood, sortFunc)
	table.sort(otherFood, sortFunc)

	-- Generate food lists for final output
	local cookingCatHeader = {
		{ id = 'melvorD:Fire', header = Icons.Icon({'Normal Cooking Fire', 'Cooking Fire', type='upgrade', nolink=true}) },
		{ id = 'melvorD:Furnace', header = Icons.Icon({'Basic Furnace', 'Furnace', type='upgrade', nolink=true}) },
		{ id = 'melvorD:Pot', header = Icons.Icon({'Basic Pot', 'Pot', type='upgrade', nolink=true}) }
	}
	local getFoodList = function(foodTable)
		local listPart = {}
		if type(foodTable) == 'table' then
			for i, food in ipairs(foodTable) do
				local foodText = Icons.Icon({food.name, type='item'})
				if food.perfectName ~= nil then
					foodText = Icons.Icon({food.perfectName, type='item', notext=true}) .. ' ' .. foodText
				end
				table.insert(listPart, foodText)
			end
		end
		return table.concat(listPart, ' • ')
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:0 auto 10px; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"| ')
	table.insert(resultPart, Icons.Icon({'Food', type='item', img='Crab'}))
	table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Cooked')
	for i, cat in ipairs(cookingCatHeader) do
		table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. cat.header)
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getFoodList(cookedFood[cat.id]))
	end
	table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Harvested')
	table.insert(resultPart, '\r\n|-\r\n|colspan="2" style="text-align:center;"| ' .. getFoodList(harvestedFood))
	table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Other')
	table.insert(resultPart, '\r\n|-\r\n|colspan="2" style="text-align:center;"| ' .. getFoodList(otherFood))
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getPotionNavbox(frame)
	local catList = {
		{ ["categoryID"] = 0, ["name"] = 'Combat' },
		{ ["categoryID"] = 1, ["name"] = 'Skill' }
	}
	table.sort(catList, function(a, b) return a.name < b.name end)

	-- Compile list of potions to be included
	local potList = {}
	for i, potData in ipairs(SkillData.Herblore.recipes) do
		if potList[potData.categoryID] == nil then
			potList[potData.categoryID] = {}
		end
		local potFirstItem = Items.getItemByID(potData.potionIDs[1])
		local potName = string.gsub(potFirstItem.name, ' Potion [IV]+$', '')
		table.insert(potList[potData.categoryID], { ["name"] = potName, ["order"] = potData.level, ["img"] = potFirstItem.name })
	end

	local resultPart = {}
	-- Generate table header
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({ 'Herblore', 'Potions', type='skill' }))
	-- Generate section for each category of potions
	for i, catData in ipairs(SkillData.Herblore.categories) do
		-- Compile list of potions
		local potListText = {}
		table.sort(potList[catData.id], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
		for j, potData in ipairs(potList[catData.id]) do
			table.insert(potListText, Icons.Icon({ potData.name .. ' Potion', potData.name, img=potData.img, type='item' }))
		end
		table.insert(resultPart, '\r\n|-\r\n! ' .. catData.name)
		table.insert(resultPart, '\r\n|class="center" style="vertical-align:middle;"| ' .. table.concat(potListText, ' • '))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getPrayerNavbox(frame)
	local prayerList = {
		["Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isUnholy == nil and prayer.isAbyssal == nil end),
		["Unholy Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isUnholy end),
		["Abyssal Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isAbyssal end)
	}

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Prayer', 'Prayers', type='skill'}))
	for catName, subList in pairs(prayerList) do
		table.sort(subList, function(a, b)
			if a.level == b.level then
				return a.name < b.name
			else
				return a.level < b.level
			end
		end)
		local prayerText = {}

		-- get expansion icon for prayer categories other than base/TotH
		expicon = ''
		if catName ~= "Prayers" then
			for i, prayer in ipairs(subList) do
				expicon = Icons.getExpansionIcon(prayer.id)
				break
			end
		end

		table.insert(resultPart, '\r\n|-\r\n!style="text-align:center;"| ' .. expicon .. catName)
		for i, prayer in ipairs(subList) do
			-- Only show expansion in the base/TotH subtable
			expicon = (catName == "Prayers") and Icons.getExpansionIcon(prayer.id)
			table.insert(prayerText, Icons.Icon({prayer.name, type='prayer', expicon = expicon}))
		end
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. table.concat(prayerText, ' • '))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getRuneNavbox(frame)
	-- Assumes all runes are from Runecrafting, which may need revising in future updates
	local categoryIDs = {
		'melvorF:StandardRunes',
		'melvorF:CombinationRunes',
		'melvorItA:AbyssalRunes',
		'melvorItA:AbyssalComboRunes'
	}
	local runeList = {}
	for i, recipe in ipairs(SkillData.Runecrafting.recipes) do
		local catID = recipe.categoryID
		if Shared.contains(categoryIDs, catID) then
			if runeList[catID] == nil then
				runeList[catID] = {}
			end
			local product = Items.getItemByID(recipe.productID)
			if product ~= nil then
				table.insert(runeList[catID], { ["name"] = product.name, ["order"] = recipe.level })
			end
		end
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan="2"|' .. Icons.Icon({'Runes', type='item', img='Air Rune'}))
	for i, catID in ipairs(categoryIDs) do
		local category = GameData.getEntityByID(SkillData.Runecrafting.categories, catID)
		if category ~= nil then
			table.sort(runeList[catID], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
			table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. category.name)

			local listPart = {}
			for j, rune in ipairs(runeList[catID]) do
				table.insert(listPart, Icons.Icon({rune.name, type='item'}))
			end
			table.insert(resultPart, '\r\n|style="text-align:center;"|'..table.concat(listPart, ' • '))
		end
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getSkillcapeNavbox(frame)
	local capeList = {
		["Normal"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, false, nil) end),
		["Superior"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, true, nil) end)
	}

	local capeNames = {}
	for catName, subList in pairs(capeList) do
		for i, cape in ipairs(subList) do
			capeNames[cape.id] = Shop._getPurchaseName(cape)
		end
		
		table.sort(capeList[catName], function(a, b)
			local costA, costB = Shop._getPurchaseSortValue(a), Shop._getPurchaseSortValue(b)
			if costA == costB then
				return capeNames[a.id] < capeNames[b.id]
			else
				return costA < costB
			end
		end)
	end

	local capeText = {}
	for i, purch in ipairs(capeList) do
		table.insert(capeText, Icons.Icon({capeNames[purch.id], type='item'}))
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan="2"| ' .. Icons.Icon({'Skillcapes', type='item', img='Cape of Completion'}))
	for catName, subList in pairs(capeList) do
		local capeText = {}
		table.insert(resultPart, '\r\n|-\r\n!style="text-align:center;"| ' .. catName)
		for i, cape in ipairs(subList) do
			table.insert(capeText, Icons.Icon({capeNames[cape.id], type='item'}))
		end
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. table.concat(capeText, ' • '))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getSpellNavbox(frame)
	local spellTable = {}
	for i, spellBook in ipairs(Magic.spellBooks) do
		spellTable[spellBook.id] = {}
		local spells = Magic.getSpellsBySpellBook(spellBook.id)
		for j, spell in ipairs(spells) do
			table.insert(spellTable[spellBook.id], { ["name"] = spell.name, ["order"] = spell.level })
		end
	end

	local getSpellList = function(spellTable, imgType)
		local listPart = {}
		for i, obj in ipairs(spellTable) do
			table.insert(listPart, Icons.Icon({obj.name, type=imgType}))
		end
		return table.concat(listPart, ' • ')
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Spells', type='skill', img='Magic'}))
	for i, spellBook in ipairs(Magic.spellBooks) do
		table.sort(spellTable[spellBook.id], function(a, b)
			if a.order == b.order then
				return a.name < b.name
			else
				return a.order < b.order
			end
		end)
		table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. Magic.getSpellTypeLink(spellBook.id))
		table.insert(resultPart, '\r\n|style="text-align:center;| ' .. getSpellList(spellTable[spellBook.id], spellBook.imgType))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getFishingNavbox(frame)
	local categoryHeader = {}
	local categoryItems = {}
	local addCatData = function(cat, catLink, itemName, itemOrder)
		if categoryItems[cat] == nil then
			-- Initialize category
			table.insert(categoryHeader, { ["name"] = cat, ["link"] = catLink })
			categoryItems[cat] = {}
		end
		table.insert(categoryItems[cat], { ["name"] = itemName, ["order"] = itemOrder })
	end

	-- Fishing areas
	-- Iterate through all fishing areas, identifying fish within each
	for i, area in ipairs(SkillData.Fishing.areas) do
		for j, fishID in ipairs(area.fishIDs) do
			local fishItem = Items.getItemByID(fishID)
			local recipe = GameData.getEntityByID(SkillData.Fishing.fish, fishID)
			if fishItem ~= nil and recipe ~= nil then
				addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, recipe.level)
			end
		end
	end
	-- Junk items
	for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do
		local item = Items.getItemByID(itemID)
		if item ~= nil then
			addCatData('Junk', 'Fishing#Junk', item.name, 1)
		end
	end
	-- Special items
	for i, itemDef in ipairs(SkillData.Fishing.specialItems) do
		local item = Items.getItemByID(itemDef.itemID)
		if item ~= nil then
			local weight = itemDef.weight or 1
			addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
		end
	end

	local resultPart = {}
	-- Generate navbox header
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n|-\r\n!colspan="2" | ' .. Icons.Icon({'Fishing', type='skill'}))
	-- Generate section for each fishing area, junk, and special
	for i, cat in ipairs(categoryHeader) do
		local itemList = {}
		if categoryItems[cat.name] ~= nil then
			table.sort(categoryItems[cat.name], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
			for j, item in ipairs(categoryItems[cat.name]) do
				table.insert(itemList, Icons.Icon({item.name, type='item'}))
			end
		end

		table.insert(resultPart, '\r\n|-\r\n!class="center" style="min-width:140px" | [[' .. (cat.link or cat.name) .. '|' .. cat.name .. ']]')
		table.insert(resultPart, '\r\n| class="center" style="vertical-align:middle;" | ' .. table.concat(itemList, ' • '))
	end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getOreBarNavbox(frame)
	local categoryList = { 'Ores', 'Bars' }
	local categoryItems = { ["Ores"] = {}, ["Bars"] = {} }
	local barOreIDs = {}
	-- Compile list of bars
	for i, recipe in ipairs(SkillData.Smithing.recipes) do
		if Shared.contains({'melvorD:Bars','melvorItA:AbyssalBars'}, recipe.categoryID) then
			local item = Items.getItemByID(recipe.productID)
			if item ~= nil then
				local displayName = string.gsub(item.name, ' Bar$', '')
				local level = recipe.level
				if recipe.abyssalLevel then
					level = recipe.abyssalLevel + 120
				end
				table.insert(categoryItems['Bars'], { ["name"] = item.name, ["display"] = displayName, ["order"] = level })
			end
		end
	end
	-- Compile list of ores
	for i, recipe in ipairs(SkillData.Mining.rockData) do
		local item = Items.getItemByID(recipe.productId)
		if item ~= nil then
			local level = recipe.level
				if recipe.abyssalLevel then
					level = recipe.abyssalLevel + 120
				end
			table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = level })
		end
	end
	
	local resultPart = {}
	-- Generate navbox header
	table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2" | ' .. '<b>Ores & Bars</b>')
	-- Generate navbox content
	for i, cat in ipairs(categoryList) do
		table.sort(categoryItems[cat], function(a, b) return (a.order == b.order and a.display < b.display) or a.order < b.order end)
		local listPart = {}
		for j, listItem in ipairs(categoryItems[cat]) do
			table.insert(listPart, Icons.Icon({ listItem.name, listItem.display, type = 'item' }))
		end
		table.insert(resultPart, '\r\n|-\r\n! ' .. cat .. '\r\n|style="text-align:center;"| ' .. table.concat(listPart, ' • '))
	end
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

function p.getPetNavbox(frame)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; text-align:center; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n|-\r\n!colspan="2"|[[Pets]]')

	local petListOrder = { 'skill', 'boss', 'other' }
	local petList = {
		["skill"] = {},
		["boss"] = {},
		["other"] = {}
	}
	for i, petData in ipairs(GameData.rawData.pets) do
		local source = Pets._getPetSource(petData)
		local listCat = 'other'
		if type(source) == 'table' and source.type ~= nil then
			if source.type == 'skill' then
				listCat = 'skill'
			elseif Shared.contains({'dungeon', 'abyssDepth', 'stronghold'}, source.type)  then
				listCat = 'boss'
			else
				listCat = 'other'
			end
		end
		table.insert(petList[listCat], petData.name)
	end

	local getIconList =
	function(pets)
		local result = {}
		for i, pet in ipairs(pets) do
			table.insert(result, Icons.Icon({pet, type='pet'}))
		end
		return table.concat(result, ' • ')
	end
	for i, catName in ipairs(petListOrder) do
		local catData = petList[catName]
		table.sort(catData, function(a, b) return a < b end)
		table.insert(resultPart, '\r\n|-\r\n!' .. Shared.titleCase(catName) .. ' Pets\r\n|' .. getIconList(catData))
	end
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

return p