Module:Skills/Gathering: Difference between revisions

From Melvor Idle
(fix formatting)
(Undo revision 47205 by Valdar (talk))
Tag: Undo
Line 1: Line 1:
--Splitting some functions into here to avoid bloating a single file
--Splitting some functions into here to avoid bloating a single file
--Contains function for skills that consume resources (ie smithing, cooking, herblore, etc.)
local p = {}
local p = {}


local SkillData = mw.loadData('Module:Skills/data')
local SkillData = mw.loadData('Module:Skills/data')
local ShopData = mw.loadData('Module:Shop/data')


local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')


function p.getCookedItemsTable(frame)
local thievingNormalLootChance = 75
local category = frame.args ~= nil and frame.args[1] or frame
local thievingAreaLootChance = 0.2
local itemArray = nil
 
function p.getAxeTable(frame)
if category == "Cooking Fire" then
local toolArray = {}
itemArray = Items.getItems(function(item) return item.cookingCategory == 0 end)
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
elseif category == "Furnace" then
if Shared.contains(upgrade.name, 'Axe') then
itemArray = Items.getItems(function(item) return item.cookingCategory == 1 and item.name ~= 'Lemon Cake' end)
table.insert(toolArray, upgrade)
elseif category == "Pot"  then  
end
itemArray = Items.getItems(function(item) return item.cookingCategory == 2 end)
else
itemArray = Items.getItems(function(item) return item.cookingCategory ~= nil and item.name ~= 'Lemon Cake' end)
end
end
table.sort(itemArray, function(a, b) return a.cookingLevel < b.cookingLevel end)
 
local result = '{| class="wikitable"'
-- Logic for generating some cells of the table which are consistent for normal & perfect items
result = result..'\r\n!colspan="4"| !!colspan="2"|Cut Time Decrease'
local getHealingCell = function(item, qty)
result = result..'\r\n|- class="headerRow-0"'
if item ~= nil then
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
return 'data-sort-value="'..(math.floor(item.healsFor) * qty)..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..math.floor(item.healsFor * 10)..(qty > 1 and ' (x'..qty..')' or '')
result = result..'!!Cost!!This Axe!!Total'
else
 
return ' '
local total = 0
 
for i, tool in Shared.skpairs(toolArray) do
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'||'..tool.name
local level = 1
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
end
result = result..'||style="text-align:right"|'..level
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
total = total + cutTime
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right"|-'..total..'%'
end
end
local getSaleValueCell = function(item, qty)
 
if item ~= nil then
result = result..'\r\n|}'
return 'data-sort-value="'..(math.floor(item.sellsFor) * qty)..'"|'..Icons.GP(math.floor(item.sellsFor))..(qty > 1 and ' (x'..qty..')' or '')
return result
else
end
return ' '
 
function p.getPickaxeTable(frame)
local toolArray = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
if Shared.contains(upgrade.name, 'Pickaxe') then
table.insert(toolArray, upgrade)
end
end
end
end
 
local result = '{| class="wikitable sortable stickyHeader"'
local result = '{| class="wikitable"'
result = result..'\r\n!colspan="4"| !!colspan="2"|Mine Time Decrease!!colspan="2"|2x Ore Chance'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="3" rowspan="2"|Cooked Item!!rowspan="2"|'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
result = result..'!!rowspan="2"|Cook Time!!rowspan="2"|XP!!colspan="2"|Healing!!colspan="2"|Value!!rowspan="2"|Ingredients'
result = result..'!!Cost!!This Pick!!Total!!This Pick!!Total'
result = result..'\r\n|- class="headerRow-1"'
 
result = result..'\r\n!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true}) .. '!!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true})
local total = 0
local total2 = 0
for i, item in Shared.skpairs(itemArray) do
 
local perfectItem = nil
for i, tool in Shared.skpairs(toolArray) do
if item.perfectItem ~= nil then
result = result..'\r\n|-'
perfectItem = Items.getItemByID(item.perfectItem)
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'||'..tool.name
local level = 1
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
end
local qty = item.cookingQty
result = result..'||style="text-align:right"|'..level
if qty == nil then
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
qty = 1
 
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
total = total + cutTime
 
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right"|-'..total..'%'
 
local OreDouble = tool.contains.modifiers.increasedChanceToDoubleOres
total2 = total2 + OreDouble
 
result = result..'||style="text-align:right"|+'..OreDouble..'%'
result = result..'||style="text-align:right"|+'..total2..'%'
end
 
result = result..'\r\n|}'
return result
end
 
function p.getRodTable(frame)
local toolArray = {}
for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
if Shared.contains(upgrade.name, 'Fishing Rod') then
table.insert(toolArray, upgrade)
end
end
end
local result = '{| class="wikitable"'
result = result..'\r\n!colspan="4"| !!colspan="2"|Catch Time Decrease'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level'
result = result..'!!Cost!!This Rod!!Total'
local total = 0
for i, tool in Shared.skpairs(toolArray) do
result = result..'\r\n|-'
result = result..'\r\n|-'
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
result = result..'\r\n|style="min-width:25px"| '
result = result..'||'..tool.name
if perfectItem ~= nil then
local level = 1
result = result..Icons.Icon({perfectItem.name, type='item', notext=true, size='50'})
if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
--Gonna be lazy and assume there's only the one skill level and it's the one we care about
level = tool.unlockRequirements.skillLevel[1][2]
end
end
result = result..'||'
result = result..'||style="text-align:right"|'..level
if qty > 1 then
result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)
result = result..qty..'x '
 
end
local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
result = result..Icons.Icon({item.name, type='item', noicon = true})
total = total + cutTime
result = result..'||style="text-align:right"|'..item.cookingLevel
result = result..'||style="text-align:right"|-'..cutTime..'%'
result = result..'||style="text-align:right" data-sort-value="' .. item.cookingInterval .. '"|'..Shared.timeString(item.cookingInterval / 1000, true)
result = result..'||style="text-align:right"|-'..total..'%'
result = result..'||style="text-align:right"|'..item.cookingXP
end
result = result..'||'..getHealingCell(item, qty)..'||'..getHealingCell(perfectItem, qty)
 
result = result..'||'..getSaleValueCell(item, qty)..'||'..getSaleValueCell(perfectItem, qty)
result = result..'\r\n|}'
    local matArray = {}
return result
    for j, reqSet in pairs(item.recipeRequirements) do
end
    for k, mat in pairs(reqSet) do
 
local matItem = Items.getItemByID(mat.id)
function p.getTreesTable(frame)
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
local result = '{| class="wikitable sortable"'
    end
result = result..'\r\n|- class="headerRow-0"'
end
result = result..'\r\n!colspan="2"|Tree!!colspan="2"|Logs!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
    result = result..'\r\n|'..table.concat(matArray, ' ')
result = result..'!!XP!!Cut Time!!XP/s!!GP/s'
 
for i, tree in Shared.skpairs(SkillData.Woodcutting.Trees) do
result = result..'\r\n|-'
local treeName = Shared.titleCase(tree.type..' tree')
local logName = Shared.titleCase(tree.type..' logs')
result = result..'\r\n|style="min-width:25px" data-sort-value="'..treeName..'"|'..Icons.Icon({logName, img=treeName, type='tree', notext=true, size=50})
result = result..'||'..treeName..''
result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50})
result = result..'||'..Icons.Icon({logName, type='item', noicon=true})
result = result..'||style="text-align:right"|'..tree.level
result = result..'||style="text-align:right"|'..tree.xp
result = result..'||style="text-align:right" data-sort-value="'..tree.interval..'"|'..Shared.timeString(tree.interval/1000, true)
local XPs = tree.xp / (tree.interval / 1000)
local Log = Items.getItemByID(i - 1)
local GPs = Log.sellsFor / (tree.interval / 1000)
result = result..'||style="text-align:right"|'..Shared.round(XPs, 2, 2)
result = result..'||style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))
end
end


Line 86: Line 163:
end
end


local tierSuffix = { 'I', 'II', 'III', 'IV' }
function p.getSpecialFishingTable(frame)
function p._getHerblorePotionTable(category)
local lootValue = 0
  if string.upper(category) == 'COMBAT' then
local totalWt = Items.specialFishWt
    category = 0
 
  elseif string.upper(category) == 'SKILL' then
local result = ''
    category = 1
result = result..'\r\n{|class="wikitable sortable stickyHeader"'
  elseif type(category) == 'string' then
result = result..'\r\n|- class="headerRow-0"'
    category = tonumber(category)
result = result..'\r\n!Item'
  end
result = result..'!!Price!!colspan="2"|Chance'


  local potionArray = {}
--Sort the loot table by weight in descending order
  for i, potion in Shared.skpairs(SkillData.Herblore.ItemData) do
table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
    if potion.category == category then
for i, row in pairs(Items.specialFishLoot) do
      table.insert(potionArray, potion)
local thisItem = Items.getItemByID(row[1])
    end
result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
  end
result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
result = result..'|'..Icons.GP(thisItem.sellsFor)


  local result = '{|class = "wikitable sortable stickyHeader"'
local dropChance = (row[2] / totalWt) * 100
  result = result..'\r\n|- class="headerRow-0"'
result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
  result = result..'\r\n!Potion!!'..Icons.Icon({'Herblore', type='skill', notext=true})..' Level'
result = result..'|'..Shared.fraction(row[2], totalWt)
  result = result..'!!XP!!Ingredients!!colspan="2"|Tier!!Value!!Charges!!Effect'
result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
end
result = result..'\r\n|}'
result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))


  table.sort(potionArray, function(a, b) return a.level < b.level end)
return result
end


  for i, potion in Shared.skpairs(potionArray) do
function p.getFishingJunkTable(frame)
    local tierPots = {}
local result = '{| class="wikitable sortable stickyHeader"'
    for j = 1, 4, 1 do
result = result..'\r\n|- class="headerRow-0"'
      table.insert(tierPots, Items.getItemByID(potion.itemID[j]))
result = result..'\r\n!colspan="2"|Item!!Value'
    end
    result = result..'\r\n|-'
local itemArray = Items.getItems(function(item) return item.type == "Junk" end)
    if potion.name == 'Bird Nests Potion' then
 
      result = result..'\r\n|rowspan="4"|[[Bird Nest Potion]]'
table.sort(itemArray, function(a, b) return a.name < b.name end)
    else
 
      result = result..'\r\n|rowspan="4"|[['..potion.name..']]'
for i, item in Shared.skpairs(itemArray) do
    end
result = result..'\r\n|-'
    result = result..'||rowspan="4" style="text-align:right"|'..potion.level
result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
    result = result..'||rowspan="4" style="text-align:right"|'..potion.herbloreXP
result = result..'||'..Icons.Icon({item.name, type='item', noicon=true})
result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor)
end


    local matArray = {}
result = result..'\r\n|}'
    for j, mat in Shared.skpairs(tierPots[1].herbloreReq) do
      local matItem = Items.getItemByID(mat.id)
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
    end
    result = result..'||rowspan="4"|'..table.concat(matArray, ', ')..'||'
   
    local tierRows = {}
    for j, tierPot in Shared.skpairs(tierPots) do
      local rowTxt = Icons.Icon({tierPot.name, type='item', notext=true})
      rowTxt = rowTxt..'||'..Icons.Icon({tierPot.name, tierSuffix[j], type = 'item', noicon = true})
      rowTxt = rowTxt..'||style="text-align:right;" data-sort-value="'..tierPot.sellsFor..'"|'..Icons.GP(tierPot.sellsFor)
      rowTxt = rowTxt..'||style="text-align:right;"|'..tierPot.potionCharges..'||'..tierPot.description
      table.insert(tierRows, rowTxt)
    end
    result = result..table.concat(tierRows, '\r\n|-\r\n|')
  end


  result = result..'\r\n|}'
return result
  return result
end
end


function p.getHerblorePotionTable(frame)
function p.getMiningOresTable(frame)
  local category = frame.args ~= nil and frame.args[1] or frame
local result = '{|class="wikitable sortable stickyHeader"'
  return p._getHerblorePotionTable(category)
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
result = result..'!!XP!!Respawn Time!!Ore Value'
 
local mineData = Shared.clone(SkillData.Mining.Rocks)
 
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|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})
result = result..'||'..Icons.Icon({ore.name, type='item', noicon=true})
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|}'
return result
end
end


function p.getRunecraftingTable(frame)
function p.getMiningGemsTable(frame)
  local category = frame.args ~= nil and frame.args[1] or frame
local result = '{|class="wikitable sortable stickyHeader"'
  local data = nil
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Gem!!Gem Chance!!Gem Price'


  if    category == "Runes"      then data =
-- Sort gems by ID order
        Items.getItems(function(item) return item.runecraftingCategory == 0 end)
for i, gemData in Shared.spairs(Items.GemTable, function(t,a,b) return t[a].id < t[b].id end) do
  elseif category == "ComboRunes" then data =  
local gem = Items.getItemByID(gemData.id)
        Items.getItems(function(item) return item.runecraftingCategory == 1 end)
result = result..'\r\n|-\r\n|style="min-width:25px"|'
  elseif category == "Weapons"   then data =  
result = result..Icons.Icon({gem.name, type='item', size='50', notext=true})
        Items.getItems(function(item) return item.runecraftingCategory == 2 and item.runecraftingID end)
result = result..'||'..Icons.Icon({gem.name, type='item', noicon=true})
  elseif category == "AirGear"    then data =
result = result..'||style="text-align:right"|'..string.format("%.1f%%", gemData.chance)
        Items.getItems(function(item) return item.runecraftingCategory == 3 end)
result = result..'||data-sort-value="'..gem.sellsFor..'"|'..Icons.GP(gem.sellsFor)
  elseif category == "WaterGear" then data =
end
        Items.getItems(function(item) return item.runecraftingCategory == 4 end)
  elseif category == "EarthGear" then data =
        Items.getItems(function(item) return item.runecraftingCategory == 5 end)
  elseif category == "FireGear"  then data =
        Items.getItems(function(item) return item.runecraftingCategory == 6 end)
  end


  if data == nil then
result = result..'\r\n|}'
    return "ERROR: Invalid Runecrafting category name.[[Category:Pages with script errors]]"
return result
  end
end


  local result = '{| class="wikitable sortable stickyHeader"'
function p.getFishTable(frame)
  result = result..'\r\n|- class="headerRow-0"'
local data = Items.getItems(function(item) return item.fishingID ~= nil end)
  result = result..'\r\n!Item\r\n!Name\r\n!'..Icons.Icon({'Runecrafting', type='skill', notext=true})..' Level\r\n!Experience'
  result = result..'\r\n!Item Price\r\n!Ingredients\r\n!XP/s\r\n!GP/s'


  table.sort(data, function(a, b) return (a.runecraftingLevel == b.runecraftingLevel and a.id < b.id)
table.sort(data, function(a, b) return a.fishingID < b.fishingID end)
                                      or a.runecraftingLevel < b.runecraftingLevel end)


  for i, rune in Shared.skpairs(data) do
local result = '{| class="wikitable sortable stickyHeader"'
    result = result..'\r\n|-'
result = result..'\r\n|- class="headerRow-0"'
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({rune.name, type='item', size='50', notext=true})
result = result..'\r\n!Fish\r\n!Name\r\n!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level\r\n!Catch Time'
    result = result..'\r\n| style ="text-align: left;" |'..Icons.Icon({rune.name, type='item', noicon=true})
result = result..'\r\n!Experience\r\n!Fish Price\r\n!XP/s\r\n!GP/s\r\n!'
    result = result..'\r\n| style="text-align:right"|'..rune.runecraftingLevel
result = result..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'
    result = result..'\r\n| style="text-align:right"|'..rune.runecraftingXP
 
    result = result..'\r\n| style="text-align:right"|'..rune.sellsFor
for i, fish in Shared.skpairs(data) do
result = result..'\r\n|-'
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', size='50', notext=true})
result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', noicon=true})
result = result..'\r\n| style="text-align:right"|'..fish.fishingLevel


    local matArray = {}
local timeSortVal = (fish.minFishingInterval + fish.maxFishingInterval) / 2
    for j, mat in Shared.skpairs(rune.runecraftReq) do
local timeStr = string.format("%.1fs-%.1fs", (fish.minFishingInterval/1000), (fish.maxFishingInterval/1000))
      local matItem = Items.getItemByID(mat.id)
result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
result = result..'\r\n| style="text-align:right"|'..fish.fishingXP
    end
result = result..'\r\n| style="text-align:right"|'..fish.sellsFor
    result = result..'\r\n|'..table.concat(matArray, ' ')
local XPs = fish.fishingXP / (timeSortVal / 1000)
local GPs = fish.sellsFor / (timeSortVal / 1000)
result = result..'\r\n| style="text-align:right"|'..Shared.round(XPs, 2, 2)
result = result..'\r\n| style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))


    local rcCraftTime = 2.00
local cookStr = "N/A"
    local xps = rune.runecraftingXP / rcCraftTime
if fish.cookingLevel ~= nil then
    local gps = rune.sellsFor / rcCraftTime
cookStr = fish.cookingLevel
    result = result..'\r\n| style="text-align:right"|'..string.format("%.2f", xps)
end
    result = result..'\r\n| style="text-align:right"|'..string.format("%.2f", gps)
result = result..'\r\n| style="text-align:right"|'..cookStr
  end
end


  result = result..'\r\n|}'
result = result..'\r\n|}'
  return result
return result
end
end


function p.getFletchingTable(frame)
function p.getFishingAreasTable(frame)
  local category = frame.args ~= nil and frame.args[1] or frame
  local data = nil


  if    category == "Arrows"   then
local result = '{| class="wikitable sortable stickyHeader"'
  data = Items.getItems(function(item) return item.fletchingCategory == 0 end)
result = result..'\r\n|- class="headerRow-0"'
  elseif category == "Shortbows" then
result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance'
  data = Items.getItems(function(item) return item.fletchingCategory == 1 end)
result = result..'\r\n!Junk Chance\r\n!Special Chance'
  elseif category == "Longbows"  then
  data = Items.getItems(function(item) return item.fletchingCategory == 2 end)
  elseif category == "Bolts"    then
  data =  Items.getItems(function(item) return item.fletchingCategory == 3 end)
  elseif category == "Crossbows" then
  data = Items.getItems(function(item) return item.fletchingCategory == 4 end)
  elseif category == "Javelins"  then
  data = Items.getItems(function(item) return item.fletchingCategory == 5 end)
  end


  if data == nil then
for i, area in Shared.skpairs(SkillData.Fishing.Areas) do
    return "ERROR: Invalid Fletching category name.[[Category:Pages with script errors]]"
result = result..'\r\n|-'
  end
result = result..'\r\n| style ="text-align: left;" |'..area.name


  local result = '{| class="wikitable sortable stickyHeader"'
local fishArray = {}
  result = result..'\r\n|- class="headerRow-0"'
for j, fish in Shared.skpairs(area.fish) do
  result = result..'\r\n!Item\r\n!Name\r\n!Fletching Level\r\n!Experience'
local fishTable = Items.getItems(function(item) return item.fishingID == fish end)
  result = result..'\r\n!Quantity\r\n!Sells For\r\n!Ingredients'
local fishItem = fishTable[0] or fishTable[1]
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
end
result = result..'\r\n|'..table.concat(fishArray, '<br />')


  table.sort(data, function(a, b) return (a.fletchingLevel == b.fletchingLevel and a.id < b.id)
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%'
                                      or a.fletchingLevel <  b.fletchingLevel end)
result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%'
result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%'
end
 
result = result..'\r\n|}'
return result
end


  for i, fletch in Shared.skpairs(data) do
function p.getThievingNPC(npcName)
    result = result..'\r\n|-'
local result = nil
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fletch.name, type='item', size='50', notext=true})
for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do
    result = result..'\r\n| style ="text-align: left;" |'..Icons.Icon({fletch.name, type='item', noicon=true})
if npc.name == npcName then
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchingLevel
result = Shared.clone(npc)
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchingXP
break
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchQty
end
    result = result..'\r\n| style="text-align:right"|'..fletch.sellsFor
end
return result
end


    local matArray = {}
function p.getThievingNPCArea(npc)
    for j, mat in Shared.skpairs(fletch.fletchReq) do
if type(npc) == 'string' then
      local matItem = Items.getItemByID(mat.id)
npc = p.getThievingNPC(npc)
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
end
    end
    result = result..'\r\n|'..table.concat(matArray, ' ')
local result = nil
  end
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


  result = result..'\r\n|}'
function p._getThievingNPCStat(npc, statName)
  return result
local result = nil
if statName == 'level' then
result = Icons._SkillReq('Thieving', npc.level)
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
end


function p.getCraftingTable(frame)
function p.getThievingNPCStat(frame)
  local category = frame.args ~= nil and frame.args[1] or frame
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
  local data = nil
local statName = frame.args ~= nil and frame.args[2] or frame[2]
local npc = p.getThievingNPC(npcName)
if npc == nil then
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
end
return p._getThievingNPCStat(npc, statName)
end


  if    category == "Leather"   then data =  
function p.getThievingGeneralRareTable(frame)
        Items.getItems(function(item) return item.tier == "Leather" or item.tier == "Hard Leather" end)
local rareTxt = '{|class="wikitable sortable"'
  elseif category == "Dragonhide" then data =  
rareTxt = rareTxt..'\r\n!Item!!Qty'
        Items.getItems(function(item) return item.tier == "Dragonhide" and item.craftingLevel ~= nil end)
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
  elseif category == "Rings"     then data =
for i, drop in pairs(SkillData.Thieving.RareItems) do
        Items.getItems(function(item) return item.type == "Ring" and item.craftingLevel ~= nil end)
local thisItem = Items.getItemByID(drop.itemID)
  elseif category == "Necklaces" then data =  
local odds = drop.chance
        Items.getItems(function(item) return item.type == "Amulet" and item.craftingLevel ~= nil end)
  elseif category == "Bags"   then data =
rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
  Items.getItems(function(item) return item.type == 'Bag' and item.craftingLevel ~= nil end)
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
  end
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0))
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
end
rareTxt = rareTxt..'\r\n|}'
return rareTxt
end


  if data == nil then
function p._getThievingNPCLootTables(npc)
    return "ERROR: Invalid Crafting category name.[[Category:Pages with script errors]]"
local result = ''
  end
local sectionTxt = {}
--Five sections here: GP, normal loot, area loot, rare loot, and unique item
--First up, GP:
local gpTxt = 'Successfully pickpocketing the '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
table.insert(sectionTxt, gpTxt)
--Next up, normal loot:
--(Skip if no loot)
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:'
local totalWt = 0
local lootChance = thievingNormalLootChance
local lootValue = 0
--First loop through to get the total weight so we have it for later
for i, loot in pairs(npc.lootTable) do
totalWt = totalWt + loot[2]
end
normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
normalTxt = normalTxt..'\r\n!Item!!Qty'
normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
--Then sort the loot table by weight
table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end)
for i, row in Shared.skpairs(npc.lootTable) do
local thisItem = Items.getItemByID(row[1])
local maxQty = row[3]
if thisItem ~= nil then
normalTxt = normalTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
else
normalTxt = normalTxt..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]'
end
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
if maxQty > 1 then
normalTxt = normalTxt.. '1 - '
end
normalTxt = normalTxt..Shared.formatnum(row[3])
--Adding price columns
local itemPrice = 0
if thisItem == nil then
normalTxt = normalTxt..'||data-sort-value="0"|???'
else
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
if itemPrice == 0 or maxQty == 1 then
normalTxt = normalTxt..'||'..Icons.GP(itemPrice)
else
normalTxt = normalTxt..'||'..Icons.GP(itemPrice, itemPrice * maxQty)
end
end
--Getting the drop chance
local dropChance = (row[2] / totalWt * lootChance)
if dropChance ~= 100 then
--Show fraction as long as it isn't going to be 1/1
normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..row[2]..'"'
normalTxt = normalTxt..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100)
normalTxt = normalTxt..'||'
else
normalTxt = normalTxt..'||colspan="2" data-sort-value="'..row[2]..'"'
end
normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
--Adding to the average loot value based on price & dropchance
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
end
if multiDrop then
normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
if lootChance < 100 then
normalTxt = normalTxt..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
else
normalTxt = normalTxt..'\r\n|colspan="2" '
end
normalTxt = normalTxt..'style="text-align:right"|'..lootChance..'.00%'
end
normalTxt = normalTxt..'\r\n|}'
table.insert(sectionTxt, normalTxt)
end
--After normal drops, add in rare drops
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket'
rareTxt = rareTxt..'\r\n'..p.getThievingGeneralRareTable()
table.insert(sectionTxt, rareTxt)
local areaTxt = '===Possible Area Unique Drops==='
areaTxt = areaTxt..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. '
areaTxt = areaTxt..'\r\nEach Area Unique Drop is rolled for separately, so it is possible to receive multiple Area Unique Drops from a single action. '
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.'
local area = p.getThievingNPCArea(npc)
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
areaTxt = areaTxt..'\r\n!Item!!Qty'
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
local dropCount = Shared.tableCount(area.uniqueDrops)
local dropLines = {}
for i, drop in pairs(area.uniqueDrops) do
local thisItem = Items.getItemByID(drop.itemID)
local lineTxt = ''
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
lineTxt = lineTxt..'||'..drop.qty..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))
lineTxt = lineTxt..'||'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
end
for i, txt in Shared.skpairs(dropLines) do
areaTxt = areaTxt..txt
end
areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))..'||'
areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
areaTxt = areaTxt..'\r\n|}'
table.insert(sectionTxt, areaTxt)
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
local uniqueTxt = '===Possible NPC Unique Drop==='
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
local thisItem = Items.getItemByID(npc.uniqueDrop.itemID)
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
if npc.uniqueDrop.qty > 1 then
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.qty})
else
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
end
table.insert(sectionTxt, uniqueTxt)
end
return table.concat(sectionTxt, '\r\n')
end


  table.sort(data, function(a, b) return (a.craftingLevel == b.craftingLevel and a.id < b.id)  
function p.getThievingNPCLootTables(frame)
                                      or a.craftingLevel <  b.craftingLevel end)
local npcName = frame.args ~= nil and frame.args[1] or frame
local npc = p.getThievingNPC(npcName)
if npc == nil then
return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
end
return p._getThievingNPCLootTables(npc)
end


  local result = '{| class="wikitable sortable stickyHeader"'
function p.getThievingNPCTable()
  result = result..'\r\n|- class="headerRow-0"'
local result = '{| class="wikitable sortable stickyHeader"'
  result = result..'\r\n!Item\r\n!Name\r\n!'..Icons.Icon({'Crafting', type='skill', notext=true})..' Level\r\n!Experience'
result = result..'\r\n|- class="headerRow-0"'
  result = result..'\r\n!Item Price\r\n!Ingredients'
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!Unique Drop'
local npcArray = Shared.clone(SkillData.Thieving.NPCs)
table.sort(npcArray, function(a, b) return a.level < b.level end)
for i, npc in Shared.skpairs(npcArray) do
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true})


  for i, craft in Shared.skpairs(data) do
local area = p.getThievingNPCArea(npc)
    result = result..'\r\n|-'
result = result..'||'..area.name
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({craft.name, type='item', size='50', notext=true})
result = result..'||'..Icons._SkillReq('Thieving', npc.level)
    result = result..'\r\n| style ="text-align: left;" |'..Icons.Icon({craft.name, type='item', noicon=true})
result = result..'||style="text-align:right"|'..npc.xp
    result = result..'\r\n| style="text-align:right"|'..craft.craftingLevel
result = result..'||style="text-align:right"|'..(npc.maxHit * 10)
    result = result..'\r\n| style="text-align:right"|'..craft.craftingXP
result = result..'||style="text-align:right"|'..npc.perception
    result = result..'\r\n| style="text-align:right"|'..craft.sellsFor
if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.itemID)
if npc.uniqueDrop.qty > 1 then
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.qty})
else
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
end
else
result = result..'|| '
end
end
result = result..'\r\n|}'
return result
end


    local matArray = {}
function p.getThievingAreaTable(frame)
    if craft.craftGPCost ~= nil and craft.craftGPCost > 0 then
local resultPart = {}
    table.insert(matArray, Icons.GP(craft.craftGPCost))
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
    end
table.insert(resultPart, '\r\n|- class="headerRow-0"')
    for j, mat in Shared.skpairs(craft.craftReq) do
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
      local matItem = Items.getItemByID(mat.id)
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
local areaArray = Shared.clone(SkillData.Thieving.Areas)
    end
table.sort(areaArray, function(a, b) return a.id < b.id end)
    result = result..'\r\n|'..table.concat(matArray, ', ')
for i, area in ipairs(areaArray) do
  end
local minLevel, npcList, areaItemList = nil, {}, {}
-- Build NPC list & determine level for area, this is the minimum
-- Thieving level required for all NPCs within that area
if area.npcs ~= nil and Shared.tableCount(area.npcs) > 0 then
for j, npcID in ipairs(area.npcs) do
-- Don't bother cloning the NPC below since we aren't modifying any part of it
local npc = SkillData.Thieving.NPCs[npcID + 1]
if minLevel == nil or npc.level < minLevel then
minLevel = npc.level
end
table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
end
else
table.insert(npcList, '')
end
-- Build area unique item list
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
for k, drop in ipairs(area.uniqueDrops) do
local areaItem = Items.getItemByID(drop.itemID)
if areaItem == nil then
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
else
local iconDef = {areaItem.name, type='item'}
if drop.qty > 1 then
iconDef.qty = drop.qty
end
table.insert(areaItemList, Icons.Icon(iconDef))
end
end
else
table.insert(areaItemList, '')
end
-- Generate table row
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|' .. area.name)
table.insert(resultPart, '\r\n|' .. Icons._SkillReq('Thieving', minLevel))
table.insert(resultPart, '\r\n|' .. table.concat(npcList, '<br/>'))
table.insert(resultPart, '\r\n|' .. table.concat(areaItemList, '<br/>'))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end


  result = result..'\r\n|}'
function p.getThievingSourcesForItem(itemID)
  return result
local resultArray = {}
local areaNPCs = {}
--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 * thievingNormalLootChance, 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 = thievingAreaLootChance, totalWt = 100, level = npc.level})
end
end
for i, drop in pairs(SkillData.Thieving.RareItems) do
if drop.itemID == itemID then
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
end
end
return resultArray
end
end


return p
return p

Revision as of 22:15, 5 November 2021

Documentation for this module may be created at Module:Skills/Gathering/doc

--Splitting some functions into here to avoid bloating a single file
local p = {}

local SkillData = mw.loadData('Module:Skills/data')
local ShopData = mw.loadData('Module:Shop/data')

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Items = require('Module:Items')
local Icons = require('Module:Icons')

local thievingNormalLootChance = 75
local thievingAreaLootChance = 0.2

function p.getAxeTable(frame)
	local toolArray = {}
	for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
		if Shared.contains(upgrade.name, 'Axe') then
			table.insert(toolArray, upgrade)
		end
	end

	local result = '{| class="wikitable"'
	result = result..'\r\n!colspan="4"| !!colspan="2"|Cut Time Decrease'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
	result = result..'!!Cost!!This Axe!!Total'

	local total = 0

	for i, tool in Shared.skpairs(toolArray) do
		result = result..'\r\n|-'
		result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
		result = result..'||'..tool.name
		local level = 1
		if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
			--Gonna be lazy and assume there's only the one skill level and it's the one we care about
			level = tool.unlockRequirements.skillLevel[1][2]
		end
		result = result..'||style="text-align:right"|'..level
		result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)

		local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
		total = total + cutTime
		result = result..'||style="text-align:right"|-'..cutTime..'%'
		result = result..'||style="text-align:right"|-'..total..'%'
	end

	result = result..'\r\n|}'
	return result
end

function p.getPickaxeTable(frame)
	local toolArray = {}
	for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
		if Shared.contains(upgrade.name, 'Pickaxe') then
			table.insert(toolArray, upgrade)
		end
	end

	local result = '{| class="wikitable"'
	result = result..'\r\n!colspan="4"| !!colspan="2"|Mine Time Decrease!!colspan="2"|2x Ore Chance'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
	result = result..'!!Cost!!This Pick!!Total!!This Pick!!Total'

	local total = 0
	local total2 = 0

	for i, tool in Shared.skpairs(toolArray) do
		result = result..'\r\n|-'
		result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
		result = result..'||'..tool.name
		local level = 1
		if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
			--Gonna be lazy and assume there's only the one skill level and it's the one we care about
			level = tool.unlockRequirements.skillLevel[1][2]
		end
		result = result..'||style="text-align:right"|'..level
		result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)

		local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
		total = total + cutTime

		result = result..'||style="text-align:right"|-'..cutTime..'%'
		result = result..'||style="text-align:right"|-'..total..'%'

		local OreDouble = tool.contains.modifiers.increasedChanceToDoubleOres
		total2 = total2 + OreDouble

		result = result..'||style="text-align:right"|+'..OreDouble..'%'
		result = result..'||style="text-align:right"|+'..total2..'%'
	end

	result = result..'\r\n|}'
	return result
end

function p.getRodTable(frame)
	local toolArray = {}
	for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
		if Shared.contains(upgrade.name, 'Fishing Rod') then
			table.insert(toolArray, upgrade)
		end
	end

	local result = '{| class="wikitable"'
	result = result..'\r\n!colspan="4"| !!colspan="2"|Catch Time Decrease'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level'
	result = result..'!!Cost!!This Rod!!Total'

	local total = 0

	for i, tool in Shared.skpairs(toolArray) do
		result = result..'\r\n|-'
		result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
		result = result..'||'..tool.name
		local level = 1
		if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
			--Gonna be lazy and assume there's only the one skill level and it's the one we care about
			level = tool.unlockRequirements.skillLevel[1][2]
		end
		result = result..'||style="text-align:right"|'..level
		result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Icons.GP(tool.cost.gp)

		local cutTime = tool.contains.modifiers.decreasedSkillIntervalPercent[1][2]
		total = total + cutTime
		result = result..'||style="text-align:right"|-'..cutTime..'%'
		result = result..'||style="text-align:right"|-'..total..'%'
	end

	result = result..'\r\n|}'
	return result
end

function p.getTreesTable(frame)
	local result = '{| class="wikitable sortable"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Tree!!colspan="2"|Logs!!'..Icons.Icon({'Woodcutting', type='skill', notext=true})..' Level'
	result = result..'!!XP!!Cut Time!!XP/s!!GP/s'

	for i, tree in Shared.skpairs(SkillData.Woodcutting.Trees) do
		result = result..'\r\n|-'
		local treeName = Shared.titleCase(tree.type..' tree')
		local logName = Shared.titleCase(tree.type..' logs')
		result = result..'\r\n|style="min-width:25px" data-sort-value="'..treeName..'"|'..Icons.Icon({logName, img=treeName, type='tree', notext=true, size=50})
		result = result..'||'..treeName..''
		result = result..'||style="min-width:25px" data-sort-value="'..logName..'"|'..Icons.Icon({logName, type='item', notext=true, size=50})
		result = result..'||'..Icons.Icon({logName, type='item', noicon=true})
		result = result..'||style="text-align:right"|'..tree.level
		result = result..'||style="text-align:right"|'..tree.xp
		result = result..'||style="text-align:right" data-sort-value="'..tree.interval..'"|'..Shared.timeString(tree.interval/1000, true)
		local XPs = tree.xp / (tree.interval / 1000)
		local Log = Items.getItemByID(i - 1)
		local GPs = Log.sellsFor / (tree.interval / 1000)
		result = result..'||style="text-align:right"|'..Shared.round(XPs, 2, 2)
		result = result..'||style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))
	end

	result = result..'\r\n|}'
	return result
end

function p.getSpecialFishingTable(frame)
	local lootValue = 0
	local totalWt = Items.specialFishWt

	local result = ''
	result = result..'\r\n{|class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!Item'
	result = result..'!!Price!!colspan="2"|Chance'

	--Sort the loot table by weight in descending order
	table.sort(Items.specialFishLoot, function(a, b) return a[2] > b[2] end)
	for i, row in pairs(Items.specialFishLoot) do
		local thisItem = Items.getItemByID(row[1])
		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
		result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
		result = result..'|'..Shared.fraction(row[2], totalWt)
		result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
		lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
	end
	result = result..'\r\n|}'
	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
end

function p.getFishingJunkTable(frame)
	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Item!!Value'
	
	local itemArray = Items.getItems(function(item) return item.type == "Junk" end)

	table.sort(itemArray, function(a, b) return a.name < b.name end)

	for i, item in Shared.skpairs(itemArray) do
		result = result..'\r\n|-'
		result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})
		result = result..'||'..Icons.Icon({item.name, type='item', noicon=true})
		result = result..'||style="text-align:right;" data-sort-value="'..item.sellsFor..'"|'..Icons.GP(item.sellsFor)
	end

	result = result..'\r\n|}'

	return result
end

function p.getMiningOresTable(frame)
	local result = '{|class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan=2|Ore!!'..Icons.Icon({'Mining', type='skill', notext=true})..' Level'
	result = result..'!!XP!!Respawn Time!!Ore Value'

	local mineData = Shared.clone(SkillData.Mining.Rocks)

	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|style="min-width:25px"|'..Icons.Icon({ore.name, type='item', size='50', notext=true})
		result = result..'||'..Icons.Icon({ore.name, type='item', noicon=true})
		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|}'
	return result
end

function p.getMiningGemsTable(frame)
	local result = '{|class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan=2|Gem!!Gem Chance!!Gem Price'

	-- Sort gems by ID order
	for i, gemData in Shared.spairs(Items.GemTable, function(t,a,b) return t[a].id < t[b].id end) do
		local gem = Items.getItemByID(gemData.id)
		result = result..'\r\n|-\r\n|style="min-width:25px"|'
		result = result..Icons.Icon({gem.name, type='item', size='50', notext=true})
		result = result..'||'..Icons.Icon({gem.name, type='item', noicon=true})
		result = result..'||style="text-align:right"|'..string.format("%.1f%%", gemData.chance)
		result = result..'||data-sort-value="'..gem.sellsFor..'"|'..Icons.GP(gem.sellsFor)
	end

	result = result..'\r\n|}'
	return result
end

function p.getFishTable(frame)
	local data = Items.getItems(function(item) return item.fishingID ~= nil end)

	table.sort(data, function(a, b) return a.fishingID < b.fishingID end)

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!Fish\r\n!Name\r\n!'..Icons.Icon({'Fishing', type='skill', notext=true})..' Level\r\n!Catch Time'
	result = result..'\r\n!Experience\r\n!Fish Price\r\n!XP/s\r\n!GP/s\r\n!'
	result = result..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'

	for i, fish in Shared.skpairs(data) do
		result = result..'\r\n|-'
		result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', size='50', notext=true})
		result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fish.name, type='item', noicon=true})
		result = result..'\r\n| style="text-align:right"|'..fish.fishingLevel

		local timeSortVal = (fish.minFishingInterval + fish.maxFishingInterval) / 2
		local timeStr = string.format("%.1fs-%.1fs", (fish.minFishingInterval/1000), (fish.maxFishingInterval/1000))
		result = result..'\r\n| style="text-align:right" data-sort-value="'..timeSortVal..'"|'..timeStr
		result = result..'\r\n| style="text-align:right"|'..fish.fishingXP
		result = result..'\r\n| style="text-align:right"|'..fish.sellsFor
		local XPs = fish.fishingXP / (timeSortVal / 1000)
		local GPs = fish.sellsFor / (timeSortVal / 1000)
		result = result..'\r\n| style="text-align:right"|'..Shared.round(XPs, 2, 2)
		result = result..'\r\n| style="text-align:right" data-sort-value="'..GPs..'"|'..Icons.GP(Shared.round(GPs, 2, 2))

		local cookStr = "N/A"
		if fish.cookingLevel ~= nil then 
			cookStr = fish.cookingLevel
		end
		result = result..'\r\n| style="text-align:right"|'..cookStr
	end

	result = result..'\r\n|}'
	return result
end

function p.getFishingAreasTable(frame)

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance'
	result = result..'\r\n!Junk Chance\r\n!Special Chance'

	for i, area in Shared.skpairs(SkillData.Fishing.Areas) do
		result = result..'\r\n|-'
		result = result..'\r\n| style ="text-align: left;" |'..area.name

		local fishArray = {}
		for j, fish in Shared.skpairs(area.fish) do
			local fishTable = Items.getItems(function(item) return item.fishingID == fish end)
			local fishItem = fishTable[0] or fishTable[1]
			table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
		end
		result = result..'\r\n|'..table.concat(fishArray, '<br />')

		result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%'
		result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%'
		result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%'
	end

	result = result..'\r\n|}'
	return result
end

function p.getThievingNPC(npcName)
	local result = nil
	for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do
		if npc.name == npcName then
			result = Shared.clone(npc)
			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

function p._getThievingNPCStat(npc, statName)
	local result = nil
	
	if statName == 'level' then
		result = Icons._SkillReq('Thieving', npc.level)
	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

function p.getThievingNPCStat(frame)
	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 "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
	end
	
	return p._getThievingNPCStat(npc, statName)
end

function p.getThievingGeneralRareTable(frame)
	local rareTxt = '{|class="wikitable sortable"'
	rareTxt = rareTxt..'\r\n!Item!!Qty'
	rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
	for i, drop in pairs(SkillData.Thieving.RareItems) do
		local thisItem = Items.getItemByID(drop.itemID)
		local odds = drop.chance
		
		rareTxt = rareTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
		rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
		rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0))
		rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
	end
	rareTxt = rareTxt..'\r\n|}'
	return rareTxt
end

function p._getThievingNPCLootTables(npc)
	local result = ''
	local sectionTxt = {}
	
	--Five sections here: GP, normal loot, area loot, rare loot, and unique item
	--First up, GP:
	local gpTxt = 'Successfully pickpocketing the '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
	table.insert(sectionTxt, gpTxt)
	
	--Next up, normal loot:
	--(Skip if no loot)
	if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then
		local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:'
		local totalWt = 0
		local lootChance = thievingNormalLootChance
		local lootValue = 0
		
		--First loop through to get the total weight so we have it for later
		for i, loot in pairs(npc.lootTable) do
			totalWt = totalWt + loot[2]
		end
		
		normalTxt = normalTxt..'\r\n{|class="wikitable sortable"'
		normalTxt = normalTxt..'\r\n!Item!!Qty'
		normalTxt = normalTxt..'!!Price!!colspan="2"|Chance'
		
		--Then sort the loot table by weight
		table.sort(npc.lootTable, function(a, b) return a[2] > b[2] end)
		for i, row in Shared.skpairs(npc.lootTable) do
			local thisItem = Items.getItemByID(row[1])
			local maxQty = row[3]
			if thisItem ~= nil then
				normalTxt = normalTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
			else
				normalTxt = normalTxt..'\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]'
			end
			normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..maxQty..'"|'
		
			if maxQty > 1 then
				normalTxt = normalTxt.. '1 - '
			end
			normalTxt = normalTxt..Shared.formatnum(row[3])
			
			--Adding price columns
			local itemPrice = 0
			if thisItem == nil then
				normalTxt = normalTxt..'||data-sort-value="0"|???'
			else
				itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
				if itemPrice == 0 or maxQty == 1 then
					normalTxt = normalTxt..'||'..Icons.GP(itemPrice)
				else
					normalTxt = normalTxt..'||'..Icons.GP(itemPrice, itemPrice * maxQty)
				end
			end
		
			--Getting the drop chance
			local dropChance = (row[2] / totalWt * lootChance)
			if dropChance ~= 100 then
				--Show fraction as long as it isn't going to be 1/1
				normalTxt = normalTxt..'||style="text-align:right" data-sort-value="'..row[2]..'"'
				normalTxt = normalTxt..'|'..Shared.fraction(row[2] * lootChance, totalWt * 100)
				normalTxt = normalTxt..'||'
			else
				normalTxt = normalTxt..'||colspan="2" data-sort-value="'..row[2]..'"'
			end
			normalTxt = normalTxt..'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
	
			--Adding to the average loot value based on price & dropchance
			lootValue = lootValue + (dropChance * 0.01 * itemPrice * ((1 + maxQty) / 2))
		end
		if multiDrop then
			normalTxt = normalTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
			if lootChance < 100 then
				normalTxt = normalTxt..'\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||'
			else
				normalTxt = normalTxt..'\r\n|colspan="2" '
			end
			normalTxt = normalTxt..'style="text-align:right"|'..lootChance..'.00%'
		end
		normalTxt = normalTxt..'\r\n|}'
		table.insert(sectionTxt, normalTxt)
	end
	
	--After normal drops, add in rare drops
	local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket'
	rareTxt = rareTxt..'\r\n'..p.getThievingGeneralRareTable()
	table.insert(sectionTxt, rareTxt)
	
	local areaTxt = '===Possible Area Unique Drops==='
	areaTxt = areaTxt..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. '
	areaTxt = areaTxt..'\r\nEach Area Unique Drop is rolled for separately, so it is possible to receive multiple Area Unique Drops from a single action. '
	areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.'
	
	local area = p.getThievingNPCArea(npc)
	areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
	areaTxt = areaTxt..'\r\n!Item!!Qty'
	areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
	local dropCount = Shared.tableCount(area.uniqueDrops)
	local dropLines = {}
	for i, drop in pairs(area.uniqueDrops) do
		local thisItem = Items.getItemByID(drop.itemID)
		local lineTxt = ''
		lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
		lineTxt = lineTxt..'||'..drop.qty..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
		lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))
		lineTxt = lineTxt..'||'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
		dropLines[thisItem.name] = lineTxt
	end
	for i, txt in Shared.skpairs(dropLines) do
		areaTxt = areaTxt..txt
	end
	areaTxt = areaTxt..'\r\n|-class="sortbottom" \r\n!colspan="3"|Total:'
	areaTxt = areaTxt..'\r\n|style="text-align:right"|'..Shared.fraction(1, 1/(thievingAreaLootChance/100))..'||'
	areaTxt = areaTxt..'style="text-align:right"|'..Shared.round(thievingAreaLootChance, 2, 2)..'%'
	areaTxt = areaTxt..'\r\n|}'
	table.insert(sectionTxt, areaTxt)
	
	if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
		local uniqueTxt = '===Possible NPC Unique Drop==='
		uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
		uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
		local thisItem = Items.getItemByID(npc.uniqueDrop.itemID)
		uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
		if npc.uniqueDrop.qty > 1 then
			uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.qty})
		else
			uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'})
		end
		
		table.insert(sectionTxt, uniqueTxt)
	end
	
	return table.concat(sectionTxt, '\r\n')
end

function p.getThievingNPCLootTables(frame)
	local npcName = frame.args ~= nil and frame.args[1] or frame
	local npc = p.getThievingNPC(npcName)
	if npc == nil then
		return "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]"
	end
	
	return p._getThievingNPCLootTables(npc)
end

function p.getThievingNPCTable()
	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!Unique Drop'
	local npcArray = Shared.clone(SkillData.Thieving.NPCs)
	table.sort(npcArray, function(a, b) return a.level < b.level end)
	for i, npc in Shared.skpairs(npcArray) do
		result = result..'\r\n|-'
		result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
		result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true})

		local area = p.getThievingNPCArea(npc)
		result = result..'||'..area.name
		result = result..'||'..Icons._SkillReq('Thieving', npc.level)
		result = result..'||style="text-align:right"|'..npc.xp
		result = result..'||style="text-align:right"|'..(npc.maxHit * 10)
		result = result..'||style="text-align:right"|'..npc.perception
		if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID > -1 then
			local uniqueDrop = Items.getItemByID(npc.uniqueDrop.itemID)
			if npc.uniqueDrop.qty > 1 then
				result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.qty})
			else
				result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
			end
		else
			result = result..'|| '
		end
	end
	result = result..'\r\n|}'
	
	return result
end

function p.getThievingAreaTable(frame)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
	table.insert(resultPart, '\r\n|- class="headerRow-0"')
	table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
	
	local areaArray = Shared.clone(SkillData.Thieving.Areas)
	table.sort(areaArray, function(a, b) return a.id < b.id end)
	for i, area in ipairs(areaArray) do
		local minLevel, npcList, areaItemList = nil, {}, {}
		-- Build NPC list & determine level for area, this is the minimum
		-- Thieving level required for all NPCs within that area
		if area.npcs ~= nil and Shared.tableCount(area.npcs) > 0 then
			for j, npcID in ipairs(area.npcs) do
				-- Don't bother cloning the NPC below since we aren't modifying any part of it
				local npc = SkillData.Thieving.NPCs[npcID + 1]
				if minLevel == nil or npc.level < minLevel then
					minLevel = npc.level
				end
				table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
			end
		else
			table.insert(npcList, '')
		end
		
		-- Build area unique item list
		if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
			for k, drop in ipairs(area.uniqueDrops) do
				local areaItem = Items.getItemByID(drop.itemID)
				if areaItem == nil then
					table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
				else
					local iconDef = {areaItem.name, type='item'}
					if drop.qty > 1 then
						iconDef.qty = drop.qty
					end
					table.insert(areaItemList, Icons.Icon(iconDef))
				end
			end
		else
			table.insert(areaItemList, '')
		end
		
		-- Generate table row
		table.insert(resultPart, '\r\n|-')
		table.insert(resultPart, '\r\n|' .. area.name)
		table.insert(resultPart, '\r\n|' .. Icons._SkillReq('Thieving', minLevel))
		table.insert(resultPart, '\r\n|' .. table.concat(npcList, '<br/>'))
		table.insert(resultPart, '\r\n|' .. table.concat(areaItemList, '<br/>'))
	end
	table.insert(resultPart, '\r\n|}')
	
	return table.concat(resultPart)
end

function p.getThievingSourcesForItem(itemID)
	local resultArray = {}
	
	local areaNPCs = {}
	
	--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 * thievingNormalLootChance, 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 = thievingAreaLootChance, totalWt = 100, level = npc.level})
		end
	end
	
	for i, drop in pairs(SkillData.Thieving.RareItems) do
		if drop.itemID == itemID then
			table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1})
		end
	end
	
	return resultArray
end

return p