Module:Items/SourceTables: Difference between revisions

From Melvor Idle
(_getItemSources: Add event items)
m (Correct item name error)
(42 intermediate revisions by 5 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


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


local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Magic = require('Module:Magic')
local Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
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')
local Skills = require('Module:Skills')


-- Implements overrides for sources which cannot be obtained from game data
function p._getCreationTable(item)
-- Currently only overrides for dungeon sources are implemented here
local skill = ''
local sourceOverrides = {
local specialReq = nil
  ['Dungeon'] = {
local time = 0
  [361] = 'Volcanic Cave', -- Fire Cape
local maxTime = nil
  [941] = 'Infernal Stronghold', -- Infernal Cape
local lvl = 0
    [950] = 'Volcanic Cave', -- A Tale of the Past, a future's prophecy
local xp = 0
    [951] = 'Fire God Dungeon', -- The First Hero and an Unknown Evil
local qty = nil
    [1116] = 'Into the Mist' -- Beginning of the End
local req = nil
  }
 
}
local tables = {}
local itemID = item.id
--First figure out what skill is used to make this...


function p._getCreationTable(item)
local skillIDs = {
  local skill = ''
['Gathering'] = {
  local specialReq = nil
['Woodcutting'] = { recipeKey = 'trees' },
  local time = 0
['Fishing'] = { recipeKey = 'fish' },
  local maxTime = nil
['Mining'] = { recipeKey = 'rockData' },
  local lvl = 0
['Farming'] = { recipeKey = 'recipes' }
  local xp = 0
},
  local qty = nil
['Artisan'] = {
  local req = nil
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}


  local tables = {}
-- Gathering skills
  --First figure out what skill is used to make this...
-- All follow a similar data structure
  if item.smithingLevel ~= nil then
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
    skill = 'Smithing'
local skillData = SkillData[localSkillID]
    lvl = item.smithingLevel
local skill = skillData.name
    xp = item.smithingXP
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
    req = item.smithReq
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
    qty = item.smithingQty
if recipe.productId == itemID then
    time = 2
lvl = recipe.level
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
xp = recipe.baseExperience
  end
qty = recipe.baseQuantity or 1
  if item.craftingLevel ~= nil then
if localSkillID == 'Farming' then
    skill = 'Crafting'
req = { recipe.seedCost }
    lvl = item.craftingLevel
end
    xp = item.craftingXP
-- Action time
    req = item.craftReq
if recipe.baseMinInterval ~= nil then
    qty = item.craftQty
time = recipe.baseMinInterval / 1000
    time = 3
if recipe.baseMaxInterval ~= nil then
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, nil, item.craftGPCost))
maxTime = recipe.baseMaxInterval / 1000
  end
end
  if item.runecraftingLevel ~= nil then
elseif recipe.baseInterval ~= nil then
    skill = 'Runecrafting'
time = recipe.baseInterval /1000
    lvl = item.runecraftingLevel
elseif skillData.baseInterval ~= nil then
    xp = item.runecraftingXP
time = skillData.baseInterval / 1000
    req = item.runecraftReq
end
    qty = item.runecraftQty
-- Special requirements
    time = 2
if recipe.totalMasteryRequired ~= nil then
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
specialReq = Icons.Icon({'Mastery', notext=true}) .. Shared.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
  end
end
  if item.fletchingLevel ~= nil then
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq))
    skill = 'Fletching'
-- Assumes item has a single source per skill
    lvl = item.fletchingLevel
break
    xp = item.fletchingXP
end
    req = item.fletchReq
end
    qty = item.fletchQty
end
    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.cookingLevel ~= nil and item.recipeRequirements ~= nil then
  for i, reqSet in pairs(item.recipeRequirements) do
    skill = 'Cooking'
    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
  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.level
    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].levelRequired
    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
-- Artisan skills
      specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
-- Allow follow a similar data structure
    end
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
local skillData = SkillData[localSkillID]
  end
local skill = skillData.name
  if item.type == "Logs" then
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
    --Well this feels like cheating, but for as long as logs are the first items by ID it works
for i, recipe in ipairs(skillData.recipes) do
    local treeData = SkillData.Woodcutting.Trees[item.id + 1]
if recipe.productID == itemID or
    skill = 'Woodcutting'
(localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or
    lvl = treeData.level
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then
    time = treeData.interval / 1000
lvl = recipe.level
    xp = treeData.xp
xp = recipe.baseExperience
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
qty = recipe.baseQuantity or 1
  end
-- Action time
  if item.fishingLevel ~= nil then
if recipe.baseMinInterval ~= nil then
    skill = 'Fishing'
time = recipe.baseMinInterval / 1000
    lvl = item.fishingLevel
if recipe.baseMaxInterval ~= nil then
    xp = item.fishingXP
maxTime = recipe.baseMaxInterval / 1000
    time = item.minFishingInterval/1000
end
    maxTime = item.maxFishingInterval/1000
elseif recipe.baseInterval ~= nil then
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime))
time = recipe.baseInterval /1000
  end
elseif skillData.baseInterval ~= nil then
  --had to add cooking to the list of valid categories here to account for cherries/apples
time = skillData.baseInterval / 1000
  if item.category == 'Cooking' or item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" or Shared.contains(item.name, '(Perfect)') then
end
    --Harvest/Herb means farming
-- Special requirements
    --Logs might mean farming or might not. Depends on the logs
-- Potions have a mastery level requirement depending on the tier
    for i, item2 in pairs(ItemData.Items) do
if item.charges ~= nil and item.tier ~= nil then
      if item2.grownItemID == item.id then
local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
        skill = 'Farming'
if levelUnlock ~= nil then
        lvl = item2.farmingLevel
specialReq = Icons._MasteryReq(item.name, levelUnlock.level, false)
        xp = item2.farmingXP
end
        time = item2.timeToGrow
end
        if item.type == 'Logs' then
-- Materials & output quantity
          qty = 35
-- Special case for Summoning recipes
        else
if localSkillID == 'Summoning' then
          qty = 15
local shardCostArray, otherCostArray = {}, {}
        end
local recipeGPCost = skillData.recipeGPCost
        req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
-- Shards
        table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
for j, itemCost in ipairs(recipe.itemCosts) do
        break
local shard = Items.getItemByID(itemCost.id)
      end
if shard ~= nil then
table.insert(shardCostArray, Icons.Icon({shard.name, type='item', notext=true, qty=itemCost.quantity}))
end
end
-- Other costs
if recipe.gpCost > 0 then
table.insert(otherCostArray, Icons.GP(recipe.gpCost))
end
if recipe.scCost > 0 then
table.insert(otherCostArray, Icons.SC(recipe.scCost))
end
for j, nonShardID in ipairs(recipe.nonShardItemCosts) do
local nonShard = Items.getItemByID(nonShardID)
if nonShard ~= nil then
local itemValue = math.max(nonShard.sellsFor, 20)
local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
table.insert(otherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty}))
end
end
req = table.concat(shardCostArray, ', ')
if not Shared.tableIsEmpty(otherCostArray) then
local costLen = Shared.tableCount(otherCostArray)
req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ")
end
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
-- Some items (such as Arrow shafts) have multiple recipes
elseif type(recipe.alternativeCosts) == 'table' then
local reqPart, qtyPart = {}, {}
for j, altCost in ipairs(recipe.alternativeCosts) do
local reqSubPart = {}
for k, itemCost in ipairs(altCost.itemCosts) do
local reqItem = Items.getItemByID(itemCost.id)
if reqItem == nil then
table.insert(reqSubPart, itemCost.quantity .. 'x ?????')
else
table.insert(reqSubPart, Icons.Icon({reqItem.name, type='item', qty=itemCost.quantity}))
end
end
if recipe.gpCost ~= nil and recipe.gpCost > 0 then
table.insert(reqSubPart, Icons.GP(recipe.GPCost))
end
if recipe.scCost ~= nil and recipe.scCost > 0 then
table.insert(reqSubPart, Icons.SC(recipe.SCCost))
end
table.insert(reqPart, table.concat(reqSubPart, ', '))
table.insert(qtyPart, Shared.formatnum(qty * altCost.quantityMultiplier))
end
local sep = "<br/>'''OR''' "
req = table.concat(reqPart, sep)
local qtyText = table.concat(qtyPart, sep)
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qtyText, time, maxTime, specialReq))
-- Finally, normal recipes with a single set of item costs
elseif type(recipe.itemCosts) == 'table' and not Shared.tableIsEmpty(recipe.itemCosts) then
table.insert(tables, p.buildCreationTable(skill, lvl, xp, recipe.itemCosts, qty, time, maxTime, specialReq, recipe.gpCost, recipe.scCost))
end
end
end
end


      --If this is a perfect item, need to find the original
-- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars
      if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
-- Bars are handled by getItemSuperheatTable()
  for j, reqSet in pairs(item2.recipeRequirements) do
-- Gems are handled by _getItemLootSourceTable()
      skill = 'Cooking'
for i, altSpell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
    lvl = item2.cookingLevel
if altSpell.produces == item.id then
    xp = item2.cookingXP
table.insert(tables, p._buildAltMagicTable(altSpell))
    req = reqSet
    qty = item2.cookingQty
    time = item2.cookingInterval / 1000
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
end
  end
end
    end
 
  end
if Shared.tableIsEmpty(tables) then
  if item.summoningLevel ~= nil then
return ''
    skill = 'Summoning'
else
    lvl = item.summoningLevel
return table.concat(tables, '\r\n')
    --Summoning uses a formula to calculate XP for creation instead of referring to the item's XP data directly
end
    xp = (5 + 2 * math.floor(item.summoningLevel / 5))
end
    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
function p.getAltMagicTable(frame)
    return ""
local spellName = frame.args ~= nil and frame.args[1] or frame
  else
local spell = Magic.getSpell(spellName, 'altMagic')
    return table.concat(tables, '\r\n')
if spell == nil then
  end
return 'ERROR: Could not find Alt. Magic spell "' .. spellName .. '"[[Category:Pages with script errors]]'
else
return p._buildAltMagicTable(spell)
end
end
end


function p.buildAltMagicTable(spellName)
function p._buildAltMagicTable(spell)
  local spell = Magic.getSpell(spellName, 'AltMagic')
local resultPart = {}
  local resultPart = {}
local imgType = Magic._getSpellIconType(spell)
  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=imgType}))
  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.level))
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
local costText = Magic._getAltSpellCostText(spell)
  if spell.selectItem == 1 then
if costText ~= nil then
    table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
    table.insert(resultPart, '\r\n|1 of any item')
table.insert(resultPart, '\r\n| ' .. costText)
  end
end
  --Add runes
 
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n|')
--Add runes
  for i, req in pairs(spell.runesRequired) do
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. Magic._getSpellRunes(spell))
    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)
--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.convertToQty)
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.magicXP)
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|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
  table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
  return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost)
function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq, gpCost, scCost)
  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
table.insert(resultPart, '\r\n!colspan="2"|Item ' .. (req == nil and 'Creation' or 'Production'))
    table.insert(resultPart, '\r\n!colspan="2"|Item Creation')
table.insert(resultPart, '\r\n|-\r\n!style="text-align: right;"|Requirements')
  else
table.insert(resultPart, '\r\n|'..Icons._SkillReq(skill, lvl))
    table.insert(resultPart, '\r\n!colspan="2"|Item Production')
if specialReq ~= nil then table.insert(resultPart, '<br/>'..specialReq) end
  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
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 ipairs(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.quantity..'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.quantity}))
        end
end
      end
end
      if gpCost ~= nil and gpCost > 0 then
if gpCost ~= nil and gpCost > 0 then
    table.insert(resultPart, '<br/>')
table.insert(resultPart, '<br/>')
    table.insert(resultPart, Icons.GP(gpCost))
table.insert(resultPart, Icons.GP(gpCost))
      end
end
    else
if scCost ~= nil and scCost > 0 then
    table.insert(resultPart, req)
table.insert(resultPart, '<br/>')
    end
table.insert(resultPart, Icons.SC(scCost))
  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.timeString(time, true))
if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.timeString(maxTime, true)) 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 killStrPart = {}
local killStrPart = {}
  local dungeonStrPart = {}
for i, monster in ipairs(GameData.rawData.monsters) do
  for i, monster in ipairs(MonsterData.Monsters) do
local isDrop = false
  local isDrop = false
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then
  if monster.bones == item.id and ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, 'Shard')) then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
  -- 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
  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
      -- 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:
          for j, loot in ipairs(monster.lootTable) do
--  - A monster before the boss, which doesn't drop anything except shards (checked above)
              if loot[1] == item.id then
--  - A boss monster, whose drops are accounted for in data from Areas instead
            isDrop = true
for j, loot in ipairs(monster.lootTable) do
            break
if loot.itemID == item.id and not Monsters._isDungeonOnlyMonster(monster) then
            end
isDrop = true
        end
break
      end
end
  if isDrop then
end
  if not Shared.contains(item.name, 'Shard') and Monsters._isDungeonOnlyMonster(monster) then
end
  -- For dungeon exclusive monsters, loot is only rolled when they are the last
if isDrop then
  -- monster within that dungeon (unless it is a shard)
-- Item drops when the monster is killed
  if monster.isBoss then
table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
  local areaList = Areas.getMonsterAreas(monster.id)
end
  for k, area in ipairs(areaList) do
end
  if area.type == 'dungeon' and area.monsters[#area.monsters] == monster.id then
-- Is the item dropped from any dungeon?
  table.insert(dungeonStrPart, Icons.Icon({area.name, type='dungeon', notext=true}))
local dungeonStrPart = {}
  end
for i, dungeon in ipairs(GameData.rawData.dungeons) do
  end
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
  end
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
  else
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
  -- Item is not an end of dungeon reward, and drops when the monster is killed
elseif dungeon.eventID ~= nil then
  table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
  end
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
  end
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
  end
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
  -- Is the item dropped from a cycle of the Impending Darkness event?
if item.id == itemRewardID then
  for i, eventItemID in ipairs(Areas.eventData.rewards) do
local dungPrefix = (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or eventCycle .. (eventCycle == 1 and ' cycle' or ' cycles') .. ' of ')
      if item.id == eventItemID then
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='dungeon', notext=true}))
  local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ')
break
          table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true}))
end
  break
end
  end
end
  end
end
  -- Special exceptions for Fire/Infernal Cape and first two lore books
end
  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
if not Shared.tableIsEmpty(dungeonStrPart) then
  table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
  end
end
  if Shared.tableCount(killStrPart) > 0 then
if not Shared.tableIsEmpty(killStrPart) then
  table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
  end
end


  --Next: Can we find it in a box?
-- Can we find it in an openable item?
  --While we're here, check for upgrades, originals (for perfect items), and growing
local lootPart = {}
  local lootStr = ''
for i, item2 in ipairs(GameData.rawData.items) do
  local upgradeStr = ''
if item2.dropTable ~= nil then
  local cookStr = ''
for j, loot in ipairs(item2.dropTable) do
  local growStr = ''
if loot.itemID == item.id then
  local count1 = 0
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
  local count2 = 0
break
  for i, item2 in pairs(ItemData.Items) do
end
    if item2.dropTable ~= nil then
end
      for j, loot in pairs(item2.dropTable) do
end
        if loot[1] == item.id then
end
          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
        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
    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?
if not Shared.tableIsEmpty(lootPart) then
  local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, ','))
  local thiefStr = ''
end
  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
-- Is the item a result of upgrading/downgrading another item?
  --AstrologyCheck
local upgradePart = { up = {}, down = {} }
  --(Just a brute force for now because only two items and I'm lazy)
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
  if item.name == 'Stardust' or item.name == 'Golden Stardust' then
if item.id == upgrade.upgradedItemID then
  table.insert(lineArray, Icons.Icon({"Astrology", type="skill"}))
local key = (upgrade.isDowngrade and 'down' or 'up')
  end
for j, rootItemID in ipairs(upgrade.rootItemIDs) do
local rootItem = Items.getItemByID(rootItemID)
if rootItem ~= nil then
table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true}))
end
end
end
end


  --SmithCheck:
local upgradeCat = false
  if item.smithingLevel ~= nil then
for catName, parts in pairs(upgradePart) do
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
if not Shared.tableIsEmpty(parts) then
  end
if not upgradeCat then
table.insert(categoryArray, '[[Category:Upgraded Items]]')
upgradeCat = true
end
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, ','))
end
end


  --CraftCheck:
--Next: Can we take it from somebody else -without- killing them?
  if item.craftingLevel ~= nil then
local thiefItems = Skills.getThievingSourcesForItem(item.id)
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
if type(thiefItems) == 'table' then
  end
local includedNPCs = {}
local thiefPart = {}
for i, thiefRow in ipairs(thiefItems) do
if thiefRow.npc == 'all' then
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
table.insert(includedNPCs, thiefRow.npc)
end
end
if not Shared.tableIsEmpty(thiefPart) then
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, ','))
end
end
-- Can we get this item by casting an Alt. Magic spell?
local castPart = {}
for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
end
if not Shared.tableIsEmpty(castPart) then
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, ','))
end


  --FletchCheck:
--Check if we can make it ourselves
  if item.fletchingLevel ~= nil then
local skillIDs = {
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
['Gathering'] = {
  end
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Mining'] = { recipeKey = 'rockData' },
['Farming'] = { recipeKey = 'recipes' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}


  --RunecraftCheck:
-- Gathering skills
  if item.runecraftingLevel ~= nil then
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
    table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
local skillData = SkillData[localSkillID]
  end
local skill = skillData.name
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
if recipe.productId == item.id then
if localSkillID == 'Farming' and recipe.seedCost ~= nil then
local seedItem = Items.getItemByID(recipe.seedCost.id)
if seedItem ~= nil then
table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
end
else
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
end
break
end
end
end


  --CookCheck
-- Artisan skills
  if item.cookingLevel ~= nil and item.recipeRequirements ~= nil then
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
  table.insert(lineArray, Icons._SkillReq('Cooking', item.cookingLevel))
local skillData = SkillData[localSkillID]
  end
local skill = skillData.name
for i, recipe in ipairs(skillData.recipes) do
if recipe.productID == item.id or
(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
break
end
end
end


  --MineCheck:
--AstrologyCheck (Just a brute force for now because only two items)
  if item.masteryID ~= nil and item.masteryID[1] == 4 then
if Shared.contains({SkillData.Astrology.stardustItemID, SkillData.Astrology.goldenStardustItemID}, item.id) then
    table.insert(lineArray, Icons._SkillReq("Mining", SkillData.Mining.Rocks[item.masteryID[2] + 1].levelRequired))
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
  end
end


  --FishCheck:
-- Woodcutting
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
-- Raven Nest
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
if item.id == SkillData.Woodcutting.ravenNestItemID then
  elseif item.fishingLevel ~= nil then
local levelReq = nil
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
for i, tree in ipairs(SkillData.Woodcutting.trees) do
  end
if tree.canDropRavenNest and (levelReq == nil or tree.level < levelReq) then
levelReq = tree.level
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq))
-- Bird Nest, Ash, and Mushroom
elseif Shared.contains({
SkillData.Woodcutting.nestItemID,
SkillData.Woodcutting.ashItemID,
SkillData.Woodcutting.mushroomItemID
}, item.id) then
table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
end


  --HerbCheck:
-- Fishing
  if item.masteryID ~= nil and item.masteryID[1] == 19 then
-- Junk
    local potionData = SkillData.Herblore.ItemData[item.masteryID[2] + 1].level
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
  end
-- Specials
elseif GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'itemID', item.id) ~= nil then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
elseif item.id == SkillData.Fishing.lostChestItem then
table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100))
end


  --WoodcuttingCheck:
-- Firemaking: Coal
  if item.type == 'Logs' then
if Shared.contains({SkillData.Firemaking.coalItemID,
    local treeData = SkillData.Woodcutting.Trees[item.id + 1]
SkillData.Firemaking.ashItemID,
    local lvl = treeData.level
SkillData.Firemaking.charcoalItemID,
    table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
SkillData.Firemaking.fireSpiritItemID,
  end
SkillData.Firemaking.diamondItemID
}, item.id) then
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
end


  --SummoningCheck:
-- Mining: Gems
  if item.summoningLevel ~= nil then
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
    table.insert(lineArray, Icons._SkillReq("Summoning", item.summoningLevel))
GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil) then
  end
table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
elseif item.id == SkillData.Mining.runestoneItemID then
-- From pure essence mining
local recipe = GameData.getEntityByID(SkillData.Mining.rockData, 'melvorTotH:Pure_Essence')
if recipe ~= nil then
table.insert(lineArray, Icons._SkillReq(SkillData.Mining.name, recipe.level))
end
end


  --Finally there are some weird exceptions:
--Finally there are some weird exceptions:
  --Coal can be acquired via firemaking
--Rhaelyx pieces
  if item.name == "Coal Ore" then
if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
local rhaSkills = {
  end
Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
}
local rhaSkList, subText = nil, ''
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, rhaSkills.Circlet}
subText = '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'})
end


  --Gems can be acquired from mining, fishing, and alt. magic
local rhaStrPart = {}
  if item.type == 'Gem' and item.name ~= 'Jadestone' then
for i, skillList in ipairs(rhaSkList) do
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
for j, skillName in ipairs(skillList) do
    table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
end
  end
end
table.insert(lineArray, 'Any action in: ' .. table.concat(rhaStrPart, ', ') .. subText)
end


  --Bars and some other stuff can also be acquired via Alt. Magic
--Tokens are from the appropriate skill
  if item.type == 'Bar' or Shared.contains(Items.AltMagicProducts, item.name) then
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
for localSkillID, skillData in pairs(SkillData) do
  end
if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
break
end
end
end


  --Rhaelyx pieces are also special
-- Golbin Raid exclusive items
  if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
if item.golbinRaidExclusive then
    local rhaSkills = {
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
      Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
end
      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 = {}
--Shop items (including special items like gloves that aren't otherwise listed)
    for i, skillName in ipairs(rhaSkList) do
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
      table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
table.insert(lineArray, Icons.Icon({'Shop'}))
    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


  --Tokens are from the appropriate skill
--Easter Eggs (manual list 'cause don't have a better way to do that)
  if item.isToken and item.skill ~= nil then
if Shared.contains(Items.EasterEggs, item.name) then
    table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 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


  --Shop items (including special items like gloves that aren't otherwise listed)
--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring)
  local shopSources = Shop.getItemSourceArray(item.id)
--Also handling Signet Ring things here
  if Shared.tableCount(shopSources) > 0 then
if item.id == 'melvorD:Gold_Topaz_Ring' then
    table.insert(lineArray, Icons.Icon({'Shop'}))
table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')')
  end
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
elseif item.id == 'melvorD:Signet_Ring_Half_A' then
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
elseif item.id == 'melvorD:Signet_Ring_Half_B' then
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
elseif item.id == 'melvorTotH:Deadly_Toxins_Potion' then
--Adding a special override for Deadly Toxins potions
table.insert(lineArray, 'Brewing [[Lethal Toxins Potion]]s while wearing '..Icons.Icon({'Toxic Maker Gloves', type='item'}))
end


  --Easter Eggs (manual list 'cause don't have a better way to do that)
--Township Task reward
  if Shared.contains(Items.EasterEggs, item.name) then
for _, task in ipairs(SkillData.Township.tasks) do
    table.insert(lineArray, '[[Easter Eggs]]')
if task.rewards.items[1] ~= nil then -- Skip tasks with no items
  end
if GameData.getEntityByID(task.rewards.items, item.id) then
  -- Event exclusive items (also a manual list)
table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
  if Shared.contains(Items.EventItems, item.name) then
break
  table.insert(lineArray, '[[Events]]')
end
  end
end
 
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 = {}
local resultPart = {}
  if asList then
if asList then
    table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
  else
else
    table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
  end
end
  if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
  return table.concat(resultPart)
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
--Set up function for adding rows
  local buildRow = function(source, type, minqty, qty, weight, totalWeight)
local buildRow = function(source, type, minqty, qty, weight, totalWeight, expIcon)
    if minqty == nil then minqty = 1 end
if minqty == nil then minqty = 1 end
    local rowPart = {}
if expIcon == nil then expIcon = '' end
    table.insert(rowPart, '\r\n|-')
local rowPart = {}
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
table.insert(rowPart, '\r\n|-')
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
--Weeding out brackets since they don't play nice with data-sort-value
local _, _, typeText = string.find(type, "%|([%a%s]+)%]")
if typeText == nil then _, _, typeText = string.find(type, "%[%[([%a%s]+)%]") end
if typeText == nil then typeText = type end
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..typeText..'"|'..expIcon..type)


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


  --Alright, time to go through a few ways to get the item
--Alright, time to go through a few ways to get the item
  --First up: Can we kill somebody and take theirs?
--First up: Can we kill somebody and take theirs?
  for i, monster in ipairs(MonsterData.Monsters) do
for i, drop in ipairs(p._getItemMonsterSources(item)) do
    local minqty = 1
local monster = GameData.getEntityByID('monsters', drop.id)
    local qty = 1
if monster ~= nil then
    local wt = 0
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = drop.minQty, qty = drop.maxQty, weight = drop.dropWt, totalWeight = drop.totalWt, expIcon = Icons.getExpansionIcon(drop.id)})
    local totalWt = 0
end
    --Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss
end
    --... 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 ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, 'Shard')) 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
      for j, loot in ipairs(monster.lootTable) do
        totalWt = totalWt + loot[2]
        if loot[1] == item.id 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
-- Is the item dropped from any dungeon?
  if not Shared.contains(item.name, 'Shard') and Monsters._isDungeonOnlyMonster(monster) then
for i, dungeon in ipairs(GameData.rawData.dungeons) do
-- For dungeon exclusive monsters, loot is only rolled when they are the last
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
  -- monster within that dungeon (unless it is a shard)
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
if monster.isBoss then
table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1, expIcon = Icons.getExpansionIcon(dungeon.id)})
  local areaList = Areas.getMonsterAreas(monster.id)
elseif dungeon.eventID ~= nil then
  for k, area in ipairs(areaList) do
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
            if area.type == 'dungeon' and area.monsters[#area.monsters] == monster.id then
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
  table.insert(dropRows, {source = Icons.Icon({area.name, type='dungeon'}), type = '[[Dungeon]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
    end
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
  end
if item.id == itemRewardID then
    end
local sourceTxt = Icons.Icon({dungeon.name, type='dungeon'}) .. (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. eventCycle)
  else
table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
        -- Item is not an end of dungeon reward, and drops when the monster is killed
break
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
end
  end
end
end
end
end
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?
-- Can we find it in an openable item?
  for i, item2 in pairs(ItemData.Items) do
for i, item2 in ipairs(GameData.rawData.items) do
    if item2.dropTable ~= nil then
if item2.dropTable ~= nil then
      local qty = 1
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
      local wt = 0
for j, loot in ipairs(item2.dropTable) do
      local totalWt = 0
totalWt = totalWt + loot.weight
      for j, loot in pairs(item2.dropTable) do
if loot.itemID == item.id then
        totalWt = totalWt + loot[2]
wt = loot.weight
        if loot[1] == item.id then
minQty = loot.minQuantity
          wt = loot[2]
maxQty = loot.maxQuantity
          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 = minQty, qty = maxQty, weight = wt, totalWeight = totalWt, expIcon = Icons.getExpansionIcon(item2.id)})
      end
end
    end
end
  end
end


  --Finally, let's try just stealing it
-- Can it be obtained from Thieving?
  local thiefType = Icons.Icon({"Thieving", type='skill'})
local thiefItems = Skills.getThievingSourcesForItem(item.id)
  local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
for i, thiefRow in ipairs(thiefItems) do
  for i, thiefRow in pairs(thiefItems) do
local sourceTxt = ''
  local sourceTxt = ''
if thiefRow.npc == 'all' then
    if thiefRow.npc == 'all' then
sourceTxt = 'Thieving Rare Drop'
    sourceTxt = "Thieving Rare Drop"
else
    else
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
    sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
end
table.insert(dropRows, {source = sourceTxt, type = Icons.Icon({SkillData.Thieving.name, type='skill'}), minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt, expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
end
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
-- Fishing: Junk & Specials
  --Jadestone is special and doesn't count
if Shared.contains(SkillData.Fishing.junkItems, item.id) then
  if item.type == 'Gem' and item.name ~= 'Jadestone' then
local fishSource = '[[Fishing#Junk|Junk]]'
    local mineType = Icons.Icon({'Mining', type='skill'})
local fishType = Icons.Icon({'Fishing', type='skill'})
    local thisGemChance = Items.GemTable[item.name].chance
local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems)
    local totalGemChance = 0
table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = fishTotWeight})
    for i, gem in pairs(Items.GemTable) do
else
      totalGemChance = totalGemChance + gem.chance
local fishTotWeight, fishItem = 0, nil
    end
for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
    table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
if specialItem.itemID == item.id then
    local magicType = Icons.Icon({'Magic', type = 'skill'})
fishItem = specialItem
    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})
fishTotWeight = fishTotWeight + specialItem.weight
  end
end
if fishItem ~= nil then
local fishSource = '[[Fishing#Special|Special]]'
local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'})
table.insert(dropRows, {source = fishSource, type = fishType, minqty = fishItem.minQuantity, qty = fishItem.maxQuantity, weight = fishItem.weight, totalWeight = fishTotWeight})
end
end


  if item.fishingCatchWeight ~= nil then
-- Mining: Gems, and also Alt. Magic spells producing random gems
    local fishSource = '[[Fishing#Special|Special]]'
if Shared.contains({'Gem', 'Superior Gem'}, item.type) then
    local fishType = Icons.Icon({'Fishing', type='skill'})
local gemKeys = { 'randomGems', 'randomSuperiorGems' }
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
for i, gemKey in ipairs(gemKeys) do
  end
local thisGem, totalGemWeight = nil, 0
for j, gem in ipairs(GameData.rawData[gemKey]) do
totalGemWeight = totalGemWeight + gem.weight
if gem.itemID == item.id then
thisGem = gem
end
end
if thisGem ~= nil then
local mineType = Icons.Icon({SkillData.Mining.name, type='skill'})
local expIcon = ''
local sourceTxt
if item.type == 'Superior Gem' then
expIcon = Icons.TotH()
sourceTxt = '[[Mining#Superior Gems|Superior Gem]]'
else
sourceTxt = '[[Mining#Gems|Gem]]'
end
table.insert(dropRows, {source = sourceTxt, type = mineType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight, expIcon = expIcon})
-- Check for Alt. Magic spells also
local magicType = Icons.Icon({SkillData.Magic.name, type = 'skill'})
local producesKey = (gemKey == 'randomGems' and 'RandomGem') or 'RandomSuperiorGem'
for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
if spell.produces ~= nil and spell.produces == producesKey then
table.insert(dropRows, {source = Icons.Icon({spell.name, type=Magic._getSpellIconType(spell)}), type = magicType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight, expIcon = Icons.getExpansionIcon(spell.id)})
end
end
end
end
end


  if item.type == 'Junk' then
--Make sure to return nothing if there are no drop sources
    local fishSource = '[[Fishing#Junk|Junk]]'
if Shared.tableIsEmpty(dropRows) then return '' end
    local fishType = Icons.Icon({'Fishing', type='skill'})
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = Items.junkCount})
table.sort(dropRows, function(a, b)
  end
if a.weight / a.totalWeight == b.weight / b.totalWeight then
 
if a.minqty + a.qty == b.minqty + b.qty then
  --Make sure to return nothing if there are no drop sources
return (a.type == b.type and a.source < b.source) or a.type < b.type
  if Shared.tableCount(dropRows) == 0 then return '' end
else
 
return a.minqty + a.qty > b.minqty + b.qty
  table.sort(dropRows, function(a, b)
end
                        if a.weight / a.totalWeight == b.weight / b.totalWeight then
else
                          return a.minqty + a.qty > b.minqty + b.qty
return a.weight / a.totalWeight > b.weight / b.totalWeight
                        else
end
                          return a.weight / a.totalWeight > b.weight / b.totalWeight
end)
                        end
for i, data in ipairs(dropRows) do
                      end)
table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon))
  for i, data in pairs(dropRows) do
end
    table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
  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
local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id)
    --First, get details on all the required materials
if upgrade ~= nil then
    local upgradeFrom = {}
local upgradeCost = {}
    local materials = {}
for i, itemCost in ipairs(upgrade.itemCosts) do
    for i, row in pairs(item.itemsRequired) do
local costItem = Items.getItemByID(itemCost.id)
      local mat = Items.getItemByID(row[1])
if costItem ~= nil then
      --Check to see if the source item can trigger the upgrade
table.insert(upgradeCost, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity}))
      if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then
end
        table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'}))
end
      end
if type(upgrade.gpCost) == 'number' and upgrade.gpCost > 0 then
      table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]}))
table.insert(upgradeCost, Icons.GP(upgrade.gpCost))
    end
end
    if item.trimmedGPCost ~= nil then
if type(upgrade.scCost) == 'number' and upgrade.scCost > 0 then
      table.insert(materials, Icons.GP(item.trimmedGPCost))
table.insert(upgradeCost, Icons.SC(upgrade.scCost))
    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|'
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
    result = result..table.concat(upgradeFrom, '<br/>')--]]
table.insert(resultPart, table.concat(upgradeCost, '<br/>'))
    table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
table.insert(resultPart, '\r\n|}')
    table.insert(resultPart, table.concat(materials, '<br/>'))
end
    table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
  end
  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)
end


  return p._getItemUpgradeTable(item)
function p._getSuperheatSmithRecipe(item)
local smithRecipe = GameData.getEntityByProperty(SkillData.Smithing.recipes, 'productID', item.id)
if smithRecipe ~= nil and smithRecipe.categoryID == 'melvorD:Bars' then
return smithRecipe
end
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 smithRecipe = p._getSuperheatSmithRecipe(item)
  for i, mat in pairs(item.smithReq) do
if smithRecipe == nil then
    local thisMat = Items.getItemByID(mat.id)
return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]'
    if thisMat.name == 'Coal Ore' then
end
      coalString = ', '..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
 
    else
local oreStringPart, coalString = {}, ''
      if string.len(oreString) > 0 then oreString = oreString..', ' end
for i, mat in ipairs(smithRecipe.itemCosts) do
      oreString =  oreString..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
local matItem = Items.getItemByID(mat.id)
    end
if mat.id == 'melvorD:Coal_Ore' then
  end
coalString = Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity})
  --Set up the header
else
  local superheatTable = {}
table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}))
  table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
end
  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')
--Set up the header
  table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
local superheatTable = {}
  table.insert(superheatTable, '!!Ore!!Runes')
table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
  --Loop through all the variants
table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
  local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
  for i, sName in pairs(spellNames) do
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
    local spell = Magic.getSpell(sName, 'AltMagic')
table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
    local rowPart = {}
table.insert(superheatTable, '!!Ore!!Runes')
    table.insert(rowPart, '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50}))
 
    table.insert(rowPart, '||'..Icons.Icon({spell.name, type='spell', noicon=true})..'||style="text-align:right;"|'..item.smithingLevel)
--Loop through all the variants
    table.insert(rowPart, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.magicXP..'||style="text-align:right;"|'..spell.convertToQty)
local spells = Magic.getSpellsProducingItem(item.id)
    table.insert(rowPart, '||'..oreString)
for i, spell in ipairs(spells) do
    if spell.ignoreCoal ~= nil and not spell.ignoreCoal then table.insert(rowPart, coalString) end
if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
    table.insert(rowPart, '||style="text-align:center"|')
local imgType = Magic._getSpellIconType(spell)
    for i, req in pairs(spell.runesRequired) do
table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type=imgType, notext=true, size=50}))
      local rune = Items.getItemByID(req.id)
table.insert(superheatTable, '||'..Icons.Icon({spell.name, type=imgType, noicon=true})..'||style="text-align:right;"|'..smithRecipe.level)
      if i > 1 then table.insert(rowPart, ', ') end
table.insert(superheatTable, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.baseExperience)
      table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
table.insert(superheatTable, '||style="text-align:right;"|'..spell.productionRatio)
    end
table.insert(superheatTable, '|| '..table.concat(oreStringPart, ', '))
    table.insert(rowPart, "<br/>'''OR'''<br/>")
if spell.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
    for i, req in pairs(spell.runesRequiredAlt) do
table.insert(superheatTable, (not Shared.tableIsEmpty(oreStringPart) and ', ' or '') .. coalString)
      local rune = Items.getItemByID(req.id)
end
      if i > 1 then table.insert(rowPart, ', ') end
table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell))
      table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
end
    end
end
    table.insert(superheatTable, table.concat(rowPart))
 
  end
--Add the table end and add the table to the result string
  --Add the table end and add the table to the result string
table.insert(superheatTable, '\r\n|}')
  table.insert(superheatTable, '\r\n|}')
return table.concat(superheatTable)
  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 shopTable ~= '' 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 creationTable ~= '' then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if not Shared.tableIsEmpty(resultPart) 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 upgradeTable ~= '' then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
    if string.len(creationTable) == 0 then table.insert(resultPart, '===Creation===\r\n') end
if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end
    table.insert(resultPart, upgradeTable)
table.insert(resultPart, upgradeTable)
  end
end


  if item.type == 'Bar' then
if p._getSuperheatSmithRecipe(item) ~= nil then
    table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
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 lootTable ~= '' then
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
if not Shared.tableIsEmpty(resultPart) 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 (item.golbinRaidExclusive == nil or not item.golbinRaidExclusive) 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
local passiveDesc = item.customDescription or Constants.getModifiersText(item.modifiers, false)
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, '| '..passiveDesc..'\r\n')
end


  for i, item in ipairs(itemArray) do
table.insert(resultPart, '|}')
    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


   return table.concat(resultPart)
function p._getItemMonsterSources(item)
local resultArray = {}
for i, monster in ipairs(GameData.rawData.monsters) do
local chance = 0
local weight = 0
local minQty = 1
local maxQty = 1
if monster.bones ~= nil and monster.bones.itemID == 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
maxQty = (monster.bones.quantity ~= nil and monster.bones.quantity) or 1
minQty = maxQty
chance = 1
weight = 1
elseif monster.lootTable ~= nil and not Monsters._isDungeonOnlyMonster(monster) 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
weight = weight + loot.weight
if loot.itemID == item.id then
chance = loot.weight
minQty = loot.minQuantity
maxQty = loot.maxQuantity
end
end
local lootChance = monster.lootChance ~= nil and (monster.bones == nil or monster.bones.itemID ~= item.id) 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
 
--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
local checkItems = {
'Gold Bar',
'Raw Shrimp',
'Coal Ore',
'Rune Platebody',
'Arrow Shafts',
'Yew Longbow',
'Water Rune',
'Steam Rune',
'Controlled Heat Potion II',
'Wolf',
'Cyclops',
'Leprechaun',
'Redwood Logs',
'Carrot Cake',
'Carrot Cake (Perfect)',
'Mantalyme Herb',
'Carrot',
'Topaz',
'Rune Essence',
'Sanguine Blade',
'Ring of Power',
'Infernal Claw',
'Chapeau Noir',
'Stardust',
'Rope',
'Ancient Ring of Mastery',
'Mysterious Stone',
'Mastery Token (Cooking)',
'Gem Gloves',
"Thief's Moneysack",
"Golden Stardust"
}
local checkFuncs = {
p.getItemSourceTables,
--p.getCreationTable,
p.getItemSources,
--p.getItemLootSourceTable,
}
local errCount = 0
for i, item in ipairs(checkItems) do
local param = {args={item}}
mw.log('==' .. item .. '==')
for j, func in ipairs(checkFuncs) do
local callSuccess, retVal = pcall(func, param)
if not callSuccess then
errCount = errCount + 1
mw.log('Error with item "' .. item .. '": ' .. retVal)
else
mw.log(retVal)
end
end
end
if errCount == 0 then
mw.log('Test successful')
else
mw.log('Test failed with ' .. errCount .. ' failures')
end
end
end
--]==]


return p
return p

Revision as of 22:10, 27 November 2022

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

local p = {}


local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Magic = require('Module:Magic')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Monsters = require('Module:Monsters')
local Skills = require('Module:Skills')

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 = {}
	local itemID = item.id
	--First figure out what skill is used to make this...

	local skillIDs = {
		['Gathering'] = {
			['Woodcutting'] = { recipeKey = 'trees' },
			['Fishing'] = { recipeKey = 'fish' },
			['Mining'] = { recipeKey = 'rockData' },
			['Farming'] = { recipeKey = 'recipes' }
		},
		['Artisan'] = {
			['Cooking'] = { },
			['Smithing'] = { },
			['Fletching'] = { },
			['Crafting'] = { },
			['Runecrafting'] = { },
			['Herblore'] = { },
			['Summoning'] = { }
		}
 	}

	-- Gathering skills
	-- All follow a similar data structure
	for localSkillID, dataProp in pairs(skillIDs.Gathering) do
		local skillData = SkillData[localSkillID]
		local skill = skillData.name
		local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
		for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
			if recipe.productId == itemID then
				lvl = recipe.level
				xp = recipe.baseExperience
				qty = recipe.baseQuantity or 1
				if localSkillID == 'Farming' then
					req = { recipe.seedCost }
				end
				-- Action time
				if recipe.baseMinInterval ~= nil then
					time = recipe.baseMinInterval / 1000
					if recipe.baseMaxInterval ~= nil then
						maxTime = recipe.baseMaxInterval / 1000
					end
				elseif recipe.baseInterval ~= nil then
					time = recipe.baseInterval /1000
				elseif skillData.baseInterval ~= nil then
					time = skillData.baseInterval / 1000
				end
				-- Special requirements
				if recipe.totalMasteryRequired ~= nil then
					specialReq = Icons.Icon({'Mastery', notext=true}) .. Shared.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
				end
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq))
				-- Assumes item has a single source per skill
				break
			end
		end
	end

	-- Artisan skills
	-- Allow follow a similar data structure
	for localSkillID, dataProp in pairs(skillIDs.Artisan) do
		local skillData = SkillData[localSkillID]
		local skill = skillData.name
		local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
		for i, recipe in ipairs(skillData.recipes) do
			if recipe.productID == itemID or
				(localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or
				(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then
				lvl = recipe.level
				xp = recipe.baseExperience
				qty = recipe.baseQuantity or 1
				-- Action time
				if recipe.baseMinInterval ~= nil then
					time = recipe.baseMinInterval / 1000
					if recipe.baseMaxInterval ~= nil then
						maxTime = recipe.baseMaxInterval / 1000
					end
				elseif recipe.baseInterval ~= nil then
					time = recipe.baseInterval /1000
				elseif skillData.baseInterval ~= nil then
					time = skillData.baseInterval / 1000
				end
				-- Special requirements
				-- Potions have a mastery level requirement depending on the tier
				if item.charges ~= nil and item.tier ~= nil then
					local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
					if levelUnlock ~= nil then
						specialReq = Icons._MasteryReq(item.name, levelUnlock.level, false)
					end
				end
				-- Materials & output quantity
				-- Special case for Summoning recipes
				if localSkillID == 'Summoning' then
					local shardCostArray, otherCostArray = {}, {}
					local recipeGPCost = skillData.recipeGPCost
					-- Shards
					for j, itemCost in ipairs(recipe.itemCosts) do
						local shard = Items.getItemByID(itemCost.id)
						if shard ~= nil then
							table.insert(shardCostArray, Icons.Icon({shard.name, type='item', notext=true, qty=itemCost.quantity}))
						end
					end
					-- Other costs
					if recipe.gpCost > 0 then
						table.insert(otherCostArray, Icons.GP(recipe.gpCost))
					end
					if recipe.scCost > 0 then
						table.insert(otherCostArray, Icons.SC(recipe.scCost))
					end
					for j, nonShardID in ipairs(recipe.nonShardItemCosts) do
						local nonShard = Items.getItemByID(nonShardID)
						if nonShard ~= nil then
							local itemValue = math.max(nonShard.sellsFor, 20)
							local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
							table.insert(otherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty}))
						end
					end
					req = table.concat(shardCostArray, ', ')
					if not Shared.tableIsEmpty(otherCostArray) then
						local costLen = Shared.tableCount(otherCostArray)
						req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ")
					end
					table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				-- Some items (such as Arrow shafts) have multiple recipes
				elseif type(recipe.alternativeCosts) == 'table' then
					local reqPart, qtyPart = {}, {}
					for j, altCost in ipairs(recipe.alternativeCosts) do
						local reqSubPart = {}
						for k, itemCost in ipairs(altCost.itemCosts) do
							local reqItem = Items.getItemByID(itemCost.id)
							if reqItem == nil then
								table.insert(reqSubPart, itemCost.quantity .. 'x ?????')
							else
								table.insert(reqSubPart, Icons.Icon({reqItem.name, type='item', qty=itemCost.quantity}))
							end
						end
						if recipe.gpCost ~= nil and recipe.gpCost > 0 then
							table.insert(reqSubPart, Icons.GP(recipe.GPCost))
						end
						if recipe.scCost ~= nil and recipe.scCost > 0 then
							table.insert(reqSubPart, Icons.SC(recipe.SCCost))
						end
						table.insert(reqPart, table.concat(reqSubPart, ', '))
							table.insert(qtyPart, Shared.formatnum(qty * altCost.quantityMultiplier))
					end
					local sep = "<br/>'''OR''' "
					req = table.concat(reqPart, sep)
					local qtyText = table.concat(qtyPart, sep)
					table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qtyText, time, maxTime, specialReq))
				-- Finally, normal recipes with a single set of item costs
				elseif type(recipe.itemCosts) == 'table' and not Shared.tableIsEmpty(recipe.itemCosts) then
					table.insert(tables, p.buildCreationTable(skill, lvl, xp, recipe.itemCosts, qty, time, maxTime, specialReq, recipe.gpCost, recipe.scCost))
				end
			end
		end
	end

	-- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars
	-- Bars are handled by getItemSuperheatTable()
	-- Gems are handled by _getItemLootSourceTable()
	for i, altSpell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
		if altSpell.produces == item.id then
			table.insert(tables, p._buildAltMagicTable(altSpell))
		end
	end

	if Shared.tableIsEmpty(tables) then
		return ''
	else
		return table.concat(tables, '\r\n')
	end
end

function p.getAltMagicTable(frame)
	local spellName = frame.args ~= nil and frame.args[1] or frame
	local spell = Magic.getSpell(spellName, 'altMagic')
	if spell == nil then
		return 'ERROR: Could not find Alt. Magic spell "' .. spellName .. '"[[Category:Pages with script errors]]'
	else
		return p._buildAltMagicTable(spell)
	end
end

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

	local costText = Magic._getAltSpellCostText(spell)
	if costText ~= nil then
		table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
		table.insert(resultPart, '\r\n| ' .. costText)
	end

	--Add runes
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. Magic._getSpellRunes(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, scCost)
	if qty == nil then qty = 1 end
	local resultPart = {}
	table.insert(resultPart, '{|class="wikitable"')
	table.insert(resultPart, '\r\n!colspan="2"|Item ' .. (req == nil and 'Creation' or 'Production'))
	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 ipairs(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.quantity..'x ?????')
				else
					table.insert(resultPart, Icons.Icon({matItem.name, type='item', qty=mat.quantity}))
				end
			end
			if gpCost ~= nil and gpCost > 0 then
				table.insert(resultPart, '<br/>')
				table.insert(resultPart, Icons.GP(gpCost))
			end
			if scCost ~= nil and scCost > 0 then
				table.insert(resultPart, '<br/>')
				table.insert(resultPart, Icons.SC(scCost))
			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.timeString(time, true))
	if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.timeString(maxTime, true)) 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(GameData.rawData.monsters) do
		local isDrop = false
		if monster.bones ~= nil and monster.bones.itemID == 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.itemID == 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 = {}
	for i, dungeon in ipairs(GameData.rawData.dungeons) do
		if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
			(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
			table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
		elseif dungeon.eventID ~= nil then
			-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
			local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
			if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
				for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
					if item.id == itemRewardID then
						local dungPrefix = (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or eventCycle .. (eventCycle == 1 and ' cycle' or ' cycles') .. ' of ')
						table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='dungeon', notext=true}))
						break
					end
				end
			end
		end
	end

	if not Shared.tableIsEmpty(dungeonStrPart) then
		table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, ','))
	end
	if not Shared.tableIsEmpty(killStrPart) then
		table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, ','))
	end

	-- Can we find it in an openable item?
	local lootPart = {}
	for i, item2 in ipairs(GameData.rawData.items) do
		if item2.dropTable ~= nil then
			for j, loot in ipairs(item2.dropTable) do
				if loot.itemID == item.id then
					table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
					break
				end
			end
		end
	end

	if not Shared.tableIsEmpty(lootPart) then
		table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, ','))
	end

	-- Is the item a result of upgrading/downgrading another item?
	local upgradePart = { up = {}, down = {} }
	for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
		if item.id == upgrade.upgradedItemID then
			local key = (upgrade.isDowngrade and 'down' or 'up')
			for j, rootItemID in ipairs(upgrade.rootItemIDs) do
				local rootItem = Items.getItemByID(rootItemID)
				if rootItem ~= nil then
					table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true}))
				end
			end
		end
	end

	local upgradeCat = false
	for catName, parts in pairs(upgradePart) do
		if not Shared.tableIsEmpty(parts) then
			if not upgradeCat then
				table.insert(categoryArray, '[[Category:Upgraded Items]]')
				upgradeCat = true
			end
			local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
			table.insert(lineArray, typeText .. ': ' .. table.concat(parts, ','))
		end
	end

	--Next: Can we take it from somebody else -without- killing them?
	local thiefItems = Skills.getThievingSourcesForItem(item.id)
	if type(thiefItems) == 'table' then
		local includedNPCs = {}
		local thiefPart = {}
		for i, thiefRow in ipairs(thiefItems) do
			if thiefRow.npc == 'all' then
				--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
				table.insert(lineArray, Icons._SkillReq('Thieving', 1))
			elseif not Shared.contains(includedNPCs, thiefRow.npc) then
				table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
				table.insert(includedNPCs, thiefRow.npc)
			end
		end
		if not Shared.tableIsEmpty(thiefPart) then
			table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, ','))
		end
	end
	
	-- Can we get this item by casting an Alt. Magic spell?
	local castPart = {}
	for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
		table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
	end
	if not Shared.tableIsEmpty(castPart) then
		table.insert(lineArray, 'Casting: ' .. table.concat(castPart, ','))
	end

	--Check if we can make it ourselves
	local skillIDs = {
		['Gathering'] = {
			['Woodcutting'] = { recipeKey = 'trees' },
			['Fishing'] = { recipeKey = 'fish' },
			['Mining'] = { recipeKey = 'rockData' },
			['Farming'] = { recipeKey = 'recipes' }
		},
		['Artisan'] = {
			['Cooking'] = { },
			['Smithing'] = { },
			['Fletching'] = { },
			['Crafting'] = { },
			['Runecrafting'] = { },
			['Herblore'] = { },
			['Summoning'] = { }
		}
 	}

	-- Gathering skills
	for localSkillID, dataProp in pairs(skillIDs.Gathering) do
		local skillData = SkillData[localSkillID]
		local skill = skillData.name
		for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
			if recipe.productId == item.id then
				if localSkillID == 'Farming' and recipe.seedCost ~= nil then
					local seedItem = Items.getItemByID(recipe.seedCost.id)
					if seedItem ~= nil then
						table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
					end
				else
					table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
				end
				break
			end
		end
	end

	-- Artisan skills
	for localSkillID, dataProp in pairs(skillIDs.Artisan) do
		local skillData = SkillData[localSkillID]
		local skill = skillData.name
		for i, recipe in ipairs(skillData.recipes) do
			if recipe.productID == item.id or
				(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or
				(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then
				table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
				break
			end
		end
	end

	--AstrologyCheck (Just a brute force for now because only two items)
	if Shared.contains({SkillData.Astrology.stardustItemID, SkillData.Astrology.goldenStardustItemID}, item.id) then
		table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
	end

	-- Woodcutting
	-- Raven Nest
	if item.id == SkillData.Woodcutting.ravenNestItemID then
		local levelReq = nil
		for i, tree in ipairs(SkillData.Woodcutting.trees) do
			if tree.canDropRavenNest and (levelReq == nil or tree.level < levelReq) then
				levelReq = tree.level
			end
		end
		table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, levelReq))
	-- Bird Nest, Ash, and Mushroom
	elseif Shared.contains({
		SkillData.Woodcutting.nestItemID,
		SkillData.Woodcutting.ashItemID,
		SkillData.Woodcutting.mushroomItemID
		}, item.id) then
		table.insert(lineArray, Icons._SkillReq(SkillData.Woodcutting.name, 1))
	end

	-- Fishing
	-- Junk
	if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
		table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
	-- Specials
	elseif GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'itemID', item.id) ~= nil then
		table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Special|Special]]')
	elseif item.id == SkillData.Fishing.lostChestItem then
		table.insert(lineArray, Icons._SkillReq(SkillData.Fishing.name, 100))
	end

	-- Firemaking: Coal
	if Shared.contains({SkillData.Firemaking.coalItemID,
		SkillData.Firemaking.ashItemID,
		SkillData.Firemaking.charcoalItemID,
		SkillData.Firemaking.fireSpiritItemID,
		SkillData.Firemaking.diamondItemID
		}, item.id) then
		table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
	end

	-- Mining: Gems
	if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
		GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil) then
		table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
	elseif item.id == SkillData.Mining.runestoneItemID then
		-- From pure essence mining
		local recipe = GameData.getEntityByID(SkillData.Mining.rockData, 'melvorTotH:Pure_Essence')
		if recipe ~= nil then
			table.insert(lineArray, Icons._SkillReq(SkillData.Mining.name, recipe.level))
		end
	end

	--Finally there are some weird exceptions:
	--Rhaelyx pieces
	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, subText = nil, ''
		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, rhaSkills.Circlet}
			subText = '<br/>after finding ' .. Icons.Icon({'Crown of Rhaelyx', type='item'})
		end

		local rhaStrPart = {}
		for i, skillList in ipairs(rhaSkList) do
			for j, skillName in ipairs(skillList) do
				table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
			end
		end
		table.insert(lineArray, 'Any action in: ' .. table.concat(rhaStrPart, ', ') .. subText)
	end

	--Tokens are from the appropriate skill
	if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
		for localSkillID, skillData in pairs(SkillData) do
			if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
				table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
				break
			end
		end
	end

	-- Golbin Raid exclusive items
	if item.golbinRaidExclusive then
		table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
	end

	--Shop items (including special items like gloves that aren't otherwise listed)
	if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) 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.id == 'melvorD: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.id == 'melvorD:Signet_Ring_Half_A' then
		table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	elseif item.id == 'melvorD:Signet_Ring_Half_B' then
		table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	elseif item.id == 'melvorTotH:Deadly_Toxins_Potion' then
		--Adding a special override for Deadly Toxins potions
		table.insert(lineArray, 'Brewing [[Lethal Toxins Potion]]s while wearing '..Icons.Icon({'Toxic Maker Gloves', type='item'}))
	end

	--Township Task reward
	for _, task in ipairs(SkillData.Township.tasks) do
		if task.rewards.items[1] ~= nil then -- Skip tasks with no items
			if GameData.getEntityByID(task.rewards.items, item.id) then
				table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
				break
			end
		end
	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, expIcon)
		if minqty == nil then minqty = 1 end
		if expIcon == nil then expIcon = '' end
		local rowPart = {}
		table.insert(rowPart, '\r\n|-')
		table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
		--Weeding out brackets since they don't play nice with data-sort-value
		local _, _, typeText = string.find(type, "%|([%a%s]+)%]")
		if typeText == nil then _, _, typeText = string.find(type, "%[%[([%a%s]+)%]") end
		if typeText == nil then typeText = type end
		table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..typeText..'"|'..expIcon..type)

		table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..Shared.formatnum(minqty))
		if qty ~= minqty then table.insert(rowPart, ' - '..Shared.formatnum(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'
		local chanceStr = string.format(fmt, chance)
		if weight >= totalWeight then
			-- Fraction would be 1/1, so only show the percentage
			chanceStr = '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="' .. chanceStr .. '"| ' .. 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="'.. chanceStr .. '"|'..chanceStr..'%')
		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, drop in ipairs(p._getItemMonsterSources(item)) do
		local monster = GameData.getEntityByID('monsters', drop.id)
		if monster ~= nil then
			table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = drop.minQty, qty = drop.maxQty, weight = drop.dropWt, totalWeight = drop.totalWt, expIcon = Icons.getExpansionIcon(drop.id)})
		end
	end

	-- Is the item dropped from any dungeon?
	for i, dungeon in ipairs(GameData.rawData.dungeons) do
		if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
			(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
				table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1, expIcon = Icons.getExpansionIcon(dungeon.id)})
		elseif dungeon.eventID ~= nil then
			-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
			local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
			if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
				for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
					if item.id == itemRewardID then
						local sourceTxt = Icons.Icon({dungeon.name, type='dungeon'}) .. (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or ', Cycle ' .. eventCycle)
						table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
						break
					end
				end
			end
		end
	end

	-- Can we find it in an openable item?
	for i, item2 in ipairs(GameData.rawData.items) do
		if item2.dropTable ~= nil then
			local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
			for j, loot in ipairs(item2.dropTable) do
				totalWt = totalWt + loot.weight
				if loot.itemID == item.id then
					wt = loot.weight
					minQty = loot.minQuantity
					maxQty = loot.maxQuantity
				end
			end

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

	-- Can it be obtained from Thieving?
	local thiefItems = Skills.getThievingSourcesForItem(item.id)
	for i, thiefRow in ipairs(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 = Icons.Icon({SkillData.Thieving.name, type='skill'}), minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt, expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
	end

	-- Fishing: Junk & Specials
	if Shared.contains(SkillData.Fishing.junkItems, item.id) then
		local fishSource = '[[Fishing#Junk|Junk]]'
		local fishType = Icons.Icon({'Fishing', type='skill'})
		local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems)
		table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = fishTotWeight})
	else
		local fishTotWeight, fishItem = 0, nil
		for i, specialItem in ipairs(SkillData.Fishing.specialItems) do
			if specialItem.itemID == item.id then
				fishItem = specialItem
			end
			fishTotWeight = fishTotWeight + specialItem.weight
		end
		if fishItem ~= nil then
			local fishSource = '[[Fishing#Special|Special]]'
			local fishType = Icons.Icon({SkillData.Fishing.name, type='skill'})
			table.insert(dropRows, {source = fishSource, type = fishType, minqty = fishItem.minQuantity, qty = fishItem.maxQuantity, weight = fishItem.weight, totalWeight = fishTotWeight})
		end
	end

	-- Mining: Gems, and also Alt. Magic spells producing random gems
	if Shared.contains({'Gem', 'Superior Gem'}, item.type) then
		local gemKeys = { 'randomGems', 'randomSuperiorGems' }
		for i, gemKey in ipairs(gemKeys) do
			local thisGem, totalGemWeight = nil, 0
			for j, gem in ipairs(GameData.rawData[gemKey]) do
				totalGemWeight = totalGemWeight + gem.weight
				if gem.itemID == item.id then
					thisGem = gem
				end
			end
			if thisGem ~= nil then
				local mineType = Icons.Icon({SkillData.Mining.name, type='skill'})
				local expIcon = ''
				local sourceTxt
				if item.type == 'Superior Gem' then
					expIcon = Icons.TotH()
					sourceTxt = '[[Mining#Superior Gems|Superior Gem]]'
				else
					sourceTxt = '[[Mining#Gems|Gem]]'
				end
				table.insert(dropRows, {source = sourceTxt, type = mineType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight, expIcon = expIcon})
				
				-- Check for Alt. Magic spells also
				local magicType = Icons.Icon({SkillData.Magic.name, type = 'skill'})
				local producesKey = (gemKey == 'randomGems' and 'RandomGem') or 'RandomSuperiorGem'
				for j, spell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
					if spell.produces ~= nil and spell.produces == producesKey then
						table.insert(dropRows, {source = Icons.Icon({spell.name, type=Magic._getSpellIconType(spell)}), type = magicType, minqty = thisGem.minQuantity, qty = thisGem.maxQuantity, weight = thisGem.weight, totalWeight = totalGemWeight, expIcon = Icons.getExpansionIcon(spell.id)})
					end
				end
			end
		end
	end

	--Make sure to return nothing if there are no drop sources
	if Shared.tableIsEmpty(dropRows) then return '' end
	
	table.sort(dropRows, function(a, b)
							if a.weight / a.totalWeight == b.weight / b.totalWeight then
								if a.minqty + a.qty == b.minqty + b.qty then
									return (a.type == b.type and a.source < b.source) or a.type < b.type
								else
									return a.minqty + a.qty > b.minqty + b.qty
								end
							else
								return a.weight / a.totalWeight > b.weight / b.totalWeight
							end
						end)
	for i, data in ipairs(dropRows) do
		table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon))
	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 = {}
	local upgrade = GameData.getEntityByProperty('itemUpgrades', 'upgradedItemID', item.id)
	if upgrade ~= nil then
		local upgradeCost = {}
		for i, itemCost in ipairs(upgrade.itemCosts) do
			local costItem = Items.getItemByID(itemCost.id)
			if costItem ~= nil then
				table.insert(upgradeCost, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity}))
			end
		end
		if type(upgrade.gpCost) == 'number' and upgrade.gpCost > 0 then
			table.insert(upgradeCost, Icons.GP(upgrade.gpCost))
		end
		if type(upgrade.scCost) == 'number' and upgrade.scCost > 0 then
			table.insert(upgradeCost, Icons.SC(upgrade.scCost))
		end
		table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Upgrading Items|Item Upgrade]]')
		table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials\r\n|')
		table.insert(resultPart, table.concat(upgradeCost, '<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._getSuperheatSmithRecipe(item)
	local smithRecipe = GameData.getEntityByProperty(SkillData.Smithing.recipes, 'productID', item.id)
	if smithRecipe ~= nil and smithRecipe.categoryID == 'melvorD:Bars' then
		return smithRecipe
	end
end

function p._getItemSuperheatTable(item)
	--Manually build the Superheat Item table
	-- Validate that the item can be superheated
	local smithRecipe = p._getSuperheatSmithRecipe(item)
	if smithRecipe == nil then
		return 'ERROR: The item "' .. item.name .. '" cannot be superheated[[Category:Pages with script errors]]'
	end

	local oreStringPart, coalString = {}, ''
	for i, mat in ipairs(smithRecipe.itemCosts) do
		local matItem = Items.getItemByID(mat.id)
		if mat.id == 'melvorD:Coal_Ore' then
			coalString = Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity})
		else
			table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}))
		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 spells = Magic.getSpellsProducingItem(item.id)
	for i, spell in ipairs(spells) do
		if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
			local imgType = Magic._getSpellIconType(spell)
			table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type=imgType, notext=true, size=50}))
			table.insert(superheatTable, '||'..Icons.Icon({spell.name, type=imgType, 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.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
				table.insert(superheatTable, (not Shared.tableIsEmpty(oreStringPart) and ', ' or '') .. coalString)
			end
			table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell))
		end
	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 shopTable ~= '' then
		table.insert(resultPart, '===Shop===\r\n'..shopTable)
	end

	local creationTable = p._getCreationTable(item)
	if creationTable ~= '' then
		if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
		table.insert(resultPart, '===Creation===\r\n'..creationTable)
	end

	local upgradeTable = p._getItemUpgradeTable(item)
	if upgradeTable ~= '' then
		if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
		if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end
		table.insert(resultPart, upgradeTable)
	end

	if p._getSuperheatSmithRecipe(item) ~= nil then
		table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
	end

	local lootTable = p._getItemLootSourceTable(item)
	if lootTable ~= '' then
		if not Shared.tableIsEmpty(resultPart) 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 (item.golbinRaidExclusive == nil or not item.golbinRaidExclusive) and Shared.contains(item.validSlots, 'Passive') end)

	for i, item in ipairs(itemArray) do
		local passiveDesc = item.customDescription or Constants.getModifiersText(item.modifiers, false)
		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, '| '..passiveDesc..'\r\n')
	end

	table.insert(resultPart, '|}')

	return table.concat(resultPart)
end

function p._getItemMonsterSources(item)
	local resultArray = {}
	for i, monster in ipairs(GameData.rawData.monsters) do
		local chance = 0
		local weight = 0
		local minQty = 1
		local maxQty = 1
		if monster.bones ~= nil and monster.bones.itemID == 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
			maxQty = (monster.bones.quantity ~= nil and monster.bones.quantity) or 1
			minQty = maxQty
			chance = 1
			weight = 1
		elseif monster.lootTable ~= nil and not Monsters._isDungeonOnlyMonster(monster) 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
				weight = weight + loot.weight
				if loot.itemID == item.id then
					chance = loot.weight
					minQty = loot.minQuantity
					maxQty = loot.maxQuantity
				end
			end
			local lootChance = monster.lootChance ~= nil and (monster.bones == nil or monster.bones.itemID ~= item.id) 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

--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
	local checkItems = {
		'Gold Bar',
		'Raw Shrimp',
		'Coal Ore',
		'Rune Platebody',
		'Arrow Shafts',
		'Yew Longbow',
		'Water Rune',
		'Steam Rune',
		'Controlled Heat Potion II',
		'Wolf',
		'Cyclops',
		'Leprechaun',
		'Redwood Logs',
		'Carrot Cake',
		'Carrot Cake (Perfect)',
		'Mantalyme Herb',
		'Carrot',
		'Topaz',
		'Rune Essence',
		'Sanguine Blade',
		'Ring of Power',
		'Infernal Claw',
		'Chapeau Noir',
		'Stardust',
		'Rope',
		'Ancient Ring of Mastery',
		'Mysterious Stone',
		'Mastery Token (Cooking)',
		'Gem Gloves',
		"Thief's Moneysack",
		"Golden Stardust"
	}
	local checkFuncs = {
		p.getItemSourceTables,
		--p.getCreationTable,
		p.getItemSources,
		--p.getItemLootSourceTable,
	}
	local errCount = 0
	for i, item in ipairs(checkItems) do
		local param = {args={item}}
		mw.log('==' .. item .. '==')
		for j, func in ipairs(checkFuncs) do
			local callSuccess, retVal = pcall(func, param)
			if not callSuccess then
				errCount = errCount + 1
				mw.log('Error with item "' .. item .. '": ' .. retVal)
			else
				mw.log(retVal)
			end
		end
	end
	if errCount == 0 then
		mw.log('Test successful')
	else
		mw.log('Test failed with ' .. errCount .. ' failures')
	end
end
--]==]

return p