Module:Navboxes

From Melvor Idle
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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 end),
		["Unholy Prayers"] = Prayer.getPrayers(function(prayer) return prayer.isUnholy 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 = {}
		table.insert(resultPart, '\r\n|-\r\n!style="text-align:center;"| ' .. catName)
		for i, prayer in ipairs(subList) do
			table.insert(prayerText, Icons.Icon({prayer.name, type='prayer', expicon = Icons.getExpansionIcon(prayer.id)}))
		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' }
	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 purch.category == 'melvorD:Skillcapes' end),
		["Superior"] = Shop.getPurchases(function(purch) return purch.category == 'melvorTotH:SuperiorSkillcapes' 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
	-- 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.productID)
			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

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 source.type == 'dungeon' 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