Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
Move Farming functions to Module:Skills/Gathering
m (_formatLootTable: Apply secondary sort on item ID in addition to primary sort on drop chance)
(Move Farming functions to Module:Skills/Gathering)
(26 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 = {}
Line 10: Line 18:
local ItemData = mw.loadData('Module:Items/data')
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/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 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}
local MasteryCheckpoints = {.1, .25, .5, .95}


function p.getSkillID(skillName)
-- Thieving
  for skName, ID in Shared.skpairs(Constants.skill) do
function p.getThievingNPC(npcName)
    if skName == skillName then
local result = nil
      return ID
for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do
    end
if npc.name == npcName then
  end
result = Shared.clone(npc)
  return nil
break
end
end
return result
end
end


function p.getSkillName(skillID)
function p.getThievingNPCArea(npc)
  for skName, ID in Shared.skpairs(Constants.skill) do
if type(npc) == 'string' then
    if ID == skillID then
npc = p.getThievingNPC(npc)
      return skName
end
    end
 
  end
local result = nil
  return nil
for i, area in Shared.skpairs(SkillData.Thieving.Areas) do
for j, npcID in pairs(area.npcs) do
if npcID == npc.id then
result = area
break
end
end
end
return result
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 "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
    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
  if stat == 'maxHit' then
    result = result * 10
  elseif stat == 'lootTable' then
    return p._formatLootTable(npc['lootTable'])
  elseif stat == 'requirements' then
  if npc['level'] ~= nil then
    result = Icons._SkillReq('Thieving', npc['level'], true)
  else
    result = 'None'
  end
  end


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


function p._formatLootTable(lootTableIn)
--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 = 0
for j, drop in pairs(npc.lootTable) do
totalWt = totalWt + drop[2]
if drop[1] == itemID then
dropWt = drop[2]
dropQty = drop[3]
end
end
if dropWt > 0 then
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = dropQty, wt = dropWt * SkillData.Thieving.ItemChance, totalWt = totalWt * 100, level = npc.level})
end


  local lootTable = Shared.clone(lootTableIn)
--Chance of -1 on unique drops is to indicate variable chance
  -- Sort table from most to least common drop
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID == itemID then
  table.sort(lootTable, function(a, b)
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.qty, maxQty = npc.uniqueDrop.qty, wt = -1, totalWt = -1, level = npc.level})
                          if a[2] == b[2] then
end
                            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.AreaUniqueChance, totalWt = 100, level = npc.level})
    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 pairs(SkillData.Thieving.RareItems) do
  local maxDropLen = string.len(Shared.round(lootTable[1][2] / totalWeight * 100, 2, 2))
if drop.itemID == itemID then
  local returnPart = {}
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
  for i, drop in pairs(lootTable) do
end
    local item, itemText, dropChance = Items.getItemByID(drop[1]), nil, Shared.round(drop[2] / totalWeight * 100, 2, 2)
end
    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')
return resultArray
end
end


function p.getMasteryUnlockTable(frame)
-- Astrology
  local skillName = frame.args ~= nil and frame.args[1] or frame
function p.getConstellationByID(constID)
  local skillID = p.getSkillID(skillName)
return SkillData.Astrology.Constellations[constID]
  if skillID == nil then
end
    return "ERROR: Failed to find a skill ID for "..skillName
  end


  local unlockTable = SkillData.MasteryUnlocks[skillID]
function p.getConstellation(constName)
  if unlockTable == nil then
for i, const in ipairs(SkillData.Astrology.Constellations) do
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
if const.name == constName then
  end
return const
end
end
return nil
end


  local result = '{|class="wikitable"\r\n!Level!!Unlock'
function p.getConstellations(checkFunc)
  for i, unlock in Shared.skpairs(unlockTable) do
local result = {}
    result = result..'\r\n|-'
for i, const in ipairs(SkillData.Astrology.Constellations) do
    result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
if checkFunc(const) then
  end
table.insert(result, const)
  result = result..'\r\n|}'
end
  return result
end
return result
end
end


function p.getMasteryCheckpointTable(frame)
-- For a given constellation cons and modifier value modValue, generates and returns
  local skillName = frame.args ~= nil and frame.args[1] or frame
-- a table of modifiers, much like any other item/object elsewhere in the game.
  local skillID = p.getSkillID(skillName)
-- includeStandard: true|false, determines whether standard modifiers are included
  if skillID == nil then
-- includeUnique: true|false, determines whether unique modifiers are included
    return "ERROR: Failed to find a skill ID for "..skillName
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
  end
-- 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
 
local addToArray = function(modArray, modNew)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
table.insert(modArray, modNew)
end
end


  if SkillData.MasteryCheckpoints[skillID] == nil then
local modTypes = {}
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
if includeStandard then
  end
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end


  local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
local modArray = {}
  local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
local isSkillMod = {}
for _, modType in ipairs(modTypes) do
for i, skillMods in ipairs(cons[modType]) do
local skillID = cons.skills[i]
if skillID ~= nil then
for j, modName in ipairs(skillMods) do
local modBaseName, modText, sign, isNegative, unsign, modBase = Constants.getModifierDetails(modName)
-- Check if modifier varies by skill, and amend the modifier value accordingly
local modVal = modValue
if Shared.contains(modText, '{SV0}') then
isSkillMod[modName] = true
modVal = {skillID, modValue}
end
addToArray(modArray, {modName, modVal})
end
end
end
end


  local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
if asKeyValue then
  for i, bonus in Shared.skpairs(bonuses) do
local modArrayKV = {}
    result = result..'\r\n|-'
for i, modDefn in ipairs(modArray) do
    result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||'
local modName, modVal = modDefn[1], modDefn[2]
    result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus
local isSkill = isSkillMod[modName]
  end
if modArrayKV[modName] == nil then
  result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
modArrayKV[modName] = (isSkill and { modVal } or modVal)
  result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
elseif isSkill then
  result = result..'\r\n|}'
table.insert(modArrayKV[modName], modVal)
  return result
else
modArrayKV[modName] = modArrayKV[modName] + modVal
end
end
return modArrayKV
else
return modArray
end
end
end


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


  local result = '{|class="wikitable sortable stickyHeader"'
local unlockTable = SkillData.MasteryUnlocks[skillID]
  result = result..'\r\n|- class="headerRow-0"'
if unlockTable == nil then
  result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
  result = result..'!!XP!!Growth Time!!Seed Value'
end
  if category == 'Allotment' then
    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 result = '{|class="wikitable"\r\n!Level!!Unlock'
    result = result..'\r\n|-'
for i, unlock in Shared.skpairs(unlockTable) do
    result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
result = result..'\r\n|-'
    result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
    result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
end
    result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
result = result..'\r\n|}'
return result
end


    local crop = Items.getItemByID(seed.grownItemID)
function p.getMasteryCheckpointTable(frame)
    result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
local skillName = frame.args ~= nil and frame.args[1] or frame
    if category == 'Allotment' then
local skillID = Constants.getSkillID(skillName)
      result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
if skillID == nil then
    end
return "ERROR: Failed to find a skill ID for "..skillName
    result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
end
    result = result..'||'..ItemSourceTables._getItemSources(seed)
  end


  result = result..'\r\n|}'
if SkillData.MasteryCheckpoints[skillID] == nil then
  return result
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
end


function p.getFarmingTable(frame)
local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
  local category = frame.args ~= nil and frame.args[1] or frame
local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]


  return p._getFarmingTable(category)
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
for i, bonus in Shared.skpairs(bonuses) do
result = result..'\r\n|-'
result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||'
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.getFarmingFoodTable(frame)
function p.getMasteryTokenTable()
  local result = '{| class="wikitable sortable stickyHeader"'
local baseTokenChance = 18500
  result = result..'\r\n|- class="headerRow-0"'
local masterySkills = {}
  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)


  for i, item in Shared.skpairs(itemArray) do
-- Find all mastery tokens
    local crop = Items.getItemByID(item.grownItemID)
local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
    if crop.healsFor ~= nil and crop.healsFor > 0 then
for i, item in pairs(masteryTokens) do
      result = result..'\r\n|-'
local milestones = SkillData.Milestones[item.skill + 1]
      result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
if milestones ~= nil then
      result = result..'||style="text-align:right;"|'..item.farmingLevel
table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones})
      result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
end
      result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
end
    end
table.sort(masterySkills, function(a, b)
  end
if a['milestoneCount'] == b['milestoneCount'] then
return a['skillID'] < b['skillID']
else
return a['milestoneCount'] > b['milestoneCount']
end
end)


  result = result..'\r\n|}'
-- Generate output table
 
local resultPart = {}
  return result
local CCI = Items.getItem('Clue Chasers Insignia')
end
local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true})
if CCI == nil then return '' end


function p.getFarmingPlotTable(frame)
table.insert(resultPart, '{| class="wikitable sortable"')
  local areaName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
  local patches = nil
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
  for i, area in Shared.skpairs(SkillData.Farming.Patches) do
    if area.areaName == areaName then
      patches = area.patches
      break
    end
  end
  if patches == nil then
    return "ERROR: Invalid area name.[[Category:Pages with script errors"
  end


  local result = '{|class="wikitable"'
for i, m in ipairs(masterySkills) do
  result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
local token = masteryTokens[m.tokenRef]
local denom = math.floor(baseTokenChance / m['milestoneCount'])
local denomCCI = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 100)), 0, 0)


  for i, patch in Shared.skpairs(patches) do
table.insert(resultPart, '\r\n|-')
    result = result..'\r\n|-\r\n|'..i
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
    result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
table.insert(resultPart, '\r\n|' .. Icons.Icon({Constants.getSkillName(m['skillID']), type='skill'}))
    if patch.cost == 0 then
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
      result = result..'||Free'
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
    else
end
      result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
table.insert(resultPart, '\r\n|}')
    end
  end


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


function p.getPotionNavbox(frame)
-- Skill unlock costs for Adventure game mode
  --•
function p.getSkillUnlockCostTable()
  local result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"'
local returnPart = {}
  result = result..'\r\n!colspan=2|'..Icons.Icon({'Herblore', 'Potions', type='skill'})
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')


  local CombatPots = {}
local accCost = 0
  local SkillPots = {}
for i, cost in ipairs(SkillData.SkillUnlockCosts) do
  for i, potData in Shared.skpairs(SkillData.Herblore.ItemData) do
accCost = accCost + cost
    if potData.category == 0 then
table.insert(returnPart, '|-')
      table.insert(CombatPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')}))
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
    else
end
      if potData.name == 'Bird Nests Potion' then
table.insert(returnPart, '|}')
        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;"'
return table.concat(returnPart, '\r\n')
  result = result..'|'..table.concat(CombatPots, ' • ')
  result = result..'\r\n|-\r\n!Skill Potions\r\n|class="center" style="vertical-align:middle;"'
  result = result..'|'..table.concat(SkillPots, ' • ')
  result = result..'\r\n|}'
  return result
end
end


-- Accepts 1 parameter, being either:
--  'Smelting', for which a table of all bars is generated, or
--  A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier
function p.getSmithingTable(frame)
function p.getSmithingTable(frame)
  local tableType = frame.args ~= nil and frame.args[1] or frame
local tableType = frame.args ~= nil and frame.args[1] or frame
  local bar = nil
tableType = Shared.splitString(tableType, ' ')[1]
  if tableType ~= 'Smelting' then
-- Translates Smithing category names to Smithing recipe data categories
    bar = Items.getItem(tableType)
local categoryMap = {
    if bar == nil then
['Smelting'] = 0,
      return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
['Bronze'] = 1,
    elseif bar.type ~= 'Bar' then
['Iron'] = 2,
      return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
['Steel'] = 3,
    end
['Mithril'] = 4,
  end
['Adamant'] = 5,
['Adamantite'] = 5,
['Rune'] = 6,
['Runite'] = 6,
['Dragon'] = 7,
['Dragonite'] = 7
}
local categoryID = categoryMap[tableType]
if categoryID == nil then
return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]'
end
 
-- Build a list of recipes to be included, and a list of bars while we're at it
-- The bar list will be used later for value/bar calculations
local recipeList, barIDList = {}, {}
for i, recipe in ipairs(SkillData.Smithing.Recipes) do
if recipe.category == categoryID then
local recipeItem = Items.getItemByID(recipe.itemID)
if recipeItem ~= nil then
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor })
end
elseif recipe.category == 0 then
barIDList[recipe.itemID] = true
end
end


  local smithList = {}
-- Generate output table
  for i, item in pairs(ItemData.Items) do
local resultPart = {}
    if item.smithingLevel ~= nil then
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
      if tableType == 'Smelting' then
table.insert(resultPart, '\r\n|-class="headerRow-0"')
        if item.type == 'Bar' then
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients')
          table.insert(smithList, item)
--Adding value/bar for things other than smelting
        end
if categoryID > 0 then
      else
table.insert(resultPart, '!!Value/Bar')
        for j, req in pairs(item.smithReq) do
end
          if req.id == bar.id then
            table.insert(smithList, item)
          end
        end
      end
    end
  end


  local result = '{|class="wikitable sortable stickyHeader"'
table.sort(recipeList, function(a, b)
  result = result..'\r\n|-class="headerRow-0"'
if a.level ~= b.level then
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
return a.level < b.level
  --Adding value/bar for things other than smelting
else
  if bar ~= nil then result = result..'!!Value/Bar' end
return a.itemName < b.itemName
end
end)


  table.sort(smithList, function(a, b)
for i, recipeDef in ipairs(recipeList) do
                          if a.smithingLevel ~= b.smithingLevel then
local recipe = SkillData.Smithing.Recipes[recipeDef.id]
                            return a.smithingLevel < b.smithingLevel
local totalValue = recipe.baseQuantity * recipeDef.itemValue
                          else
-- Determine the bar quantity & build the recipe cost string
                            return a.name < b.name
local barQty, costString = 0, {}
                          end end)
for j, itemCost in ipairs(recipe.itemCosts) do
  for i, item in Shared.skpairs(smithList) do
local costItem = Items.getItemByID(itemCost.id)
    result = result..'\r\n|-'
if costItem ~= nil then
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.qty, notext=true}))
    local qty = item.smithingQty ~= nil and item.smithingQty or 1
end
    if qty > 1 then
if barIDList[itemCost.id] then
      result = result..item.smithingQty..'x '
barQty = barQty + itemCost.qty
    end
end
    result = result..'[['..item.name..']]'
end
    result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel)
    result = result..'||'..item.smithingXP
    local totalValue = item.sellsFor * qty
    result = result..'||data-sort-value="'..totalValue..'"|'..Icons.GP(item.sellsFor)
    if qty > 1 then
      result = result..' (x'..qty..')'
    end
    result = result..'||'
    local barQty = 0
    for i, mat in Shared.skpairs(item.smithReq) do
      matItem = Items.getItemByID(mat.id)
      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|}'
table.insert(resultPart, '\r\n|-')
  return result
table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true}))
table.insert(resultPart, '\r\n| ')
if recipe.baseQuantity > 1 then
table.insert(resultPart, recipe.baseQuantity .. 'x ')
end
table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true}))
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level))
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseXP .. '"| ' .. Shared.formatnum(recipe.baseXP))
table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue))
if recipe.baseQuantity > 1 then
table.insert(resultPart, ' (x' .. recipe.baseQuantity .. ')')
end
table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', '))
if categoryID > 0 then
local barVal, barValTxt = 0, 'N/A'
if barQty > 0 then
barVal = totalValue / barQty
barTxt = Icons.GP(Shared.round(barVal, 1, 1))
end
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barTxt)
end
end
table.insert(resultPart, '\r\n|}')
 
return table.concat(resultPart)
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 Shared.skpairs(SkillData.Firemaking) do
local logs = Items.getItemByID(logData.logID)
local name = logs.name
local burnTime = logData.baseInterval / 1000
local bonfireTime = logData.baseBonfireInterval / 1000
local XPS = logData.baseXP / burnTime
local XP_BF = logData.baseXP * (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, '||[['..name..']]')
    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.baseXP)
    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