Module:Navboxes: Difference between revisions

From Melvor Idle
(getPotionNavbox: Ensure potions sorted by Herblore level requirement & remove 'Potion' name prefix for cleaner appearance)
(Changed Ores & Bars nav to show all ores and bars and sorted by (abyssalLevel+120 or level) to push ItA items to the end)
 
(26 intermediate revisions by 3 users not shown)
Line 2: Line 2:


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


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


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


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


      if item.grownItemID ~= nil then
table.insert(seedsTable[tier], { ["name"] = seed.name, ["level"] = recipe.level })
        local grownItem = Items.getItemByID(item.grownItemID)
table.insert(produceTable[tier], { ["name"] = product.name, ["level"] = recipe.level })
        if grownItem ~= nil then
end
          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
-- Generate output table
  table.insert(resultPart, '{| class="wikitable mw-collapsible" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
  table.insert(resultPart, '\r\n!colspan="2" style="padding-left:64px;"|' .. Icons.Icon({'Farming', type='skill'}))
table.insert(resultPart, '\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"|' .. Icons.Icon({'Farming', type='skill'}))


  local getItemList = function(itemTable)
local getItemList = function(itemTable)
      local listPart = {}
local listPart = {}
      for i, item in ipairs(itemTable) do
for i, item in ipairs(itemTable) do
        table.insert(listPart, Icons.Icon({item.name, type='item'}))
table.insert(listPart, Icons.Icon({item.name, type='item'}))
      end
end
      return table.concat(listPart, ' • ')
return table.concat(listPart, ' • ')
    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
-- Generate table section for each tier
  local tierList = {}
for i, category in ipairs(SkillData.Farming.categories) do
  for tier, seeds in pairs(seedsTable) do
local tier = category.id
    table.insert(tierList, tier)
-- Sort tables by Farming level order
  end
table.sort(seedsTable[tier], sortFunc)
  table.sort(tierList, function(a, b) return a < b end)
table.sort(produceTable[tier], sortFunc)


  -- Generate table section for each tier
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| ' .. category.name)
  for i, tier in pairs(tierList) do
table.insert(resultPart, '\r\n|-\r\n!scope="row"| Seeds')
    -- Sort tables by Farming level order
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(seedsTable[tier]))
    table.sort(seedsTable[tier], sortFunc)
table.insert(resultPart, '\r\n|-\r\n!scope="row"| Produce')
    table.sort(produceTable[tier], sortFunc)
table.insert(resultPart, '\r\n|style="text-align:center;"| ' .. getItemList(produceTable[tier]))
end
table.insert(resultPart, '\r\n|}')


    table.insert(resultPart, '\r\n|-\r\n!colspan="2"| ' .. tier .. 's')
return table.concat(resultPart)
    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
end


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


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


  -- Harvested food first
-- 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.grownItemID ~= nil then
if item.healsFor ~= nil and not foundIDs[item.id] then
      local grownItem = Items.getItemByID(item.grownItemID)
-- Item can be eaten but isn't cooked nor harvested
      if grownItem ~= nil and grownItem.canEat then
table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
        table.insert(harvestedFood, { ["name"] = grownItem.name, ["order"] = item.farmingLevel })
foundIDs[item.id] = true
        foundIDs[grownItem.id] = true
end
      end
end
    end
  end


  -- Any cooked & other food
-- Sort food lists
  for i, item in ipairs(ItemData.Items) do
local sortFunc = function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end
    -- If an item can be eaten then it must be food
for i, items in pairs(cookedFood) do
    if foundIDs[i - 1] == nil and item.canEat then
table.sort(cookedFood[i], sortFunc)
      if item.cookingCategory ~= nil then
end
        -- Item is cooked, such food items are split by category
table.sort(harvestedFood, sortFunc)
        if cookedFood[item.cookingCategory + 1] == nil then
table.sort(otherFood, sortFunc)
          cookedFood[item.cookingCategory + 1] = {}
        end


        local perfectName = nil
-- Generate food lists for final output
        if item.perfectItem ~= nil then
local cookingCatHeader = {
          local perfectItem = Items.getItemByID(item.perfectItem)
{ id = 'melvorD:Fire', header = Icons.Icon({'Normal Cooking Fire', 'Cooking Fire', type='upgrade', nolink=true}) },
          if perfectItem ~= nil then
{ id = 'melvorD:Furnace', header = Icons.Icon({'Basic Furnace', 'Furnace', type='upgrade', nolink=true}) },
            perfectName = perfectItem.name
{ id = 'melvorD:Pot', header = Icons.Icon({'Basic Pot', 'Pot', type='upgrade', nolink=true}) }
            foundIDs[item.perfectItem] = true
}
          end
local getFoodList = function(foodTable)
        end
local listPart = {}
        table.insert(cookedFood[item.cookingCategory + 1], { ["name"] = item.name, ["order"] = item.cookingLevel, ["perfectName"] = perfectName })
if type(foodTable) == 'table' then
      else
for i, food in ipairs(foodTable) do
        -- Item cannot be cooked or grown, but can be eaten
local foodText = Icons.Icon({food.name, type='item'})
        table.insert(otherFood, { ["name"] = item.name, ["order"] = item.id })
if food.perfectName ~= nil then
      end
foodText = Icons.Icon({food.perfectName, type='item', notext=true}) .. ' ' .. foodText
      foundIDs[i - 1] = true
end
    end
table.insert(listPart, foodText)
  end
end
end
return table.concat(listPart, ' • ')
end


  -- Sort food lists
local resultPart = {}
  local sortFunc = function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:0 auto 10px; clear:both; width: 100%"')
  for i, items in pairs(cookedFood) do
table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2"| ')
    table.sort(cookedFood[i], sortFunc)
table.insert(resultPart, Icons.Icon({'Food', type='item', img='Crab'}))
  end
table.insert(resultPart, '\r\n|-\r\n!colspan="2"| Cooked')
  table.sort(harvestedFood, sortFunc)
for i, cat in ipairs(cookingCatHeader) do
  table.sort(otherFood, sortFunc)
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|}')


  -- Generate food lists for final output
return table.concat(resultPart)
  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"| [[File:Crab_(item).svg|25px|link=Food]] [[Food]]')
  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
end


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


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


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


  return table.concat(resultPart)
return table.concat(resultPart)
end
end


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


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


function p.getRuneNavbox(frame)
function p.getRuneNavbox(frame)
  -- Assumes all runes are from Runecrafting, which may need revising in future updates
-- Assumes all runes are from Runecrafting, which may need revising in future updates
  local runeList = { ["Standard"] = {}, ["Combination"] = {} }
local categoryIDs = {
  for i, item in ipairs(ItemData.Items) do
'melvorF:StandardRunes',
    if item.category == 'Runecrafting' and item.type ~= nil and item.type == 'Rune' and item.runecraftingLevel ~= nil then
'melvorF:CombinationRunes',
      local runeType = (type(item.providesRune) == 'table' and Shared.tableCount(item.providesRune) > 1 and 'Combination') or 'Standard'
'melvorItA:AbyssalRunes',
      table.insert(runeList[runeType], { ["name"] = item.name, ["order"] = item.runecraftingLevel })
'melvorItA:AbyssalComboRunes'
    end
}
  end
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 = {}
local resultPart = {}
  table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
  table.insert(resultPart, '\r\n!colspan="2"|[[File:Air_Rune_(item).svg|25px|link=Runes]] [[Runes]]')
table.insert(resultPart, '\r\n!colspan="2"|' .. Icons.Icon({'Runes', type='item', img='Air Rune'}))
  for i, cat in ipairs({'Standard', 'Combination'}) do
for i, catID in ipairs(categoryIDs) do
    table.sort(runeList[cat], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
local category = GameData.getEntityByID(SkillData.Runecrafting.categories, catID)
    table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. cat .. ' Runes')
if category ~= nil then
table.sort(runeList[catID], function(a, b) return (a.order == b.order and a.name < b.name) or a.order < b.order end)
table.insert(resultPart, '\r\n|-\r\n!scope="row"|' .. category.name)


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


  return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getSkillcapeNavbox(frame)
function p.getSkillcapeNavbox(frame)
  local capeList = Shop.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
local capeList = {
  table.sort(capeList, function(a, b)
["Normal"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, false, nil) end),
                        if a.cost.gp == b.cost.gp then
["Superior"] = Shop.getPurchases(function(purch) return Shop.isSkillcapePurchase(purch, true, nil) end)
                          return a.name < b.name
}
                        else
                          return a.cost.gp < b.cost.gp
                        end
                      end)


  local capeText = {}
local capeNames = {}
  for i, purch in ipairs(capeList) do
for catName, subList in pairs(capeList) do
    if purch.contains ~= nil and purch.contains.items ~= nil then
for i, cape in ipairs(subList) do
      local item = Items.getItemByID(purch.contains.items[1][1])
capeNames[cape.id] = Shop._getPurchaseName(cape)
      if item ~= nil then
end
        table.insert(capeText, Icons.Icon({item.name, type='item'}))
      end
table.sort(capeList[catName], function(a, b)
    end
local costA, costB = Shop._getPurchaseSortValue(a), Shop._getPurchaseSortValue(b)
  end
if costA == costB then
return capeNames[a.id] < capeNames[b.id]
else
return costA < costB
end
end)
end


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


  return table.concat(resultPart)
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
end


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


  for i, spell in ipairs(MagicData.Spells) do
local getSpellList = function(spellTable, imgType)
  table.insert(spellTable['standard'], { ["name"] = spell.name, ["order"] = spell.level })
local listPart = {}
  end
for i, obj in ipairs(spellTable) do
  for i, spell in ipairs(MagicData.Curses) do
table.insert(listPart, Icons.Icon({obj.name, type=imgType}))
  table.insert(spellTable['curse'], { ["name"] = spell.name, ["order"] = spell.level })
end
  end
return table.concat(listPart, ' • ')
  for i, spell in ipairs(MagicData.Auroras) do
end
  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|[[File:Magic_(skill).svg|25px|link=Spells]] [[Spells]]')
  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)
local resultPart = {}
end
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|}')


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


function p.getThievingNavbox()
function p.getFishingNavbox(frame)
  local returnPart = {}
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


  -- Create table header
-- Fishing areas
  table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
-- Iterate through all fishing areas, identifying fish within each
  table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]')
for i, area in ipairs(SkillData.Fishing.areas) do
  table.insert(returnPart, '|-\r\n|')
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 npcData = {}
local resultPart = {}
  for i, npc in ipairs(SkillData.Thieving.NPCs) do
-- Generate navbox header
    table.insert(npcData, {["level"] = npc.level, ["name"] = npc.name})
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
  end
table.insert(resultPart, '\r\n|-\r\n!colspan="2" | ' .. Icons.Icon({'Fishing', type='skill'}))
  table.sort(npcData, function(a, b) return a.level < b.level end)
-- 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


  local npcList = {}
table.insert(resultPart, '\r\n|-\r\n!class="center" style="min-width:140px" | [[' .. (cat.link or cat.name) .. '|' .. cat.name .. ']]')
  -- Create row for each NPC
table.insert(resultPart, '\r\n| class="center" style="vertical-align:middle;" | ' .. table.concat(itemList, ' • '))
  for i, npc in ipairs(npcData) do
end
    table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
table.insert(resultPart, '\r\n|}')
  end
  table.insert(returnPart, table.concat(npcList, ' • '))
  table.insert(returnPart, '|}')


  return table.concat(returnPart, '\r\n')
return table.concat(resultPart)
end
end


function p.getFishingNavbox()
function p.getOreBarNavbox(frame)
  local categoryHeader = {}
local categoryList = { 'Ores', 'Bars' }
  local categoryItems = {}
local categoryItems = { ["Ores"] = {}, ["Bars"] = {} }
  local addCatData = function(cat, catLink, itemName, itemOrder)
local barOreIDs = {}
                      if categoryItems[cat] == nil then
-- Compile list of bars
                        -- Initialise category
for i, recipe in ipairs(SkillData.Smithing.recipes) do
                        table.insert(categoryHeader, { ["name"] = cat, ["link"] = catLink })
if Shared.contains({'melvorD:Bars','melvorItA:AbyssalBars'}, recipe.categoryID) then
                        categoryItems[cat] = {}
local item = Items.getItemByID(recipe.productID)
                      end
if item ~= nil then
                      table.insert(categoryItems[cat], { ["name"] = itemName, ["order"] = itemOrder })
local displayName = string.gsub(item.name, ' Bar$', '')
                    end
local level = recipe.level
 
if recipe.abyssalLevel then
  -- Identify fishing catchable items
level = recipe.abyssalLevel + 120
  local fishingToItemID = {}
end
  local junkItems = {}
table.insert(categoryItems['Bars'], { ["name"] = item.name, ["display"] = displayName, ["order"] = level })
  local specialItems = {}
end
  for i, item in ipairs(ItemData.Items) do
end
    if item.fishingID ~= nil then
end
      -- Create FishingID to item map
-- Compile list of ores
      fishingToItemID[item.fishingID] = item
for i, recipe in ipairs(SkillData.Mining.rockData) do
    elseif item.category == 'Fishing' and item.type == 'Junk' then
local item = Items.getItemByID(recipe.productId)
      table.insert(junkItems, item)
if item ~= nil then
    elseif item.fishingCatchWeight ~= nil then
local level = recipe.level
      table.insert(specialItems, item)
if recipe.abyssalLevel then
    end
level = recipe.abyssalLevel + 120
  end
end
  -- Fishing areas
table.insert(categoryItems['Ores'], { ["name"] = item.name, ["display"] = recipe.name, ["order"] = level })
  -- Iterate through all fishing areas, identifying fish within each
end
  for i, area in ipairs(SkillData.Fishing.Areas) do
end
    for j, fishID in ipairs(area.fish) do
      local fishItem = fishingToItemID[fishID]
local resultPart = {}
      if fishItem ~= nil then
-- Generate navbox header
        addCatData(area.name, 'Fishing#Fishing Areas', fishItem.name, fishItem.fishingLevel)
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
      end
table.insert(resultPart, '\r\n|-\r\n!style="background-color:#275C87;color:#FFFFFF;padding-left:64px;" colspan="2" | ' .. '<b>Ores & Bars</b>')
    end
-- Generate navbox content
  end
for i, cat in ipairs(categoryList) do
  -- Junk items
table.sort(categoryItems[cat], function(a, b) return (a.order == b.order and a.display < b.display) or a.order < b.order end)
  for i, item in ipairs(junkItems) do
local listPart = {}
    addCatData('Junk', 'Fishing#Junk', item.name, 1)
for j, listItem in ipairs(categoryItems[cat]) do
  end
table.insert(listPart, Icons.Icon({ listItem.name, listItem.display, type = 'item' }))
  -- Special items
end
  for i, item in ipairs(specialItems) do
table.insert(resultPart, '\r\n|-\r\n! ' .. cat .. '\r\n|style="text-align:center;"| ' .. table.concat(listPart, ' • '))
    addCatData('Special Items', 'Fishing#Special', item.name, 1 / (item.fishingCatchWeight or 1))
end
  end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end


  local resultPart = {}
function p.getPetNavbox(frame)
  -- Generate navbox header
local resultPart = {}
  table.insert(resultPart, '{| class="wikitable" style="margin:auto; clear:both; width: 100%"')
table.insert(resultPart, '{| class="wikitable navigation-not-searchable" style="margin:auto; text-align:center; clear:both; width: 100%"')
  table.insert(resultPart, '\r\n|-\r\n!colspan="2" | ' .. Icons.Icon({'Fishing', type='skill'}))
table.insert(resultPart, '\r\n|-\r\n!colspan="2"|[[Pets]]')
  -- 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 .. ']]')
local petListOrder = { 'skill', 'boss', 'other' }
    table.insert(resultPart, '\r\n| class="center" style="vertical-align:middle;" | ' .. table.concat(itemList, ' '))
local petList = {
  end
["skill"] = {},
  table.insert(resultPart, '\r\n|}')
["boss"] = {},
["other"] = {}
}
for i, petData in ipairs(GameData.rawData.pets) do
local source = Pets._getPetSource(petData)
local listCat = 'other'
if type(source) == 'table' and source.type ~= nil then
if source.type == 'skill' then
listCat = 'skill'
elseif Shared.contains({'dungeon', 'abyssDepth', 'stronghold'}, source.type) then
listCat = 'boss'
else
listCat = 'other'
end
end
table.insert(petList[listCat], petData.name)
end


  return table.concat(resultPart)
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
end


return p
return p

Latest revision as of 19:37, 5 August 2024

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

-- New module to stop navbox generators cluttering other modules

local p = {}

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

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

	return tostring(resultTable)
end

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

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

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

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

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

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

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

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

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

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

	return table.concat(resultPart)
end

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

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

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

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

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

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

	return table.concat(resultPart)
end

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

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

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

	return table.concat(resultPart)
end

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

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

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

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

	return table.concat(resultPart)
end

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

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

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

	return table.concat(resultPart)
end

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

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

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

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

	return table.concat(resultPart)
end

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

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

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

	return table.concat(resultPart)
end

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

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

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

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

	return table.concat(resultPart)
end

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

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

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

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

return p