Module:Items/SourceTables: Difference between revisions

m
Add custom separator for ItemSources
(Add override for Beginning of the End book)
m (Add custom separator for ItemSources)
 
(60 intermediate revisions by 7 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
local SourceOverrides = {
-- Currently only overrides for dungeon sources are implemented here
['melvorAoD:EarthGolem'] = 'Earth Golem (AoD)'
local sourceOverrides = {
  ['Dungeon'] = {
    [361] = 'Volcanic Cave', -- Fire Cape
    [941] = 'Infernal Stronghold', -- Infernal Cape
    [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)
function p._getCreationTable(item)
  local skill = ''
local skill = ''
  local specialReq = nil
local specialReq = nil
  local time = 0
local time = 0
  local maxTime = nil
local maxTime = nil
  local lvl = 0
local lvl = 0
  local xp = 0
local xp = 0
  local qty = nil
local qty = nil
  local req = nil
local req = nil
 
local tables = {}
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'] = { }
}
}


  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
local category = GameData.getEntityByID(skillData.categories, recipe.categoryID)
    xp = item.craftingXP
qty = 5 * category.harvestMultiplier
    req = item.craftReq
end
    qty = item.craftQty
-- Action time
    time = 3
if recipe.baseMinInterval ~= nil then
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, nil, item.craftGPCost))
time = recipe.baseMinInterval / 1000
  end
if recipe.baseMaxInterval ~= nil then
  if item.runecraftingLevel ~= nil then
maxTime = recipe.baseMaxInterval / 1000
    skill = 'Runecrafting'
end
    lvl = item.runecraftingLevel
elseif recipe.baseInterval ~= nil then
    xp = item.runecraftingXP
time = recipe.baseInterval /1000
    req = item.runecraftReq
elseif skillData.baseInterval ~= nil then
    qty = item.runecraftQty
time = skillData.baseInterval / 1000
    time = 2
end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
-- Special requirements
  end
if recipe.totalMasteryRequired ~= nil then
  if item.fletchingLevel ~= nil then
specialReq = Icons.Icon({'Mastery', notext=true}) .. Shared.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
    skill = 'Fletching'
end
    lvl = item.fletchingLevel
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq))
    xp = item.fletchingXP
-- Assumes item has a single source per skill
    req = item.fletchReq
break
    qty = item.fletchQty
end
    time = 2
end
    if item.name == 'Arrow Shafts' then
end
      --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}))
      --If this is a perfect item, need to find the original
end
      if item2.perfectItem == item.id and item2.recipeRequirements ~= nil then
end
  for j, reqSet in pairs(item2.recipeRequirements) do
-- Other costs
      skill = 'Cooking'
if recipe.gpCost > 0 then
    lvl = item2.cookingLevel
table.insert(otherCostArray, Icons.GP(recipe.gpCost))
    xp = item2.cookingXP
end
    req = reqSet
if recipe.scCost > 0 then
    qty = item2.cookingQty
table.insert(otherCostArray, Icons.SC(recipe.scCost))
    time = item2.cookingInterval / 1000
end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
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
specialReq = 'At least 1 ' .. Icons.Icon({'Summoning%23Summoning Marks', item.name, img=item.name, type='mark'}) .. ' mark discovered'
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
-- 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
  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
-- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars
    return ""
-- Bars are handled by getItemSuperheatTable()
  else
-- Gems are handled by _getItemLootSourceTable()
    return table.concat(tables, '\r\n')
for i, altSpell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
  end
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
end


function p.buildAltMagicTable(spellName)
function p.getAltMagicTable(frame)
  local spell = Magic.getSpell(spellName, 'AltMagic')
local spellName = frame.args ~= nil and frame.args[1] or frame
  local resultPart = {}
local spell = Magic.getSpell(spellName, 'altMagic')
  table.insert(resultPart, '{|class="wikitable"\r\n|-')
if spell == nil then
  table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'}))
return Shared.printError('Could not find Alt. Magic spell "' .. spellName .. '"')
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
else
  table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))
return p._buildAltMagicTable(spell)
  -- 1 means select any item. 0 would mean Superheat, but that's handled elsewhere
end
  -- -1 means no item is needed, so hide this section
end
  if spell.selectItem == 1 then
 
    table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
function p._buildAltMagicTable(spell)
    table.insert(resultPart, '\r\n|1 of any item')
local resultPart = {}
  end
local imgType = Magic._getSpellIconType(spell)
  --Add runes
table.insert(resultPart, '{|class="wikitable"\r\n|-')
  table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n|')
table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type=imgType}))
  for i, req in pairs(spell.runesRequired) do
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
    local rune = Items.getItemByID(req.id)
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))
    if i > 1 then table.insert(resultPart, ', ') end
 
    table.insert(resultPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
local costText = Magic._getAltSpellCostText(spell)
  end
if costText ~= nil then
  if spell.runesRequiredAlt ~= nil and Shared.tableCount(spell.runesRequired) ~= Shared.tableCount(spell.runesRequiredAlt) then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Materials')
    table.insert(resultPart, "<br/>'''OR'''<br/>")
table.insert(resultPart, '\r\n| ' .. costText)
    for i, req in pairs(spell.runesRequiredAlt) do
end
      local rune = Items.getItemByID(req.id)
 
      if i > 1 then table.insert(resultPart, ', ') end
--Add runes
      table.insert(resultPart, Icons.Icon({rune.name, type='item', notext=true, qty=req.qty}))
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. Magic._getSpellRunes(spell))
    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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


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


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


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


  if string.len(dungeonStr) > 0 then table.insert(lineArray, dungeonStr) end
local upgradeCat = false
  if string.len(killStr) > 0 then table.insert(lineArray, killStr) end
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, sep))
end
end


  --Next: Can we find it in a box?
--Next: Can we take it from somebody else -without- killing them?
  --While we're here, check for upgrades, originals (for perfect items), and growing
local thiefItems = Skills.getThievingSourcesForItem(item.id)
  local lootStr = ''
if type(thiefItems) == 'table' then
  local upgradeStr = ''
local includedNPCs = {}
  local cookStr = ''
local thiefPart = {}
  local growStr = ''
for i, thiefRow in ipairs(thiefItems) do
  local count2 = 0
if thiefRow.npc == 'all' then
  count1 = 0
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
  for i, item2 in pairs(ItemData.Items) do
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
    if item2.dropTable ~= nil then
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
      for j, loot in pairs(item2.dropTable) do
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
        if loot[1] == item.id then
table.insert(includedNPCs, thiefRow.npc)
          count1 = count1 + 1
end
          if string.len(lootStr) > 0 then
end
            lootStr = lootStr..','
if not Shared.tableIsEmpty(thiefPart) then
            --if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep))
            lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
end
          else
end
            lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
          end
-- Can we get this item by casting an Alt. Magic spell?
        end
local castPart = {}
      end
for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
    end
table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
    if item2.trimmedItemID == item.id then
end
          count2 = count2 + 1
if not Shared.tableIsEmpty(castPart) then
        if string.len(upgradeStr) > 0 then
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, sep))
          upgradeStr = upgradeStr..','
end
          --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?
--Check if we can make it ourselves
  local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
local skillIDs = {
  local thiefStr = ''
['Gathering'] = {
  if Shared.tableCount(thiefItems) > 0 then
['Woodcutting'] = { recipeKey = 'trees' },
  thiefStr = 'Pickpocketing: '
['Fishing'] = { recipeKey = 'fish' },
  for i, thiefRow in pairs(thiefItems) do
['Mining'] = { recipeKey = 'rockData' },
  if thiefRow.npc == 'all' then
['Farming'] = { recipeKey = 'recipes' }
  --if 'any' is the npc, this is a rare item so just say 'Thieving level 1'
},
  thiefStr = Icons._SkillReq('Thieving', 1)
['Artisan'] = {
  else
['Cooking'] = { },
  if i > 1 then thiefStr = thiefStr..', ' end
['Smithing'] = { },
  thiefStr = thiefStr..Icons.Icon({thiefRow.npc, type='thieving', notext='true'})
['Fletching'] = { },
  end
['Crafting'] = { },
  end
['Runecrafting'] = { },
  end
['Herblore'] = { },
  if string.len(thiefStr) > 0 then table.insert(lineArray, thiefStr) end
['Summoning'] = { }
}
}


  --If all else fails, I guess we should check if we can make it ourselves
-- Gathering skills
  --AstrologyCheck
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
  --(Just a brute force for now because only two items and I'm lazy)
local skillData = SkillData[localSkillID]
  if item.name == 'Stardust' or item.name == 'Golden Stardust' then
local skill = skillData.name
  table.insert(lineArray, Icons.Icon({"Astrology", type="skill"}))
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
  end
if recipe.productId == item.id then
 
if localSkillID == 'Farming' and recipe.seedCost ~= nil then
  --SmithCheck:
local seedItem = Items.getItemByID(recipe.seedCost.id)
  if item.smithingLevel ~= nil then
if seedItem ~= nil then
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
  end
end
else
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
end
break
end
end
end


  --CraftCheck:
-- Artisan skills
  if item.craftingLevel ~= nil then
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
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


  --FletchCheck:
-- Township trading
  if item.fletchingLevel ~= nil then
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
local found = false
  end
for j, tradeDef in ipairs(tsResource.items) do
if tradeDef.itemID == item.id then
found = true
local levelReq = nil
if tradeDef.unlockRequirements ~= nil then
for k, req in ipairs(tradeDef.unlockRequirements) do
if req.type == 'SkillLevel' and req.skillID == 'melvorD:Township' then
levelReq = req.level
break
end
end
if levelReq == nil then
table.insert(lineArray, Icons.Icon({SkillData.Township.name, type='skill'}))
else
table.insert(lineArray, Icons._SkillReq(SkillData.Township.name, levelReq))
end
end
end
if found then
break
end
end
if found then
break
end
end


  --RunecraftCheck:
-- Archaeology sources
  if item.runecraftingLevel ~= nil then
-- Digsites
    table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
for i, digsite in ipairs(SkillData.Archaeology.digSites) do
  end
local found = false
 
for artefactType, artefactItems in pairs(digsite.artefacts) do
  --CookCheck
for j, itemDef in ipairs(artefactItems) do
  if item.cookingLevel ~= nil then
if itemDef.itemID == item.id then
  table.insert(lineArray, Icons._SkillReq('Cooking', item.cookingLevel))
table.insert(lineArray, Icons._SkillReq(SkillData.Archaeology.name, digsite.level))
  end
found = true
break
end
end
if found then
break
end
end
if found then
break
end
end
-- Museum rewards
for i, museumReward in ipairs(SkillData.Archaeology.museumRewards) do
if type(museumReward.items) == 'table' and Shared.contains(museumReward.items, item.id) then
table.insert(lineArray, Icons.Icon('Museum'))
break
end
end


  --MineCheck:
-- Cartography
  if item.masteryID ~= nil and item.masteryID[1] == 4 then
-- Paper
    table.insert(lineArray, Icons._SkillReq("Mining", SkillData.Mining.Rocks[item.masteryID[2] + 1].levelRequired))
for i, recipe in ipairs(SkillData.Cartography.paperRecipes) do
  end
if recipe.productId == item.id then
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
break
end
end
-- POI discovery rewards
for i, worldMap in ipairs(SkillData.Cartography.worldMaps) do
local found = false
for j, poi in ipairs(worldMap.pointsOfInterest) do
if type(poi.discoveryRewards) == 'table' and type(poi.discoveryRewards.items) == 'table' then
for k, itemDef in ipairs(poi.discoveryRewards.items) do
if itemDef.id == item.id then
-- Find level for POI hex
local level = 1
local poiHex = nil
local skillID = SkillData.Cartography.skillID
for m, hex in ipairs(worldMap.hexes) do
if hex.coordinates.q == poi.coords.q and hex.coordinates.r == poi.coords.r then
for n, req in ipairs(hex.requirements) do
if req.type == 'SkillLevel' and req.skillID == skillID then
level = req.level
break
end
end
break
end
end
table.insert(lineArray, Icons._SkillReq(SkillData.Cartography.name, level))
found = true
break
end
end
if found then
break
end
end
end
if found then
break
end
end
-- Travel events
for i, event in ipairs(SkillData.Cartography.travelEvents) do
local found = false
if type(event.rewards) == 'table' and type(event.rewards.items) == 'table' then
for j, itemDef in ipairs(event.rewards.items) do
if itemDef.id == item.id and itemDef.quantity > 0 then
table.insert(lineArray, Icons.Icon({SkillData.Cartography.name, type='skill'}))
found = true
break
end
end
if found then
break
end
end
end


  --FishCheck:
--AstrologyCheck (Just a brute force for now because only two items)
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
if Shared.contains({SkillData.Astrology.stardustItemID, SkillData.Astrology.goldenStardustItemID}, item.id) then
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
table.insert(lineArray, Icons.Icon({SkillData.Astrology.name, type='skill'}))
  elseif item.fishingLevel ~= nil then
end
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
  end


  --HerbCheck:
-- Woodcutting
  if item.masteryID ~= nil and item.masteryID[1] == 19 then
-- Raven Nest
    local potionData = SkillData.Herblore.ItemData[item.masteryID[2] + 1].level
if item.id == SkillData.Woodcutting.ravenNestItemID then
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
local levelReq = nil
  end
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


  --WoodcuttingCheck:
-- Fishing
  if item.type == 'Logs' then
-- Junk
    local treeData = SkillData.Woodcutting.Trees[item.id + 1]
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
    local lvl = treeData.level
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
    table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
-- Specials
  end
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


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


  --Finally there are some weird exceptions:
-- Mining: Gems
  --Coal can be acquired via firemaking
if (GameData.getEntityByProperty('randomGems', 'itemID', item.id) ~= nil or
  if item.name == "Coal Ore" then
GameData.getEntityByProperty('randomSuperiorGems', 'itemID', item.id) ~= nil) then
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
  end
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


  --Gems can be acquired from mining, fishing, and alt. magic
-- General rare drops for non-combat skills
  if item.type == 'Gem' and item.name ~= 'Jadestone' then
-- Includes items like Circlet/Jewel of Rhaelyx, Mysterious stones, Signet ring half (a),
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
-- relics (for Ancient Relics mode)
    table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
local skillIconList, subText = {}, ''
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
for i, skillDataAll in ipairs(GameData.rawData.skillData) do
  end
local skillData = skillDataAll.data
local skillName, displaySkillName = skillData.name, false
-- All general rare drops within the Magic are for Alt. Magic
if skillDataAll.skillID == 'melvorD:Magic' then
skillName, displaySkillName = 'Alt. Magic', true
end
if type(skillData.rareDrops) == 'table' then
for j, rareDrop in ipairs(skillData.rareDrops) do
local isAltItem = (rareDrop.altItemID ~= nil and rareDrop.altItemID == item.id)
if isAltItem or rareDrop.itemID == item.id then
if Shared.tableIsEmpty(skillIconList) then
-- Initialize subText
if isAltItem then
local wornItem = Items.getItemByID(rareDrop.itemID)
subText = ' while wearing ' .. Icons.Icon({wornItem.name, type='item'})
elseif rareDrop.altItemID ~= nil then
-- There exists an alt item, but we are not searching for it
local altItem = Items.getItemByID(rareDrop.altItemID)
subText = ' if not worn (Instead of ' .. Icons.Icon({altItem.name, type='item'}) .. ')'
elseif rareDrop.itemID == 'melvorD:Mysterious_Stone' then
local foundItem = Items.getItemByID('melvorD:Crown_of_Rhaelyx')
subText = '<br/>after finding ' .. Icons.Icon({foundItem.name, type='item'})
end
if type(rareDrop.gamemodes) == 'table' then
local gamemodeText = {}
for k, gamemodeID in ipairs(rareDrop.gamemodes) do
local gamemode = GameData.getEntityByID('gamemodes', gamemodeID)
if gamemode ~= nil then
table.insert(gamemodeText, gamemode.name)
end
end
if not Shared.tableIsEmpty(gamemodeText) then
subText = subText .. ' (' .. table.concat(gamemodeText, ', ') .. ' only)'
end
end
end
local skillText = Icons.Icon({skillName, type='skill', notext=true})
if displaySkillName then
skillText = skillText .. ' (' .. Icons.Icon({skillName, type='skill', noicon=true}) .. ')'
end
table.insert(skillIconList, skillText)
end
end
end
end
if not Shared.tableIsEmpty(skillIconList) then
table.insert(lineArray, 'Any action in: ' .. table.concat(skillIconList, ', ') .. subText)
skillIconList, subText = {}, ''
end


  --Bars and some other stuff can also be acquired via Alt. Magic
-- Supplementary stuff on top of general rare drops
  if item.type == 'Bar' or Shared.contains(Items.AltMagicProducts, item.name) then
if item.id == 'melvorD:Gold_Topaz_Ring' then
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
  end
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


  --Rhaelyx pieces are also special
--Tokens are from the appropriate skill
  if Shared.contains({'Circlet of Rhaelyx', 'Jewel of Rhaelyx', 'Mysterious Stone'}, item.name) then
if item.modifiers ~= nil and item.modifiers.masteryToken ~= nil then
    local rhaSkills = {
for localSkillID, skillData in pairs(SkillData) do
      Circlet = {'Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'},
if skillData.masteryTokenID ~= nil and skillData.masteryTokenID == item.id then
      Jewel = {'Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'}
table.insert(lineArray, Icons._SkillReq(skillData.name, 1))
    }
break
    local rhaSkList = {}
end
    if item.name == 'Circlet of Rhaelyx' then
end
      rhaSkList = rhaSkills.Circlet
end
    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 = {}
-- Golbin Raid exclusive items
    for i, skillName in ipairs(rhaSkList) do
if item.golbinRaidExclusive then
      table.insert(rhaStrPart, Icons.Icon({skillName, type='skill', notext=true}))
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
    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
--Shop items (including special items like gloves that aren't otherwise listed)
  if item.isToken and item.skill ~= nil then
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
    table.insert(lineArray, Icons._SkillReq(Constants.getSkillName(item.skill), 1))
table.insert(lineArray, Icons.Icon({'Shop'}))
  end
end


  --Shop items (including special items like gloves that aren't otherwise listed)
--Easter Eggs (manual list 'cause don't have a better way to do that)
  local shopSources = Shop.getItemSourceArray(item.id)
if Shared.contains(Items.EasterEggs, item.name) then
  if Shared.tableCount(shopSources) > 0 then
table.insert(lineArray, '[[Easter Eggs]]')
    table.insert(lineArray, Icons.Icon({'Shop'}))
end
  end
-- Event exclusive items (also a manual list)
if Shared.contains(Items.EventItems, item.name) then
table.insert(lineArray, '[[Events]]')
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
table.insert(lineArray, Icons.Icon({'Tasks', type='township'}))
break
end
end
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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  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!!Level!!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, level, levelNum, 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|-')
if level == nil then level = 'N/A' end
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
local rowPart = {}
    table.insert(rowPart, '\r\n|style="text-align: left;"|'..type)
table.insert(rowPart, '\r\n|-')
 
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
    table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty)
-- Retrieve numeric level value for sorting, or remove anything between [[]]
    if qty ~= minqty then table.insert(rowPart, ' - '..qty) end
local levelValue = ''
    local chance = Shared.round(weight / totalWeight * 100, 2, 2)
if levelNum ~= nil then
    if weight >= totalWeight then
levelValue = tostring(levelNum)
      -- Fraction would be 1/1, so only show the percentage
else
      chance = 100
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or ''
      table.insert(rowPart, '\r\n|colspan="2" ')
end
    else
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon.. level)
local fraction = Shared.fraction(weight, totalWeight)
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..Shared.formatnum(minqty))
if Shared.contains(fraction, '%.') then
if qty ~= minqty then table.insert(rowPart, ' - '..Shared.formatnum(qty)) end
--If fraction contains decimals, something screwy happened so just show only percentage
local chance = weight / totalWeight * 100
--(happens sometimes with the rare thieving items)
-- 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" ')
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 pairs(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
local iconName = monster.name
    local wt = 0
if SourceOverrides[drop.id] ~= nil then
    local totalWt = 0
iconName = SourceOverrides[drop.id]
    --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.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, 'Shard')) and monster.bones == item.id then
      qty = monster.boneQty ~= nil and monster.boneQty or 1
      minqty = qty
      wt = 1
      totalWt = 1
    elseif monster.lootTable ~= nil then
      for j, loot in pairs(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 and not Shared.contains(item.name, 'Shard') and Monsters.isDungeonOnlyMonster({args={monster.name}}) then
if monster ~= nil then
      -- 2021-05-24 Additional checks for dungeon exclusive monsters: Loot is not rolled on
local monsterLevel = Monsters._getMonsterCombatLevel(monster)
      -- dungeon exclusive monsters unless they are the boss/last enemy of that dungeon
table.insert(dropRows, {
      local rollForLoot = false
source = Icons.Icon({iconName, type='monster'}),
      for k, area in pairs(Areas.getMonsterAreas(i - 1)) do
level = Icons.Icon({'Monsters', img='Combat', notext=true}) .. ' Level ' .. Shared.formatnum(monsterLevel),
        if area.type ~= 'dungeon' or (area.type == 'dungeon' and area.monsters[Shared.tableCount(area.monsters)] == i - 1) then
levelNum = monsterLevel,
          -- Either monster isn't dungeon exclusive, or is the boss/last enemy of a dungeon
minqty = drop.minQty,
          rollForLoot = true
qty = drop.maxQty,  
          break
weight = drop.dropWt,
        end
totalWeight = drop.totalWt,
      end
expIcon = Icons.getExpansionIcon(drop.id)})
      if not rollForLoot then
end
        wt = 0
end
      end
    end
--Patching in here because it uses the same format
    if wt > 0 and lootChance > 0 then
--Can we find this in an Archaeology digsite?
      local sourceTxt = nil
for i, drop in ipairs(p._getItemArchSources(item)) do
      local typeTxt = nil
if drop.name ~= nil then
      --If we're dealing with a boss, this is a Dungeon row instead
table.insert(dropRows, {
      if monster.isBoss and not Shared.contains(item.name, 'Shard') then
source = Icons.Icon({drop.name, type='poi'}),
        local dung = Areas.getMonsterAreas(i - 1)[1]
level = Icons._SkillReq('Archaeology', drop.level) .. ' ('..drop.size..')',
        sourceTxt = Icons.Icon({dung.name, type='dungeon'})
levelNum = drop.level,
        typeTxt = '[[Dungeon]]'
minqty = drop.minQty,  
      else
qty = drop.maxQty,  
        sourceTxt = Icons.Icon({monster.name, type='monster'})
weight = drop.dropWt,  
        typeTxt = '[[Monster]]'
totalWeight = drop.totalWt,
      end
expIcon = Icons.getExpansionIcon(drop.id)})
      table.insert(dropRows, {source = sourceTxt, type = typeTxt, minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
end
    end
end
  end


  -- Is the item dropped from a cycle of the Impending Darkness event?
-- Is the item dropped from any dungeon?
  for i, eventItemID in ipairs(Areas.eventData.rewards) do
for i, dungeon in ipairs(GameData.rawData.dungeons) do
  if item.id == eventItemID then
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
    sourceTxt = Icons.Icon({'Impending Darkness Event', type='dungeon'}) .. (i == Shared.tableCount(Areas.eventData.rewards) and '' or ', Cycle ' .. i)
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
    table.insert(dropRows, {source = sourceTxt, type = '[[Dungeon]]', minqty = 1, qty = 1, weight = 1, totalWeight = 1})
table.insert(dropRows, {
    break
source = Icons.Icon({dungeon.name, type='dungeon'}),
  end
level = '[[Dungeon]]',
  end
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,  
level = '[[Dungeon]]',
minqty = 1,  
qty = 1,  
weight = 1,  
totalWeight = 1})
break
end
end
end
end
end


  --Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops
-- Can we find it in an openable item?
  if sourceOverrides['Dungeon'][item.id] ~= nil then
for i, item2 in ipairs(GameData.rawData.items) do
    local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon'})
if item2.dropTable ~= nil then
    table.insert(dropRows, {source=sourceTxt, type='[[Dungeon]]', minqty=1, qty=1, weight = 1, totalWeight = 1})
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
  end
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


  --Next: Can we find it by rummaging around in another item?
if wt > 0 then
  for i, item2 in pairs(ItemData.Items) do
local sourceTxt = Icons.Icon({item2.name, type='item'})
    if item2.dropTable ~= nil then
table.insert(dropRows, {
      local qty = 1
source = sourceTxt,
      local wt = 0
level = '[[Chest]]',
      local totalWt = 0
minqty = minQty,
      for j, loot in pairs(item2.dropTable) do
qty = maxQty,
        totalWt = totalWt + loot[2]
weight = wt,
        if loot[1] == item.id then
totalWeight = totalWt,
          wt = loot[2]
expIcon = Icons.getExpansionIcon(item2.id)})
          if item2.dropQty ~= nil then qty = item2.dropQty[j] end
end
        end
end
      end
end


      if wt > 0 then
-- Can it be obtained from Thieving?
        local sourceTxt = Icons.Icon({item2.name, type='item'})
local thiefItems = Skills.getThievingSourcesForItem(item.id)
        table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt})
for i, thiefRow in ipairs(thiefItems) do
      end
local sourceTxt = ''
    end
if thiefRow.npc == 'all' then
  end
sourceTxt = 'Thieving Rare Drop'
else
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
end
table.insert(dropRows, {
source = sourceTxt,  
level = Icons._SkillReq("Thieving", thiefRow.level),
levelNum = thiefRow.level,
minqty = thiefRow.minQty,  
qty = thiefRow.maxQty,  
weight = thiefRow.wt,  
totalWeight = thiefRow.totalWt,
expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
end


  --Finally, let's try just stealing it
-- Fishing: Junk & Specials
  local thiefType = Icons.Icon({"Thieving", type='skill'})
if Shared.contains(SkillData.Fishing.junkItems, item.id) then
  local thiefItems = GatheringSkills.getThievingSourcesForItem(item.id)
local fishSource = '[[Fishing#Junk|Junk]]'
  for i, thiefRow in pairs(thiefItems) do
local fishType = Icons.Icon({'Fishing', type='skill'})
  local sourceTxt = ''
local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems)
    if thiefRow.npc == 'all' then
table.insert(dropRows, {
    sourceTxt = "Thieving Rare Drop"
source = fishSource,
    else
level = Icons._SkillReq("Fishing", 1),
    sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
levelNum = 1,
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,
level = Icons._SkillReq("Fishing", 1),
levelNum = 1,
minqty = fishItem.minQuantity,
qty = fishItem.maxQuantity,
weight = fishItem.weight,
totalWeight = fishTotWeight})
end
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
-- Mining: Gems, and also Alt. Magic spells producing random gems
  --Jadestone is special and doesn't count
if Shared.contains({'Gem', 'Superior Gem'}, item.type) then
  if item.type == 'Gem' and item.name ~= 'Jadestone' then
local gemKeys = { 'randomGems', 'randomSuperiorGems' }
    local mineType = Icons.Icon({'Mining', type='skill'})
for i, gemKey in ipairs(gemKeys) do
    local thisGemChance = Items.GemTable[item.name].chance
local thisGem, totalGemWeight = nil, 0
    local totalGemChance = 0
for j, gem in ipairs(GameData.rawData[gemKey]) do
    for i, gem in pairs(Items.GemTable) do
totalGemWeight = totalGemWeight + gem.weight
      totalGemChance = totalGemChance + gem.chance
if gem.itemID == item.id then
    end
thisGem = gem
    table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
end
    local magicType = Icons.Icon({'Magic', type = 'skill'})
end
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
if thisGem ~= nil then
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
local expIcon = ''
  end
local sourceTxt
local lv = nil
if item.type == 'Superior Gem' then
expIcon = Icons.TotH()
sourceTxt = '[[Mining#Superior Gems|Superior Gem]]'
-- Superior gems can only be found with Mining 100 or above
lv = 100
else
sourceTxt = '[[Mining#Gems|Gem]]'
-- Gems can only be found with any Mining level
lv = 1
end
table.insert(dropRows, {
source = sourceTxt,
level = Icons._SkillReq('Mining', lv),
levelNum = lv,
minqty = thisGem.minQuantity,  
qty = thisGem.maxQuantity,  
weight = thisGem.weight,  
totalWeight = totalGemWeight,
expIcon = expIcon})
-- Check for Alt. Magic spells also
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)}),  
level = Icons.Icon({'Alternative Magic', type='skill', img='Magic', notext=true}) .. ' Level ' .. spell.level,
levelNum = spell.level,
minqty = thisGem.minQuantity,  
qty = thisGem.maxQuantity,
weight = thisGem.weight,  
totalWeight = totalGemWeight,
expIcon = Icons.getExpansionIcon(spell.id)})
end
end
end
end
end


  if item.fishingCatchWeight ~= nil then
--Make sure to return nothing if there are no drop sources
    local fishSource = '[[Fishing#Special|Special]]'
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 = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
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
return (a.level == b.level and a.source < b.source) or a.level < b.level
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.level, data.levelNum, data.minqty, data.qty, data.weight, data.totalWeight, data.expIcon))
end


  if item.type == 'Junk' then
table.insert(resultPart, '\r\n|}')
    local fishSource = '[[Fishing#Junk|Junk]]'
return table.concat(resultPart)
    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
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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


  return p._getItemUpgradeTable(item)
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
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 Shared.printError('The item "' .. item.name .. '" cannot be superheated')
    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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


  return p._getItemSuperheatTable(item)
return p._getItemSuperheatTable(item)
end
 
function p._getTownshipTraderTable(item)
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
for j, tradeDef in ipairs(tsResource.items) do
if tradeDef.itemID == item.id then
-- Item found, build table
local res = GameData.getEntityByID(SkillData.Township.resources, tsResource.resourceID)
local resName = (res ~= nil and res.name) or 'Unknown'
local resQty = math.max(item.sellsFor, 2)
 
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\n|-')
table.insert(resultPart, '\n!colspan="2"| ' .. Icons.Icon({'Township', 'Trader', type='skill'}))
table.insert(resultPart, '\n|-\n!style="text-align:right;"| Cost')
table.insert(resultPart, '\n| ' .. Icons.Icon({resName, qty=resQty, type='resource'}))
table.insert(resultPart, '\n|-\n!style="text-align:right;| Requirements')
table.insert(resultPart, '\n| ' .. Shop.getRequirementString(tradeDef.unlockRequirements))
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
end
end
end
return ''
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
local townshipTable = p._getTownshipTraderTable(item)
    table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
if townshipTable ~= '' then
  end
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\n') end
table.insert(resultPart, '===Township===\n' .. townshipTable)
end


  local lootTable = p._getItemLootSourceTable(item)
if p._getSuperheatSmithRecipe(item) ~= nil then
  if string.len(lootTable) > 0 then
table.insert(resultPart, '\r\n==='..Icons.Icon({'Alt. Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item))
    if #resultPart > 0 then table.insert(resultPart, '\r\n') end
end
    table.insert(resultPart, '===Loot===\r\n'..lootTable)
 
  end
local lootTable = p._getItemLootSourceTable(item)
  return table.concat(resultPart)
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
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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  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 (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, '|}')


  local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and Shared.contains(item.validSlots, 'Passive') end)
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.barrierPercent ~= nil and 'melvorAoD:Barrier_Dust' == item.id and not Monsters._isDungeonOnlyMonster(monster) then
-- Item is Barrier Dust and is not a dungeon exclusive monster
maxQty = math.max(math.floor(Monsters._getMonsterStat(monster, 'Barrier') / 10 / 20), 1)
minQty = maxQty
chance = 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


  table.sort(itemArray, function(a, b) return a.id < b.id end)
function p.getItemMonsterSources(itemName)
local item = Items.getItem(itemName)
return p._getItemMonsterSources(item)
end


  for i, item in Shared.skpairs(itemArray) do
function p._getItemArchSources(item)
    table.insert(resultPart, '|-\r\n')
local check = false
    table.insert(resultPart, '! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! '..Icons.Icon({item.name, type='item', noicon=true})..'\r\n')
local itemID = item.id
    table.insert(resultPart, '| '..item.description..'\r\n')
  end
local resultArray = {}
for i, digSite in pairs(SkillData.Archaeology.digSites) do
for sizeName, size in pairs(digSite.artefacts) do
local found = nil
local sizeWeight = 0
for k, artefact in pairs(size) do
sizeWeight = sizeWeight + artefact.weight
if artefact.itemID == itemID then
found = artefact
end
end
if found ~= nil then
local min = found.minQuantity
local max = found.maxQuantity
table.insert(resultArray, {
id = digSite.id,
name = digSite.name,  
level = digSite.level,
size = sizeName,  
minQty = min,  
maxQty = max,
dropWt = found.weight,  
totalWt = sizeWeight})
end
end
end
return resultArray
end


  table.insert(resultPart, '|}')
function p.getItemArchSources(itemName)
local item = Items.getItem(itemName)
return p._getItemArchSources(item)
end


  return table.concat(resultPart)
--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
local checkItems = {
"Circlet of Rhaelyx",
"Jewel of Rhaelyx",
"Signet Ring Half (a)",
"Signet Ring Half (b)",
"Gold Topaz Ring",
"Astrology Lesser Relic",
"Mysterious Stone",
"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",
"Mastery Token (Cooking)",
"Gem Gloves",
"Thief's Moneysack",
"Golden Stardust",
"Golden Star",
"Slayer Deterer",
"Paper",
"Lemon",
"Aranite Brush",
"Barrier Dust"
}
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
915

edits