Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
Use printError function
(Added 'no item' drop chance to NPC table)
(Use printError function)
(39 intermediate revisions by 3 users not shown)
Line 1: Line 1:
--This module should avoid including skill specific functions which generate
--output for wiki pages, especially those which require() other modules. For
--these functions, consider using the appropriate module from the below list.
--Some skills have their own modules:
--Some skills have their own modules:
--Module:Magic for Magic
--Module:Magic for Magic
--Module:Prayer for Prayer
--Module:Prayer for Prayer
--Module:Agility for Agility
--Module:Skills/Agility for Agility
--Module:Skills/Summoning for Summoning
--Module:Skills/Gathering for Mining, Fishing, Woodcutting
--Module:Skills/Gathering for Mining, Fishing, Woodcutting
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc.
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc.
--Also be aware of:
--Module:Navboxes for navigation boxes appearing near the bottom of pages


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


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Constants = require('Module:Constants')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Items = require('Module:Items')
local Items = require('Module:Items')
local ItemSourceTables = require('Module:Items/SourceTables')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')


local MasteryCheckpoints = {.1, .25, .5, .95}
-- Thieving
function p.getThievingNPCByID(npcID)
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
end


function p.getSkillID(skillName)
function p.getThievingNPC(npcName)
  for skName, ID in Shared.skpairs(Constants.skill) do
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName)
    if skName == skillName then
      return ID
    end
  end
  return nil
end
end


function p.getSkillName(skillID)
function p.getThievingNPCArea(npc)
  for skName, ID in Shared.skpairs(Constants.skill) do
for i, area in ipairs(SkillData.Thieving.areas) do
    if ID == skillID then
for j, npcID in ipairs(area.npcIDs) do
      return skName
if npcID == npc.id then
    end
return area
  end
end
  return nil
end
end
end
end


function p.getThievingNPCByID(ID)
function p._getThievingNPCStat(npc, statName)
  local result = Shared.clone(SkillData.Thieving[ID + 1])
local result = nil
  if result ~= nil then
 
    result.id = ID
if statName == 'level' then
  end
result = Icons._SkillReq('Thieving', npc.level)
  return result
elseif statName == 'maxHit' then
end
result = npc.maxHit * 10
elseif statName == 'area' then
local area = p.getThievingNPCArea(npc)
result = area.name
else
result = npc[statName]
end
 
if result == nil then
result = ''
end


function p.getThievingNPC(name)
return result
  local result = nil
  for i, npc in pairs(SkillData.Thieving) do
    if name == npc.name then
      result = Shared.clone(npc)
      result.id = i - 1
      break
    end
  end
  return result
end
end


function p.getThievingNPCStat(frame)
function p.getThievingNPCStat(frame)
  local args = frame.args ~= nil and frame.args or frame
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
  local npcName = args[1]
local statName = frame.args ~= nil and frame.args[2] or frame[2]
  local statName = args[2]
local npc = p.getThievingNPC(npcName)
  local npc = p.getThievingNPC(npcName)
if npc == nil then
  if npc == nil then
return Shared.printError('Invalid Thieving NPC ' .. npcName)
    return 'ERROR: Failed to find Thieving NPC with name ' .. name .. '[[Category:Pages with script errors]]'
end
  end
 
 
return p._getThievingNPCStat(npc, statName)
  return p._getThievingNPCStat(npc, statName)
end
end


function p._getThievingNPCStat(npc, stat)
function p.getThievingSourcesForItem(itemID)
  local result = npc[stat]
local resultArray = {}
  -- Overrides below
local areaNPCs = {}
  if stat == 'maxHit' then
    result = result * 10
  elseif stat == 'lootTable' then
    return p._formatLootTable(npc['lootTable'], 0.75)
  elseif stat == 'requirements' then
  if npc['level'] ~= nil then
    result = Icons._SkillReq('Thieving', npc['level'], true)
  else
    result = 'None'
  end
  end


  return result
--First check area unique drops
end
--If an area drops the item, add all the NPC ids to the list so we can add them later
for i, area in pairs(SkillData.Thieving.areas) do
for j, drop in pairs(area.uniqueDrops) do
if drop.id == itemID then
for k, npcID in ipairs(area.npcIDs) do
areaNPCs[npcID] = drop.quantity
end
break
end
end
end


function p._formatLootTable(lootTableIn, chanceMultIn)
--Now go through and get drop chances on each NPC if needed
  -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
for i, npc in pairs(SkillData.Thieving.npcs) do
  if Shared.tableCount(lootTableIn) == 0 then
local totalWt = 0
    return ''
local dropWt = 0
  end
local dropQty = { min = 0, max = 0 }
for j, drop in ipairs(npc.lootTable) do
totalWt = totalWt + drop.weight
if drop.itemID == itemID then
dropWt = drop.weight
dropQty = { min = drop.minQuantity, max = drop.maxQuantity }
end
end
if dropWt > 0 then
table.insert(resultArray, {npc = npc.name, minQty = dropQty.min, maxQty = dropQty.max, wt = dropWt * SkillData.Thieving.itemChance, totalWt = totalWt * 100, level = npc.level, npcID = npc.id})
end


  local chanceMult = (chanceMultIn or 1) * 100
--Chance of -1 on unique drops is to indicate variable chance
  local lootTable = Shared.clone(lootTableIn)
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then
  -- Sort table from most to least common drop
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.quantity, maxQty = npc.uniqueDrop.quantity, wt = -1, totalWt = -1, level = npc.level, npcID = npc.id})
  table.sort(lootTable, function(a, b)
end
                          if a[2] == b[2] then
                            return a[1] < b[1]
                          else
                            return a[2] > b[2]
                          end
                        end)


  local totalWeight = 0
if areaNPCs[npc.id] ~= nil then
  for i, drop in pairs(lootTable) do
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level, npcID = npc.id})
    totalWeight = totalWeight + drop[2]
end
  end
end
  if totalWeight == 0 then
    return ''
  end


  -- Get the length (in characters) of the largest drop chance so that they can be right aligned
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
  -- [4/16/21]: Adding info for no drop
if drop.itemID == itemID then
  local maxDropLen = math.max(string.len(Shared.round(25, 2, 2)), string.len(Shared.round(lootTable[1][2] / totalWeight * chanceMult, 2, 2)))
if drop.npcs == nil then
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1, npcID = itemID})
else
for j, npcID in ipairs(drop.npcs) do
local npc = p.getThievingNPCByID(npcID)
if npc ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = npc.level, npcID = npc.id})
end
end
end
end
end


  local returnPart = {}
return resultArray
  table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(Shared.round(25, 2, 2))) * 2)) .. '25.00% No Item')
end
  for i, drop in pairs(lootTable) do
    local item, itemText, dropChance = Items.getItemByID(drop[1]), nil, Shared.round(drop[2] / totalWeight * chanceMult, 2, 2)
    if item == nil then
      itemText = 'Unknown'
    else
      itemText = Icons.Icon({item.name, type='item'})
    end
    table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(dropChance)) * 2)) .. dropChance .. '% ' .. itemText)
  end


  return table.concat(returnPart, '\r\n')
-- Astrology
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end
end


function p.getThievingNPCTable()
function p.getConstellation(constName)
  local returnPart = {}
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end


  -- Create table header
function p.getConstellations(checkFunc)
  table.insert(returnPart, '{| class="wikitable sortable stickyHeader"')
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
  table.insert(returnPart, '|- class="headerRow-0"\r\n!Target!!Name!!' .. Icons.Icon({'Thieving', type='skill', notext=true}).. ' Level!!Experience!!Max Hit!!Max Coins')
 
  local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    local linkText = npc.name
    if linkOverrides[npc.name] ~= nil then
      linkText = linkOverrides[npc.name] .. '|' .. npc.name
    end
    table.insert(returnPart, '|-\r\n|style="text-align: left;" |' .. Icons.Icon({npc.name, type='thieving', size=50, notext=true}))
    table.insert(returnPart, '|style="text-align: left;" |[[' .. linkText .. ']]')
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'level'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'xp'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxHit'))
    table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxCoins'))
  end
  table.insert(returnPart, '|}')
 
  return table.concat(returnPart, '\r\n')
end
end


function p.getThievingNavbox()
-- For a given constellation cons and modifier value modValue, generates and returns
  local returnPart = {}
-- a table of modifiers, much like any other item/object elsewhere in the game.
-- includeStandard: true|false, determines whether standard modifiers are included
-- includeUnique: true|false, determines whether unique modifiers are included
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects
function p._buildAstrologyModifierArray(cons, modValue, includeStandard, includeUnique, isDistinct, asKeyValue)
-- Temporary function to determine if the table already contains a given modifier
local containsMod = function(modList, modNew)
for i, modItem in ipairs(modList) do
-- Check mod names & value data types both equal
if modItem[1] == modNew[1] and type(modItem[2]) == type(modNew[2]) then
if type(modItem[2]) == 'table' then
if Shared.tablesEqual(modItem[2], modNew[2]) then
return true
end
elseif modItem[2] == modNew[2] then
return true
end
end
end
return false
end


  -- Create table header
local addToArray = function(modArray, modNew)
  table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
  table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]')
table.insert(modArray, modNew)
  table.insert(returnPart, '|-\r\n|')
end
 
end
  local npcList = {}
  local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    local linkText = npc.name
    if linkOverrides[npc.name] ~= nil then
      linkText = linkOverrides[npc.name] .. '|' .. npc.name
    end
    table.insert(npcList, Icons.Icon({npc.name, type='thieving', notext=true}) .. ' [[' .. linkText .. ']]')
  end
  table.insert(returnPart, table.concat(npcList, ' • '))
  table.insert(returnPart, '|}')


  return table.concat(returnPart, '\r\n')
local modTypes = {}
end
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end


function p.getMasteryUnlockTable(frame)
local modArray = {}
  local skillName = frame.args ~= nil and frame.args[1] or frame
local isSkillMod = {}
  local skillID = p.getSkillID(skillName)
--Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27]
  if skillID == nil then
local groupNum = 0
    return "ERROR: Failed to find a skill ID for "..skillName
  end


  local unlockTable = SkillData.MasteryUnlocks[skillID]
for _, modType in ipairs(modTypes) do
  if unlockTable == nil then
for i, modTypeData in ipairs(cons[modType]) do
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
groupNum = groupNum + 1
  end
local modVal = nil
if modValue ~= nil then
modVal = modValue
else
modVal = modTypeData.incrementValue * modTypeData.maxCount
end
for j, modifier in ipairs(modTypeData.modifiers) do
local modEntry = (modifier.skill ~= nil and { skillID = modifier.skill, value = modVal }) or modVal
addToArray(modArray, {modifier.key, modEntry, group = groupNum})
end
end
end


  local result = '{|class="wikitable"\r\n!Level!!Unlock'
if asKeyValue then
  for i, unlock in Shared.skpairs(unlockTable) do
local modArrayKV = {}
    result = result..'\r\n|-'
for i, modDefn in ipairs(modArray) do
    result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
local modName, modVal = modDefn[1], modDefn[2]
  end
local isSkill = type(modVal) == 'table' and modVal.skillID ~= nil
  result = result..'\r\n|}'
if modArrayKV[modName] == nil then
  return result
modArrayKV[modName] = (isSkill and { modVal } or modVal)
elseif isSkill then
table.insert(modArrayKV[modName], modVal)
else
modArrayKV[modName] = modArrayKV[modName] + modVal
end
end
return modArrayKV
else
return modArray
end
end
end


function p.getMasteryCheckpointTable(frame)
-- Mastery
  local skillName = frame.args ~= nil and frame.args[1] or frame
function p.getMasteryUnlockTable(frame)
  local skillID = p.getSkillID(skillName)
local skillName = frame.args ~= nil and frame.args[1] or frame
  if skillID == nil then
local skillID = Constants.getSkillID(skillName)
    return "ERROR: Failed to find a skill ID for "..skillName
if skillID == nil then
  end
return Shared.printError('Failed to find a skill ID for ' .. skillName)
end


  if SkillData.MasteryCheckpoints[skillID] == nil then
local _, localSkillID = GameData.getLocalID(skillID)
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
-- Clone so that we can sort by level
  end
local unlockTable = Shared.clone(SkillData[localSkillID].masteryLevelUnlocks)
if unlockTable == nil then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
end
table.sort(unlockTable, function(a, b) return (a.level == b.level and a.descriptionID < b.descriptionID) or a.level < b.level end)


  local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
local result = '{|class="wikitable"\r\n!Level!!Unlock'
  local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
for i, unlock in ipairs(unlockTable) do
 
result = result..'\r\n|-'
  local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
result = result..'\r\n|'..unlock.level..'||'..unlock.description
  for i, bonus in Shared.skpairs(bonuses) do
end
    result = result..'\r\n|-'
result = result..'\r\n|}'
    result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||'
return result
    result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus
  end
  result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
  result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
  result = result..'\r\n|}'
  return result
end
end


function p._getFarmingTable(category)
function p.getMasteryCheckpointTable(frame)
  local seedList = {}
local skillName = frame.args ~= nil and frame.args[1] or frame
  if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
local skillID = Constants.getSkillID(skillName)
    seedList = Items.getItems(function(item) return item.tier == category end)
if skillID == nil then
  else
return Shared.printError('Failed to find a skill ID for ' .. skillName)
    return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
end
  end


  local result = '{|class="wikitable sortable stickyHeader"'
local _, localSkillID = GameData.getLocalID(skillID)
  result = result..'\r\n|- class="headerRow-0"'
local checkpoints = SkillData[localSkillID].masteryCheckpoints
  result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
if checkpoints == nil then
  result = result..'!!XP!!Growth Time!!Seed Value'
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
  if category == 'Allotment' then
end
    result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
  elseif category == 'Herb' then
    result = result..'!!colspan="2"|Herb!!Herb Value'
  elseif category == 'Tree' then
    result = result..'!!colspan="2"|Logs!!Log Value'
  end
  result = result..'!!Seed Sources'
 
  table.sort(seedList, function(a, b) return a.farmingLevel < b.farmingLevel end)


  for i, seed in pairs(seedList) do
local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap
    result = result..'\r\n|-'
local checkpointPct = GameData.rawData.masteryCheckpoints
    result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
    result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
for i, checkpointDesc in ipairs(checkpoints) do
    result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
result = result..'\r\n|-'
    result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
result = result..'\r\n|'..checkpointPct[i]..'%||'
 
result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc
    local crop = Items.getItemByID(seed.grownItemID)
end
    result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
    if category == 'Allotment' then
result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
      result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
result = result..'\r\n|}'
    end
return result
    result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
    result = result..'||'..ItemSourceTables._getItemSources(seed)
  end
 
  result = result..'\r\n|}'
  return result
end
end


function p.getFarmingTable(frame)
function p.getMasteryTokenTable()
  local category = frame.args ~= nil and frame.args[1] or frame
local baseTokenChance = 18500
local masterySkills = {}
local CCI = Items.getItemByID('melvorD:Clue_Chasers_Insignia')
if CCI == nil then return '' end


  return p._getFarmingTable(category)
-- Build table of mastery skills
end
for skillLocalID, skill in pairs(SkillData) do
if skill.masteryTokenID ~= nil then
table.insert(masterySkills, skill)
end
end
table.sort(masterySkills,
function(a, b)
if a.milestoneCount == b.milestoneCount then
return a.name < b.name
else
return a.milestoneCount > b.milestoneCount
end
end)


function p.getFarmingFoodTable(frame)
-- Generate output table
  local result = '{| class="wikitable sortable stickyHeader"'
local resultPart = {}
  result = result..'\r\n|- class="headerRow-0"'
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})
  result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
  result = result..'!!Healing!!Value'
 
  local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end)


  table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)


  for i, item in Shared.skpairs(itemArray) do
for i, skill in ipairs(masterySkills) do
    local crop = Items.getItemByID(item.grownItemID)
local token = Items.getItemByID(skill.masteryTokenID)
    if crop.healsFor ~= nil and crop.healsFor > 0 then
local denom = math.floor(baseTokenChance / skill.milestoneCount)
      result = result..'\r\n|-'
local denomCCI = Shared.round(baseTokenChance / (skill.milestoneCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0)
      result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
      result = result..'||style="text-align:right;"|'..item.farmingLevel
      result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
      result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
    end
  end


  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
table.insert(resultPart, '\r\n|' .. Icons.Icon({skill.name, type='skill'}))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
end
table.insert(resultPart, '\r\n|}')


  return result
return table.concat(resultPart)
end
end


function p.getFarmingPlotTable(frame)
-- Skill unlock costs for Adventure game mode
  local areaName = frame.args ~= nil and frame.args[1] or frame
function p.getSkillUnlockCostTable()
  local patches = nil
local advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure')
  for i, area in Shared.skpairs(SkillData.Farming.Patches) do
if advMode ~= nil then
    if area.areaName == areaName then
local unlockCount = Shared.tableCount(GameData.skillData) - Shared.tableCount(advMode.startingSkills)
      patches = area.patches
local costLength = Shared.tableCount(advMode.skillUnlockCost)
      break
local returnPart = {}
    end
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
  end
  if patches == nil then
    return "ERROR: Invalid area name.[[Category:Pages with script errors"
  end


  local result = '{|class="wikitable"'
local accCost = 0
  result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
for i = 1, unlockCount, 1 do
local cost = advMode.skillUnlockCost[math.min(i, costLength)]
accCost = accCost + cost
table.insert(returnPart, '|-')
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
end
table.insert(returnPart, '|}')


  for i, patch in Shared.skpairs(patches) do
return table.concat(returnPart, '\r\n')
    result = result..'\r\n|-\r\n|'..i
end
    result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
    if patch.cost == 0 then
      result = result..'||Free'
    else
      result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
    end
  end
 
  result = result..'\r\n|}'
  return result
end
end


function p.getPotionNavbox(frame)
-- Accepts 1 parameter, being either:
  --•
--  'Bars', for which a table of all bars is generated, or
  local result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"'
--  A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier
  result = result..'\r\n!colspan=2|'..Icons.Icon({'Herblore', 'Potions', type='skill'})
function p.getSmithingTable(frame)
local tableType = frame.args ~= nil and frame.args[1] or frame


  local CombatPots = {}
-- Has a valid category been passed (by name)?
  local SkillPots = {}
local category = GameData.getEntityByName(SkillData.Smithing.categories, tableType)
  for i, potData in Shared.skpairs(SkillData.Herblore.ItemData) do
if category == nil then
    if potData.category == 0 then
return Shared.printError('Invalid Smithing category: "' .. tableType .. '"')
      table.insert(CombatPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')}))
end
    else
      if potData.name == 'Bird Nests Potion' then
        table.insert(SkillPots, Icons.Icon({"Bird Nest Potion", type='item', img="Bird Nest Potion I"}))
      else
        table.insert(SkillPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')}))
      end
    end
  end


  result = result..'\r\n|-\r\n!Combat Potions\r\n|class="center" style="vertical-align:middle;"'
-- Build a list of recipes to be included, and a list of bars while we're at it
  result = result..'|'..table.concat(CombatPots, ' • ')
-- The bar list will be used later for value/bar calculations
  result = result..'\r\n|-\r\n!Skill Potions\r\n|class="center" style="vertical-align:middle;"'
local recipeList, barIDList = {}, {}
  result = result..'|'..table.concat(SkillPots, ' ')
for i, recipe in ipairs(SkillData.Smithing.recipes) do
  result = result..'\r\n|}'
if recipe.categoryID == category.id then
  return result
local recipeItem = Items.getItemByID(recipe.productID)
end
if recipeItem ~= nil then
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor, expIcon = Icons.getExpansionIcon(recipeItem.id) })
end
elseif recipe.categoryID == 'melvorD:Bars' then
barIDList[recipe.productID] = true
end
end


function p.getSmithingTable(frame)
-- Generate output table
  local tableType = frame.args ~= nil and frame.args[1] or frame
local resultPart = {}
  local bar = nil
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
  if tableType ~= 'Smelting' then
table.insert(resultPart, '\r\n|-class="headerRow-0"')
    bar = Items.getItem(tableType)
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients')
    if bar == nil then
--Adding value/bar for things other than smelting
      return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
if category.id ~= 'melvorD:Bars' then
    elseif bar.type ~= 'Bar' then
table.insert(resultPart, '!!Value/Bar')
      return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
end
    end
  end


  local smithList = {}
table.sort(recipeList, function(a, b)
  for i, item in pairs(ItemData.Items) do
if a.level ~= b.level then
    if item.smithingLevel ~= nil then
return a.level < b.level
      if tableType == 'Smelting' then
else
        if item.type == 'Bar' then
return a.itemName < b.itemName
          table.insert(smithList, item)
end
        end
end)
      else
        for j, req in pairs(item.smithReq) do
          if req.id == bar.id then
            table.insert(smithList, item)
          end
        end
      end
    end
  end


  local result = '{|class="wikitable sortable stickyHeader"'
for i, recipeDef in ipairs(recipeList) do
  result = result..'\r\n|-class="headerRow-0"'
local recipe = SkillData.Smithing.recipes[recipeDef.id]
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
local totalValue = recipe.baseQuantity * recipeDef.itemValue
  --Adding value/bar for things other than smelting
-- Determine the bar quantity & build the recipe cost string
  if bar ~= nil then result = result..'!!Value/Bar' end
local barQty, costString = 0, {}
for j, itemCost in ipairs(recipe.itemCosts) do
local costItem = Items.getItemByID(itemCost.id)
if costItem ~= nil then
table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity, notext=true}))
end
if barIDList[itemCost.id] then
barQty = barQty + itemCost.quantity
end
end


  table.sort(smithList, function(a, b)
table.insert(resultPart, '\r\n|-')
                          if a.smithingLevel ~= b.smithingLevel then
table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true}))
                            return a.smithingLevel < b.smithingLevel
table.insert(resultPart, '\r\n| ')
                          else
table.insert(resultPart, recipeDef.expIcon)
                            return a.name < b.name
if recipe.baseQuantity > 1 then
                          end end)
table.insert(resultPart, recipe.baseQuantity .. 'x ')
  for i, item in Shared.skpairs(smithList) do
end
    result = result..'\r\n|-'
table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true}))
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level))
    local qty = item.smithingQty ~= nil and item.smithingQty or 1
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience))
    if qty > 1 then
table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue))
      result = result..item.smithingQty..'x '
if recipe.baseQuantity > 1 then
    end
table.insert(resultPart, ' (x' .. recipe.baseQuantity .. ')')
    result = result..'[['..item.name..']]'
end
    result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel)
table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', '))
    result = result..'||'..item.smithingXP
if category.id ~= 'melvorD:Bars' then
    local totalValue = item.sellsFor * qty
local barVal, barValTxt = 0, 'N/A'
    result = result..'||data-sort-value="'..totalValue..'"|'..Icons.GP(item.sellsFor)
if barQty > 0 then
    if qty > 1 then
barVal = totalValue / barQty
      result = result..' (x'..qty..')'
barValTxt = Icons.GP(Shared.round(barVal, 1, 1))
    end
end
    result = result..'||'
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barValTxt)
    local barQty = 0
end
    for i, mat in Shared.skpairs(item.smithReq) do
end
      matItem = Items.getItemByID(mat.id)
table.insert(resultPart, '\r\n|}')
      if i > 1 then result = result..', ' end
      result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty, notext=true})
      if bar ~= nil and mat.id == bar.id then
        barQty = mat.qty
      end
    end
    --Add the data for the value per bar
    if bar ~= nil then
      if barQty == 0 then
        result = result..'||data-sort-value="0"|N/A'
      else
        local barVal = totalValue / barQty
        result = result..'||data-sort-value="'..barVal..'"|'..Icons.GP(Shared.round(barVal, 1, 1))
      end
    end
  end


  result = result..'\r\n|}'
return table.concat(resultPart)
  return result
end
end


function p.getFiremakingTable(frame)
function p.getFiremakingTable(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
local resultPart = {}
  result = result..'\r\n|-class="headerRow-0"'
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  result = result..'\r\n!colspan="2"|Logs!!'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level'
table.insert(resultPart, '\r\n|-class="headerRow-0"')
  result = result..'!!XP!!Burn Time!!XP/s!!Bonfire Bonus!!Bonfire Time'
table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level')
table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time')
table.insert(resultPart, '\r\n|-class="headerRow-1"')
table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s')
 
for i, logData in ipairs(SkillData.Firemaking.logs) do
local logs = Items.getItemByID(logData.logID)
local name = logs.name
local burnTime = logData.baseInterval / 1000
local bonfireTime = logData.baseBonfireInterval / 1000
local XPS = logData.baseExperience / burnTime
local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100)
local XPS_BF = XP_BF / burnTime


  for i, logData in Shared.skpairs(SkillData.Firemaking) do
table.insert(resultPart, '\r\n|-')
    result = result..'\r\n|-'
table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}))
    local name = Shared.titleCase(logData.type..' Logs')
table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true}))
    result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})
table.insert(resultPart, '||style ="text-align: right;"|'..logData.level)
    result = result..'||[['..name..']]'
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true))
    result = result..'||style ="text-align: right;"|'..logData.level
table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseExperience)
    result = result..'||style ="text-align: right;"|'..logData.xp
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2))
    local burnTime = logData.interval / 1000
table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0))
    local XPS = logData.xp / burnTime
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2))
    result = result..'||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
    result = result..'||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2)
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
    result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireBonus..'"|'..logData.bonfireBonus..'%'
end
    result = result..'||style ="text-align: right;" data-sort-value="'..logData.bonfireInterval..'"|'..Shared.timeString(logData.bonfireInterval / 1000, true)
  end


  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|}')
  return result
return table.concat(resultPart)
end
end


return p
return p