Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
1,428 bytes removed ,  27 December 2022
Use printError function
(_getLootTableValue: Added for average loot table sale value; _formatLootTable: Support output in table form; _getThievingNPCStat: Add lootValue, pickpocketValue, lootList stats)
(Use printError function)
(38 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)
function p.getSkillID(skillName)
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
  for skName, ID in Shared.skpairs(Constants.skill) do
    if skName == skillName then
      return ID
    end
  end
  return nil
end
end


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


function p.getThievingNPCByID(ID)
function p.getThievingNPCArea(npc)
  local result = Shared.clone(SkillData.Thieving[ID + 1])
for i, area in ipairs(SkillData.Thieving.areas) do
  if result ~= nil then
for j, npcID in ipairs(area.npcIDs) do
    result.id = ID
if npcID == npc.id then
  end
return area
  return result
end
end
end
end
end


function p.getThievingNPC(name)
function p._getThievingNPCStat(npc, statName)
  local result = nil
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


function p.getThievingNPCStat(frame)
if statName == 'level' then
  local args = frame.args ~= nil and frame.args or frame
result = Icons._SkillReq('Thieving', npc.level)
  local npcName = args[1]
elseif statName == 'maxHit' then
  local statName = args[2]
result = npc.maxHit * 10
  local npc = p.getThievingNPC(npcName)
elseif statName == 'area' then
  if npc == nil then
local area = p.getThievingNPCArea(npc)
    return 'ERROR: Failed to find Thieving NPC with name ' .. name .. '[[Category:Pages with script errors]]'
result = area.name
  end
else
 
result = npc[statName]
  return p._getThievingNPCStat(npc, statName)
end
end


function p._getThievingNPCStat(npc, stat)
if result == nil then
  local itemDropChance = 0.75
result = ''
  local result = npc[stat]
end
  -- Overrides below
  if stat == 'maxHit' then
    result = result * 10
  elseif stat == 'lootList' then
    return p._formatLootTable(npc['lootTable'], itemDropChance, true)
  elseif stat == 'lootTable' then
    return p._formatLootTable(npc['lootTable'], itemDropChance, false)
  elseif stat == 'requirements' then
    if npc['level'] ~= nil then
      result = Icons._SkillReq('Thieving', npc['level'], true)
    else
      result = 'None'
    end
  elseif (stat == 'lootValue' or stat == 'pickpocketValue') then
    if stat == 'pickpocketValue' then
      local itemBP = Items.getItem("Bobby's Pocket")
      result = (1 + npc['maxCoins']) / 2 + itemBP.sellsFor * (1 / 120)
    else
      result = 0
    end
    result = Shared.round(result + p._getLootTableValue(npc['lootTable']) * itemDropChance, 2, 2)
  elseif stat == 'pageName' then
    local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
    result = (linkOverrides[npc['name']] ~= nil and linkOverrides[npc['name']]) or npc['name']
  end


  return result
return result
end
end


function p._getLootTableValue(lootTable)
function p.getThievingNPCStat(frame)
  -- Calculates the average GP value of a given loot table
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
  -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
local statName = frame.args ~= nil and frame.args[2] or frame[2]
  if Shared.tableCount(lootTable) == 0 then
local npc = p.getThievingNPC(npcName)
    return 0
if npc == nil then
  end
return Shared.printError('Invalid Thieving NPC ' .. npcName)
end


  local totalWeight = 0
return p._getThievingNPCStat(npc, statName)
  for i, drop in pairs(lootTable) do
end
    totalWeight = totalWeight + drop[2]
  end
  if totalWeight == 0 then
    return 0
  end


  local avgValue = 0
function p.getThievingSourcesForItem(itemID)
  for i, drop in pairs(lootTable) do
local resultArray = {}
    local item = Items.getItemByID(drop[1])
local areaNPCs = {}
    if item ~= nil then
      avgValue = avgValue + item.sellsFor * (drop[2] / totalWeight)
    end
  end
 
  return avgValue
end


function p._formatLootTable(lootTableIn, chanceMultIn, asList)
--First check area unique drops
  -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
--If an area drops the item, add all the NPC ids to the list so we can add them later
  if Shared.tableCount(lootTableIn) == 0 then
for i, area in pairs(SkillData.Thieving.areas) do
    return ''
for j, drop in pairs(area.uniqueDrops) do
  end
if drop.id == itemID then
for k, npcID in ipairs(area.npcIDs) do
areaNPCs[npcID] = drop.quantity
end
break
end
end
end


  local chanceMult = (chanceMultIn or 1) * 100
--Now go through and get drop chances on each NPC if needed
  local lootTable = Shared.clone(lootTableIn)
for i, npc in pairs(SkillData.Thieving.npcs) do
  -- Sort table from most to least common drop
local totalWt = 0
  table.sort(lootTable, function(a, b)
local dropWt = 0
                          if a[2] == b[2] then
local dropQty = { min = 0, max = 0 }
                            return a[1] < b[1]
for j, drop in ipairs(npc.lootTable) do
                          else
totalWt = totalWt + drop.weight
                            return a[2] > b[2]
if drop.itemID == itemID then
                          end
dropWt = drop.weight
                        end)
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 totalWeight = 0
--Chance of -1 on unique drops is to indicate variable chance
  for i, drop in pairs(lootTable) do
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then
    totalWeight = totalWeight + drop[2]
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
end
  if totalWeight == 0 then
    return ''
  end


  -- Get the length (in characters) of the largest drop chance so that they can be right aligned
if areaNPCs[npc.id] ~= nil then
  -- [4/16/21]: Adding info for no drop
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})
  local maxDropLen = math.max(string.len(Shared.round(100 - chanceMult, 2, 2)), string.len(Shared.round(lootTable[1][2] / totalWeight * chanceMult, 2, 2)))
end
end


  local returnPart = {}
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
  -- Generate header
if drop.itemID == itemID then
  if asList then
if drop.npcs == nil then
    if chanceMult < 100 then
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1, npcID = itemID})
      table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(Shared.round(100 - chanceMult, 2, 2))) * 2)) .. Shared.round(100 - chanceMult, 2, 2) .. '% No Item')
else
    end
for j, npcID in ipairs(drop.npcs) do
  else
local npc = p.getThievingNPCByID(npcID)
    table.insert(returnPart, '{|class="wikitable sortable"\r\n!Item!!colspan="2"|Chance!!Price')
if npc ~= nil then
  end
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})
  -- Generate row for each item
end
  for i, drop in pairs(lootTable) do
end
    local item, itemText, sellsFor, dropChance = Items.getItemByID(drop[1]), 'Unknown', 0, Shared.round(drop[2] / totalWeight * chanceMult, 2, 2)
end
    if item ~= nil then
end
      itemText, sellsFor = Icons.Icon({item.name, type='item'}), item.sellsFor
end
    end
    if asList then
      table.insert(returnPart, '* ' .. string.rep('&nbsp;', math.max(0, (maxDropLen - string.len(dropChance)) * 2)) .. dropChance .. '% ' .. itemText)
    else
      table.insert(returnPart, '|-\r\n|' .. itemText)
      table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. dropChance .. '"|' .. Shared.fraction(drop[2] * chanceMult, totalWeight * 100))
      table.insert(returnPart, '|style="text-align:right;"|' .. dropChance .. '%')
      table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. sellsFor .. '"|' .. Icons.GP(sellsFor))
    end
  end
  if not asList then
    table.insert(returnPart, '|}')
  end


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


function p.getThievingNPCTable()
-- Astrology
  local returnPart = {}
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end


  -- Create table header
function p.getConstellation(constName)
  table.insert(returnPart, '{| class="wikitable sortable stickyHeader"')
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
  table.insert(returnPart, '|- class="headerRow-0"\r\n!Target!!Name!!' .. Icons.Icon({'Thieving', type='skill', notext=true}).. ' Level!!Experience!!Max Hit!!Max Coins')
end
 
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    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;" |[[' .. p._getThievingNPCStat(npc, 'pageName') .. ']]')
    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')
function p.getConstellations(checkFunc)
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
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 = {}
  -- Create row for each NPC
  for i, npc in Shared.skpairs(SkillData.Thieving) do
    table.insert(npcList, Icons.Icon({npc.name, type='thieving', notext=true}) .. ' [[' .. p._getThievingNPCStat(npc, 'pageName') .. ']]')
  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