Module:Navboxes

From Melvor Idle

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
				-- Initialize 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 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

	-- 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
		if not foundIDs[recipe.itemID] then
			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
	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 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 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 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(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 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 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 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 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 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 = { ["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 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, 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 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, 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 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.category == 0 then
			-- Category 0 = Bars
			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.Rocks) do
		if barOreIDs[recipe.oreID] then
			local item = Items.getItemByID(recipe.oreID)
			if item ~= nil then
				table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = recipe.levelRequired })
			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