Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
Move Farming functions to Module:Skills/Gathering
(Formatting fixes to getSmithingTable)
(Move Farming functions to Module:Skills/Gathering)
(42 intermediate revisions by 4 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 and Module:Prayer being the current two examples.
--Module:Magic for Magic
--Module:Prayer for Prayer
--Module:Skills/Agility for Agility
--Module:Skills/Summoning for Summoning
--Module:Skills/Gathering for Mining, Fishing, Woodcutting
--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 6: 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
 
function p.getThievingNPCArea(npc)
if type(npc) == 'string' then
npc = p.getThievingNPC(npc)
end
 
local result = 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.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 "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
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)
 
  if skillID == nil then
local areaNPCs = {}
    return "ERROR: Failed to find a skill ID for "..skillName
 
  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
 
--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 = 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
 
--Chance of -1 on unique drops is to indicate variable chance
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID == itemID then
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.qty, maxQty = npc.uniqueDrop.qty, wt = -1, totalWt = -1, level = npc.level})
end
 
if areaNPCs[npc.id] ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.AreaUniqueChance, totalWt = 100, level = npc.level})
end
end


  if SkillData.MasteryCheckpoints[skillID] == nil then
for i, drop in pairs(SkillData.Thieving.RareItems) do
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
if drop.itemID == itemID then
  end
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
end
end


  local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
return resultArray
  local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
end


  local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
-- Astrology
  for i, bonus in Shared.skpairs(bonuses) do
function p.getConstellationByID(constID)
    result = result..'\r\n|-'
return SkillData.Astrology.Constellations[constID]
    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)
function p.getConstellation(constName)
  local seedList = {}
for i, const in ipairs(SkillData.Astrology.Constellations) do
  if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
if const.name == constName then
    seedList = Items.getItems(function(item) return item.tier == category end)
return const
  else
end
    return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
end
  end
return nil
end


  local result = '{|class="wikitable sortable stickyHeader"'
function p.getConstellations(checkFunc)
  result = result..'\r\n|- class="headerRow-0"'
local result = {}
  result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
for i, const in ipairs(SkillData.Astrology.Constellations) do
  result = result..'!!XP!!Growth Time!!Seed Value'
if checkFunc(const) then
  if category == 'Allotment' then
table.insert(result, const)
    result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
end
  elseif category == 'Herb' then
end
    result = result..'!!colspan="2"|Herb!!Herb Value'
return result
  elseif category == 'Tree' then
end
    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
-- For a given constellation cons and modifier value modValue, generates and returns
    result = result..'\r\n|-'
-- a table of modifiers, much like any other item/object elsewhere in the game.
    result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
-- includeStandard: true|false, determines whether standard modifiers are included
    result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
-- includeUnique: true|false, determines whether unique modifiers are included
    result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
    result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
-- 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 crop = Items.getItemByID(seed.grownItemID)
local addToArray = function(modArray, modNew)
    result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
    if category == 'Allotment' then
table.insert(modArray, modNew)
      result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
end
    end
end
    result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
    result = result..'||'..ItemSourceTables._getItemSources(seed)
  end


  result = result..'\r\n|}'
local modTypes = {}
  return result
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end
 
local modArray = {}
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
 
if asKeyValue then
local modArrayKV = {}
for i, modDefn in ipairs(modArray) do
local modName, modVal = modDefn[1], modDefn[2]
local isSkill = isSkillMod[modName]
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 "ERROR: Failed to find a skill ID for "..skillName
end


  return p._getFarmingTable(category)
local unlockTable = SkillData.MasteryUnlocks[skillID]
if unlockTable == nil then
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
 
local result = '{|class="wikitable"\r\n!Level!!Unlock'
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.getMiningTable(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|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
if skillID == nil then
  result = result..'!!XP!!Respawn Time!!Ore Value'
return "ERROR: Failed to find a skill ID for "..skillName
  local mineData = Shared.clone(SkillData.Mining)
end
  table.sort(mineData, function(a, b) return a.level < b.level end)
  for i, oreData in Shared.skpairs(mineData) do
    local ore = Items.getItemByID(oreData.ore)
    result = result..'\r\n|-\r\n|'..Icons.Icon({ore.name, type='item', size='50', notext=true})..'||'..ore.name
    result = result..'||style="text-align:right"|'..oreData.level..'||style="text-align:right"|'..ore.miningXP
    result = result..'||style="text-align:right" data-sort-value="'..oreData.respawnInterval..'"|'
    result = result..Shared.timeString(oreData.respawnInterval / 1000, true)
    result = result..'||data-sort-value="'..ore.sellsFor..'"|'..Icons.GP(ore.sellsFor)
  end


  result = result..'\r\n|}'
if SkillData.MasteryCheckpoints[skillID] == nil then
  return result
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
 
local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
 
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.getPotionNavbox(frame)
function p.getMasteryTokenTable()
  --
local baseTokenChance = 18500
  local result = '{| class="wikitable" style="margin:auto; clear:both; width: 100%"'
local masterySkills = {}
  result = result..'\r\n!colspan=2|'..Icons.Icon({'Herblore', 'Potions', type='skill'})
 
-- Find all mastery tokens
local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
for i, item in pairs(masteryTokens) do
local milestones = SkillData.Milestones[item.skill + 1]
if milestones ~= nil then
table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones})
end
end
table.sort(masterySkills, function(a, b)
if a['milestoneCount'] == b['milestoneCount'] then
return a['skillID'] < b['skillID']
else
return a['milestoneCount'] > b['milestoneCount']
end
end)
 
-- Generate output table
local resultPart = {}
local CCI = Items.getItem('Clue Chasers Insignia')
local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true})
if CCI == nil then return '' end


  local CombatPots = {}
table.insert(resultPart, '{| class="wikitable sortable"')
  local SkillPots = {}
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
  for i, potData in Shared.skpairs(SkillData.Herblore.ItemData) do
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
    if potData.category == 0 then
      table.insert(CombatPots, Icons.Icon({potData.name, type='item', img=(potData.name..' I')}))
    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;"'
for i, m in ipairs(masterySkills) do
  result = result..'|'..table.concat(CombatPots, ' ')
local token = masteryTokens[m.tokenRef]
  result = result..'\r\n|-\r\n!Skill Potions\r\n|class="center" style="vertical-align:middle;"'
local denom = math.floor(baseTokenChance / m['milestoneCount'])
  result = result..'|'..table.concat(SkillPots, ' • ')
local denomCCI = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 100)), 0, 0)
  result = result..'\r\n|}'
  return result
end


function p.getSpecialFishingTable(frame)
table.insert(resultPart, '\r\n|-')
  local lootValue = 0
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
  local totalWt = Items.specialFishWt
table.insert(resultPart, '\r\n|' .. Icons.Icon({Constants.getSkillName(m['skillID']), 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|}')


  local result = ''
return table.concat(resultPart)
  result = result..'\r\n{|class="wikitable sortable"'
end
  result = result..'\r\n!Item'
  result = result..'!!Price!!colspan="2"|Chance'


  --Sort the loot table by weight in descending order
-- Skill unlock costs for Adventure game mode
  table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
function p.getSkillUnlockCostTable()
  for i, row in pairs(Items.specialFishLoot) do
local returnPart = {}
    local thisItem = Items.getItemByID(row[1])
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
    result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
    result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
    result = result..'|'..Icons.GP(thisItem.sellsFor)


    local dropChance = (row[2] / totalWt) * 100
local accCost = 0
    result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
for i, cost in ipairs(SkillData.SkillUnlockCosts) do
    result = result..'|'..Shared.fraction(row[2], totalWt)
accCost = accCost + cost
    result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
table.insert(returnPart, '|-')
    lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
  end
end
  result = result..'\r\n|}'
table.insert(returnPart, '|}')
  result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))


  return result
return table.concat(returnPart, '\r\n')
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
 
-- Generate output table
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|-class="headerRow-0"')
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients')
--Adding value/bar for things other than smelting
if categoryID > 0 then
table.insert(resultPart, '!!Value/Bar')
end
 
table.sort(recipeList, function(a, b)
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.qty, notext=true}))
end
if barIDList[itemCost.id] then
barQty = barQty + itemCost.qty
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| ')
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


  local smithList = {}
function p.getFiremakingTable(frame)
  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!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level')
          table.insert(smithList, item)
table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time')
        end
table.insert(resultPart, '\r\n|-class="headerRow-1"')
      else
table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s')
        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, logData in Shared.skpairs(SkillData.Firemaking) do
  result = result..'\r\n|-class="headerRow-0"'
local logs = Items.getItemByID(logData.logID)
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true}).. Level'!!XP!!Value!!Ingredients'
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


  table.sort(smithList, function(a, b)
table.insert(resultPart, '\r\n|-')
                          if a.smithingLevel ~= b.smithingLevel then
table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}))
                            return a.smithingLevel < b.smithingLevel
table.insert(resultPart, '||[['..name..']]')
                          else
table.insert(resultPart, '||style ="text-align: right;"|'..logData.level)
                            return a.name < b.name
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true))
                          end end)
table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseXP)
  for i, item in Shared.skpairs(smithList) do
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2))
    result = result..'\r\n|-'
table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0))
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2))
    local qty = item.smithingQty ~= nil and item.smithingQty or 1
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
    if qty > 1 then
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
      result = result..item.smithingQty..'x '
end
    end
    result = result..'[['..item.name..']]'
    result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel)
    result = result..'||'..item.smithingXP
    result = result..'||data-sort-value="'..item.sellsFor * qty..'"|'..Icons.GP(item.sellsFor)
    if qty > 1 then
      result = result..' (x'..qty..')'
    end
    result = result..'||'
    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})
    end
  end


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


return p
return p