Module:Items/SourceTables: Difference between revisions

From Melvor Idle
(Remove redundant item source overrides)
(Resolve issue with dungeon sources when dungeons had more than one reward. Also fix indentation oddities)
Line 152: Line 152:
--If this is a perfect item, need to find the original
--If this is a perfect item, need to find the original
if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
for j, reqSet in pairs(item2.recipeRequirements) do
for j, reqSet in pairs(item2.recipeRequirements) do
skill = 'Cooking'
skill = 'Cooking'
lvl = item2.cookingLevel
lvl = item2.cookingLevel
xp = item2.cookingXP
xp = item2.cookingXP
req = reqSet
req = reqSet
qty = item2.cookingQty
qty = item2.cookingQty
time = item2.cookingInterval / 1000
time = item2.cookingInterval / 1000
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
end
end
end
end
end
end
end
end
Line 174: Line 174:
local item = Items.getItemByID(cost.id)
local item = Items.getItemByID(cost.id)
if item.type == 'Shard' then
if item.type == 'Shard' then
table.insert(ShardCostArray, Icons.Icon({item.name, type='item', notext=true, qty=cost.qty}))
table.insert(ShardCostArray, Icons.Icon({item.name, type='item', notext=true, qty=cost.qty}))
end
end
end
end
Line 188: Line 188:
local item = Items.getItemByID(cost.id)
local item = Items.getItemByID(cost.id)
if item.type ~= 'Shard' then
if item.type ~= 'Shard' then
  local sellPrice = math.max(item.sellsFor, 20)
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))}))
table.insert(nonShardArray, Icons.Icon({item.name, type='item', notext=true, qty=math.max(1, math.floor(recipeGPCost / sellPrice))}))
end
end
else
else
if cost.id == -4 then
if cost.id == -4 then
  table.insert(nonShardArray, Icons.GP(recipeGPCost))
table.insert(nonShardArray, Icons.GP(recipeGPCost))
elseif cost.id == -5 then
elseif cost.id == -5 then
  table.insert(nonShardArray, Icons.SC(recipeGPCost))
table.insert(nonShardArray, Icons.SC(recipeGPCost))
end
end
end
end
Line 286: Line 286:
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
else
table.insert(resultPart, req)
table.insert(resultPart, req)
end
end
end
end
Line 322: Line 322:
--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(MonsterData.Monsters) do
for i, monster in ipairs(MonsterData.Monsters) do
local isDrop, isBones = false, false
local isDrop = false
if monster.bones == item.id and Monsters.getMonsterBones(monster) ~= nil then
if monster.bones == item.id and Monsters.getMonsterBones(monster) ~= nil then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
-- 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
isBones = 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[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
  end
isDrop = true
break
end
end
end
end
end
if isDrop then
if isDrop then
if not isBones and Monsters._isDungeonOnlyMonster(monster) then
-- Item drops when the monster is killed
-- For dungeon exclusive monsters, loot is only rolled when they are the last
table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
-- monster within that dungeon (unless it is a shard)
end
if monster.isBoss then
end
local areaList = Areas.getMonsterAreas(monster.id)
-- Is the item dropped from any dungeon?
for k, area in ipairs(areaList) do
local dungeonStrPart = {}
if area.type == 'dungeon' and area.monsters[#area.monsters] == monster.id then
local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
table.insert(dungeonStrPart, Icons.Icon({area.name, type='dungeon', notext=true}))
if dungeonList ~= nil then
end
for i, dungeon in ipairs(dungeonList) do
end
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
end
else
-- Item is not an end of dungeon reward, and drops when the monster is killed
table.insert(killStrPart, Icons.Icon({monster.name, type='monster', notext=true}))
end
end
end
end
end
-- Is the item dropped from a cycle of the Impending Darkness event?
-- Is the item dropped from a cycle of the Impending Darkness event?
for i, eventItemID in ipairs(Areas.eventData.rewards) do
for i, eventItemID in ipairs(Areas.eventData.rewards) do
if item.id == eventItemID then
if item.id == eventItemID then
local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ')
local dungPrefix = (i == Shared.tableCount(Areas.eventData.rewards) and '' or i .. ' ' .. (i == 1 and 'cycle' or 'cycles') .. ' of ')
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true}))
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({'Impending Darkness Event', type='dungeon', notext=true}))
break
break
end
end
Line 390: Line 386:
count1 = count1 + 1
count1 = count1 + 1
if string.len(lootStr) > 0 then
if string.len(lootStr) > 0 then
  lootStr = lootStr..','
lootStr = lootStr..','
  --if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
--if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
  lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
else
else
  lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
end
end
end
end
Line 400: Line 396:
end
end
if item2.trimmedItemID == item.id then
if item2.trimmedItemID == item.id then
count2 = count2 + 1
count2 = count2 + 1
if string.len(upgradeStr) > 0 then
if string.len(upgradeStr) > 0 then
upgradeStr = upgradeStr..','
upgradeStr = upgradeStr..','
--if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
--if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
else
else
table.insert(categoryArray, '[[Category:Upgraded Items]]')
table.insert(categoryArray, '[[Category:Upgraded Items]]')
upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
end
end
end
end
if item2.grownItemID == item.id then
if item2.grownItemID == item.id then
if string.len(growStr) > 0 then
if string.len(growStr) > 0 then
growStr = growStr..','..Icons.Icon({item2.name, type="item", notext="true"})
growStr = growStr..','..Icons.Icon({item2.name, type="item", notext="true"})
else
else
table.insert(categoryArray, '[[Category:Harvestable Items]]')
table.insert(categoryArray, '[[Category:Harvestable Items]]')
growStr = growStr..'Growing: '..Icons.Icon({item2.name, type="item", notext="true"})
growStr = growStr..'Growing: '..Icons.Icon({item2.name, type="item", notext="true"})
end
end
end
end
if item2.perfectItem == item.id and item2.cookingLevel ~= nil then
if item2.perfectItem == item.id and item2.cookingLevel ~= nil then
Line 574: Line 570:
--Also handling Signet Ring things here
--Also handling Signet Ring things here
if item.name == 'Gold Topaz Ring' then
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, '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"})..')')
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
elseif item.name == 'Signet Ring Half (a)' then
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
elseif item.name == 'Signet Ring Half (b)' then
elseif item.name == 'Signet Ring Half (b)' then
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
Line 630: Line 626:
table.insert(rowPart, '\r\n|colspan="2" ')
table.insert(rowPart, '\r\n|colspan="2" ')
else
else
local fraction = Shared.fraction(weight, totalWeight)
local fraction = Shared.fraction(weight, totalWeight)
if Shared.contains(fraction, '%.') then
if Shared.contains(fraction, '%.') then
--If fraction contains decimals, something screwy happened so just show only percentage
--If fraction contains decimals, something screwy happened so just show only percentage
--(happens sometimes with the rare thieving items)
--(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|')
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chance .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|')
end
end
Line 664: Line 660:
totalWt = 1
totalWt = 1
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:
--  - 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
for j, loot in ipairs(monster.lootTable) do
totalWt = totalWt + loot[2]
totalWt = totalWt + loot[2]
if loot[1] == item.id then
if loot[1] == item.id and not Monsters._isDungeonOnlyMonster(monster) then
wt = loot[2]
wt = loot[2]
qty = loot[3]
qty = loot[3]
Line 675: Line 674:
local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100
local lootChance = monster.lootChance ~= nil and monster.bones ~= item.id and monster.lootChance or 100


if wt > 0 and lootChance > 0 then
if wt > 0 and lootChance > 0 then
if not Shared.contains(item.name, 'Shard') and Monsters._isDungeonOnlyMonster(monster) then
-- Item drops when the monster is killed
-- For dungeon exclusive monsters, loot is only rolled when they are the last
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
-- monster within that dungeon (unless it is a shard)
if monster.isBoss then
local areaList = Areas.getMonsterAreas(monster.id)
for k, area in ipairs(areaList) do
  if area.type == 'dungeon' and area.monsters[#area.monsters] == monster.id then
table.insert(dropRows, {source = Icons.Icon({area.name, type='dungeon'}), type = '[[Dungeon]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
end
end
end
else
-- Item is not an end of dungeon reward, and drops when the monster is killed
table.insert(dropRows, {source = Icons.Icon({monster.name, type='monster'}), type = '[[Monster]]', minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
end
end
end
end
-- Is the item dropped from any dungeon?
local dungeonList = Areas.getAreas(function(area) return area.type == 'dungeon' and type(area.rewards) == 'table' and Shared.contains(area.rewards, item.id) end)
if dungeonList ~= nil then
for i, dungeon in ipairs(dungeonList) do
table.insert(dropRows, {source = Icons.Icon({dungeon.name, type='dungeon'}), type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
end
end
end
-- Is the item dropped from a cycle of the Impending Darkness event?
-- Is the item dropped from a cycle of the Impending Darkness event?
Line 737: Line 730:
else
else
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
end
end
table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt})
table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = thiefRow.minQty, qty = thiefRow.maxQty, weight = thiefRow.wt, totalWeight = thiefRow.totalWt})
end
end


Line 772: Line 765:


table.sort(dropRows, function(a, b)
table.sort(dropRows, function(a, b)
              if a.weight / a.totalWeight == b.weight / b.totalWeight then
if a.weight / a.totalWeight == b.weight / b.totalWeight then
                return a.minqty + a.qty > b.minqty + b.qty
return a.minqty + a.qty > b.minqty + b.qty
              else
else
                return a.weight / a.totalWeight > b.weight / b.totalWeight
return a.weight / a.totalWeight > b.weight / b.totalWeight
              end
end
            end)
end)
for i, data in pairs(dropRows) do
for i, data in pairs(dropRows) do
table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
table.insert(resultPart, buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight))
Line 843: Line 836:
else
else
if string.len(oreString) > 0 then oreString = oreString..', ' end
if string.len(oreString) > 0 then oreString = oreString..', ' end
oreString = oreString..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
oreString = oreString..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
end
end
end
end

Revision as of 22:06, 3 January 2022

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

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 Shared = require('Module:Shared')
local Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Monsters = require('Module:Monsters')
local GatheringSkills = require('Module:Skills/Gathering')

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

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

	local tables = {}
	--First figure out what skill is used to make this...
	if item.smithingLevel ~= nil then
		skill = 'Smithing'
		lvl = item.smithingLevel
		xp = item.smithingXP
		req = item.smithReq
		qty = item.smithingQty
		time = 2
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
	end
	if item.craftingLevel ~= nil then
		skill = 'Crafting'
		lvl = item.craftingLevel
		xp = item.craftingXP
		req = item.craftReq
		qty = item.craftQty
		time = 3
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, nil, item.craftGPCost))
	end
	if item.runecraftingLevel ~= nil then
		skill = 'Runecrafting'
		lvl = item.runecraftingLevel
		xp = item.runecraftingXP
		req = item.runecraftReq
		qty = item.runecraftQty
		time = 2
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
	end
	if item.fletchingLevel ~= nil then
		skill = 'Fletching'
		lvl = item.fletchingLevel
		xp = item.fletchingXP
		req = item.fletchReq
		qty = item.fletchQty
		time = 2
		if item.name == 'Arrow Shafts' then
			--Arrow Shafts get special (weird) treatment
			req = '1 of any [[Log]]'
			qty = '15 - 135'
		end
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
	end
	if item.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
			specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
		end
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
	end
	if item.type == "Logs" then
		--Well this feels like cheating, but for as long as logs are the first items by ID it works
		local treeData = SkillData.Woodcutting.Trees[item.id + 1]
		skill = 'Woodcutting'
		lvl = treeData.level
		time = treeData.interval / 1000
		xp = treeData.xp
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
	end
	if item.fishingLevel ~= nil then
		skill = 'Fishing'
		lvl = item.fishingLevel
		xp = item.fishingXP
		time = item.minFishingInterval/1000
		maxTime = item.maxFishingInterval/1000
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime))
	end
	--had to add cooking to the list of valid categories here to account for cherries/apples
	if item.category == 'Cooking' or item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" or Shared.contains(item.name, '(Perfect)') then
		--Harvest/Herb means farming
		--Logs might mean farming or might not. Depends on the logs
		for i, item2 in pairs(ItemData.Items) do
			if item2.grownItemID == item.id then
				skill = 'Farming'
				lvl = item2.farmingLevel
				xp = item2.farmingXP
				time = item2.timeToGrow
				if item.type == 'Logs' then
					qty = 35
				else
					qty = 15
				end
				req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
				table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				break
			end

			--If this is a perfect item, need to find the original
			if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
				for j, reqSet in pairs(item2.recipeRequirements) do
					skill = 'Cooking'
					lvl = item2.cookingLevel
					xp = item2.cookingXP
					req = reqSet
					qty = item2.cookingQty
					time = item2.cookingInterval / 1000
					table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
				end
			end
		end
	end
	if item.summoningLevel ~= nil then
		skill = 'Summoning'
		lvl = item.summoningLevel
		--Summoning uses a formula to calculate XP for creation instead of referring to the item's XP data directly
		xp = (5 + 2 * math.floor(item.summoningLevel / 5))
		local ShardCostArray = {}
		for j, cost in Shared.skpairs(item.summoningReq[1]) do
			if cost.id >= 0 then
				local item = Items.getItemByID(cost.id)
				if item.type == 'Shard' then
					table.insert(ShardCostArray, Icons.Icon({item.name, type='item', notext=true, qty=cost.qty}))
				end
			end
		end
		req = table.concat(ShardCostArray, ', ')
		req = req..'<br/>\r\nand one of the following<br/>\r\n'
		local OtherCostArray = {}
		local recipeGPCost = SkillData.Summoning.Settings.recipeGPCost
		for j, altCost in Shared.skpairs(item.summoningReq) do
			local nonShardArray = {}
			for k, cost in Shared.skpairs(altCost) do
				if cost.id >= 0 then
					local item = Items.getItemByID(cost.id)
					if item.type ~= 'Shard' then
						local sellPrice = math.max(item.sellsFor, 20)
						table.insert(nonShardArray, Icons.Icon({item.name, type='item', notext=true, qty=math.max(1, math.floor(recipeGPCost / sellPrice))}))
					end
				else
					if cost.id == -4 then
						table.insert(nonShardArray, Icons.GP(recipeGPCost))
					elseif cost.id == -5 then
						table.insert(nonShardArray, Icons.SC(recipeGPCost))
					end
				end
			end
			table.insert(OtherCostArray, table.concat(nonShardArray, ', '))
		end
		req = req..table.concat(OtherCostArray, "<br/>'''OR''' ")
		qty = item.summoningQty
		time = 5
		table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
	end
	--A couple special exceptions for Alt Magic
	--Not Gems or Bars though since those have their own separate thing
	if item.name == 'Rune Essence' then
		table.insert(tables, p.buildAltMagicTable('Just Learning'))
	elseif item.name == 'Bones' then
		table.insert(tables, p.buildAltMagicTable('Bone Offering'))
	elseif item.name == 'Holy Dust' then
		table.insert(tables, p.buildAltMagicTable('Blessed Offering'))
	end

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

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

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

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

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

	return table.concat(resultPart)
end

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

	return p._getCreationTable(item)
end

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

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

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

	--Next: Can we find it in a box?
	--While we're here, check for upgrades, originals (for perfect items), and growing
	local lootStr = ''
	local upgradeStr = ''
	local cookStr = ''
	local growStr = ''
	local count1 = 0
	local count2 = 0
	for i, item2 in pairs(ItemData.Items) do
		if item2.dropTable ~= nil then
			for j, loot in pairs(item2.dropTable) do
				if loot[1] == item.id then
					count1 = count1 + 1
					if string.len(lootStr) > 0 then
						lootStr = lootStr..','
						--if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
						lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
					else
						lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
					end
				end
			end
		end
		if item2.trimmedItemID == item.id then
			count2 = count2 + 1
			if string.len(upgradeStr) > 0 then
				upgradeStr = upgradeStr..','
				--if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
				upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
			else
				table.insert(categoryArray, '[[Category:Upgraded Items]]')
				upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
			end
		end
		if item2.grownItemID == item.id then
			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?
	local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
	local thiefStr = ''
	if Shared.tableCount(thiefItems) > 0 then
		thiefStr = 'Pickpocketing: '
		for i, thiefRow in pairs(thiefItems) do
			if thiefRow.npc == 'all' then
				--if 'any' is the npc, this is a rare item so just say 'Thieving level 1'
				thiefStr = Icons._SkillReq('Thieving', 1)
			else
				if i > 1 then thiefStr = thiefStr..', ' end
				thiefStr = thiefStr..Icons.Icon({thiefRow.npc, type='thieving', notext='true'})
			end
		end
	end
	if string.len(thiefStr) > 0 then table.insert(lineArray, thiefStr) end

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

	--SmithCheck:
	if item.smithingLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
	end

	--CraftCheck:
	if item.craftingLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
	end

	--FletchCheck:
	if item.fletchingLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
	end

	--RunecraftCheck:
	if item.runecraftingLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
	end

	--CookCheck
	if item.cookingLevel ~= nil and item.recipeRequirements ~= nil then
		table.insert(lineArray, Icons._SkillReq('Cooking', item.cookingLevel))
	end

	--MineCheck:
	if item.masteryID ~= nil and item.masteryID[1] == 4 then
		table.insert(lineArray, Icons._SkillReq("Mining", SkillData.Mining.Rocks[item.masteryID[2] + 1].levelRequired))
	end

	--FishCheck:
	if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
		table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
	elseif item.fishingLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
	end

	--HerbCheck:
	if item.masteryID ~= nil and item.masteryID[1] == 19 then
		local potionData = SkillData.Herblore.ItemData[item.masteryID[2] + 1].level
		table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
	end

	--WoodcuttingCheck:
	if item.type == 'Logs' then
		local treeData = SkillData.Woodcutting.Trees[item.id + 1]
		local lvl = treeData.level
		table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
	end

	--SummoningCheck:
	if item.summoningLevel ~= nil then
		table.insert(lineArray, Icons._SkillReq("Summoning", item.summoningLevel))
	end

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

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

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

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

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

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

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

	--Easter Eggs (manual list 'cause don't have a better way to do that)
	if Shared.contains(Items.EasterEggs, item.name) then
		table.insert(lineArray, '[[Easter Eggs]]')
	end
	-- Event exclusive items (also a manual list)
	if Shared.contains(Items.EventItems, item.name) then
		table.insert(lineArray, '[[Events]]')
	end
	
	--Gold Topaz Ring drops from any action (when not wearing a Gold Topaz Ring)
	--Also handling Signet Ring things here
	if item.name == 'Gold Topaz Ring' then
		table.insert(lineArray, 'Any non-combat action if not worn (Instead of '..Icons.Icon({"Signet Ring Half (a)", type="item"})..')')
		table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
	elseif item.name == 'Signet Ring Half (a)' then
		table.insert(lineArray, 'Any non-combat action while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	elseif item.name == 'Signet Ring Half (b)' then
		table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
	end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return p._getItemLootSourceTable(item)
end

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

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

	return p._getItemUpgradeTable(item)
end

function p._getItemSuperheatTable(item)
	--Manually build the Superheat Item table
	local oreString = ''
	local coalString = ''
	for i, mat in pairs(item.smithReq) do
		local thisMat = Items.getItemByID(mat.id)
		if thisMat.name == 'Coal Ore' then
			coalString = ', '..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
		else
			if string.len(oreString) > 0 then oreString = oreString..', ' end
			oreString = oreString..Icons.Icon({thisMat.name, type='item', notext='true', qty=mat.qty})
		end
	end
	--Set up the header
	local superheatTable = {}
	table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
	table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
	table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
	table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
	table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
	table.insert(superheatTable, '!!Ore!!Runes')
	 --Loop through all the variants
	local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
	for i, sName in pairs(spellNames) do
		local spell = Magic.getSpell(sName, 'AltMagic')
		local rowPart = {}
		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)
		table.insert(rowPart, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.magicXP..'||style="text-align:right;"|'..spell.convertToQty)
		table.insert(rowPart, '||'..oreString)
		if spell.ignoreCoal ~= nil and not spell.ignoreCoal then table.insert(rowPart, coalString) end
		table.insert(rowPart, '||style="text-align:center"|')
		for i, req in pairs(spell.runesRequired) do
			local rune = Items.getItemByID(req.id)
			if i > 1 then table.insert(rowPart, ', ') end
			table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
		end
		table.insert(rowPart, "<br/>'''OR'''<br/>")
		for i, req in pairs(spell.runesRequiredAlt) do
			local rune = Items.getItemByID(req.id)
			if i > 1 then table.insert(rowPart, ', ') end
			table.insert(rowPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
		end
		table.insert(superheatTable, table.concat(rowPart))
	end
	 --Add the table end and add the table to the result string
	table.insert(superheatTable, '\r\n|}')
	return table.concat(superheatTable)
end

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

	return p._getItemSuperheatTable(item)
end

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

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

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

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

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

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

	return p._getItemSourceTables(item)
end

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

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

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

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

	table.insert(resultPart, '|}')

	return table.concat(resultPart)
end

return p