Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
5,586 bytes added ,  27 December 2022
Use printError function
(Added p.getFarmingPlotTable)
(Use printError function)
(47 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.getMasteryUnlockTable(frame)
function p._getThievingNPCStat(npc, statName)
  local skillName = frame.args ~= nil and frame.args[1] or frame
local result = nil
  local skillID = p.getSkillID(skillName)
 
  if skillID == nil then
if statName == 'level' then
    return "ERROR: Failed to find a skill ID for "..skillName
result = Icons._SkillReq('Thieving', npc.level)
  end
elseif statName == 'maxHit' then
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
 
return result
end


  local unlockTable = SkillData.MasteryUnlocks[skillID]
function p.getThievingNPCStat(frame)
  if unlockTable == nil then
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
local statName = frame.args ~= nil and frame.args[2] or frame[2]
  end
local npc = p.getThievingNPC(npcName)
if npc == nil then
return Shared.printError('Invalid Thieving NPC ' .. npcName)
end


  local result = '{|class="wikitable"\r\n!Level!!Unlock'
return p._getThievingNPCStat(npc, statName)
  for i, unlock in Shared.skpairs(unlockTable) do
    result = result..'\r\n|-'
    result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
  end
  result = result..'\r\n|}'
  return result
end
end


function p.getMasteryCheckpointTable(frame)
function p.getThievingSourcesForItem(itemID)
  local skillName = frame.args ~= nil and frame.args[1] or frame
local resultArray = {}
  local skillID = p.getSkillID(skillName)
local areaNPCs = {}
  if skillID == nil then
 
    return "ERROR: Failed to find a skill ID for "..skillName
--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
 
--Now go through and get drop chances on each NPC if needed
for i, npc in pairs(SkillData.Thieving.npcs) do
local totalWt = 0
local dropWt = 0
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
 
--Chance of -1 on unique drops is to indicate variable chance
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.quantity, maxQty = npc.uniqueDrop.quantity, wt = -1, totalWt = -1, level = npc.level, npcID = npc.id})
end
 
if areaNPCs[npc.id] ~= nil then
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})
end
end
 
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
if drop.itemID == itemID then
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
 
return resultArray
end


  if SkillData.MasteryCheckpoints[skillID] == nil then
-- Astrology
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
function p.getConstellationByID(constID)
  end
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end


  local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
function p.getConstellation(constName)
  local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end


  local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
function p.getConstellations(checkFunc)
  for i, bonus in Shared.skpairs(bonuses) do
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
    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._getFarmingTable(category)
-- For a given constellation cons and modifier value modValue, generates and returns
  local seedList = {}
-- a table of modifiers, much like any other item/object elsewhere in the game.
  if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
-- includeStandard: true|false, determines whether standard modifiers are included
    seedList = Items.getItems(function(item) return item.tier == category end)
-- includeUnique: true|false, determines whether unique modifiers are included
  else
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
    return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects
  end
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


  local result = '{|class="wikitable sortable stickyHeader"'
local modTypes = {}
  result = result..'\r\n|- class="headerRow-0"'
if includeStandard then
  result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
table.insert(modTypes, 'standardModifiers')
  result = result..'!!XP!!Growth Time!!Seed Value'
end
  if category == 'Allotment' then
if includeUnique then
    result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
table.insert(modTypes, 'uniqueModifiers')
  elseif category == 'Herb' then
end
    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 modArray = {}
    result = result..'\r\n|-'
local isSkillMod = {}
    result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
--Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27]
    result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
local groupNum = 0
    result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
    result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)


    local crop = Items.getItemByID(seed.grownItemID)
for _, modType in ipairs(modTypes) do
    result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
for i, modTypeData in ipairs(cons[modType]) do
    if category == 'Allotment' then
groupNum = groupNum + 1
      result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
local modVal = nil
    end
if modValue ~= nil then
    result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
modVal = modValue
    result = result..'||'..ItemSourceTables._getItemSources(seed)
else
  end
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


  result = result..'\r\n|}'
if asKeyValue then
  return result
local modArrayKV = {}
for i, modDefn in ipairs(modArray) do
local modName, modVal = modDefn[1], modDefn[2]
local isSkill = type(modVal) == 'table' and modVal.skillID ~= nil
if modArrayKV[modName] == nil then
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.getFarmingTable(frame)
-- Mastery
  local category = frame.args ~= nil and frame.args[1] or frame
function p.getMasteryUnlockTable(frame)
local skillName = frame.args ~= nil and frame.args[1] or frame
local skillID = Constants.getSkillID(skillName)
if skillID == nil then
return Shared.printError('Failed to find a skill ID for ' .. skillName)
end
 
local _, localSkillID = GameData.getLocalID(skillID)
-- Clone so that we can sort by level
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)


  return p._getFarmingTable(category)
local result = '{|class="wikitable"\r\n!Level!!Unlock'
for i, unlock in ipairs(unlockTable) do
result = result..'\r\n|-'
result = result..'\r\n|'..unlock.level..'||'..unlock.description
end
result = result..'\r\n|}'
return result
end
end


function p.getFarmingFoodTable(frame)
function p.getMasteryCheckpointTable(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
local skillName = frame.args ~= nil and frame.args[1] or frame
  result = result..'\r\n|- class="headerRow-0"'
local skillID = Constants.getSkillID(skillName)
  result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
if skillID == nil then
  result = result..'!!Healing!!Value'
return Shared.printError('Failed to find a skill ID for ' .. skillName)
 
end
  local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end)


  table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
local _, localSkillID = GameData.getLocalID(skillID)
local checkpoints = SkillData[localSkillID].masteryCheckpoints
if checkpoints == nil then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
end


  for i, item in Shared.skpairs(itemArray) do
local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap
    local crop = Items.getItemByID(item.grownItemID)
local checkpointPct = GameData.rawData.masteryCheckpoints
    if crop.healsFor ~= nil and crop.healsFor > 0 then
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
      result = result..'\r\n|-'
for i, checkpointDesc in ipairs(checkpoints) do
      result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
result = result..'\r\n|-'
      result = result..'||style="text-align:right;"|'..item.farmingLevel
result = result..'\r\n|'..checkpointPct[i]..'%||'
      result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc
      result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
end
    end
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
  end
result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
result = result..'\r\n|}'
return result
end
 
function p.getMasteryTokenTable()
local baseTokenChance = 18500
local masterySkills = {}
local CCI = Items.getItemByID('melvorD:Clue_Chasers_Insignia')
if CCI == nil then return '' end


  result = result..'\r\n|}'
-- Build table of mastery skills
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)


  return result
-- Generate output table
end
local resultPart = {}
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})


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, skill in ipairs(masterySkills) do
  result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
local token = Items.getItemByID(skill.masteryTokenID)
local denom = math.floor(baseTokenChance / skill.milestoneCount)
local denomCCI = Shared.round(baseTokenChance / (skill.milestoneCount * (1 + CCI.modifiers.increasedOffItemChance / 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({skill.name, 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 advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure')
  result = result..'\r\n!colspan=2|'..Icons.Icon({'Herblore', 'Potions', type='skill'})
if advMode ~= nil then
local unlockCount = Shared.tableCount(GameData.skillData) - Shared.tableCount(advMode.startingSkills)
local costLength = Shared.tableCount(advMode.skillUnlockCost)
local returnPart = {}
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 = 1, unlockCount, 1 do
  for i, potData in Shared.skpairs(SkillData.Herblore.ItemData) do
local cost = advMode.skillUnlockCost[math.min(i, costLength)]
    if potData.category == 0 then
accCost = accCost + cost
      table.insert(CombatPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')}))
table.insert(returnPart, '|-')
    else
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
      if potData.name == 'Bird Nests Potion' then
end
        table.insert(SkillPots, Icons.Icon({"Bird Nest Potion", type='item', img="Bird Nest Potion I"}))
table.insert(returnPart, '|}')
      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, ' • ')
end
  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:
--  'Bars', 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
  if tableType ~= 'Smelting' then
    bar = Items.getItem(tableType)
    if bar == nil then
      return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
    elseif bar.type ~= 'Bar' then
      return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
    end
  end


  local smithList = {}
-- Has a valid category been passed (by name)?
  for i, item in pairs(ItemData.Items) do
local category = GameData.getEntityByName(SkillData.Smithing.categories, tableType)
    if item.smithingLevel ~= nil then
if category == nil then
      if tableType == 'Smelting' then
return Shared.printError('Invalid Smithing category: "' .. tableType .. '"')
        if item.type == 'Bar' then
end
          table.insert(smithList, item)
        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"'
-- Build a list of recipes to be included, and a list of bars while we're at it
  result = result..'\r\n|-class="headerRow-0"'
-- The bar list will be used later for value/bar calculations
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
local recipeList, barIDList = {}, {}
  --Adding value/bar for things other than smelting
for i, recipe in ipairs(SkillData.Smithing.recipes) do
  if bar ~= nil then result = result..'!!Value/Bar' end
if recipe.categoryID == category.id then
local recipeItem = Items.getItemByID(recipe.productID)
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


  table.sort(smithList, function(a, b)
-- Generate output table
                          if a.smithingLevel ~= b.smithingLevel then
local resultPart = {}
                            return a.smithingLevel < b.smithingLevel
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
                          else
table.insert(resultPart, '\r\n|-class="headerRow-0"')
                            return a.name < b.name
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients')
                          end end)
--Adding value/bar for things other than smelting
  for i, item in Shared.skpairs(smithList) do
if category.id ~= 'melvorD:Bars' then
    result = result..'\r\n|-'
table.insert(resultPart, '!!Value/Bar')
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
end
    local qty = item.smithingQty ~= nil and item.smithingQty or 1
    if qty > 1 then
      result = result..item.smithingQty..'x '
    end
    result = result..'[['..item.name..']]'
    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.sort(recipeList, function(a, b)
  return result
if a.level ~= b.level then
return a.level < b.level
else
return a.itemName < b.itemName
end
end)
 
for i, recipeDef in ipairs(recipeList) do
local recipe = SkillData.Smithing.recipes[recipeDef.id]
local totalValue = recipe.baseQuantity * recipeDef.itemValue
-- Determine the bar quantity & build the recipe cost string
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.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true}))
table.insert(resultPart, '\r\n| ')
table.insert(resultPart, recipeDef.expIcon)
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.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience))
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 category.id ~= 'melvorD:Bars' then
local barVal, barValTxt = 0, 'N/A'
if barQty > 0 then
barVal = totalValue / barQty
barValTxt = Icons.GP(Shared.round(barVal, 1, 1))
end
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barValTxt)
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 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