Module:AuronTest/SourceTables: Difference between revisions

From Melvor Idle
m (Revert to previous revision)
(Testing Items/SourceTables for 1.0.2)
 
(2 intermediate revisions by the same user not shown)
Line 2: Line 2:


local MonsterData = mw.loadData('Module:Monsters/data')
local MonsterData = mw.loadData('Module:Monsters/data')
local ItemData = mw.loadData('Module:AuronTest/data')
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')
local SkillData = mw.loadData('Module:AuronTest/data')


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Areas = require('Module:CombatAreas')
local Magic = require('Module:AuronTest/Magic')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:AuronTest')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local Monsters = require('Module:Monsters')
local Monsters = require('Module:Monsters')
local GatheringSkills = require('Module:Skills/Gathering')


-- Implements overrides for sources which cannot be obtained from game data
-- Implements overrides for sources which cannot be obtained from game data
-- Currently only overrides for dungeon sources are implemented here
-- Currently only overrides for dungeon sources are implemented here
local sourceOverrides = {
local sourceOverrides = {
  ['Dungeon'] = {
['Dungeon'] = {
    [361] = 'Volcanic Cave', -- Fire Cape
[950] = 'Volcanic Cave', -- A Tale of the Past, a future's prophecy
    [941] = 'Infernal Stronghold', -- Infernal Cape
[951] = 'Fire God Dungeon', -- The First Hero and an Unknown Evil
    [950] = 'Volcanic Cave', -- A Tale of the Past, a future's prophecy
[1116] = 'Into the Mist' -- Beginning of the End
    [951] = 'Fire God Dungeon' -- The First Hero and an Unknown Evil
}
  }
}
}
local SkillEnum = {}
for i, skill in pairs(SkillData.Skills) do
SkillEnum[skill.name] = Constants.getSkillID(skill.name)
end


function p._getCreationTable(item)
function p._getCreationTable(item)
  local skill = ''
local skill = ''
  local specialReq = nil
local specialReq = nil
  local time = 0
local time = 0
  local maxTime = nil
local maxTime = nil
  local lvl = 0
local lvl = 0
  local xp = 0
local xp = 0
  local qty = nil
local qty = nil
  local req = nil
local req = nil
 
local tables = {}
--First figure out what skill is used to make this...
if type(item.masteryID) == 'table' then
local skillID, masteryID = item.masteryID[1], item.masteryID[2]
skill = Constants.getSkillName(skillID)
if skillID == SkillEnum.Fishing then
-- Fishing
lvl = item.fishingLevel
xp = item.fishingXP
qty = 1
time = item.minFishingInterval / 1000
maxTime = item.maxFishingInterval / 1000
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime))
elseif skillID == SkillEnum.Cooking then
-- Cooking
for i, reqSet in pairs(item.recipeRequirements) do
lvl = item.cookingLevel
xp = item.cookingXP
req = reqSet
qty = item.cookingQty
time = item.cookingInterval / 1000
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
elseif skillID == SkillEnum.Mining then
-- Mining
local rock = SkillData.Mining.Rocks[masteryID + 1]
if rock ~= nil then
lvl = rock.levelRequired
xp = rock.baseExperience
qty = rock.baseQuantity
time = 3
if item.name == 'Dragonite Ore' then
specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
end
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
end
elseif skillID == SkillEnum.Smithing then
-- Smithing
local recipe = SkillData.Smithing.Recipes[masteryID + 1]
if recipe ~= nil then
lvl = recipe.level
xp = recipe.baseXP
req = recipe.itemCosts
qty = recipe.baseQuantity
time = 2
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
elseif skillID == SkillEnum.Fletching then
-- Fletching
lvl = item.fletchingLevel
xp = item.fletchingXP
if item.name == 'Arrow Shafts' then
--Arrow Shafts get special (weird) treatment
req = '1 of any [[Log]]'
qty = '15 - 135'
else
req = item.fletchReq
qty = item.fletchQty
end
time = 2
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
elseif skillID == SkillEnum.Crafting then
-- Crafting
lvl = item.craftingLevel
xp = item.craftingXP
req = item.craftReq
qty = item.craftQty
time = 3
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
elseif skillID == SkillEnum.Runecrafting then
-- Runecrafting
lvl = item.runecraftingLevel
xp = item.runecraftingXP
req = item.runecraftReq
qty = item.runecraftQty
time = 2
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
elseif skillID == SkillEnum.Herblore then
-- Herblore
local potion = SkillData.Herblore.Potions[masteryID + 1]
if potion ~= nil then
lvl = potion.level
xp = potion.baseXP
req = potion.itemCosts
qty = 1
time = 2
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
elseif skillID == SkillEnum.Summoning then
-- Summoning
lvl = item.summoningLevel
--Summoning uses a formula to calculate XP for creation instead of referring to the item's XP data directly
xp = (5 + 2 * math.floor(item.summoningLevel / 5))
local ShardCostArray = {}
for j, cost in Shared.skpairs(item.summoningReq[1]) do
if cost.id >= 0 then
local item = Items.getItemByID(cost.id)
if item.type == 'Shard' then
table.insert(ShardCostArray, Icons.Icon({item.name, type='item', notext=true, qty=cost.qty}))
end
end
end
req = table.concat(ShardCostArray, ', ')
req = req..'<br/>\r\nand one of the following<br/>\r\n'
local OtherCostArray = {}
local recipeGPCost = SkillData.Summoning.Settings.recipeGPCost
for j, altCost in Shared.skpairs(item.summoningReq) do
local nonShardArray = {}
for k, cost in Shared.skpairs(altCost) do
if cost.id >= 0 then
local item = Items.getItemByID(cost.id)
if item.type ~= 'Shard' then
local sellPrice = math.max(item.sellsFor, 20)
table.insert(nonShardArray, Icons.Icon({item.name, type='item', notext=true, qty=math.max(1, math.floor(recipeGPCost / sellPrice))}))
end
elseif cost.id == -4 then
table.insert(nonShardArray, Icons.GP(recipeGPCost))
elseif cost.id == -5 then
table.insert(nonShardArray, Icons.SC(recipeGPCost))
end
end
table.insert(OtherCostArray, table.concat(nonShardArray, ', '))
end
req = req..table.concat(OtherCostArray, "<br/>'''OR''' ")
qty = item.summoningQty
time = 5
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
end
 
-- Woodcutting
if item.type == "Logs" then
-- Determine which tree (if any) the log is from
for i, tree in ipairs(SkillData.Woodcutting.Trees) do
if tree.logID == item.id then
skill = 'Woodcutting'
lvl = tree.levelRequired
time = tree.baseInterval / 1000
xp = tree.baseExperience
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
break
end
end
end


  local tables = {}
--had to add cooking to the list of valid categories here to account for cherries/apples
  --First figure out what skill is used to make this...
if item.category == 'Cooking' or item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" or Shared.contains(item.name, '(Perfect)') then
  if item.smithingLevel ~= nil then
--Harvest/Herb means farming
    skill = 'Smithing'
--Logs might mean farming or might not. Depends on the logs
    lvl = item.smithingLevel
for i, item2 in pairs(ItemData.Items) do
    xp = item.smithingXP
if item2.grownItemID == item.id then
    req = item.smithReq
skill = 'Farming'
    qty = item.smithingQty
lvl = item2.farmingLevel
    time = 2
xp = item2.farmingXP
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
time = item2.timeToGrow
  end
if item.type == 'Logs' then
  if item.craftingLevel ~= nil then
qty = 35
    skill = 'Crafting'
else
    lvl = item.craftingLevel
qty = 15
    xp = item.craftingXP
end
    req = item.craftReq
req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
    qty = item.craftQty
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
    time = 3
break
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
  end
  if item.runecraftingLevel ~= nil then
    skill = 'Runecrafting'
    lvl = item.runecraftingLevel
    xp = item.runecraftingXP
    req = item.runecraftReq
    qty = item.runecraftQty
    time = 2
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.fletchingLevel ~= nil then
    skill = 'Fletching'
    lvl = item.fletchingLevel
    xp = item.fletchingXP
    req = item.fletchReq
    qty = item.fletchQty
    time = 2
    if item.name == 'Arrow Shafts' then
      --Arrow Shafts get special (weird) treatment
      req = '1 of any [[Log]]'
      qty = '15 - 135'
    end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.herbloreReq ~= nil then
    skill = 'Herblore'
    req = item.herbloreReq
    --Currently using 'masteryID' as shorthand to find details, could be a better method
    local potionID = item.masteryID[2]
    local potionData = SkillData.Herblore.ItemData[potionID + 1]
    lvl = potionData.herbloreLevel
    xp = potionData.herbloreXP
    time = 2
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.masteryID ~= nil and item.masteryID[1] == 4 then
    skill = 'Mining'
    lvl = SkillData.Mining.Rocks[item.masteryID[2] + 1].level
    time = 3
    xp = item.miningXP
    --Rune Essence has double quantity, but that's a hard-coded thing in the game so it's hard-coded here
    if item.name == 'Rune Essence' then qty = 2 else qty = 1 end


    if item.name == 'Dragonite Ore' then
--If this is a perfect item, need to find the original
      specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
    end
for j, reqSet in pairs(item2.recipeRequirements) do
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
skill = 'Cooking'
  end
lvl = item2.cookingLevel
  if item.type == "Logs" then
xp = item2.cookingXP
    --Well this feels like cheating, but for as long as logs are the first items by ID it works
req = reqSet
    local treeData = SkillData.Woodcutting.Trees[item.id + 1]
qty = item2.cookingQty
    skill = 'Woodcutting'
time = item2.cookingInterval / 1000
    lvl = treeData.level
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
    time = treeData.interval / 1000
end
    xp = treeData.xp
end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
  end
end
  if item.fishingLevel ~= nil then
    skill = 'Fishing'
    lvl = item.fishingLevel
    xp = item.fishingXP
    time = item.minFishingInterval/1000
    maxTime = item.maxFishingInterval/1000
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime))
  end
  if item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" then
    --Harvest/Herb means farming
    --Logs might mean farming or might not. Depends on the logs
    for i, item2 in pairs(ItemData.Items) do
      if item2.grownItemID == item.id then
        skill = 'Farming'
        lvl = item2.farmingLevel
        xp = item2.farmingXP
        time = item2.timeToGrow
        if item.type == 'Logs' then
          qty = 35
        else
          qty = 15
        end
        req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
        table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
        break
      end
    end
  end
  if item.type == "Food" or item.type == "Cooked Fish" then
    --Food/Cooked Fish is Fishing, need to figure out source item
    for i, item2 in pairs(ItemData.Items) do
      if item2.burntItemID == item.id or item2.cookedItemID == item.id then
        skill = 'Cooking'
        lvl = item2.cookingLevel
        if item2.burntItemID == item.id then
          xp = 1
        else
          xp = item2.cookingXP
        end
        time = 3
        req = {{id = i - 1, qty = 1}}
        break
      end
    end
    if skill ~= '' then
      table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
    end
  end
  if item.summoningLevel ~= nil then
    skill = 'Summoning'
    lvl = item.summoningLevel
    --Summoning uses a formula to calculate XP for creation instead of referring to the item's XP data directly
    xp = (5 + 2 * math.floor(item.summoningLevel / 5))
    local ShardCostArray = {}
    for j, cost in Shared.skpairs(item.summoningReq[1]) do
      if cost.id >= 0 then
        local item = Items.getItemByID(cost.id)
        if item.type == 'Shard' then
          table.insert(ShardCostArray, Icons.Icon({item.name,  type='item', notext=true, qty=cost.qty}))
        end
      end
    end
    req = table.concat(ShardCostArray, ', ')
    req = req..'<br/>\r\nand one of the following<br/>\r\n'
    local OtherCostArray = {}
    local recipeGPCost = SkillData.Summoning.Settings.recipeGPCost
    for j, altCost in Shared.skpairs(item.summoningReq) do
      local nonShardArray = {}
      for k, cost in Shared.skpairs(altCost) do
        if cost.id >= 0 then
          local item = Items.getItemByID(cost.id)
          if item.type ~= 'Shard' then
            local sellPrice = math.max(item.sellsFor, 20)
            table.insert(nonShardArray, Icons.Icon({item.name, type='item', notext=true, qty=math.max(1, math.floor(recipeGPCost / sellPrice))}))
          end
        else
          if cost.id == -4 then
            table.insert(nonShardArray, Icons.GP(recipeGPCost))
          elseif cost.id == -5 then
            table.insert(nonShardArray, Icons.SC(recipeGPCost))
          end
        end
      end
      table.insert(OtherCostArray, table.concat(nonShardArray, ', '))
    end
    req = req..table.concat(OtherCostArray, "<br/>'''OR''' ")
    qty = item.summoningQty
    time = 5
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  --A couple special exceptions for Alt Magic
  --Not Gems or Bars though since those have their own separate thing
  if item.name == 'Rune Essence' then
    table.insert(tables, p.buildAltMagicTable('Just Learning'))
  elseif item.name == 'Bones' then
    table.insert(tables, p.buildAltMagicTable('Bone Offering'))
  elseif item.name == 'Holy Dust' then
    table.insert(tables, p.buildAltMagicTable('Blessed Offering'))
  end


  if Shared.tableCount(tables) == 0 then
-- Alt. Magic, excludes Gems and Bars
    return ""
-- Bars are handled by getItemSuperheatTable()
  else
-- Gems are handled by _getItemLootSourceTable()
    return table.concat(tables, '\r\n')
-- TODO Should be able to iterate AltMagic.spells, checking the produces property
  end
if item.name == 'Rune Essence' then
table.insert(tables, p.buildAltMagicTable('Just Learning'))
elseif item.name == 'Bones' then
table.insert(tables, p.buildAltMagicTable('Bone Offering'))
elseif item.name == 'Holy Dust' then
table.insert(tables, p.buildAltMagicTable('Blessed Offering'))
end
 
if Shared.tableCount(tables) == 0 then
return ""
else
return table.concat(tables, '\r\n')
end
end
 
-- Used by various Alt. Magic tables
function getRuneText(spell)
local formatRuneList = function(runes)
local runeList = {}
for i, req in ipairs(runes) do
local rune = Items.getItemByID(req.id)
if rune ~= nil then
table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
end
end
return table.concat(runeList, ', ')
end
if type(spell.runesRequired) == 'table' then
local resultPart  = {}
table.insert(resultPart, formatRuneList(spell.runesRequired))
if spell.runesRequiredAlt ~= nil and not Shared.tablesEqual(spell.runesRequired, spell.runesRequiredAlt) then
table.insert(resultPart, "<br/>'''OR'''<br/>" .. formatRuneList(spell.runesRequiredAlt))
end
return table.concat(resultPart)
else
return ''
end
end
end


function p.buildAltMagicTable(spellName)
function p.buildAltMagicTable(spellName)
  local spell = Magic.getSpell(spellName, 'AltMagic')
local spell = Magic.getSpell(spellName, 'AltMagic')
  local resultPart = {}
local resultPart = {}
  table.insert(resultPart, '{|class="wikitable"\r\n|-')
table.insert(resultPart, '{|class="wikitable"\r\n|-')
  table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'}))
table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'}))
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
  table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.magicLevelRequired))
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))
  -- 1 means select any item. 0 would mean Superheat, but that's handled elsewhere
  -- -1 means no item is needed, so hide this section
  if spell.selectItem == 1 then
    table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
    table.insert(resultPart, '\r\n|1 of any item')
  end
  --Add runes
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n|')
  for i, req in pairs(spell.runesRequired) do
    local rune = Items.getItemByID(req.id)
    if i > 1 then table.insert(resultPart, ', ') end
    table.insert(resultPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
  end
  if spell.runesRequiredAlt ~= nil and Shared.tableCount(spell.runesRequired) ~= Shared.tableCount(spell.runesRequiredAlt) then
    table.insert(resultPart, "<br/>'''OR'''<br/>")
    for i, req in pairs(spell.runesRequiredAlt) do
      local rune = Items.getItemByID(req.id)
      if i > 1 then table.insert(resultPart, ', ') end
      table.insert(resultPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
    end
  end


  --Now just need the output quantity, xp, and casting time (which is always 2)
-- The produces property of Alt magic spells is as follows:
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|'..spell.convertToQty)
-- -3 = A random gem, using the same weights as Mining
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|'..spell.magicXP)
-- -2 = A bar of the type being created (Superheat)
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
-- -1 = GP (Alchemy)
  table.insert(resultPart, '\r\n|}')
--  0 = Undefined
  return table.concat(resultPart)
-- >0 = Item ID of the item being produced
-- The amount produced is determined by the productionRatio property
 
-- The consumes property of Alt Magic spells is as follows:
-- 0 = Any item
-- 1 = Junk item
-- 2 = Superheat/ores with Coal
-- 3 = Superheat/ores without Coal
-- 4 = Nothing
-- 5 = Coal ore
-- Superheat (2, 3) is handled by _getItemSuperheatTable()
--
if spell.consumes ~= nil then
local consumeText = {
'1 of any item',
'1 of any [[Fishing#Junk|Junk]] item',
'1 x required ores for the chosen bar',
'1 x required ores (except ' .. Icons.Icon({'Coal Ore', type='item'}) .. ') for the chosen bar',
nil,
Icons.Icon({'Coal Ore', type='item', qty=1})
}
local consumeStr = consumeText[spell.consumes + 1]
if consumeStr ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
table.insert(resultPart, '\r\n| ' .. consumeStr)
end
end
 
--Add runes
local formatRuneList = function(runes)
local runeList = {}
for i, req in ipairs(runes) do
local rune = Items.getItemByID(req.id)
if rune ~= nil then
table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
end
end
return table.concat(runeList, ', ')
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. getRuneText(spell))
 
--Now just need the output quantity, xp, and casting time (which is always 2)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|'..spell.productionRatio)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|'..spell.baseExperience)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
end


function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)
function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost)
  if qty == nil then qty = 1 end
if qty == nil then qty = 1 end
  local resultPart = {}
local resultPart = {}
  table.insert(resultPart, '{|class="wikitable"')
table.insert(resultPart, '{|class="wikitable"')
  if req ~= nil then
if req ~= nil then
    table.insert(resultPart, '\r\n!colspan="2"|Item Creation')
table.insert(resultPart, '\r\n!colspan="2"|Item Creation')
  else
else
    table.insert(resultPart, '\r\n!colspan="2"|Item Production')
table.insert(resultPart, '\r\n!colspan="2"|Item Production')
  end
end
  table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements')
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements')
  table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl))
table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl))
  if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end
if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end


  if req ~= nil then
if req ~= nil then
    table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Materials\r\n|')
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Materials\r\n|')
    if type(req) == 'table' then
if type(req) == 'table' then
      for i, mat in pairs(req) do
for i, mat in pairs(req) do
        if i > 1 then table.insert(resultPart, '<br/>') end
if i > 1 then table.insert(resultPart, '<br/>') end
        local matItem = Items.getItemByID(mat.id)
local matItem = Items.getItemByID(mat.id)
        if matItem == nil then
if matItem == nil then
          table.insert(resultPart, mat.qty..'x ?????')
table.insert(resultPart, mat.qty..'x ?????')
        else
else
          table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.qty}))
table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.qty}))
        end
end
      end
end
    else
if gpCost ~= nil and gpCost > 0 then
    table.insert(resultPart, req)
table.insert(resultPart, '<br/>')
    end
table.insert(resultPart, Icons.GP(gpCost))
  end
end
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity')
else
  table.insert(resultPart, '\r\n|'..qty)
table.insert(resultPart, req)
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience')
end
  table.insert(resultPart, '\r\n|'..Shared.formatnum(xp)..' XP')
end
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity')
  table.insert(resultPart, '\r\n|'..Shared.formatnum(Shared.round(time, 2, 0))..'s')
table.insert(resultPart, '\r\n|'..qty)
  if maxTime ~= nil then table.insert(resultPart, ' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s') end
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience')
  table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|'..Shared.formatnum(xp)..' XP')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time')
table.insert(resultPart, '\r\n|'..Shared.formatnum(Shared.round(time, 2, 0))..'s')
if maxTime ~= nil then table.insert(resultPart, ' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s') end
table.insert(resultPart, '\r\n|}')


  return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getCreationTable(frame)
function p.getCreationTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getCreationTable(item)
return p._getCreationTable(item)
end
end


function p._getItemSources(item, asList, addCategories)
function p._getItemSources(item, asList, addCategories)
  local lineArray = {}
local lineArray = {}
  local categoryArray = {}
local categoryArray = {}


  --Alright, time to go through all the ways you can get an item...
--Alright, time to go through all the ways you can get an item...
  --First up: Can we kill somebody and take theirs?
--First up: Can we kill somebody and take theirs?
  local killStr = ''
local killStrPart = {}
  local dungeonStr = ''
for i, monster in ipairs(MonsterData.Monsters) do
  local count1 = 0
local isDrop = false
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
    local isDrop = false
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
    if monster.bones == item.id and ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, "Shard")) then
isDrop = true
      isDrop = true
elseif monster.lootTable ~= nil then
    elseif monster.lootTable ~= nil then
-- If the monster has a loot table, check if the item we are looking for is in there
      for j, loot in pairs(monster.lootTable) do
-- Dungeon exclusive monsters don't count as they are either:
        if loot[1] == item.id then
--  - A monster before the boss, which doesn't drop anything except shards (checked above)
          isDrop = true
--  - A boss monster, whose drops are accounted for in data from Areas instead
          break
for j, loot in ipairs(monster.lootTable) do
        end
if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
      end
isDrop = true
      if isDrop and Monsters.isDungeonOnlyMonster({args={monster.name}}) then
break
        -- 2021-05-24 Additional checks for dungeon exclusive monsters: Loot is not rolled on
end
        -- dungeon exclusive monsters unless they are the boss/last enemy of that dungeon
end
        local rollForLoot = false
end
        for k, area in pairs(Areas.getMonsterAreas(i - 1)) do
if isDrop then
          if not (area.type == 'dungeon') or (area.type == 'dungeon' and area.monsters[Shared.tableCount(area.monsters)] == i - 1) then
-- Item drops when the monster is killed
            -- Either monster isn't dungeon exclusive, or is the boss/last enemy of a dungeon
table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
            rollForLoot = true
end
            break
end
          end
-- Is the item dropped from any dungeon?
        end
local dungeonStrPart = {}
        isDrop = rollForLoot
local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
      end
if dungeonList ~= nil then
    end
for i, dungeon in ipairs(dungeonList) do
    if isDrop then
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
      if monster.isBoss and not Shared.contains(item.name, "Shard") then
end
        local areaList = Areas.getMonsterAreas(i - 1)
end
        --If this is a boss then we actually are completing dungeons for this and need to figure out which one
-- Is the item dropped from a cycle of the Impending Darkness event?
        for j, dung in pairs(areaList) do
for i, eventItemID in ipairs(Areas.eventData.rewards) do
          if string.len(dungeonStr) > 0 then
if item.id == eventItemID then
            dungeonStr = dungeonStr..','
local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ')
          else
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true}))
            dungeonStr = 'Completing: '
break
          end
end
          dungeonStr = dungeonStr..Icons.Icon({dung.name, type="dungeon", notext=true})
end
        end
-- Special exceptions for lore books
      else
if sourceOverrides['Dungeon'][item.id] ~= nil then
        count1 = count1 + 1
table.insert(dungeonStrPart, Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true}))
        if string.len(killStr) > 0 then
end
          killStr = killStr..','
          --if count1 % 3 == 1 and count1 > 1 then killStr = killStr..'<br/>' end
          killStr = killStr..Icons.Icon({monster.name, type="monster", notext="true"})
        else
          killStr = killStr..'Killing: '..Icons.Icon({monster.name, type="monster", notext="true"})
        end
      end
    end
  end
  -- Special exceptions for Fire/Infernal Cape and first two lore books
  if sourceOverrides['Dungeon'][item.id] ~= nil then
    if string.len(dungeonStr) > 0 then
      dungeonStr = dungeonStr .. ','
    else
      dungeonStr = 'Completing: '
    end
    dungeonStr = dungeonStr .. Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true})
  end


  if string.len(dungeonStr) > 0 then table.insert(lineArray, dungeonStr) end
if Shared.tableCount(dungeonStrPart) > 0 then
  if string.len(killStr) > 0 then table.insert(lineArray, killStr) end
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
end
if Shared.tableCount(killStrPart) > 0 then
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
end


  --Next: Can we find it in a box?
--Next: Can we find it in a box?
  --While we're here, check for upgrades, cooking, and growing
--While we're here, check for upgrades, originals (for perfect items), and growing
  local lootStr = ''
local lootStr = ''
  local upgradeStr = ''
local upgradeStr = ''
  local cookStr = ''
local cookStr = ''
  local burnStr = ''
local growStr = ''
  local growStr = ''
local count1 = 0
  local count2 = 0
local count2 = 0
  count1 = 0
for i, item2 in pairs(ItemData.Items) do
  for i, item2 in pairs(ItemData.Items) do
if item2.dropTable ~= nil then
    if item2.dropTable ~= nil then
for j, loot in pairs(item2.dropTable) do
      for j, loot in pairs(item2.dropTable) do
if loot[1] == item.id then
        if loot[1] == item.id then
count1 = count1 + 1
          count1 = count1 + 1
if string.len(lootStr) > 0 then
          if string.len(lootStr) > 0 then
lootStr = lootStr..','
            lootStr = lootStr..','
--if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
            --if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
            lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
else
          else
lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
            lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
end
          end
end
        end
end
      end
end
    end
if item2.trimmedItemID == item.id then
    if item2.trimmedItemID == item.id then
count2 = count2 + 1
          count2 = count2 + 1
if string.len(upgradeStr) > 0 then
        if string.len(upgradeStr) > 0 then
upgradeStr = upgradeStr..','
          upgradeStr = upgradeStr..','
--if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
          --if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
          upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
else
        else
table.insert(categoryArray, '[[Category:Upgraded Items]]')
          table.insert(categoryArray, '[[Category:Upgraded Items]]')
upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
          upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
end
        end
end
    end
if item2.grownItemID == item.id then
    if item2.cookedItemID == item.id then
-- Farming
        if string.len(cookStr) > 0 then
if string.len(growStr) > 0 then
          cookStr = cookStr..','..Icons.Icon({item2.name, type="item", notext="true"})
growStr = growStr..','..Icons.Icon({item2.name, type="item", notext="true"})
        else
else
          table.insert(categoryArray, '[[Category:Cooked Items]]')
table.insert(categoryArray, '[[Category:Harvestable Items]]')
          cookStr = cookStr..'Cooking: '..Icons.Icon({item2.name, type="item", notext="true"})
growStr = growStr..'Growing: '..Icons.Icon({item2.name, type="item", notext="true"})
        end
end
    end
end
    if item2.burntItemID == item.id then
if item2.perfectItem == item.id and item2.cookingLevel ~= nil then
        if string.len(burnStr) > 0 then
-- Cooking (Perfect items)
          burnStr = burnStr..','..Icons.Icon({item2.name, type="item", notext="true"})
table.insert(lineArray, Icons._SkillReq('Cooking', item2.cookingLevel))
        else
end
          table.insert(categoryArray, '[[Category:Burnt Items]]')
end
          burnStr = burnStr..'Burning: '..Icons.Icon({item2.name, type="item", notext="true"})
if string.len(lootStr) > 0 then table.insert(lineArray, lootStr) end
        end
if string.len(upgradeStr) > 0 then table.insert(lineArray, upgradeStr) end
    end
if string.len(cookStr) > 0 then table.insert(lineArray, cookStr) end
    if item2.grownItemID == item.id then
if string.len(growStr) > 0 then table.insert(lineArray, growStr) end
        if string.len(growStr) > 0 then
          growStr = growStr..','..Icons.Icon({item2.name, type="item", notext="true"})
        else
          table.insert(categoryArray, '[[Category:Harvestable Items]]')
          growStr = growStr..'Growing: '..Icons.Icon({item2.name, type="item", notext="true"})
        end
    end
  end
  if string.len(lootStr) > 0 then table.insert(lineArray, lootStr) end
  if string.len(upgradeStr) > 0 then table.insert(lineArray, upgradeStr) end
  if string.len(cookStr) > 0 then table.insert(lineArray, cookStr) end
  if string.len(burnStr) > 0 then table.insert(lineArray, burnStr) end
  if string.len(growStr) > 0 then table.insert(lineArray, growStr) end


  --Next: Can we take it from somebody else -without- killing them?
--Next: Can we take it from somebody else -without- killing them?
  local thiefStr = ''
local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
  for i, npc in pairs(SkillData.Thieving) do
local thiefStr = ''
    if npc.lootTable ~= nil then
if Shared.tableCount(thiefItems) > 0 then
      for j, loot in pairs(npc.lootTable) do
thiefStr = 'Pickpocketing: '
        if loot[1] == item.id then
for i, thiefRow in pairs(thiefItems) do
          if string.len(thiefStr) > 0 then
if thiefRow.npc == 'all' then
            thiefStr = thiefStr..','..Icons.Icon({npc.name, type="thieving", notext="true"})
--if 'any' is the npc, this is a rare item so just say 'Thieving level 1'
          else
thiefStr = Icons._SkillReq('Thieving', 1)
            thiefStr = thiefStr..'Pickpocketing: '..Icons.Icon({npc.name, type="thieving", notext="true"})
else
          end
if i > 1 then thiefStr = thiefStr..', ' end
        end
thiefStr = thiefStr..Icons.Icon({thiefRow.npc, type='thieving', notext='true'})
      end
end
    end
end
  end
end
  if string.len(thiefStr) > 0 then table.insert(lineArray, thiefStr) end
if string.len(thiefStr) > 0 then table.insert(lineArray, thiefStr) end


  --If all else fails, I guess we should check if we can make it ourselves
--If all else fails, I guess we should check if we can make it ourselves
  --SmithCheck:
--AstrologyCheck
  if item.smithingLevel ~= nil then
--(Just a brute force for now because only two items and I'm lazy)
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
if item.name == 'Stardust' or item.name == 'Golden Stardust' then
  end
table.insert(lineArray, Icons.Icon({"Astrology", type="skill"}))
end


  --CraftCheck:
-- Sources discoverable through mastery IDs
  if item.craftingLevel ~= nil then
if type(item.masteryID) == 'table' then
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
local skillID, masteryID = item.masteryID[1], item.masteryID[2]
  end
local skill, levelReq = Constants.getSkillName(skillID), nil
if skillID == SkillEnum.Fishing then
-- Fishing (less Junk & Special items)
levelReq = item.fishingLevel
elseif skillID == SkillEnum.Cooking then
-- Cooking (less Perfect items)
levelReq = item.cookingLevel
elseif skillID == SkillEnum.Mining then
-- Mining
local rock = SkillData.Mining.Rocks[masteryID + 1]
if rock ~= nil then
levelReq = rock.levelRequired
end
elseif skillID == SkillEnum.Smithing then
-- Smithing
local recipe = SkillData.Smithing.Recipes[masteryID + 1]
if recipe ~= nil then
levelReq = recipe.level
end
elseif skillID == SkillEnum.Fletching then
-- Fletching
levelReq = item.fletchingLevel
elseif skillID == SkillEnum.Crafting then
-- Crafting
levelReq = item.craftingLevel
elseif skillID == SkillEnum.Runecrafting then
-- Runecrafting
levelReq = item.runecraftingLevel
elseif skillID == SkillEnum.Herblore then
-- Herblore
local potion = SkillData.Herblore.Potions[masteryID + 1]
if potion ~= nil then
levelReq = potion.level
end
elseif skillID == SkillEnum.Summoning then
-- Summoning
levelReq = item.summoningLevel
end
if levelReq ~= nil then
table.insert(lineArray, Icons._SkillReq(skill, levelReq))
end
end


  --FletchCheck:
-- Woodcutting
  if item.fletchingLevel ~= nil then
for i, tree in ipairs(SkillData.Woodcutting.Trees) do
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
if tree.logID == item.id then
  end
table.insert(lineArray, Icons._SkillReq('Woodcutting', tree.levelRequired))
break
end
end


  --RunecraftCheck:
-- Woodcutting (Nests)
  if item.runecraftingLevel ~= nil then
if item.name == 'Bird Nest' then
    table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
table.insert(lineArray, Icons._SkillReq('Woodcutting', 1))
  end
end


  --MineCheck:
-- Fishing (Junk & Special items)
  if item.masteryID ~= nil and item.masteryID[1] == 4 then
if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
    table.insert(lineArray, Icons._SkillReq("Mining", SkillData.Mining.Rocks[item.masteryID[2] + 1].level))
table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
  end
end


  --FishCheck:
--Finally there are some weird exceptions:
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
--Coal can be acquired via firemaking
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
if item.name == "Coal Ore" then
  elseif item.fishingLevel ~= nil then
table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
end
  end


  --HerbCheck:
--Gems can be acquired from mining, fishing, and alt. magic
  if item.masteryID ~= nil and item.masteryID[1] == 19 then
if item.type == 'Gem' and item.name ~= 'Jadestone' then
    local potionData = SkillData.Herblore.ItemData[item.masteryID[2] + 1].herbloreLevel
table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
  end
table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
end


  --WoodcuttingCheck:
--Bars and some other stuff can also be acquired via Alt. Magic
  if item.type == 'Logs' then
if item.type == 'Bar' or Shared.contains(Items.AltMagicProducts, item.name) then
    local treeData = SkillData.Woodcutting.Trees[item.id + 1]
table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
    local lvl = treeData.level
end
    table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
  end


  --SummoningCheck:
--Rhaelyx pieces are also special
  if item.summoningLevel ~= nil then
if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
    table.insert(lineArray, Icons._SkillReq("Summoning", item.summoningLevel))
local rhaSkills = {
  end
Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
}
local rhaSkList = {}
if item.name == 'Circlet of Rhaelyx' then
rhaSkList = rhaSkills.Circlet
elseif item.name == 'Jewel of Rhaelyx' then
rhaSkList = rhaSkills.Jewel
elseif item.name == 'Mysterious Stone' then
rhaSkList = rhaSkills.Jewel
for i, v in ipairs(rhaSkills.Circlet) do
table.insert(rhaSkList, v)
end
end


  --Finally there are some weird exceptions:
local rhaStrPart = {}
  --Coal can be acquired via firemaking
for i, skillName in ipairs(rhaSkList) do
  if item.name == "Coal Ore" then
table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
end
  end
local rhaStr = 'Any action in: ' .. table.concat(rhaStrPart, ', ')
if item.name == 'Mysterious Stone' then rhaStr = rhaStr .. '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}) end
table.insert(lineArray, rhaStr)
end


  --Gems can be acquired from mining, fishing, and alt. magic
--Tokens are from the appropriate skill
  if item.type == 'Gem' then
if item.isToken and item.skill ~= nil then
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 1))
    table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
end
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
  end


  --Bars and some other stuff can also be acquired via Alt. Magic
--Shop items (including special items like gloves that aren't otherwise listed)
  if type == 'Bar' or Shared.contains(AltMagicProducts, item.name) then
local shopSources = Shop.getItemSourceArray(item.id)
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
if Shared.tableCount(shopSources) > 0 then
  end
table.insert(lineArray, Icons.Icon({'Shop'}))
end


  --Chapeau Noir & Bobby's Pocket are special Thieving items
--Easter Eggs (manual list 'cause don't have a better way to do that)
  if item.name == "Chapeau Noir" or item.name == "Bobby&apos;s Pocket" then
if Shared.contains(Items.EasterEggs, item.name) then
    table.insert(lineArray, Icons._SkillReq("Thieving", 1))
table.insert(lineArray, '[[Easter Eggs]]')
  end
end
-- Event exclusive items (also a manual list)
if Shared.contains(Items.EventItems, item.name) then
table.insert(lineArray, '[[Events]]')
end


  --Rhaelyx pieces are also special
--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring)
  if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
--Also handling Signet Ring things here
    local rhaSkills = {
if item.name == 'Gold Topaz Ring' then
      Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility'},
table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')')
      Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
    }
elseif item.name == 'Signet Ring Half (a)' then
    local rhaSkList = {}
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
    if item.name == 'Circlet of Rhaelyx' then
elseif item.name == 'Signet Ring Half (b)' then
      rhaSkList = rhaSkills.Circlet
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
    elseif item.name == 'Jewel of Rhaelyx' then
end
      rhaSkList = rhaSkills.Jewel
    elseif item.name == 'Mysterious Stone' then
      rhaSkList = rhaSkills.Jewel
      for i, v in ipairs(rhaSkills.Circlet) do
        table.insert(rhaSkList, v)
      end
    end


    local rhaStrPart = {}
local resultPart = {}
    for i, skillName in ipairs(rhaSkList) do
if asList then
      table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
    end
else
    local rhaStr = 'Any action in: ' .. table.concat(rhaStrPart, ', ')
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
    if item.name == 'Mysterious Stone' then rhaStr = rhaStr .. '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}) end
end
    table.insert(lineArray, rhaStr)
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
  end
return table.concat(resultPart)
 
  --Tokens are from the appropriate skill
  if item.isToken and item.skill ~= nil then
    table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 1))
  end
 
  --Shop items (including special items like gloves that aren't otherwise listed)
  local shopSources = Shop.getItemSourceArray(item.id)
  if Shared.tableCount(shopSources) > 0 then
    table.insert(lineArray, Icons.Icon({'Shop'}))
  end
 
  --Easter Eggs (manual list 'cause don't have a better way to do that)
  if Shared.contains(Items.EasterEggs, item.name) then
    table.insert(lineArray, '[[Easter Eggs]]')
  end
 
  local resultPart = {}
  if asList then
    table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
  else
    table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
  end
  if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
  return table.concat(resultPart)
end
end


function p.getItemSources(frame)
function p.getItemSources(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  local asList = false
local asList = false
  local addCategories = false
local addCategories = false
  if frame.args ~= nil then
if frame.args ~= nil then
    asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
    addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
  end
end
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getItemSources(item, asList, addCategories)
return p._getItemSources(item, asList, addCategories)
end
end


function p._getItemLootSourceTable(item)
function p._getItemLootSourceTable(item)
  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!Source!!Source Type!!Quantity!!colspan="2"|Chance')
table.insert(resultPart, '\r\n!Source!!Source Type!!Quantity!!colspan="2"|Chance')
 
  --Set up function for adding rows
  local buildRow = function(source, type, minqty, qty, weight, totalWeight)
    if minqty == nil then minqty = 1 end
    local rowPart = {}
    table.insert(rowPart, '\r\n|-')
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)


    table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty)
--Set up function for adding rows
    if qty ~= minqty then table.insert(rowPart, ' - '..qty) end
local buildRow = function(source, type, minqty, qty, weight, totalWeight)
    local chance = Shared.round(weight / totalWeight * 100, 2, 2)
if minqty == nil then minqty = 1 end
    if weight >= totalWeight then
local rowPart = {}
      -- Fraction would be 1/1, so only show the percentage
table.insert(rowPart, '\r\n|-')
      chance = 100
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
      table.insert(rowPart, '\r\n|colspan="2" ')
table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)
    else
      table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chance .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|')
    end
    table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chance .. '"|'..chance..'%')
    return table.concat(rowPart)
  end
  local dropRows = {}


  --Alright, time to go through a few ways to get the item
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty)
  --First up: Can we kill somebody and take theirs?
if qty ~= minqty then table.insert(rowPart, ' - '..qty) end
  for i, monster in pairs(MonsterData.Monsters) do
local chance = weight / totalWeight * 100
    local minqty = 1
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
    local qty = 1
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
    local wt = 0
chance = string.format(fmt, chance)
    local totalWt = 0
if weight >= totalWeight then
    --Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss
-- Fraction would be 1/1, so only show the percentage
    --... unless we're looking for Shards of course, at which point we'll take any monster with the right bones
chance = 100
    if ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, 'Shard')) and monster.bones == item.id then
table.insert(rowPart, '\r\n|colspan="2" ')
      qty = monster.boneQty ~= nil and monster.boneQty or 1
else
      minqty = qty
local fraction = Shared.fraction(weight, totalWeight)
      wt = 1
if Shared.contains(fraction, '%.') then
      totalWt = 1
--If fraction contains decimals, something screwy happened so just show only percentage
    elseif monster.lootTable ~= nil then
--(happens sometimes with the rare thieving items)
      for j, loot in pairs(monster.lootTable) do
table.insert(rowPart, '\r\n|colspan="2" ')
        totalWt = totalWt + loot[2]
else
        if loot[1] == item.id then
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chance .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|')
          wt = loot[2]
end
          qty = loot[3]
end
        end
if weight == -1 then
      end
--Weight of -1 means this is a weird row that has a variable percentage
    end
table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)')
    local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100
else
table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chance .. '"|'..chance..'%')
end
return table.concat(rowPart)
end
local dropRows = {}


    if wt > 0 and lootChance > 0 and not Shared.contains(item.name, 'Shard') and Monsters.isDungeonOnlyMonster({args={monster.name}}) then
--Alright, time to go through a few ways to get the item
      -- 2021-05-24 Additional checks for dungeon exclusive monsters: Loot is not rolled on
--First up: Can we kill somebody and take theirs?
      -- dungeon exclusive monsters unless they are the boss/last enemy of that dungeon
for i, monster in ipairs(MonsterData.Monsters) do
      local rollForLoot = false
local minqty = 1
      for k, area in pairs(Areas.getMonsterAreas(i - 1)) do
local qty = 1
        if area.type ~= 'dungeon' or (area.type == 'dungeon' and area.monsters[Shared.tableCount(area.monsters)] == i - 1) then
local wt = 0
          -- Either monster isn't dungeon exclusive, or is the boss/last enemy of a dungeon
local totalWt = 0
          rollForLoot = true
--Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss
          break
--... unless we're looking for Shards of course, at which point we'll take any monster with the right bones
        end
if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
      end
qty = monster.boneQty ~= nil and monster.boneQty or 1
      if not rollForLoot then
minqty = qty
        wt = 0
wt = 1
      end
totalWt = 1
    end
elseif monster.lootTable ~= nil then
    if wt > 0 and lootChance > 0 then
-- If the monster has a loot table, check if the item we are looking for is in there
      local sourceTxt = nil
-- Dungeon exclusive monsters don't count as they are either:
      local typeTxt = nil
--  - A monster before the boss, which doesn't drop anything except shards (checked above)
      --If we're dealing with a boss, this is a Dungeon row instead
--  - A boss monster, whose drops are accounted for in data from Areas instead
      if monster.isBoss and not Shared.contains(item.name, 'Shard') then
for j, loot in ipairs(monster.lootTable) do
        local dung = Areas.getMonsterAreas(i - 1)[1]
totalWt = totalWt + loot[2]
        sourceTxt = Icons.Icon({dung.name, type='dungeon', notext=true})
if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
        typeTxt = '[[Dungeon]]'
wt = loot[2]
      else
qty = loot[3]
        sourceTxt = Icons.Icon({monster.name, type='monster'})
end
        typeTxt = '[[Monster]]'
end
      end
end
      table.insert(dropRows, {source = sourceTxt, type = typeTxt, minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100
    end
  end


  --Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops
if wt > 0 and lootChance > 0 then
  if sourceOverrides['Dungeon'][item.id] ~= nil then
-- Item drops when the monster is killed
    local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true})
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
    table.insert(dropRows, {source=sourceTxt, type='[[Dungeon]]', minqty=1, qty=1, weight = 1, totalWeight = 1})
end
  end
end
-- Is the item dropped from any dungeon?
local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
if dungeonList ~= nil then
for i, dungeon in ipairs(dungeonList) do
table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
end
end
-- Is the item dropped from a cycle of the Impending Darkness event?
for i, eventItemID in ipairs(Areas.eventData.rewards) do
if item.id == eventItemID then
sourceTxt = Icons.Icon({'Impending Darkness Event', type='dungeon'}) .. (i == Shared.tableCount(Areas.eventData.rewards) and '' or ', Cycle ' .. i)
table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
break
end
end
--Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops
if sourceOverrides['Dungeon'][item.id] ~= nil then
local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon'})
table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
end


  --Next: Can we find it by rummaging around in another item?
--Next: Can we find it by rummaging around in another item?
  for i, item2 in pairs(ItemData.Items) do
for i, item2 in pairs(ItemData.Items) do
    if item2.dropTable ~= nil then
if item2.dropTable ~= nil then
      local qty = 1
local qty = 1
      local wt = 0
local wt = 0
      local totalWt = 0
local totalWt = 0
      for j, loot in pairs(item2.dropTable) do
for j, loot in pairs(item2.dropTable) do
        totalWt = totalWt + loot[2]
totalWt = totalWt + loot[2]
        if loot[1] == item.id then
if loot[1] == item.id then
          wt = loot[2]
wt = loot[2]
          if item2.dropQty ~= nil then qty = item2.dropQty[j] end
if item2.dropQty ~= nil then qty = item2.dropQty[j] end
        end
end
      end
end


      if wt > 0 then
if wt > 0 then
        local sourceTxt = Icons.Icon({item2.name, type='item'})
local sourceTxt = Icons.Icon({item2.name, type='item'})
        table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt})
table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt})
      end
end
    end
end
  end
end


  --Finally, let's try just stealing it
--Finally, let's try just stealing it
  local thiefType = Icons.Icon({"Thieving", type='skill'})
local thiefType = Icons.Icon({"Thieving", type='skill'})
  for i, npc in pairs(SkillData.Thieving) do
local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
    local qty = 1
for i, thiefRow in pairs(thiefItems) do
    local wt = 0
local sourceTxt = ''
    local totalWt = 0
if thiefRow.npc == 'all' then
    if npc.lootTable ~= nil then
sourceTxt = "Thieving Rare Drop"
      for j, loot in pairs(npc.lootTable) do
else
        totalWt = totalWt + loot[2]
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
        if loot[1] == item.id then
end
          wt = loot[2]
table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt})
        end
end
      end
      if wt > 0 then
        -- There is a constant 75% chance of gaining an item per successful steal attempt
        local sourceTxt = Icons.Icon({npc.name, type='thieving'})
        table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = 1, qty = qty, weight = wt * 75, totalWeight = totalWt * 100})
      end
    end
  end


  --Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches
--Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches
  if item.type == 'Gem' then
--Jadestone is special and doesn't count
    local mineType = Icons.Icon({'Mining', type='skill'})
if item.type == 'Gem' and item.name ~= 'Jadestone' then
    local thisGemChance = Items.GemTable[item.name].chance
local mineType = Icons.Icon({'Mining', type='skill'})
    local totalGemChance = 0
local thisGemChance = Items.GemTable[item.name].chance
    for i, gem in pairs(Items.GemTable) do
local totalGemChance = 0
      totalGemChance = totalGemChance + gem.chance
for i, gem in pairs(Items.GemTable) do
    end
totalGemChance = totalGemChance + gem.chance
    table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
end
    local magicType = Icons.Icon({'Magic', type = 'skill'})
table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
local magicType = Icons.Icon({'Magic', type = 'skill'})
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
  end
table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
end


  if item.fishingCatchWeight ~= nil then
if item.fishingCatchWeight ~= nil then
    local fishSource = '[[Fishing#Special|Special]]'
local fishSource = '[[Fishing#Special|Special]]'
    local fishType = Icons.Icon({'Fishing', type='skill'})
local fishType = Icons.Icon({'Fishing', type='skill'})
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
  end
end


  if item.type == 'Junk' then
if item.type == 'Junk' then
    local fishSource = '[[Fishing#Junk|Junk]]'
local fishSource = '[[Fishing#Junk|Junk]]'
    local fishType = Icons.Icon({'Fishing', type='skill'})
local fishType = Icons.Icon({'Fishing', type='skill'})
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = Items.junkCount})
table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = Items.junkCount})
  end
end


  --Make sure to return nothing if there are no drop sources
--Make sure to return nothing if there are no drop sources
  if Shared.tableCount(dropRows) == 0 then return '' end
if Shared.tableCount(dropRows) == 0 then return '' end


  table.sort(dropRows, function(a, b)
table.sort(dropRows, function(a, b)
                        if a.weight / a.totalWeight == b.weight / b.totalWeight then
if a.weight / a.totalWeight == b.weight / b.totalWeight then
                          return a.minqty + a.qty > b.minqty + b.qty
return a.minqty + a.qty > b.minqty + b.qty
                        else
else
                          return a.weight / a.totalWeight > b.weight / b.totalWeight
return a.weight / a.totalWeight > b.weight / b.totalWeight
                        end
end
                      end)
end)
  for i, data in pairs(dropRows) do
for i, data in pairs(dropRows) do
    table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
  end
end


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


function p.getItemLootSourceTable(frame)
function p.getItemLootSourceTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getItemLootSourceTable(item)
return p._getItemLootSourceTable(item)
end
end


function p._getItemUpgradeTable(item)
function p._getItemUpgradeTable(item)
  local resultPart = {}
local resultPart = {}
  if item.itemsRequired ~= nil then
if item.itemsRequired ~= nil then
    --First, get details on all the required materials
--First, get details on all the required materials
    local upgradeFrom = {}
local upgradeFrom = {}
    local materials = {}
local materials = {}
    for i, row in pairs(item.itemsRequired) do
for i, row in pairs(item.itemsRequired) do
      local mat = Items.getItemByID(row[1])
local mat = Items.getItemByID(row[1])
      --Check to see if the source item can trigger the upgrade
--Check to see if the source item can trigger the upgrade
      if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then
if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then
        table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'}))
table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'}))
      end
end
      table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]}))
table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]}))
    end
end
    if item.trimmedGPCost ~= nil then
if item.trimmedGPCost ~= nil then
      table.insert(materials, Icons.GP(item.trimmedGPCost))
table.insert(materials, Icons.GP(item.trimmedGPCost))
    end
end
    table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
    --[[result = result..'\r\n|-\r\n!style="text-align:right;"|Upgrades From\r\n|'
--[[result = result..'\r\n|-\r\n!style="text-align:right;"|Upgrades From\r\n|'
    result = result..table.concat(upgradeFrom, '<br/>')--]]
result = result..table.concat(upgradeFrom, '<br/>')--]]
    table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
    table.insert(resultPart, table.concat(materials, '<br/>'))
table.insert(resultPart, table.concat(materials, '<br/>'))
    table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
  end
end
  return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getItemUpgradeTable(frame)
function p.getItemUpgradeTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getItemUpgradeTable(item)
return p._getItemUpgradeTable(item)
end
end


function p._getItemSuperheatTable(item)
function p._getItemSuperheatTable(item)
  --Manually build the Superheat Item table
--Manually build the Superheat Item table
  local oreString = ''
-- Validate that the item can be superheated
  local coalString = ''
local canSuperheat, smithRecipe = true, nil
  for i, mat in pairs(item.smithReq) do
if type(item.masteryID) ~= 'table' then
    local thisMat = Items.getItemByID(mat.id)
canSuperheat = false
    if thisMat.name == 'Coal Ore' then
elseif item.masteryID[1] ~= SkillEnum.Smithing then
      coalString = ', '..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
canSuperheat = false
    else
else
      if string.len(oreString) > 0 then oreString = oreString..', ' end
smithRecipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1]
      oreString =  oreString..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
if smithRecipe == nil or smithRecipe.category ~= 0 then
    end
canSuperheat = false
  end
end
  --Set up the header
end
  local superheatTable = {}
if not canSuperheat then
  table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]'
  table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
end
  table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
 
  table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
local oreStringPart, coalString = {}, ''
  table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
for i, mat in ipairs(item.smithReq) do
  table.insert(superheatTable, '!!Ore!!Runes')
local thisMat = Items.getItemByID(mat.id)
  --Loop through all the variants
if thisMat.name == 'Coal Ore' then
  local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
coalString = Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
  for i, sName in pairs(spellNames) do
else
    local spell = Magic.getSpell(sName, 'AltMagic')
table.insert(oreStringPart, Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty}))
    local rowPart = {}
end
    table.insert(rowPart, '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50}))
end
    table.insert(rowPart, '||[['..spell.name..']]||'..item.smithingLevel)
--Set up the header
    table.insert(rowPart, '||'..spell.magicLevelRequired..'||'..spell.magicXP..'||'..spell.convertToQty)
local superheatTable = {}
    table.insert(rowPart, '||'..oreString)
table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
    if spell.ignoreCoal ~= nil and not spell.ignoreCoal then table.insert(rowPart, coalString) end
table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
    table.insert(rowPart, '||style="text-align:center"|')
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
    for i, req in pairs(spell.runesRequired) do
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
      local rune = Items.getItemByID(req.id)
table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
      if i > 1 then table.insert(rowPart, ', ') end
table.insert(superheatTable, '!!Ore!!Runes')
      table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
--Loop through all the variants
    end
local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
    table.insert(rowPart, "<br/>'''OR'''<br/>")
for i, sName in pairs(spellNames) do
    for i, req in pairs(spell.runesRequiredAlt) do
local spell = Magic.getSpell(sName, 'AltMagic')
      local rune = Items.getItemByID(req.id)
table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50}))
      if i > 1 then table.insert(rowPart, ', ') end
table.insert(superheatTable, '||'..Icons.Icon({spell.name, type='spell', noicon=true})..'||style="text-align:right;"|'..smithRecipe.level)
      table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
table.insert(superheatTable, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.baseExperience)
    end
table.insert(superheatTable, '||style="text-align:right;"|'..spell.productionRatio)
    table.insert(superheatTable, table.concat(rowPart))
table.insert(superheatTable, '|| '..table.concat(oreStringPart, ', '))
  end
if spell.consumes == 2 then
  --Add the table end and add the table to the result string
-- 2 = Superheat with coal, 3 = Superheat without coal
  table.insert(superheatTable, '\r\n|}')
table.insert(superheatTable, (Shared.tableCount(oreStringPart) > 0 and ', ' or '') .. coalString)
  return table.concat(superheatTable)
end
table.insert(superheatTable, '||style="text-align:center"| ' .. getRuneText(spell))
end
--Add the table end and add the table to the result string
table.insert(superheatTable, '\r\n|}')
return table.concat(superheatTable)
end
end


function p.getItemSuperheatTable(frame)
function p.getItemSuperheatTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getItemSuperheatTable(item)
return p._getItemSuperheatTable(item)
end
end


function p._getItemSourceTables(item)
function p._getItemSourceTables(item)
  local resultPart = {}
local resultPart = {}
  local shopTable = Shop._getItemShopTable(item)
local shopTable = Shop._getItemShopTable(item)
  if string.len(shopTable) > 0 then
if string.len(shopTable) > 0 then
    table.insert(resultPart, '===Shop===\r\n'..shopTable)
table.insert(resultPart, '===Shop===\r\n'..shopTable)
  end
end


  local creationTable = p._getCreationTable(item)
local creationTable = p._getCreationTable(item)
  if string.len(creationTable) > 0 then
if string.len(creationTable) > 0 then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if #resultPart > 0 then table.insert(resultPart, '\r\n') end
    table.insert(resultPart, '===Creation===\r\n'..creationTable)
table.insert(resultPart, '===Creation===\r\n'..creationTable)
  end
end


  local upgradeTable = p._getItemUpgradeTable(item)
local upgradeTable = p._getItemUpgradeTable(item)
  if string.len(upgradeTable) > 0 then
if string.len(upgradeTable) > 0 then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if #resultPart > 0 then table.insert(resultPart, '\r\n') end
    if string.len(creationTable) == 0 then table.insert(resultPart, '===Creation===\r\n') end
if string.len(creationTable) == 0 then table.insert(resultPart, '===Creation===\r\n') end
    table.insert(resultPart, upgradeTable)
table.insert(resultPart, upgradeTable)
  end
end


  if item.type == 'Bar' then
if type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Smithing then
    table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
local recipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1]
  end
if recipe ~= nil and recipe.category == 0 then
table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
end
end


  local lootTable = p._getItemLootSourceTable(item)
local lootTable = p._getItemLootSourceTable(item)
  if string.len(lootTable) > 0 then
if string.len(lootTable) > 0 then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if #resultPart > 0 then table.insert(resultPart, '\r\n') end
    table.insert(resultPart, '===Loot===\r\n'..lootTable)
table.insert(resultPart, '===Loot===\r\n'..lootTable)
  end
end
  return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getItemSourceTables(frame)
function p.getItemSourceTables(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
  end
end


  return p._getItemSourceTables(item)
return p._getItemSourceTables(item)
end
end


function p.getCombatPassiveSlotItems(frame)
function p.getCombatPassiveSlotItems(frame)
  local resultPart = {}
local resultPart = {}
  table.insert(resultPart, '{| class="wikitable"\r\n')
table.insert(resultPart, '{| class="wikitable"\r\n')
  table.insert(resultPart, '|-\r\n')
table.insert(resultPart, '|-\r\n')
  table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n')
table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n')


  local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and Shared.contains(item.validSlots, 'Passive') end)
local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and Shared.contains(item.validSlots, 'Passive') end)


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


  for i, item in Shared.skpairs(itemArray) do
for i, item in ipairs(itemArray) do
    table.insert(resultPart, '|-\r\n')
table.insert(resultPart, '|-\r\n')
    table.insert(resultPart, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! [['..item.name..']]\r\n')
table.insert(resultPart, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! '..Icons.Icon({item.name, type='item', noicon=true})..'\r\n')
    table.insert(resultPart, '| '..item.description..'\r\n')
table.insert(resultPart, '| '..item.description..'\r\n')
  end
end


  table.insert(resultPart, '|}')
table.insert(resultPart, '|}')
 
return table.concat(resultPart)
end
 
function p._getItemMonsterSources(item)
local resultArray = {}
for i, monster in ipairs(MonsterData.Monsters) do
local chance = 0
local weight = 0
local minQty = 1
local maxQty = 1
if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
chance = 1
weight = 1
if monster.boneQty ~= nil then
minQty = monster.boneQty
maxQty = monster.boneQty
end
elseif monster.lootTable ~= nil then
-- If the monster has a loot table, check if the item we are looking for is in there
-- Dungeon exclusive monsters don't count as they are either:
--  - A monster before the boss, which doesn't drop anything except shards (checked above)
--  - A boss monster, whose drops are accounted for in data from Areas instead
local monsterWeight
for j, loot in ipairs(monster.lootTable) do
weight = weight + loot[2]
if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
chance = loot[2]
maxQty = loot[3]
end
end
local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
chance = chance * lootChance
weight = weight * 100
chance, weight = Shared.fractionpair(chance, weight)
end
if chance > 0 then
-- Item drops when the monster is killed
table.insert(resultArray, {id = monster.id, dropWt = chance, totalWt = weight, minQty = minQty, maxQty = maxQty})
end
end
return resultArray
end


  return table.concat(resultPart)
function p.getItemMonsterSources(itemName)
local item = Items.getItem(itemName)
return p._getItemMonsterSources(item)
end
end


return p
return p

Latest revision as of 21:45, 2 February 2022

Documentation for this module may be created at Module:AuronTest/SourceTables/doc

local p = {}

local MonsterData = mw.loadData('Module:Monsters/data')
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:AuronTest/data')

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Areas = require('Module:CombatAreas')
local Magic = require('Module:AuronTest/Magic')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Monsters = require('Module:Monsters')
local GatheringSkills = require('Module:Skills/Gathering')

-- Implements overrides for sources which cannot be obtained from game data
-- Currently only overrides for dungeon sources are implemented here
local sourceOverrides = {
	['Dungeon'] = {
		[950] = 'Volcanic Cave', -- A Tale of the Past, a future's prophecy
		[951] = 'Fire God Dungeon', -- The First Hero and an Unknown Evil
		[1116] = 'Into the Mist' -- Beginning of the End
	}
}

local SkillEnum = {}
for i, skill in pairs(SkillData.Skills) do
	SkillEnum[skill.name] = Constants.getSkillID(skill.name)
end

function p._getCreationTable(item)
	local skill = ''
	local specialReq = nil
	local time = 0
	local maxTime = nil
	local lvl = 0
	local xp = 0
	local qty = nil
	local req = nil

	local tables = {}
	--First figure out what skill is used to make this...
	if type(item.masteryID) == 'table' then
		local skillID, masteryID = item.masteryID[1], item.masteryID[2]
		skill = Constants.getSkillName(skillID)
		if skillID == SkillEnum.Fishing then
			-- Fishing
			lvl = item.fishingLevel
			xp = item.fishingXP
			qty = 1
			time = item.minFishingInterval / 1000
			maxTime = item.maxFishingInterval / 1000
			table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime))
		elseif skillID == SkillEnum.Cooking then
			-- Cooking
			for i, reqSet in pairs(item.recipeRequirements) do
				lvl = item.cookingLevel
				xp = item.cookingXP
				req = reqSet
				qty = item.cookingQty
				time = item.cookingInterval / 1000
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
			end
		elseif skillID == SkillEnum.Mining then
			-- Mining
			local rock = SkillData.Mining.Rocks[masteryID + 1]
			if rock ~= nil then
				lvl = rock.levelRequired
				xp = rock.baseExperience
				qty = rock.baseQuantity
				time = 3
				if item.name == 'Dragonite Ore' then
					specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
				end
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
			end
		elseif skillID == SkillEnum.Smithing then
			-- Smithing
			local recipe = SkillData.Smithing.Recipes[masteryID + 1]
			if recipe ~= nil then
				lvl = recipe.level
				xp = recipe.baseXP
				req = recipe.itemCosts
				qty = recipe.baseQuantity
				time = 2
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
			end
		elseif skillID == SkillEnum.Fletching then
			-- Fletching
			lvl = item.fletchingLevel
			xp = item.fletchingXP
			if item.name == 'Arrow Shafts' then
				--Arrow Shafts get special (weird) treatment
				req = '1 of any [[Log]]'
				qty = '15 - 135'
			else
				req = item.fletchReq
				qty = item.fletchQty
			end
			time = 2
			table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
		elseif skillID == SkillEnum.Crafting then
			-- Crafting
			lvl = item.craftingLevel
			xp = item.craftingXP
			req = item.craftReq
			qty = item.craftQty
			time = 3
			table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
		elseif skillID == SkillEnum.Runecrafting then
			-- Runecrafting
			lvl = item.runecraftingLevel
			xp = item.runecraftingXP
			req = item.runecraftReq
			qty = item.runecraftQty
			time = 2
			table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
		elseif skillID == SkillEnum.Herblore then
			-- Herblore
			local potion = SkillData.Herblore.Potions[masteryID + 1]
			if potion ~= nil then
				lvl = potion.level
				xp = potion.baseXP
				req = potion.itemCosts
				qty = 1
				time = 2
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
			end
		elseif skillID == SkillEnum.Summoning then
			-- Summoning
			lvl = item.summoningLevel
			--Summoning uses a formula to calculate XP for creation instead of referring to the item's XP data directly
			xp = (5 + 2 * math.floor(item.summoningLevel / 5))
			local ShardCostArray = {}
			for j, cost in Shared.skpairs(item.summoningReq[1]) do
				if cost.id >= 0 then
					local item = Items.getItemByID(cost.id)
					if item.type == 'Shard' then
						table.insert(ShardCostArray, Icons.Icon({item.name, type='item', notext=true, qty=cost.qty}))
					end
				end
			end
			req = table.concat(ShardCostArray, ', ')
			req = req..'<br/>\r\nand one of the following<br/>\r\n'
			local OtherCostArray = {}
			local recipeGPCost = SkillData.Summoning.Settings.recipeGPCost
			for j, altCost in Shared.skpairs(item.summoningReq) do
				local nonShardArray = {}
				for k, cost in Shared.skpairs(altCost) do
					if cost.id >= 0 then
						local item = Items.getItemByID(cost.id)
						if item.type ~= 'Shard' then
							local sellPrice = math.max(item.sellsFor, 20)
							table.insert(nonShardArray, Icons.Icon({item.name, type='item', notext=true, qty=math.max(1, math.floor(recipeGPCost / sellPrice))}))
						end
					elseif cost.id == -4 then
						table.insert(nonShardArray, Icons.GP(recipeGPCost))
					elseif cost.id == -5 then
						table.insert(nonShardArray, Icons.SC(recipeGPCost))
					end
				end
				table.insert(OtherCostArray, table.concat(nonShardArray, ', '))
			end
			req = req..table.concat(OtherCostArray, "<br/>'''OR''' ")
			qty = item.summoningQty
			time = 5
			table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
		end
	end

	-- Woodcutting
	if item.type == "Logs" then
		-- Determine which tree (if any) the log is from
		for i, tree in ipairs(SkillData.Woodcutting.Trees) do
			if tree.logID == item.id then
				skill = 'Woodcutting'
				lvl = tree.levelRequired
				time = tree.baseInterval / 1000
				xp = tree.baseExperience
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				break
			end
		end
	end

	--had to add cooking to the list of valid categories here to account for cherries/apples
	if item.category == 'Cooking' or item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" or Shared.contains(item.name, '(Perfect)') then
		--Harvest/Herb means farming
		--Logs might mean farming or might not. Depends on the logs
		for i, item2 in pairs(ItemData.Items) do
			if item2.grownItemID == item.id then
				skill = 'Farming'
				lvl = item2.farmingLevel
				xp = item2.farmingXP
				time = item2.timeToGrow
				if item.type == 'Logs' then
					qty = 35
				else
					qty = 15
				end
				req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				break
			end

			--If this is a perfect item, need to find the original
			if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
				for j, reqSet in pairs(item2.recipeRequirements) do
					skill = 'Cooking'
					lvl = item2.cookingLevel
					xp = item2.cookingXP
					req = reqSet
					qty = item2.cookingQty
					time = item2.cookingInterval / 1000
					table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				end
			end
		end
	end

	-- Alt. Magic, excludes Gems and Bars
	-- Bars are handled by getItemSuperheatTable()
	-- Gems are handled by _getItemLootSourceTable()
	-- TODO Should be able to iterate AltMagic.spells, checking the produces property
	if item.name == 'Rune Essence' then
		table.insert(tables, p.buildAltMagicTable('Just Learning'))
	elseif item.name == 'Bones' then
		table.insert(tables, p.buildAltMagicTable('Bone Offering'))
	elseif item.name == 'Holy Dust' then
		table.insert(tables, p.buildAltMagicTable('Blessed Offering'))
	end

	if Shared.tableCount(tables) == 0 then
		return ""
	else
		return table.concat(tables, '\r\n')
	end
end

-- Used by various Alt. Magic tables
function getRuneText(spell)
	local formatRuneList = function(runes)
			local runeList = {}
			for i, req in ipairs(runes) do
				local rune = Items.getItemByID(req.id)
				if rune ~= nil then
					table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
				end
			end
			return table.concat(runeList, ', ')
		end
	if type(spell.runesRequired) == 'table' then
		local resultPart  = {}
		table.insert(resultPart, formatRuneList(spell.runesRequired))
		if spell.runesRequiredAlt ~= nil and not Shared.tablesEqual(spell.runesRequired, spell.runesRequiredAlt) then
			table.insert(resultPart, "<br/>'''OR'''<br/>" .. formatRuneList(spell.runesRequiredAlt))
		end
		return table.concat(resultPart)
	else
		return ''
	end
end

function p.buildAltMagicTable(spellName)
	local spell = Magic.getSpell(spellName, 'AltMagic')
	local resultPart = {}
	table.insert(resultPart, '{|class="wikitable"\r\n|-')
	table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'}))
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
	table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))

	-- The produces property of Alt magic spells is as follows:
	-- -3 = A random gem, using the same weights as Mining
	-- -2 = A bar of the type being created (Superheat)
	-- -1 = GP (Alchemy)
	--  0 = Undefined
	-- >0 = Item ID of the item being produced
	-- The amount produced is determined by the productionRatio property

	-- The consumes property of Alt Magic spells is as follows:
	-- 0 = Any item
	-- 1 = Junk item
	-- 2 = Superheat/ores with Coal
	-- 3 = Superheat/ores without Coal
	-- 4 = Nothing
	-- 5 = Coal ore
	-- Superheat (2, 3) is handled by _getItemSuperheatTable()
	--
	if spell.consumes ~= nil then
		local consumeText = {
			'1 of any item',
			'1 of any [[Fishing#Junk|Junk]] item',
			'1 x required ores for the chosen bar',
			'1 x required ores (except ' .. Icons.Icon({'Coal Ore', type='item'}) .. ') for the chosen bar',
			nil,
			Icons.Icon({'Coal Ore', type='item', qty=1})
		}
		local consumeStr = consumeText[spell.consumes + 1]
		if consumeStr ~= nil then
			table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
			table.insert(resultPart, '\r\n| ' .. consumeStr)
		end
	end

	--Add runes
	local formatRuneList = function(runes)
			local runeList = {}
			for i, req in ipairs(runes) do
				local rune = Items.getItemByID(req.id)
				if rune ~= nil then
					table.insert(runeList, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
				end
			end
			return table.concat(runeList, ', ')
		end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. getRuneText(spell))

	--Now just need the output quantity, xp, and casting time (which is always 2)
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|'..spell.productionRatio)
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|'..spell.baseExperience)
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost)
	if qty == nil then qty = 1 end
	local resultPart = {}
	table.insert(resultPart, '{|class="wikitable"')
	if req ~= nil then
		table.insert(resultPart, '\r\n!colspan="2"|Item Creation')
	else
		table.insert(resultPart, '\r\n!colspan="2"|Item Production')
	end
	table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements')
	table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl))
	if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end

	if req ~= nil then
		table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Materials\r\n|')
		if type(req) == 'table' then
			for i, mat in pairs(req) do
				if i > 1 then table.insert(resultPart, '<br/>') end
				local matItem = Items.getItemByID(mat.id)
				if matItem == nil then
					table.insert(resultPart, mat.qty..'x ?????')
				else
					table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.qty}))
				end
			end
			if gpCost ~= nil and gpCost > 0 then
				table.insert(resultPart, '<br/>')
				table.insert(resultPart, Icons.GP(gpCost))
			end
		else
			table.insert(resultPart, req)
		end
	end
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity')
	table.insert(resultPart, '\r\n|'..qty)
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience')
	table.insert(resultPart, '\r\n|'..Shared.formatnum(xp)..' XP')
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time')
	table.insert(resultPart, '\r\n|'..Shared.formatnum(Shared.round(time, 2, 0))..'s')
	if maxTime ~= nil then table.insert(resultPart, ' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s') end
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getCreationTable(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getCreationTable(item)
end

function p._getItemSources(item, asList, addCategories)
	local lineArray = {}
	local categoryArray = {}

	--Alright, time to go through all the ways you can get an item...
	--First up: Can we kill somebody and take theirs?
	local killStrPart = {}
	for i, monster in ipairs(MonsterData.Monsters) do
		local isDrop = false
		if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
			-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
			isDrop = true
		elseif monster.lootTable ~= nil then
			-- If the monster has a loot table, check if the item we are looking for is in there
			-- Dungeon exclusive monsters don't count as they are either:
			--   - A monster before the boss, which doesn't drop anything except shards (checked above)
			--   - A boss monster, whose drops are accounted for in data from Areas instead
			for j, loot in ipairs(monster.lootTable) do
				if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
					isDrop = true
					break
				end
			end
		end
		if isDrop then
			-- Item drops when the monster is killed
			table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
		end
	end
	-- Is the item dropped from any dungeon?
	local dungeonStrPart = {}
	local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
	if dungeonList ~= nil then
		for i, dungeon in ipairs(dungeonList) do
			table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
		end
	end
	-- Is the item dropped from a cycle of the Impending Darkness event?
	for i, eventItemID in ipairs(Areas.eventData.rewards) do
		if item.id == eventItemID then
			local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ')
			table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true}))
			break
		end
	end
	-- Special exceptions for lore books
	if sourceOverrides['Dungeon'][item.id] ~= nil then
		table.insert(dungeonStrPart, Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true}))
	end

	if Shared.tableCount(dungeonStrPart) > 0 then
		table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
	end
	if Shared.tableCount(killStrPart) > 0 then
		table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
	end

	--Next: Can we find it in a box?
	--While we're here, check for upgrades, originals (for perfect items), and growing
	local lootStr = ''
	local upgradeStr = ''
	local cookStr = ''
	local growStr = ''
	local count1 = 0
	local count2 = 0
	for i, item2 in pairs(ItemData.Items) do
		if item2.dropTable ~= nil then
			for j, loot in pairs(item2.dropTable) do
				if loot[1] == item.id then
					count1 = count1 + 1
					if string.len(lootStr) > 0 then
						lootStr = lootStr..','
						--if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
						lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
					else
						lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
					end
				end
			end
		end
		if item2.trimmedItemID == item.id then
			count2 = count2 + 1
			if string.len(upgradeStr) > 0 then
				upgradeStr = upgradeStr..','
				--if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
				upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
			else
				table.insert(categoryArray, '[[Category:Upgraded Items]]')
				upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
			end
		end
		if item2.grownItemID == item.id then
			-- Farming
			if string.len(growStr) > 0 then
				growStr = growStr..','..Icons.Icon({item2.name, type="item", notext="true"})
			else
				table.insert(categoryArray, '[[Category:Harvestable Items]]')
				growStr = growStr..'Growing: '..Icons.Icon({item2.name, type="item", notext="true"})
			end
		end
		if item2.perfectItem == item.id and item2.cookingLevel ~= nil then
			-- Cooking (Perfect items)
			table.insert(lineArray, Icons._SkillReq('Cooking', item2.cookingLevel))
		end
	end
	if string.len(lootStr) > 0 then table.insert(lineArray, lootStr) end
	if string.len(upgradeStr) > 0 then table.insert(lineArray, upgradeStr) end
	if string.len(cookStr) > 0 then table.insert(lineArray, cookStr) end
	if string.len(growStr) > 0 then table.insert(lineArray, growStr) end

	--Next: Can we take it from somebody else -without- killing them?
	local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
	local thiefStr = ''
	if Shared.tableCount(thiefItems) > 0 then
		thiefStr = 'Pickpocketing: '
		for i, thiefRow in pairs(thiefItems) do
			if thiefRow.npc == 'all' then
				--if 'any' is the npc, this is a rare item so just say 'Thieving level 1'
				thiefStr = Icons._SkillReq('Thieving', 1)
			else
				if i > 1 then thiefStr = thiefStr..', ' end
				thiefStr = thiefStr..Icons.Icon({thiefRow.npc, type='thieving', notext='true'})
			end
		end
	end
	if string.len(thiefStr) > 0 then table.insert(lineArray, thiefStr) end

	--If all else fails, I guess we should check if we can make it ourselves
	--AstrologyCheck
	--(Just a brute force for now because only two items and I'm lazy)
	if item.name == 'Stardust' or item.name == 'Golden Stardust' then
		table.insert(lineArray, Icons.Icon({"Astrology", type="skill"}))
	end

	-- Sources discoverable through mastery IDs
	if type(item.masteryID) == 'table' then
		local skillID, masteryID = item.masteryID[1], item.masteryID[2]
		local skill, levelReq = Constants.getSkillName(skillID), nil
		if skillID == SkillEnum.Fishing then
			-- Fishing (less Junk & Special items)
			levelReq = item.fishingLevel
		elseif skillID == SkillEnum.Cooking then
			-- Cooking (less Perfect items)
			levelReq = item.cookingLevel
		elseif skillID == SkillEnum.Mining then
			-- Mining
			local rock = SkillData.Mining.Rocks[masteryID + 1]
			if rock ~= nil then
				levelReq = rock.levelRequired
			end
		elseif skillID == SkillEnum.Smithing then
			-- Smithing
			local recipe = SkillData.Smithing.Recipes[masteryID + 1]
			if recipe ~= nil then
				levelReq = recipe.level
			end
		elseif skillID == SkillEnum.Fletching then
			-- Fletching
			levelReq = item.fletchingLevel
		elseif skillID == SkillEnum.Crafting then
			-- Crafting
			levelReq = item.craftingLevel
		elseif skillID == SkillEnum.Runecrafting then
			-- Runecrafting
			levelReq = item.runecraftingLevel
		elseif skillID == SkillEnum.Herblore then
			-- Herblore
			local potion = SkillData.Herblore.Potions[masteryID + 1]
			if potion ~= nil then
				levelReq = potion.level
			end
		elseif skillID == SkillEnum.Summoning then
			-- Summoning
			levelReq = item.summoningLevel
		end
		if levelReq ~= nil then
			table.insert(lineArray, Icons._SkillReq(skill, levelReq))
		end
	end

	-- Woodcutting
	for i, tree in ipairs(SkillData.Woodcutting.Trees) do
		if tree.logID == item.id then
			table.insert(lineArray, Icons._SkillReq('Woodcutting', tree.levelRequired))
			break
		end
	end

	-- Woodcutting (Nests)
	if item.name == 'Bird Nest' then
		table.insert(lineArray, Icons._SkillReq('Woodcutting', 1))
	end

	-- Fishing (Junk & Special items)
	if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
		table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
	end

	--Finally there are some weird exceptions:
	--Coal can be acquired via firemaking
	if item.name == "Coal Ore" then
		table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
	end

	--Gems can be acquired from mining, fishing, and alt. magic
	if item.type == 'Gem' and item.name ~= 'Jadestone' then
		table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
		table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
		table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
	end

	--Bars and some other stuff can also be acquired via Alt. Magic
	if item.type == 'Bar' or Shared.contains(Items.AltMagicProducts, item.name) then
		table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
	end

	--Rhaelyx pieces are also special
	if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
		local rhaSkills = {
			Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
			Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
		}
		local rhaSkList = {}
		if item.name == 'Circlet of Rhaelyx' then
			rhaSkList = rhaSkills.Circlet
		elseif item.name == 'Jewel of Rhaelyx' then
			rhaSkList = rhaSkills.Jewel
		elseif item.name == 'Mysterious Stone' then
			rhaSkList = rhaSkills.Jewel
			for i, v in ipairs(rhaSkills.Circlet) do
				table.insert(rhaSkList, v)
			end
		end

		local rhaStrPart = {}
		for i, skillName in ipairs(rhaSkList) do
			table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
		end
		local rhaStr = 'Any action in: ' .. table.concat(rhaStrPart, ', ')
		if item.name == 'Mysterious Stone' then rhaStr = rhaStr .. '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}) end
		table.insert(lineArray, rhaStr)
	end

	--Tokens are from the appropriate skill
	if item.isToken and item.skill ~= nil then
		table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 1))
	end

	--Shop items (including special items like gloves that aren't otherwise listed)
	local shopSources = Shop.getItemSourceArray(item.id)
	if Shared.tableCount(shopSources) > 0 then
		table.insert(lineArray, Icons.Icon({'Shop'}))
	end

	--Easter Eggs (manual list 'cause don't have a better way to do that)
	if Shared.contains(Items.EasterEggs, item.name) then
		table.insert(lineArray, '[[Easter Eggs]]')
	end
	-- Event exclusive items (also a manual list)
	if Shared.contains(Items.EventItems, item.name) then
		table.insert(lineArray, '[[Events]]')
	end

	--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring)
	--Also handling Signet Ring things here
	if item.name == 'Gold Topaz Ring' then
		table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')')
		table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
	elseif item.name == 'Signet Ring Half (a)' then
		table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	elseif item.name == 'Signet Ring Half (b)' then
		table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	end

	local resultPart = {}
	if asList then
		table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
	else
		table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
	end
	if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
	return table.concat(resultPart)
end

function p.getItemSources(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	local asList = false
	local addCategories = false
	if frame.args ~= nil then
		asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
		addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
	end
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getItemSources(item, asList, addCategories)
end

function p._getItemLootSourceTable(item)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
	table.insert(resultPart, '\r\n|- class="headerRow-0"')
	table.insert(resultPart, '\r\n!Source!!Source Type!!Quantity!!colspan="2"|Chance')

	--Set up function for adding rows
	local buildRow = function(source, type, minqty, qty, weight, totalWeight)
		if minqty == nil then minqty = 1 end
		local rowPart = {}
		table.insert(rowPart, '\r\n|-')
		table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
		table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)

		table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty)
		if qty ~= minqty then table.insert(rowPart, ' - '..qty) end
		local chance = weight / totalWeight * 100
		-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
		local fmt = (chance < 0.10 and '%.2g') or '%.2f'
		chance = string.format(fmt, chance)
		if weight >= totalWeight then
			-- Fraction would be 1/1, so only show the percentage
			chance = 100
			table.insert(rowPart, '\r\n|colspan="2" ')
		else
			local fraction = Shared.fraction(weight, totalWeight)
			if Shared.contains(fraction, '%.') then
				--If fraction contains decimals, something screwy happened so just show only percentage
				--(happens sometimes with the rare thieving items)
				table.insert(rowPart, '\r\n|colspan="2" ')
			else
				table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chance .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|')
			end
		end
		if weight == -1 then
			--Weight of -1 means this is a weird row that has a variable percentage
			table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)')
		else
			table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chance .. '"|'..chance..'%')
		end
		return table.concat(rowPart)
	end
	local dropRows = {}

	--Alright, time to go through a few ways to get the item
	--First up: Can we kill somebody and take theirs?
	for i, monster in ipairs(MonsterData.Monsters) do
		local minqty = 1
		local qty = 1
		local wt = 0
		local totalWt = 0
		--Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss
		--... unless we're looking for Shards of course, at which point we'll take any monster with the right bones
		if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
			qty = monster.boneQty ~= nil and monster.boneQty or 1
			minqty = qty
			wt = 1
			totalWt = 1
		elseif monster.lootTable ~= nil then
			-- If the monster has a loot table, check if the item we are looking for is in there
			-- Dungeon exclusive monsters don't count as they are either:
			--   - A monster before the boss, which doesn't drop anything except shards (checked above)
			--   - A boss monster, whose drops are accounted for in data from Areas instead
			for j, loot in ipairs(monster.lootTable) do
				totalWt = totalWt + loot[2]
				if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
					wt = loot[2]
					qty = loot[3]
				end
			end
		end
		local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100

		if wt > 0 and lootChance > 0 then
			-- Item drops when the monster is killed
			table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
		end
	end
	-- Is the item dropped from any dungeon?
	local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
	if dungeonList ~= nil then
		for i, dungeon in ipairs(dungeonList) do
			table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
		end
	end
	-- Is the item dropped from a cycle of the Impending Darkness event?
	for i, eventItemID in ipairs(Areas.eventData.rewards) do
		if item.id == eventItemID then
			sourceTxt = Icons.Icon({'Impending Darkness Event', type='dungeon'}) .. (i == Shared.tableCount(Areas.eventData.rewards) and '' or ', Cycle ' .. i)
			table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
			break
		end
	end
	--Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops
	if sourceOverrides['Dungeon'][item.id] ~= nil then
		local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon'})
		table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
	end

	--Next: Can we find it by rummaging around in another item?
	for i, item2 in pairs(ItemData.Items) do
		if item2.dropTable ~= nil then
			local qty = 1
			local wt = 0
			local totalWt = 0
			for j, loot in pairs(item2.dropTable) do
				totalWt = totalWt + loot[2]
				if loot[1] == item.id then
					wt = loot[2]
					if item2.dropQty ~= nil then qty = item2.dropQty[j] end
				end
			end

			if wt > 0 then
				local sourceTxt = Icons.Icon({item2.name, type='item'})
				table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt})
			end
		end
	end

	--Finally, let's try just stealing it
	local thiefType = Icons.Icon({"Thieving", type='skill'})
	local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
	for i, thiefRow in pairs(thiefItems) do
		local sourceTxt = ''
		if thiefRow.npc == 'all' then
			sourceTxt = "Thieving Rare Drop"
		else
			sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
		end
		table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt})
	end

	--Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches
	--Jadestone is special and doesn't count
	if item.type == 'Gem' and item.name ~= 'Jadestone' then
		local mineType = Icons.Icon({'Mining', type='skill'})
		local thisGemChance = Items.GemTable[item.name].chance
		local totalGemChance = 0
		for i, gem in pairs(Items.GemTable) do
			totalGemChance = totalGemChance + gem.chance
		end
		table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
		local magicType = Icons.Icon({'Magic', type = 'skill'})
		table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
		table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
	end

	if item.fishingCatchWeight ~= nil then
		local fishSource = '[[Fishing#Special|Special]]'
		local fishType = Icons.Icon({'Fishing', type='skill'})
		table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
	end

	if item.type == 'Junk' then
		local fishSource = '[[Fishing#Junk|Junk]]'
		local fishType = Icons.Icon({'Fishing', type='skill'})
		table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = Items.junkCount})
	end

	--Make sure to return nothing if there are no drop sources
	if Shared.tableCount(dropRows) == 0 then return '' end

	table.sort(dropRows, function(a, b)
							if a.weight / a.totalWeight == b.weight / b.totalWeight then
								return a.minqty + a.qty > b.minqty + b.qty
							else
								return a.weight / a.totalWeight > b.weight / b.totalWeight
							end
						end)
	for i, data in pairs(dropRows) do
		table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
	end

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

function p.getItemLootSourceTable(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getItemLootSourceTable(item)
end

function p._getItemUpgradeTable(item)
	local resultPart = {}
	if item.itemsRequired ~= nil then
		--First, get details on all the required materials
		local upgradeFrom = {}
		local materials = {}
		for i, row in pairs(item.itemsRequired) do
			local mat = Items.getItemByID(row[1])
			--Check to see if the source item can trigger the upgrade
			if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then
				table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'}))
			end
			table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]}))
		end
		if item.trimmedGPCost ~= nil then
			table.insert(materials, Icons.GP(item.trimmedGPCost))
		end
		table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
		--[[result = result..'\r\n|-\r\n!style="text-align:right;"|Upgrades From\r\n|'
		result = result..table.concat(upgradeFrom, '<br/>')--]]
		table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
		table.insert(resultPart, table.concat(materials, '<br/>'))
		table.insert(resultPart, '\r\n|}')
	end
	return table.concat(resultPart)
end

function p.getItemUpgradeTable(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getItemUpgradeTable(item)
end

function p._getItemSuperheatTable(item)
	--Manually build the Superheat Item table
	-- Validate that the item can be superheated
	local canSuperheat, smithRecipe = true, nil
	if type(item.masteryID) ~= 'table' then
		canSuperheat = false
	elseif item.masteryID[1] ~= SkillEnum.Smithing then
		canSuperheat = false
	else
		smithRecipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1]
		if smithRecipe == nil or smithRecipe.category ~= 0 then
			canSuperheat = false
		end
	end
	if not canSuperheat then
		return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]'
	end

	local oreStringPart, coalString = {}, ''
	for i, mat in ipairs(item.smithReq) do
		local thisMat = Items.getItemByID(mat.id)
		if thisMat.name == 'Coal Ore' then
			coalString = Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
		else
			table.insert(oreStringPart, Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty}))
		end
	end
	--Set up the header
	local superheatTable = {}
	table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
	table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
	table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
	table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
	table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
	table.insert(superheatTable, '!!Ore!!Runes')
	 --Loop through all the variants
	local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
	for i, sName in pairs(spellNames) do
		local spell = Magic.getSpell(sName, 'AltMagic')
		table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50}))
		table.insert(superheatTable, '||'..Icons.Icon({spell.name, type='spell', noicon=true})..'||style="text-align:right;"|'..smithRecipe.level)
		table.insert(superheatTable, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.baseExperience)
		table.insert(superheatTable, '||style="text-align:right;"|'..spell.productionRatio)
		table.insert(superheatTable, '|| '..table.concat(oreStringPart, ', '))
		if spell.consumes == 2 then
			-- 2 = Superheat with coal, 3 = Superheat without coal
			table.insert(superheatTable, (Shared.tableCount(oreStringPart) > 0 and ', ' or '') .. coalString)
		end
		table.insert(superheatTable, '||style="text-align:center"| ' .. getRuneText(spell))
	end
	 --Add the table end and add the table to the result string
	table.insert(superheatTable, '\r\n|}')
	return table.concat(superheatTable)
end

function p.getItemSuperheatTable(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getItemSuperheatTable(item)
end

function p._getItemSourceTables(item)
	local resultPart = {}
	local shopTable = Shop._getItemShopTable(item)
	if string.len(shopTable) > 0 then
		table.insert(resultPart, '===Shop===\r\n'..shopTable)
	end

	local creationTable = p._getCreationTable(item)
	if string.len(creationTable) > 0 then
		if #resultPart > 0 then table.insert(resultPart, '\r\n') end
		table.insert(resultPart, '===Creation===\r\n'..creationTable)
	end

	local upgradeTable = p._getItemUpgradeTable(item)
	if string.len(upgradeTable) > 0 then
		if #resultPart > 0 then table.insert(resultPart, '\r\n') end
		if string.len(creationTable) == 0 then table.insert(resultPart, '===Creation===\r\n') end
		table.insert(resultPart, upgradeTable)
	end

	if type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Smithing then
		local recipe = SkillData.Smithing.Recipes[item.masteryID[2] + 1]
		if recipe ~= nil and recipe.category == 0 then
			table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
		end
	end

	local lootTable = p._getItemLootSourceTable(item)
	if string.len(lootTable) > 0 then
		if #resultPart > 0 then table.insert(resultPart, '\r\n') end
		table.insert(resultPart, '===Loot===\r\n'..lootTable)
	end
	return table.concat(resultPart)
end

function p.getItemSourceTables(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = Items.getItem(itemName)
	if item == nil then
		return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
	end

	return p._getItemSourceTables(item)
end

function p.getCombatPassiveSlotItems(frame)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"\r\n')
	table.insert(resultPart, '|-\r\n')
	table.insert(resultPart, '!colspan="2"|Item\r\n! Passive\r\n')

	local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and Shared.contains(item.validSlots, 'Passive') end)

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

	for i, item in ipairs(itemArray) do
		table.insert(resultPart, '|-\r\n')
		table.insert(resultPart, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! '..Icons.Icon({item.name, type='item', noicon=true})..'\r\n')
		table.insert(resultPart, '| '..item.description..'\r\n')
	end

	table.insert(resultPart, '|}')

	return table.concat(resultPart)
end

function p._getItemMonsterSources(item)
	local resultArray = {}
	for i, monster in ipairs(MonsterData.Monsters) do
		local chance = 0
		local weight = 0
		local minQty = 1
		local maxQty = 1
		if monster.bones == item.id and Monsters._getMonsterBones(monster) ~= nil then
			-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
			chance = 1
			weight = 1
			if monster.boneQty ~= nil then
				minQty = monster.boneQty
				maxQty = monster.boneQty
			end
		elseif monster.lootTable ~= nil then
			-- If the monster has a loot table, check if the item we are looking for is in there
			-- Dungeon exclusive monsters don't count as they are either:
			--   - A monster before the boss, which doesn't drop anything except shards (checked above)
			--   - A boss monster, whose drops are accounted for in data from Areas instead
			local monsterWeight
			for j, loot in ipairs(monster.lootTable) do
				weight = weight + loot[2]
				if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
					chance = loot[2]
					maxQty = loot[3]
				end
			end
			local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
			chance = chance * lootChance
			weight = weight * 100
			chance, weight = Shared.fractionpair(chance, weight)
		end
		if chance > 0 then
			-- Item drops when the monster is killed
			table.insert(resultArray, {id = monster.id, dropWt = chance, totalWt = weight, minQty = minQty, maxQty = maxQty})
		end
	end
	return resultArray
end

function p.getItemMonsterSources(itemName)
	local item = Items.getItem(itemName)
	return p._getItemMonsterSources(item)
end

return p