Module:Navboxes: Difference between revisions

From Melvor Idle
m (Alt Magic -> Alt. Magic for consistency)
(Update for v1.1)
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')


Line 17: Line 16:
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
-- Initialize 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


Line 48: Line 42:
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 63:
function p.getFoodNavbox(frame)
function p.getFoodNavbox(frame)
local foundIDs, cookedFood, harvestedFood, otherFood = {}, {}, {}, {}
local foundIDs, cookedFood, harvestedFood, otherFood = {}, {}, {}, {}
-- Hide Lemon cake
foundIDs[1029] = true
foundIDs[1061] = true


-- Cooked food
-- Cooked food
for i, recipe in ipairs(SkillData.Cooking.Recipes) do
for i, recipe in ipairs(SkillData.Cooking.recipes) do
if not foundIDs[recipe.itemID] then
if not foundIDs[recipe.productID] then
foundIDs[recipe.itemID] = true
foundIDs[recipe.productID] = true
local cookedItem = Items.getItemByID(recipe.itemID)
if recipe.noMastery == nil then
if cookedItem ~= nil then
local cookedItem = Items.getItemByID(recipe.itemID)
local catIdx = recipe.category + 1
if cookedItem ~= nil then
-- Initialize category if it doesn't already exist
local catIdx = recipe.categoryID
if cookedFood[catIdx] == nil then
-- Initialize category if it doesn't already exist
cookedFood[catIdx] = {}
if cookedFood[catIdx] == nil then
end
cookedFood[catIdx] = {}
end
local perfectName = nil
if recipe.perfectCookID ~= nil then
local perfectName = nil
local perfectItem = Items.getItemByID(recipe.perfectCookID)
if recipe.perfectCookID ~= nil then
if perfectItem ~= nil then
local perfectItem = Items.getItemByID(recipe.perfectCookID)
perfectName = perfectItem.name
if perfectItem ~= nil then
foundIDs[recipe.perfectCookID] = true
perfectName = perfectItem.name
foundIDs[recipe.perfectCookID] = true
end
end
end
table.insert(cookedFood[catIdx], { ["name"] = cookedItem.name, ["order"] = recipe.level, ["perfectName"] = perfectName })
end
end
table.insert(cookedFood[catIdx], { ["name"] = cookedItem.name, ["order"] = recipe.level, ["perfectName"] = perfectName })
end
end
end
end
Line 106: Line 92:


-- Harvested foods
-- Harvested foods
for i, item in ipairs(ItemData.Items) do
for i, recipe in ipairs(SkillData.Farming.recipes) do
if item.grownItemID ~= nil and not foundIDs[item.grownItemID] then
if not foundIDs[recipe.productID] then
-- Item is grown from farming and can be eaten
local product = Items.getItemByID(recipe.productID)
local grownItem = Items.getItemByID(item.grownItemID)
if product.healsFor ~= nil then
if grownItem ~= nil and grownItem.canEat then
table.insert(harvestedFood, { ["name"] = product.name, ["order"] = recipe.level })
table.insert(harvestedFood, { ["name"] = grownItem.name, ["order"] = item.farmingLevel })
foundIDs[product.id] = true
foundIDs[grownItem.id] = true
end
end
end
end
Line 118: Line 103:


-- Other foods, must be checked after cooked & harvested foods have been identified
-- 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 item.canEat and not foundIDs[item.id] then
if item.healsFor ~= nil and not foundIDs[item.id] then
-- Item can be eaten but isn't cooked nor harvested
-- Item can be eaten but isn't cooked nor harvested
table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
Line 136: Line 121:
-- 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)
Line 157: Line 142:
table.insert(resultPart, Icons.Icon({'Food', type='item', img='Crab'}))
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 179: Line 164:
-- 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.Potions) 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.potionIDs[1])
local potFirstItem = Items.getItemByID(potData.potionIDs[1])
Line 193: Line 178:
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 210: Line 195:
function p.getPrayerNavbox(frame)
function p.getPrayerNavbox(frame)
local prayerList = {}
local prayerList = {}
for i, prayer in Shared.skpairs(SkillData.Prayer) do
for i, prayer in ipairs(GameData.rawData.prayers) do
table.insert(prayerList, { ["name"] = prayer.name, ["order"] = prayer.prayerLevel })
table.insert(prayerList, { ["name"] = prayer.name, ["order"] = prayer.level })
end
end
table.sort(prayerList, function(a, b)
table.sort(prayerList, function(a, b)
Line 236: Line 221:
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 catNames = { 'Standard', 'Combination' }
local categoryIDs = { 'melvorF:StandardRunes', 'melvorF:CombinationRunes' }
local runeList = { {}, {} }
local runeList = {}
for i, recipe in ipairs(SkillData.Runecrafting.Recipes) do
for i, recipe in ipairs(SkillData.Runecrafting.recipes) do
local catIdx = recipe.category + 1
local catID = recipe.categoryID
if catNames[catIdx] ~= nil then
if Shared.contains(categoryIDs, catID) then
local item = Items.getItemByID(recipe.itemID)
if runeList[catID] == nil then
if item ~= nil then
runeList[catID] = {}
table.insert(runeList[catIdx], { ["name"] = item.name, ["order"] = recipe.level })
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
Line 251: Line 239:
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" 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({'Runes', type='item', img='Air Rune'}))
table.insert(resultPart, '\r\n!colspan="2"|' .. Icons.Icon({'Runes', type='item', img='Air Rune'}))
for catIdx, catName in ipairs(catNames) do
for i, catID in ipairs(categoryIDs) do
table.sort(runeList[catIdx], 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)
table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. catName .. ' 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[catIdx]) 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 267: Line 258:


function p.getSkillcapeNavbox(frame)
function p.getSkillcapeNavbox(frame)
local capeList = Shop.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
local capeList = Shop.getPurchases(function(purch) return purch.category == 'melvorD:Skillcapes' end)
 
local capeNames = {}
for i, cape in ipairs(capeList) do
if cape.contains ~= nil and cape.contains.items ~= nil then
local item = Items.getItemByID(cape.contains.items[1].id)
if item ~= nil then
capeNames[cape.id] = item.name
end
end
end
 
table.sort(capeList, function(a, b)
table.sort(capeList, function(a, b)
if a.cost.gp == b.cost.gp then
if a.cost.gp == b.cost.gp then
return a.name < b.name
return capeNames[a.id] < capeNames[b.id]
else
else
return a.cost.gp < b.cost.gp
return a.cost.gp < b.cost.gp
Line 278: Line 280:
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


Line 296: Line 293:


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 = p.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 332: Line 313:
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" 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({'Spells', type='skill', img='Magic'}))
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 340: Line 321:
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 350: Line 331:
function p.getFamiliarNavbox(frame)
function p.getFamiliarNavbox(frame)
local familiarList = {}
local familiarList = {}
for i, recipe in ipairs(SkillData.Summoning.Marks) do
for i, recipe in ipairs(SkillData.Summoning.recipes) do
local item = Items.getItemByID(recipe.itemID)
local item = Items.getItemByID(recipe.productID)
if item ~= nil then
if item ~= nil then
table.insert(familiarList, { ["name"] = item.name, ["order"] = recipe.level })
table.insert(familiarList, { ["name"] = item.name, ["order"] = recipe.level })
Line 379: Line 360:


local npcData = {}
local npcData = {}
for i, npc in ipairs(SkillData.Thieving.NPCs) do
for i, npc in ipairs(SkillData.Thieving.npcs) do
table.insert(npcData, {["level"] = npc.level, ["name"] = npc.name})
table.insert(npcData, {["level"] = npc.level, ["name"] = npc.name})
end
end
Line 409: Line 390:
-- 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, recipe in ipairs(area.fish) do
for j, fishID in ipairs(area.fishIDs) do
local fishItem = Items.getItemByID(recipe.itemID)
local fishItem = Items.getItemByID(fishID)
if fishItem ~= nil then
local recipe = GameData.getEntityByID(SkillData.Fishing.recipes, fishID)
if fishItem ~= nil and recipe ~= nil then
addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, recipe.level)
addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, recipe.level)
end
end
Line 418: Line 400:
end
end
-- Junk items
-- Junk items
for i, itemID in ipairs(SkillData.Fishing.JunkItems) do
for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do
local item = Items.getItemByID(itemID)
local item = Items.getItemByID(itemID)
if item ~= nil then
if item ~= nil then
Line 425: Line 407:
end
end
-- Special items
-- Special items
for i, itemDef in ipairs(SkillData.Fishing.SpecialItems) do
for i, itemDef in ipairs(SkillData.Fishing.specialItems) do
local item = Items.getItemByID(itemDef[1])
local item = Items.getItemByID(itemDef.itemID)
if item ~= nil then
if item ~= nil then
local weight = itemDef[2] or 1
local weight = itemDef.weight or 1
addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
end
end
Line 461: Line 443:
-- Compile list of bars
-- Compile list of bars
-- Also compile a list of ores used to create these bars, used to filter out non-bar ores later
-- Also compile a list of ores used to create these bars, used to filter out non-bar ores later
for i, recipe in ipairs(SkillData.Smithing.Recipes) do
for i, recipe in ipairs(SkillData.Smithing.recipes) do
if recipe.category == 0 then
if recipe.categoryID == 'melvorD:Bars' then
-- Category 0 = Bars
local item = Items.getItemByID(recipe.itemID)
local item = Items.getItemByID(recipe.itemID)
if item ~= nil then
if item ~= nil then
Line 478: Line 459:
end
end
-- Compile list of ores
-- Compile list of ores
for i, recipe in ipairs(SkillData.Mining.Rocks) do
for i, recipe in ipairs(SkillData.Mining.rockData) do
if barOreIDs[recipe.oreID] then
if barOreIDs[recipe.productID] then
local item = Items.getItemByID(recipe.oreID)
local item = Items.getItemByID(recipe.productID)
if item ~= nil then
if item ~= nil then
table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = recipe.levelRequired })
table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = recipe.level })
end
end
end
end

Revision as of 15:51, 22 October 2022

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')

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 = {}, {}, {}, {}

	-- Cooked food
	for i, recipe in ipairs(SkillData.Cooking.recipes) do
		if not foundIDs[recipe.productID] then
			foundIDs[recipe.productID] = true
			if recipe.noMastery == nil then
				local cookedItem = Items.getItemByID(recipe.itemID)
				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
							foundIDs[recipe.perfectCookID] = true
						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 = {}
		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
		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.category], { ["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 = {}
	for i, prayer in ipairs(GameData.rawData.prayers) do
		table.insert(prayerList, { ["name"] = prayer.name, ["order"] = prayer.level })
	end
	table.sort(prayerList, function(a, b)
		if a.order == b.order then
			return a.name < b.name
		else
			return a.order < b.order
		end
	end)

	local prayerListText = {}
	for i, prayer in ipairs(prayerList) do
		table.insert(prayerListText, Icons.Icon({ prayer.name, type='prayer' }))
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable navigation-not-searchable" 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)
end

function p.getRuneNavbox(frame)
	-- Assumes all runes are from Runecrafting, which may need revising in future updates
	local categoryIDs = { 'melvorF:StandardRunes', 'melvorF:CombinationRunes' }
	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)
		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 = Shop.getPurchases(function(purch) return purch.category == 'melvorD:Skillcapes' end)

	local capeNames = {}
	for i, cape in ipairs(capeList) do
		if cape.contains ~= nil and cape.contains.items ~= nil then
			local item = Items.getItemByID(cape.contains.items[1].id)
			if item ~= nil then
				capeNames[cape.id] = item.name
			end
		end
	end

	table.sort(capeList, function(a, b)
		if a.cost.gp == b.cost.gp then
			return capeNames[a.id] < capeNames[b.id]
		else
			return a.cost.gp < b.cost.gp
		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!' .. Icons.Icon({'Skillcapes', type='item', img='Cape of Completion'}))
	table.insert(resultPart, '\r\n|-\r\n|style="text-align:center;"|'..table.concat(capeText, ' • '))
	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 = p.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.getFamiliarNavbox(frame)
	local familiarList = {}
	for i, recipe in ipairs(SkillData.Summoning.recipes) do
		local item = Items.getItemByID(recipe.productID)
		if item ~= nil then
			table.insert(familiarList, { ["name"] = item.name, ["order"] = recipe.level })
		end
	end
	table.sort(familiarList, function(a, b) return a.order < b.order 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({'Summoning', 'Summoning Familiars', type='skill'}))
	local iconArray = {}
	for i, fam in ipairs(familiarList) do
		table.insert(iconArray, Icons.Icon({fam.name, type='item'}))
	end
	table.insert(resultPart, '\r\n|-\r\n|style="text-align:center;"|'..table.concat(iconArray, ' • '))
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

function p.getThievingNavbox()
	local returnPart = {}

	-- Create table header
	table.insert(returnPart, '{| class="wikitable navigation-not-searchable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
	table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', 'Thieving Targets', type='skill'}))
	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 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.recipes, 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
	-- Also compile a list of ores used to create these bars, used to filter out non-bar ores later
	for i, recipe in ipairs(SkillData.Smithing.recipes) do
		if recipe.categoryID == 'melvorD:Bars' then
			local item = Items.getItemByID(recipe.itemID)
			if item ~= nil then
				local displayName = string.gsub(item.name, ' Bar$', '')
				table.insert(categoryItems['Bars'], { ["name"] = item.name, ["display"] = displayName, ["order"] = recipe.level })
			end
			-- Add to barOreIDs list
			for j, costDef in ipairs(recipe.itemCosts) do
				if barOreIDs[costDef.id] == nil then
					barOreIDs[costDef.id] = true
				end
			end
		end
	end
	-- Compile list of ores
	for i, recipe in ipairs(SkillData.Mining.rockData) do
		if barOreIDs[recipe.productID] then
			local item = Items.getItemByID(recipe.productID)
			if item ~= nil then
				table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = recipe.level })
			end
		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

return p