Module:Skills: Difference between revisions

Adjusted the appearance of the Lesser Relics table
(Removed redundant references to getSkillID since that's now a Constants function)
(Adjusted the appearance of the Lesser Relics table)
 
(37 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
Line 11: Line 15:


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


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Constants = require('Module:Constants')
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}
-- Given a skill ID, returns the key for that skill's recipe data.
-- If the skill has no recipes (e.g. is a combat skill) then the
-- return value is nil
function p.getSkillRecipeKey(skillID)
-- Convert skillID to local ID if not already
local ns, localSkillID = GameData.getLocalID(skillID)
local recipeIDs = {
["Woodcutting"] = 'trees',
["Fishing"] = 'fish',
["Firemaking"] = 'logs',
["Mining"] = 'rockData',
["Thieving"] = 'npcs',
["Agility"] = 'obstacles',
["Cooking"] = 'recipes',
["Smithing"] = 'recipes',
["Farming"] = 'recipes',
["Summoning"] = 'recipes',
["Fletching"] = 'recipes',
["Crafting"] = 'recipes',
["Runecrafting"] = 'recipes',
["Herblore"] = 'recipes',
["Astrology"] = 'recipes'
}
return recipeIDs[localSkillID]
end
 
-- Given a skill ID & recipe, returns the skill level requirement for
-- that recipe. If the level could not be determined, then the return
-- value is nil
function p.getRecipeLevel(skillID, recipe)
-- Convert skillID to local ID if not already
local ns, localSkillID = GameData.getLocalID(skillID)
if localSkillID == 'Agility' then
-- For Agility, level is derived from obstacle category
if recipe.category ~= nil then
-- Obstacle
return SkillData.Agility.obstacleUnlockLevels[recipe.category+1]
else
-- Pillar
local nsR, localRecipeID = GameData.getLocalID(recipe.id)
if localRecipeID ~= nil then
if string.find(localRecipeID, '^Pillar') ~= nil then
return 99
elseif string.find(localRecipeID, '^ElitePillar') ~= nil then
return 120
end
end
end
else
-- For all other skills, the recipe should have a level property
return recipe.level
end
end


function p.getMasteryUnlockTable(frame)
-- Thieving
  local skillName = frame.args ~= nil and frame.args[1] or frame
function p.getThievingNPCByID(npcID)
  local skillID = Constants.getSkillID(skillName)
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
  if skillID == nil then
end
    return "ERROR: Failed to find a skill ID for "..skillName
  end


  local unlockTable = SkillData.MasteryUnlocks[skillID]
function p.getThievingNPC(npcName)
  if unlockTable == nil then
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName)
    return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
  end


  local result = '{|class="wikitable"\r\n!Level!!Unlock'
function p.getThievingNPCArea(npc)
  for i, unlock in Shared.skpairs(unlockTable) do
for i, area in ipairs(SkillData.Thieving.areas) do
    result = result..'\r\n|-'
for j, npcID in ipairs(area.npcIDs) do
    result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
if npcID == npc.id then
  end
return area
  result = result..'\r\n|}'
end
  return result
end
end
end
end


function p.getMasteryCheckpointTable(frame)
function p._getThievingNPCStat(npc, statName)
  local skillName = frame.args ~= nil and frame.args[1] or frame
local result = nil
  local skillID = Constants.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


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


  local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
function p.getThievingNPCStat(frame)
  local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
local statName = frame.args ~= nil and frame.args[2] or frame[2]
local npc = p.getThievingNPC(npcName)
if npc == nil then
return Shared.printError('Invalid Thieving NPC ' .. npcName)
end


  local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
return p._getThievingNPCStat(npc, statName)
  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._getFarmingTable(category)
function p.getThievingSourcesForItem(itemID)
  local seedList = {}
local resultArray = {}
  if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
local areaNPCs = {}
    seedList = Items.getItems(function(item) return item.tier == category end)
  else
    return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
  end


  local result = '{|class="wikitable sortable stickyHeader"'
--First check area unique drops
  result = result..'\r\n|- class="headerRow-0"'
--If an area drops the item, add all the NPC ids to the list so we can add them later
  result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
for i, area in pairs(SkillData.Thieving.areas) do
  result = result..'!!XP!!Growth Time!!Seed Value'
for j, drop in pairs(area.uniqueDrops) do
  if category == 'Allotment' then
if drop.id == itemID then
    result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
for k, npcID in ipairs(area.npcIDs) do
  elseif category == 'Herb' then
areaNPCs[npcID] = { qty = drop.quantity, area = area }
    result = result..'!!colspan="2"|Herb!!Herb Value'
end
  elseif category == 'Tree' then
break
    result = result..'!!colspan="2"|Logs!!Log Value'
end
  end
end
  result = result..'!!Seed Sources'
end
 
  table.sort(seedList, function(a, b) return a.farmingLevel < b.farmingLevel end)


  for i, seed in pairs(seedList) do
--Now go through and get drop chances on each NPC if needed
    result = result..'\r\n|-'
for i, npc in pairs(SkillData.Thieving.npcs) do
    result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
local totalWt = 0
    result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
local dropWt = 0
    result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
local dropQty = { min = 0, max = 0 }
    result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
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, type = 'npc'})
end


    local crop = Items.getItemByID(seed.grownItemID)
--Chance of -1 on unique drops is to indicate variable chance
    result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then
    if category == 'Allotment' 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, type = 'npcUnique'})
      result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
end
    end
    result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
    result = result..'||'..ItemSourceTables._getItemSources(seed)
  end


  result = result..'\r\n|}'
local areaNPC = areaNPCs[npc.id]
  return result
if areaNPC ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = areaNPC.qty, maxQty = areaNPC.qty, wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level, npcID = npc.id, area = areaNPC.area, type = 'areaUnique'})
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, type = 'generalRare'})
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, type = 'generalRare'})
end
end
end
end
end
 
return resultArray
end
end


function p.getFarmingTable(frame)
-- Astrology
  local category = frame.args ~= nil and frame.args[1] or frame
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end


  return p._getFarmingTable(category)
function p.getConstellation(constName)
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end
end


function p.getFarmingFoodTable(frame)
function p.getConstellations(checkFunc)
  local result = '{| class="wikitable sortable stickyHeader"'
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
  result = result..'\r\n|- class="headerRow-0"'
end
  result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
 
  result = result..'!!Healing!!Value'
-- For a given constellation cons and modifier value modValue, generates and returns
 
-- a table of modifiers, much like any other item/object elsewhere in the game.
  local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end)
-- 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
 
local addToArray = function(modArray, modNew)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
table.insert(modArray, modNew)
end
end


  table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
local modTypes = {}
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end
local masteryReq = {
['standardModifiers'] = { 1, 40, 80 },
['uniqueModifiers'] = { 20, 60, 99 }
}


  for i, item in Shared.skpairs(itemArray) do
local modArray = {}
    local crop = Items.getItemByID(item.grownItemID)
local isSkillMod = {}
    if crop.healsFor ~= nil and crop.healsFor > 0 then
--Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27]
      result = result..'\r\n|-'
local groupNum = 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|}'
for _, modType in ipairs(modTypes) do
for i, modTypeData in ipairs(cons[modType]) do
groupNum = masteryReq[modType][i]
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


  return result
if asKeyValue then
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.getFarmingPlotTable(frame)
-- Mastery
  local areaName = frame.args ~= nil and frame.args[1] or frame
function p.getMasteryUnlockTable(frame)
  local patches = nil
local skillName = frame.args ~= nil and frame.args[1] or frame
  for i, area in Shared.skpairs(SkillData.Farming.Patches) do
local skillID = Constants.getSkillID(skillName)
    if area.areaName == areaName then
if skillID == nil then
      patches = area.patches
return Shared.printError('Failed to find a skill ID for ' .. skillName)
      break
end
    end
  end
  if patches == nil then
    return "ERROR: Invalid area name.[[Category:Pages with script errors"
  end


  local result = '{|class="wikitable"'
local _, localSkillID = GameData.getLocalID(skillID)
  result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
-- 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)


  for i, patch in Shared.skpairs(patches) do
local result = '{|class="wikitable"\r\n!Level!!Unlock'
    result = result..'\r\n|-\r\n|'..i
for i, unlock in ipairs(unlockTable) do
    result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
result = result..'\r\n|-'
    if patch.cost == 0 then
result = result..'\r\n|'..unlock.level..'||'..unlock.description
      result = result..'||Free'
end
    else
result = result..'\r\n|}'
      result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
return result
    end
end
  end


  result = result..'\r\n|}'
function p.getMasteryCheckpointTable(frame)
  return result
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)
local checkpoints = SkillData[localSkillID].masteryCheckpoints
if checkpoints == nil then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
end
 
local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap
local checkpointPct = GameData.rawData.masteryCheckpoints
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
for i, checkpointDesc in ipairs(checkpoints) do
result = result..'\r\n|-'
result = result..'\r\n|'..checkpointPct[i]..'%||'
result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc
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.getSmithingTable(frame)
function p.getMasteryTokenTable()
  local tableType = frame.args ~= nil and frame.args[1] or frame
-- Defines which skill levels should be included within the output
  local bar = nil
local skillLevels = {
  if tableType ~= 'Smelting' then
{
    bar = Items.getItem(tableType)
["id"] = 'Base',
    if bar == nil then
["level"] = 99,
      return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
["description"] = '[[Full Version|Base Game]] (Level 99)'
    elseif bar.type ~= 'Bar' then
}, {
      return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
["id"] = 'TotH',
    end
["level"] = 120,
  end
["description"] = Icons.TotH() .. ' [[Throne of the Herald Expansion|Throne of the Herald]] (Level 120)'
}
}
local baseTokenChance = 18500
local masteryActionCount = {}
local CCI_ID = 'melvorD:Clue_Chasers_Insignia'
local CCI = Items.getItemByID(CCI_ID)
if CCI == nil then
return Shared.printError('Failed to find item with ID ' .. CCI_ID)
end
 
-- Iterate over each skill with mastery, determining the number of
-- mastery actions for each
for skillLocalID, skill in pairs(SkillData) do
if skill.masteryTokenID ~= nil then
local actCount = { ["skill"] = skill }
for i, levelDef in ipairs(skillLevels) do
actCount[levelDef.id] = 0
end
 
local recipeKey = p.getSkillRecipeKey(skillLocalID)
if recipeKey ~= nil then
local recipeData = skill[recipeKey]
for i, recipe in ipairs(recipeData) do
if recipe.noMastery == nil or not recipe.noMastery then
local skillLevel = p.getRecipeLevel(skillLocalID, recipe)
if skillLevel ~= nil then
for j, levelDef in ipairs(skillLevels) do
if skillLevel <= levelDef.level then
actCount[levelDef.id] = actCount[levelDef.id] + 1
end
end
end
end
end
end
table.insert(masteryActionCount, actCount)
end
end
 
local firstID = skillLevels[1].id
table.sort(masteryActionCount,
function(a, b)
if a[firstID] == b[firstID] then
return a.skill.name < b.skill.name
else
return a[firstID] > b[firstID]
end
end)
-- Generate output table
local resultPart = {}
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})
local columnPairs = Shared.tableCount(skillLevels)
 
-- Generate header
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\n!rowspan="3"|Token!!rowspan="3"|Skill!!colspan="' .. columnPairs * 2 .. '"|Approximate Mastery Token Chance')
table.insert(resultPart, '\n|-')
for i, levelDef in ipairs(skillLevels) do
table.insert(resultPart, '\n!colspan="2"| ' .. levelDef.description)
end
table.insert(resultPart, '\n|-' .. string.rep('\n!Without ' .. CCIIcon .. '\n!With ' .. CCIIcon, columnPairs))


  local smithList = {}
for i, rowData in ipairs(masteryActionCount) do
  for i, item in pairs(ItemData.Items) do
local token = Items.getItemByID(rowData.skill.masteryTokenID)
    if item.smithingLevel ~= nil then
table.insert(resultPart, '\n|-')
      if tableType == 'Smelting' then
table.insert(resultPart, '\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
        if item.type == 'Bar' then
table.insert(resultPart, '\n|' .. Icons.Icon({rowData.skill.name, type='skill'}))
          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"'
for j, levelDef in ipairs(skillLevels) do
  result = result..'\r\n|-class="headerRow-0"'
local actCount = rowData[levelDef.id]
  result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
local denom, denomCCI = 0, 0
  --Adding value/bar for things other than smelting
if actCount > 0 then
  if bar ~= nil then result = result..'!!Value/Bar' end
denom = math.floor(baseTokenChance / actCount)
denomCCI = Shared.round(baseTokenChance / (actCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0)
end
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
end
end
table.insert(resultPart, '\n|}')


  table.sort(smithList, function(a, b)
return table.concat(resultPart)
                          if a.smithingLevel ~= b.smithingLevel then
end
                            return a.smithingLevel < b.smithingLevel
                          else
                            return a.name < b.name
                          end end)
  for i, item in Shared.skpairs(smithList) do
    result = result..'\r\n|-'
    result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
    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|}'
-- Skill unlock costs for Adventure game mode
  return result
function p.getSkillUnlockCostTable()
local advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure')
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 accCost = 0
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, '|}')
 
return table.concat(returnPart, '\r\n')
end
end
end


function p.getFiremakingTable(frame)
function p.getFiremakingTable(frame)
  local resultPart = {}
local resultPart = {}
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '\r\n|-class="headerRow-0"')
table.insert(resultPart, '\r\n|-class="headerRow-0"')
  table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level')
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, '!!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|-class="headerRow-1"')
  table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s')
table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s')


  for i, logData in Shared.skpairs(SkillData.Firemaking) do
for i, logData in ipairs(SkillData.Firemaking.logs) do
  local logs = Items.getItemByID(logData.logID)
local logs = Items.getItemByID(logData.logID)
    local name = logs.name
local name = logs.name
    local burnTime = logData.baseInterval / 1000
local burnTime = logData.baseInterval / 1000
    local bonfireTime = logData.baseBonfireInterval / 1000
local bonfireTime = logData.baseBonfireInterval / 1000
    local XPS = logData.baseXP / burnTime
local XPS = logData.baseExperience / burnTime
    local XP_BF = logData.baseXP * (1 + logData.bonfireXPBonus / 100)
local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100)
    local XPS_BF = XP_BF / burnTime
local XPS_BF = Shared.round(XP_BF / burnTime, 2, 2)
XP_BF = Shared.round(XP_BF, 2, 0)


    table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|-')
    table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}))
table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}))
    table.insert(resultPart, '||[['..name..']]')
table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true}))
    table.insert(resultPart, '||style ="text-align: right;"|'..logData.levelRequired)
table.insert(resultPart, '||style ="text-align: right;"|'..logData.level)
    table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true))
    table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseXP)
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. logData.baseExperience .. '"| ' .. Shared.formatnum(logData.baseExperience))
    table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.formatnum(Shared.round(XPS, 2, 2)))
    table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. XP_BF .. '"| ' .. Shared.formatnum(XP_BF))
    table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.formatnum(XPS_BF, 2, 2))
    table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
    table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
  end
end


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


function p.getMasteryTokenTable()
function p.getAncientRelicsTable(frame)
  local baseTokenChance = 18500
local skillName = frame.args ~= nil and frame.args[1] or frame
  local masterySkills = {}
local skillID = nil
 
if skillName ~= nil and skillName ~= '' then
  -- Find all mastery tokens
skillID = Constants.getSkillID(skillName)
  local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
if skillID == nil then
  for i, item in pairs(masteryTokens) do
return Shared.printError('Failed to find a skill ID for ' .. skillName)
    local milestones = SkillData.Milestones[item.skill + 1]
end
    if milestones ~= nil then
end
      table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones})
 
    end
local resultPart = {}
  end
table.insert(resultPart, '{| class="wikitable sortable stickyHeader lighttable"')
  table.sort(masterySkills, function(a, b)
table.insert(resultPart, '\n|-class="headerRow-0"')
                              if a['milestoneCount'] == b['milestoneCount'] then
table.insert(resultPart, '\n|-\n!colspan="2"|Skill\n!Relic\n!Modifiers')
                                return a['skillID'] < b['skillID']
 
                              else
local relics = GameData.getEntities('ancientRelics',
                                return a['milestoneCount'] > b['milestoneCount']
function(relic)
                              end
return skillID == nil or relic.skillID == skillID
                            end)
end)
 
table.sort(relics,
  -- Generate output table
function (a, b)
  local resultPart = {}
local skillNameA, skillNameB = Constants.getSkillName(a.skillID), Constants.getSkillName(b.skillID)
  local CCI = Items.getItem('Clue Chasers Insignia')
if skillNameA == skillNameB then
  local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true})
-- Order by numbers at the end of relic IDs
  if CCI == nil then return '' end
-- Relics have a 'number' property, but this appears to contain duplicates
return string.sub(a.id, string.len(a.id)) < string.sub(b.id, string.len(b.id))
else
return skillNameA < skillNameB
end
end)
 
local function appendSkillRows(resultTable, rowTable, relicCount, skillID)
local skillName = Constants.getSkillName(skillID)
table.insert(resultTable, '\n|-\n|rowspan="' .. relicCount .. '"| ' .. Icons.Icon({skillName, type='skill', notext=true, size=50}))
table.insert(resultTable, '\n|rowspan="' .. relicCount .. '"| ' .. Icons.Icon({skillName, type='skill', noicon=true}))
table.insert(resultTable, table.concat(rowTable))
end


  table.insert(resultPart, '{| class="wikitable sortable"')
local skillRelicCount, currentSkillID, tablePart = 0, nil, {}
  table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
for i, relic in ipairs(relics) do
  table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
if currentSkillID == nil then
currentSkillID = relic.skillID
elseif relic.skillID ~= currentSkillID then
appendSkillRows(resultPart, tablePart, skillRelicCount, currentSkillID)
tablePart = {}
currentSkillID = relic.skillID
skillRelicCount = 0
end


  for i, m in ipairs(masterySkills) do
skillRelicCount = skillRelicCount + 1
    local token = masteryTokens[m.tokenRef]
if skillRelicCount > 1 then
    local denom = math.floor(baseTokenChance / m['milestoneCount'])
table.insert(tablePart, '\n|-')
    local denomCCI = math.floor(baseTokenChance / m['milestoneCount'] * (1 - CCI.increasedItemChance / 100))
end
table.insert(tablePart, '\n| ' .. skillRelicCount .. '\n| ' .. Constants.getModifiersText(relic.modifiers))
end
appendSkillRows(resultPart, tablePart, skillRelicCount, currentSkillID)
table.insert(resultPart, '\n|}')


    table.insert(resultPart, '\r\n|-')
return table.concat(resultPart)
    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({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|}')
 
  return table.concat(resultPart)
end
end


function p.getSkillUnlockCostTable()
function p.getLesserRelicsTable(frame)
  local returnPart = {}
local lesserRelics = {}
  table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
-- Iterate over each skill with a global rare drop then check
-- if the skill has a Lesser Relic drop
for skillLocalID, skill in pairs(SkillData) do
if skill.rareDrops ~= nil then
for i, drops in pairs(skill.rareDrops) do
if string.match(drops.itemID, '_Lesser_Relic') then
table.insert(lesserRelics, Items.getItemByID(drops.itemID))
end
end
end
end
table.sort(lesserRelics, function(a, b) return a.name < b.name end)


  local accCost = 0
-- Create the Table
  for i, cost in ipairs(SkillData.SkillUnlockCosts) do
local resultTable = mw.html.create('table')
    accCost = accCost + cost
resultTable:addClass('wikitable sortable')
    table.insert(returnPart, '|-')
resultTable:tag('tr'):addClass('headerRow-0')
    table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
:tag('th'):wikitext('Icon')
  end
:tag('th'):wikitext('Lesser Relic')
  table.insert(returnPart, '|}')
:tag('th'):wikitext('Modifiers')


  return table.concat(returnPart, '\r\n')
for _, relic in ipairs(lesserRelics) do
local tr = mw.html.create('tr')
tr:tag('td'):wikitext(Icons.Icon({relic.name, type='item', size='50', notext=true}))
tr:tag('td'):wikitext(Icons.Icon({relic.name, type='item', noicon=true}))
tr:tag('td'):wikitext(Constants.getModifiersText(relic.modifiers))
resultTable:node(tr)
end
return resultTable
end
end


return p
return p
463

edits