Module:Navboxes: Difference between revisions

From Melvor Idle
(Update for v1.0.2)
(Update for v1.0.3)
Line 80: Line 80:
foundIDs[1061] = true
foundIDs[1061] = true


-- Harvested food first
-- Cooked food
for i, recipe in ipairs(SkillData.Cooking.Recipes) do
foundIDs[recipe.itemID] = true
local cookedItem = Items.getItemByID(recipe.itemID)
if cookedItem ~= nil then
local catIdx = recipe.category + 1
-- 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
 
-- Harvested foods
for i, item in ipairs(ItemData.Items) do
for i, item in ipairs(ItemData.Items) do
if item.grownItemID ~= nil then
if item.grownItemID ~= nil and not foundIDs[item.grownItemID] then
-- Item is grown from farming and can be eaten
local grownItem = Items.getItemByID(item.grownItemID)
local grownItem = Items.getItemByID(item.grownItemID)
if grownItem ~= nil and grownItem.canEat then
if grownItem ~= nil and grownItem.canEat then
Line 91: Line 115:
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(ItemData.Items) do
-- If an item can be eaten then it must be food
if item.canEat 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 146: Line 152:
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" 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 catID, foodTable in ipairs(cookedFood) do
Line 227: Line 234:
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 catNames = { 'Standard', 'Combination' }
for i, item in ipairs(ItemData.Items) do
local runeList = { {}, {} }
if item.category == 'Runecrafting' and item.type ~= nil and item.type == 'Rune' and item.runecraftingLevel ~= nil then
for i, recipe in ipairs(SkillData.Runecrafting.Recipes) do
local runeType = (type(item.providesRune) == 'table' and Shared.tableCount(item.providesRune) > 1 and 'Combination') or 'Standard'
local catIdx = recipe.category + 1
table.insert(runeList[runeType], { ["name"] = item.name, ["order"] = item.runecraftingLevel })
if catNames[catIdx] ~= nil then
local item = Items.getItemByID(recipe.itemID)
if item ~= nil then
table.insert(runeList[catIdx], { ["name"] = item.name, ["order"] = recipe.level })
end
end
end
end
end
Line 237: Line 248:
local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable" 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 catIdx, catName in ipairs(catNames) do
table.sort(runeList[cat], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
table.sort(runeList[catIdx], 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"|' .. cat .. ' Runes')
table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. catName .. ' Runes')


local listPart = {}
local listPart = {}
for j, rune in ipairs(runeList[cat]) do
for j, rune in ipairs(runeList[catIdx]) do
table.insert(listPart, Icons.Icon({rune.name, type='item'}))
table.insert(listPart, Icons.Icon({rune.name, type='item'}))
end
end
Line 275: Line 286:
local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable" 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!' .. 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|-\r\n|style="text-align:center;"|'..table.concat(capeText, ' • '))
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
Line 318: Line 329:
local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable" 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, catDefn in ipairs(catData) do
table.sort(spellTable[catDefn.name], function(a, b)
table.sort(spellTable[catDefn.name], function(a, b)
Line 336: Line 347:


function p.getFamiliarNavbox(frame)
function p.getFamiliarNavbox(frame)
local familiars = Items.getItems(function(item) return item.type == 'Familiar' end)
local familiarList = {}
table.sort(familiars, function(a, b) return a.summoningLevel < b.summoningLevel end)
for i, recipe in ipairs(SkillData.Summoning.Marks) do
local item = Items.getItemByID(recipe.itemID)
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 result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"'
local resultPart = {}
result = result..'\r\n!colspan=2|[[File:Summoning_(skill).svg|25px|link=Summoning]] [[Summoning|Summoning Familiars]]'
table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Summoning', 'Summoning Familiars', type='skill'}))
local iconArray = {}
local iconArray = {}
for i, fam in Shared.skpairs(familiars) do
for i, fam in ipairs(familiarList) do
table.insert(iconArray, Icons.Icon({fam.name, type='item'}))
table.insert(iconArray, Icons.Icon({fam.name, type='item'}))
end
end
result = result..'\r\n|-\r\n|style="text-align:center;"|'..table.concat(iconArray, ' • ')
table.insert(resultPart, '\r\n|-\r\n|style="text-align:center;"|'..table.concat(iconArray, ' • '))
result = result..'\r\n|}'
table.insert(resultPart, '\r\n|}')
return result
return table.concat(resultPart)
end
end


Line 355: Line 373:
-- Create table header
-- Create table header
table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
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!' .. Icons.Icon({'Thieving', 'Thieving Targets', type='skill'}))
table.insert(returnPart, '|-\r\n|')
table.insert(returnPart, '|-\r\n|')


Line 387: Line 405:
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, recipe in ipairs(area.fish) do
local fishItem = fishingToItemID[fishID]
local fishItem = Items.getItemByID(recipe.itemID)
if fishItem ~= nil then
if fishItem ~= nil then
addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, fishItem.fishingLevel)
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.JunkItems) 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[1])
if item ~= nil then
local weight = itemDef[2] or 1
addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
end
end
end



Revision as of 12:25, 5 March 2022

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

-- New module to stop navbox generators cluttering other modules

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 Icons = require('Module:Icons')
local Items = require('Module:Items')
local Shop = require('Module:Shop')

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

	for i, item in ipairs(ItemData.Items) do
		if item.farmingLevel ~= nil then
			local tier = item.tier
			if seedsTable[tier] == nil then
				-- Initialise tier tables
				seedsTable[tier] = {}
				produceTable[tier] = {}
			end

			if item.grownItemID ~= nil then
				local grownItem = Items.getItemByID(item.grownItemID)
				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

	-- Generate output table
	table.insert(resultPart, '{| class="wikitable mw-collapsible" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan="2" style="padding-left:64px;"|' .. 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

	-- 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
	for i, tier in pairs(tierList) do
		-- 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"| ' .. tier .. 's')
		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 = {}, {}, {}, {}

	-- Hide Lemon cake
	foundIDs[1029] = true
	foundIDs[1061] = true

	-- Cooked food
	for i, recipe in ipairs(SkillData.Cooking.Recipes) do
		foundIDs[recipe.itemID] = true
		local cookedItem = Items.getItemByID(recipe.itemID)
		if cookedItem ~= nil then
			local catIdx = recipe.category + 1
			-- 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

	-- Harvested foods
	for i, item in ipairs(ItemData.Items) do
		if item.grownItemID ~= nil and not foundIDs[item.grownItemID] then
			-- Item is grown from farming and can be eaten
			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

	-- Other foods, must be checked after cooked & harvested foods have been identified
	for i, item in ipairs(ItemData.Items) do
		if item.canEat 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 = {
		Icons.Icon({'Normal Cooking Fire', 'Cooking Fire', type='upgrade', nolink=true}),
		Icons.Icon({'Basic Furnace', 'Furnace', type='upgrade', nolink=true}),
		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" 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 catID, foodTable in ipairs(cookedFood) do
		table.insert(resultPart, '\r\n|-\r\n!scope="row"| ' .. cookingCatHeader[catID])
		table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getFoodList(foodTable))
	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.Potions) do
		if potList[potData.category] == nil then
			potList[potData.category] = {}
		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" 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(catList) do
		-- Compile list of potions
		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)
		for j, potData in ipairs(potList[catData.categoryID]) 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 .. ' Potions')
		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 Shared.skpairs(SkillData.Prayer) do
		table.insert(prayerList, { ["name"] = prayer.name, ["order"] = prayer.prayerLevel })
	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" 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 catNames = { 'Standard', 'Combination' }
	local runeList = { {}, {} }
	for i, recipe in ipairs(SkillData.Runecrafting.Recipes) do
		local catIdx = recipe.category + 1
		if catNames[catIdx] ~= nil then
			local item = Items.getItemByID(recipe.itemID)
			if item ~= nil then
				table.insert(runeList[catIdx], { ["name"] = item.name, ["order"] = recipe.level })
			end
		end
	end

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan="2"|' .. Icons.Icon({'Runes', type='item', img='Air Rune'}))
	for catIdx, catName in ipairs(catNames) do
		table.sort(runeList[catIdx], 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"|' .. catName .. ' Runes')

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

	return table.concat(resultPart)
end

function p.getSkillcapeNavbox(frame)
	local capeList = Shop.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
	table.sort(capeList, function(a, b)
		if a.cost.gp == b.cost.gp then
			return a.name < b.name
		else
			return a.cost.gp < b.cost.gp
		end
	end)

	local capeText = {}
	for i, purch in ipairs(capeList) do
		if purch.contains ~= nil and purch.contains.items ~= nil then
			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

	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable" 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 = { ["standard"] = {}, ["curse"] = {}, ["aurora"] = {}, ["ancient"] = {}, ["alt"] = {} }
	local catData = {
		{ ["name"] = 'standard', ["header"] = '[[Magic#Standard_Magic|Standard Spells]]', ["imgType"] = 'spell' },
		{ ["name"] = 'curse', ["header"] = '[[Magic#Curses|Curses]]', ["imgType"] = 'curse' },
		{ ["name"] = 'aurora', ["header"] = '[[Magic#Auroras|Auroras]]', ["imgType"] = 'aurora' },
		{ ["name"] = 'ancient', ["header"] = '[[Magic#Ancient_Magicks|Ancient Magicks]]', ["imgType"] = 'spell' },
		{ ["name"] = 'alt', ["header"] = '[[Alternative_Magic|Alt Magic]]', ["imgType"] = 'spell' },
	}

	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

	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" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan=2|' .. Icons.Icon({'Spells', type='skill', img='Magic'}))
	for i, catDefn in ipairs(catData) do
		table.sort(spellTable[catDefn.name], 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"| ' .. catDefn.header)
		table.insert(resultPart, '\r\n|style="text-align:center;| ' .. getSpellList(spellTable[catDefn.name], catDefn.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.Marks) do
		local item = Items.getItemByID(recipe.itemID)
		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" 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" 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
			-- Initialise 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, recipe in ipairs(area.fish) do
			local fishItem = Items.getItemByID(recipe.itemID)
			if fishItem ~= nil then
				addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, recipe.level)
			end
		end
	end
	-- Junk items
	for i, itemID in ipairs(SkillData.Fishing.JunkItems) 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[1])
		if item ~= nil then
			local weight = itemDef[2] or 1
			addCatData('Special Items', 'Fishing#Special', item.name, 1 / weight)
		end
	end

	local resultPart = {}
	-- Generate navbox header
	table.insert(resultPart, '{| class="wikitable" 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

return p