Module:Items/SourceTables: Difference between revisions

m
Add custom separator for ItemSources
(_getItemSources: Exclude loot tables of dungeon exclusive non-boss monsters)
m (Add custom separator for ItemSources)
 
(92 intermediate revisions by 9 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


local MonsterData = mw.loadData('Module:Monsters/data')
local Constants = require('Module:Constants')
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')
local Constants = mw.loadData('Module:Constants/data')
 
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 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
  }
}
}


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 result = ''
 
local tables = {}
local itemID = item.id
--First figure out what skill is used to make this...
 
local skillIDs = {
['Gathering'] = {
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Mining'] = { recipeKey = 'rockData' },
['Farming'] = { recipeKey = 'recipes' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}
 
-- Gathering skills
-- All follow a similar data structure
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
if recipe.productId == itemID then
lvl = recipe.level
xp = recipe.baseExperience
qty = recipe.baseQuantity or 1
if localSkillID == 'Farming' then
req = { recipe.seedCost }
local category = GameData.getEntityByID(skillData.categories, recipe.categoryID)
qty = 5 * category.harvestMultiplier
end
-- Action time
if recipe.baseMinInterval ~= nil then
time = recipe.baseMinInterval / 1000
if recipe.baseMaxInterval ~= nil then
maxTime = recipe.baseMaxInterval / 1000
end
elseif recipe.baseInterval ~= nil then
time = recipe.baseInterval /1000
elseif skillData.baseInterval ~= nil then
time = skillData.baseInterval / 1000
end
-- Special requirements
if recipe.totalMasteryRequired ~= nil then
specialReq = Icons.Icon({'Mastery', notext=true}) .. Shared.formatnum(recipe.totalMasteryRequired) .. ' total [[' .. skill .. ']] [[Mastery]]'
end
table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq))
-- Assumes item has a single source per skill
break
end
end
end
 
-- Artisan skills
-- Allow follow a similar data structure
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
local skillData = SkillData[localSkillID]
local skill = skillData.name
local lvl, xp, qty, req, time, maxTime = 0, 0, 0, nil, 0, nil
for i, recipe in ipairs(skillData.recipes) do
if recipe.productID == itemID or
(localSkillID == 'Cooking' and recipe.perfectCookID == itemID) or
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, itemID)) then
lvl = recipe.level
xp = recipe.baseExperience
qty = recipe.baseQuantity or 1
-- Action time
if recipe.baseMinInterval ~= nil then
time = recipe.baseMinInterval / 1000
if recipe.baseMaxInterval ~= nil then
maxTime = recipe.baseMaxInterval / 1000
end
elseif recipe.baseInterval ~= nil then
time = recipe.baseInterval /1000
elseif skillData.baseInterval ~= nil then
time = skillData.baseInterval / 1000
end
-- Special requirements
-- Potions have a mastery level requirement depending on the tier
if item.charges ~= nil and item.tier ~= nil then
local levelUnlock = GameData.getEntityByProperty(skillData.masteryLevelUnlocks, 'descriptionID', item.tier + 1)
if levelUnlock ~= nil then
specialReq = Icons._MasteryReq(item.name, levelUnlock.level, false)
end
end
-- Materials & output quantity
-- Special case for Summoning recipes
if localSkillID == 'Summoning' then
local shardCostArray, otherCostArray = {}, {}
local recipeGPCost = skillData.recipeGPCost
-- Shards
for j, itemCost in ipairs(recipe.itemCosts) do
local shard = Items.getItemByID(itemCost.id)
if shard ~= nil then
table.insert(shardCostArray, Icons.Icon({shard.name, type='item', notext=true, qty=itemCost.quantity}))
end
end
-- Other costs
if recipe.gpCost > 0 then
table.insert(otherCostArray, Icons.GP(recipe.gpCost))
end
if recipe.scCost > 0 then
table.insert(otherCostArray, Icons.SC(recipe.scCost))
end
for j, nonShardID in ipairs(recipe.nonShardItemCosts) do
local nonShard = Items.getItemByID(nonShardID)
if nonShard ~= nil then
local itemValue = math.max(nonShard.sellsFor, 20)
local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
table.insert(otherCostArray, Icons.Icon({nonShard.name, type='item', notext=true, qty=nonShardQty}))
end
end
req = table.concat(shardCostArray, ', ')
if not Shared.tableIsEmpty(otherCostArray) then
local costLen = Shared.tableCount(otherCostArray)
req = req .. '<br/>' .. (costLen == 1 and '' or 'and one of the following:<br/>') .. table.concat(otherCostArray, "<br/>'''OR''' ")
end
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


  local tables = {}
-- Alt. Magic, excludes spells which can produce a variety of items, such as Gems and Bars
  --First figure out what skill is used to make this...
-- Bars are handled by getItemSuperheatTable()
  if item.smithingLevel ~= nil then
-- Gems are handled by _getItemLootSourceTable()
    skill = 'Smithing'
for i, altSpell in ipairs(Magic.getSpellsBySpellBook('altMagic')) do
    lvl = item.smithingLevel
if altSpell.produces == item.id then
    xp = item.smithingXP
table.insert(tables, p._buildAltMagicTable(altSpell))
    req = item.smithReq
end
    qty = item.smithingQty
end
    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))
  end
  if item.runecraftingLevel ~= nil then
    skill = 'Runecrafting'
    lvl = item.runecraftingLevel
    xp = item.runecraftingXP
    req = item.runecraftReq
    qty = item.runecraftQty
    time = 2
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.fletchingLevel ~= nil then
    skill = 'Fletching'
    lvl = item.fletchingLevel
    xp = item.fletchingXP
    req = item.fletchReq
    qty = item.fletchQty
    time = 2
    if item.name == 'Arrow Shafts' then
      --Arrow Shafts get special (weird) treatment
      req = '1 of any [[Log]]'
      qty = '15 - 135'
    end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.herbloreReq ~= nil then
    skill = 'Herblore'
    req = item.herbloreReq
    --Currently using 'herbloreMasteryID' as shorthand to find details, could be a better method
    local potionID = item.herbloreMasteryID
    local potionData = SkillData.Herblore.ItemData[potionID + 1]
    lvl = potionData.herbloreLevel
    xp = potionData.herbloreXP
    time = 2
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.miningID ~= nil then
    skill = 'Mining'
    lvl = SkillData.Mining.Rocks[item.masteryID[2] + 1].level
    time = 3
    xp = item.miningXP
    --Rune Essence has double quantity, but that's a hard-coded thing in the game so it's hard-coded here
    if item.name == 'Rune Essence' then qty = 2 else qty = 1 end


    if item.name == 'Dragonite Ore' then
if Shared.tableIsEmpty(tables) then
      specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
return ''
    end
else
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
return table.concat(tables, '\r\n')
  end
end
  if item.type == "Logs" then
end
    --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
  if item.type == "Harvest" or item.type == "Herb" or item.type == "Logs" then
    --Harvest/Herb means farming
    --Logs might mean farming or might not. Depends on the logs
    for i, item2 in pairs(ItemData.Items) do
      if item2.grownItemID == item.id then
        skill = 'Farming'
        lvl = item2.farmingLevel
        xp = item2.farmingXP
        time = item2.timeToGrow
        if item.type == 'Logs' then
          qty = 35
        else
          qty = 15
        end
        req = {{id = i - 1, qty = (item2.seedsRequired ~= nil and item2.seedsRequired or 1)}}
        table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
        break
      end
    end
  end
  if item.type == "Food" or item.type == "Cooked Fish" then
    --Food/Cooked Fish is Fishing, need to figure out source item
    for i, item2 in pairs(ItemData.Items) do
      if item2.burntItemID == item.id or item2.cookedItemID == item.id then
        skill = 'Cooking'
        lvl = item2.cookingLevel
        if item2.burntItemID == item.id then
          xp = 1
        else
          xp = item2.cookingXP
        end
        time = 3
        req = {{id = i - 1, qty = 1}}
        break
      end
    end
    if skill ~= '' then
      table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
    end
  end
  --A couple special exceptions for Alt Magic
  --Not Gems or Bars though since those have their own separate thing
  if item.name == 'Rune Essence' then
    table.insert(tables, p.buildAltMagicTable('Just Learning'))
  elseif item.name == 'Bones' then
    table.insert(tables, p.buildAltMagicTable('Bone Offering')) 
  elseif item.name == 'Holy Dust' then
    table.insert(tables, p.buildAltMagicTable('Blessed Offering'))
  end


  if Shared.tableCount(tables) == 0 then
function p.getAltMagicTable(frame)
    return ""
local spellName = frame.args ~= nil and frame.args[1] or frame
  else
local spell = Magic.getSpell(spellName, 'altMagic')
    return table.concat(tables, '\r\n')
if spell == nil then
  end
return Shared.printError('Could not find Alt. Magic spell "' .. spellName .. '"')
else
return p._buildAltMagicTable(spell)
end
end
end


function p.buildAltMagicTable(spellName)
function p._buildAltMagicTable(spell)
  local spell = Magic.getSpell(spellName, 'AltMagic')
local resultPart = {}
  local result = '{|class="wikitable"\r\n|-'
local imgType = Magic._getSpellIconType(spell)
  result = result..'\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'})
table.insert(resultPart, '{|class="wikitable"\r\n|-')
  result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements'
table.insert(resultPart, '\r\n!colspan="2"|'..Icons.Icon({spell.name, type=imgType}))
  result = result..'\r\n|'..Icons._SkillReq('Magic', spell.magicLevelRequired)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Requirements')
  -- 1 means select any item. 0 would mean Superheat, but that's handled elsewhere
table.insert(resultPart, '\r\n|'..Icons._SkillReq('Magic', spell.level))
  -- -1 means no item is needed, so hide this section
  if spell.selectItem == 1 then
    result = result..'\r\n|-\r\n!style="text-align:right;"|Materials'
    result = result..'\r\n|1 of any item'
  end
  --Add runes
  result = result..'\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 result = result..', ' end
    result = result..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
    result = result.."<br/>'''OR'''<br/>"
    for i, req in pairs(spell.runesRequiredAlt) do
      local rune = Items.getItemByID(req.id)
      if i > 1 then result = result..', ' end
      result = result..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)
local costText = Magic._getAltSpellCostText(spell)
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|'..spell.convertToQty
if costText ~= nil then
  result = result..'\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;"|Materials')
  result = result..'\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s'
table.insert(resultPart, '\r\n| ' .. costText)
  result = result..'\r\n|}'
end
  return result
 
--Add runes
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Runes\r\n| ' .. Magic._getSpellRunes(spell))
 
--Now just need the output quantity, xp, and casting time (which is always 2)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|' .. spell.productionRatio)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|' .. spell.baseExperience)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s')
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
end


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


  if req ~= nil then  
if req ~= nil then
    result = result..'\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 result = result..'<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
          result = result..mat.qty..'x ?????'
table.insert(resultPart, mat.quantity..'x ?????')
        else
else
          result = result..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
    else
if gpCost ~= nil and gpCost > 0 then
    result = result..req
table.insert(resultPart, '<br/>')
    end
table.insert(resultPart, Icons.GP(gpCost))
  end
end
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Quantity'
if scCost ~= nil and scCost > 0 then
  result = result..'\r\n|'..qty
table.insert(resultPart, '<br/>')
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Experience'
table.insert(resultPart, Icons.SC(scCost))
  result = result..'\r\n|'..Shared.formatnum(xp)..' XP'
end
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Creation Time'
else
  result = result..'\r\n|'..Shared.formatnum(Shared.round(time, 2, 0))..'s'
table.insert(resultPart, req)
  if maxTime ~= nil then result = result..' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s' end
end
  result = result..'\r\n|}'
end
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Quantity')
table.insert(resultPart, '\r\n|'..qty)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Experience')
table.insert(resultPart, '\r\n|'..Shared.formatnum(xp)..' XP')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"|Base Creation Time')
table.insert(resultPart, '\r\n|'..Shared.timeString(time, true))
if maxTime ~= nil and maxTime > time then table.insert(resultPart, ' - '..Shared.timeString(maxTime, true)) end
table.insert(resultPart, '\r\n|}')


  return result
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 result = nil
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...
--First up: Can we kill somebody and take theirs?
local killStrPart = {}
for i, monster in ipairs(GameData.rawData.monsters) do
local isDrop = false
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
isDrop = true
elseif monster.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
isDrop = true
elseif monster.lootTable ~= nil then
-- If the monster has a loot table, check if the item we are looking for is in there
-- Dungeon exclusive monsters don't count as they are either:
--   - A monster before the boss, which doesn't drop anything except shards (checked above)
--  - A boss monster, whose drops are accounted for in data from Areas instead
for j, loot in ipairs(monster.lootTable) do
if loot.itemID == item.id and not Monsters._isDungeonOnlyMonster(monster) then
isDrop = true
break
end
end
end
if isDrop then
-- Item drops when the monster is killed
local iconName = monster.name
if SourceOverrides[monster.id] ~= nil then
iconName = SourceOverrides[monster.id]
end
table.insert(killStrPart, Icons.Icon({iconName, type='monster', notext=true}))
end
end
-- Is the item dropped from any dungeon?
local dungeonStrPart = {}
for i, dungeon in ipairs(GameData.rawData.dungeons) do
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
table.insert(dungeonStrPart, Icons.Icon({dungeon.name, type='dungeon', notext=true}))
elseif dungeon.eventID ~= nil then
-- Is the item dropped from a combat event (e.g. Impending Darkness event)?
local event = GameData.getEntityByID('combatEvents', dungeon.eventID)
if type(event) == 'table' and type(event.itemRewardIDs) == 'table' then
for eventCycle, itemRewardID in ipairs(event.itemRewardIDs) do
if item.id == itemRewardID then
local dungPrefix = (eventCycle == Shared.tableCount(event.itemRewardIDs) and '' or eventCycle .. (eventCycle == 1 and ' cycle' or ' cycles') .. ' of ')
table.insert(dungeonStrPart, dungPrefix .. Icons.Icon({dungeon.name, type='dungeon', notext=true}))
break
end
end
end
end
end
 
if not Shared.tableIsEmpty(dungeonStrPart) then
table.insert(lineArray, 'Completing: ' .. table.concat(dungeonStrPart, sep))
end
if not Shared.tableIsEmpty(killStrPart) then
table.insert(lineArray, 'Killing: ' .. table.concat(killStrPart, sep))
end
 
-- Can we find it in an openable item?
local lootPart = {}
for i, item2 in ipairs(GameData.rawData.items) do
if item2.dropTable ~= nil then
for j, loot in ipairs(item2.dropTable) do
if loot.itemID == item.id then
table.insert(lootPart, Icons.Icon({item2.name, type='item', notext=true}))
break
end
end
end
end
 
if not Shared.tableIsEmpty(lootPart) then
table.insert(lineArray, 'Opening: ' .. table.concat(lootPart, sep))
end
 
-- Is the item a result of upgrading/downgrading another item?
local upgradePart = { up = {}, down = {} }
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
if item.id == upgrade.upgradedItemID then
local key = (upgrade.isDowngrade and 'down' or 'up')
for j, rootItemID in ipairs(upgrade.rootItemIDs) do
local rootItem = Items.getItemByID(rootItemID)
if rootItem ~= nil then
table.insert(upgradePart[key], Icons.Icon({rootItem.name, type='item', notext=true}))
end
end
end
end
 
local upgradeCat = false
for catName, parts in pairs(upgradePart) do
if not Shared.tableIsEmpty(parts) then
if not upgradeCat then
table.insert(categoryArray, '[[Category:Upgraded Items]]')
upgradeCat = true
end
local typeText = (catName == 'up' and 'Upgrading') or 'Downgrading'
table.insert(lineArray, typeText .. ': ' .. table.concat(parts, sep))
end
end


  --Alright, time to go through all the ways you can get an item...
--Next: Can we take it from somebody else -without- killing them?
  --First up: Can we kill somebody and take theirs?
local thiefItems = Skills.getThievingSourcesForItem(item.id)
  local killStr = ''
if type(thiefItems) == 'table' then
  local dungeonStr = ''
local includedNPCs = {}
  local count1 = 0
local thiefPart = {}
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
for i, thiefRow in ipairs(thiefItems) do
    local isDrop = false
if thiefRow.npc == 'all' then
    if monster.bones == item.id and ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, "Shard")) then
--if 'all' is the npc, this is a rare item so just say 'Thieving level 1'
      isDrop = true
table.insert(lineArray, Icons._SkillReq('Thieving', 1))
    elseif monster.lootTable ~= nil then
elseif not Shared.contains(includedNPCs, thiefRow.npc) then
      for j, loot in pairs(monster.lootTable) do
table.insert(thiefPart, Icons.Icon({thiefRow.npc, type='thieving', notext=true}))
        if loot[1] == item.id then
table.insert(includedNPCs, thiefRow.npc)
          isDrop = true
end
          break
end
        end
if not Shared.tableIsEmpty(thiefPart) then
      end
table.insert(lineArray, 'Pickpocketing: ' .. table.concat(thiefPart, sep))
      if isDrop then
end
        -- 2021-05-24 Additional checks for dungeon exclusive monsters: Loot is not rolled on
end
        -- dungeon exclusive monsters unless they are the boss/last enemy of that dungeon
        local rollForLoot = false
-- Can we get this item by casting an Alt. Magic spell?
        for k, area in pairs(Areas.getMonsterAreas(i - 1)) do
local castPart = {}
          if not (area.type == 'dungeon') or (area.type == 'dungeon' and area.monsters[Shared.tableCount(area.monsters)] == i - 1) then
for i, spell in ipairs(Magic.getSpellsProducingItem(item.id)) do
            -- Either monster isn't dungeon exclusive, or is the boss/last enemy of a dungeon
table.insert(castPart, Icons.Icon({spell.name, type=Magic._getSpellIconType(spell), notext=true}))
            rollForLoot = true
end
            break
if not Shared.tableIsEmpty(castPart) then
          end
table.insert(lineArray, 'Casting: ' .. table.concat(castPart, sep))
        end
end
        isDrop = rollForLoot
      end
    end
    if isDrop then
      if monster.isBoss then
        local areaList = Areas.getMonsterAreas(i - 1)
        --If this is a boss then we actually are completing dungeons for this and need to figure out which one
        for j, dung in pairs(areaList) do
          if string.len(dungeonStr) > 0 then  
            dungeonStr = dungeonStr..','
          else
            dungeonStr = 'Completing: '
          end
          dungeonStr = dungeonStr..Icons.Icon({dung.name, type="dungeon", notext=true})
        end
      else
        count1 = count1 + 1
        if string.len(killStr) > 0 then
          killStr = killStr..','
          --if count1 % 3 == 1 and count1 > 1 then killStr = killStr..'<br/>' end
          killStr = killStr..Icons.Icon({monster.name, type="monster", notext="true"})
        else
          killStr = killStr..'Killing: '..Icons.Icon({monster.name, type="monster", notext="true"})
        end
      end
    end
  end
  -- Special exceptions for Fire/Infernal Cape and first two lore books
  if sourceOverrides['Dungeon'][item.id] ~= nil then
    if string.len(dungeonStr) > 0 then
      dungeonStr = dungeonStr .. ','
    else
      dungeonStr = 'Completing: '
    end
    dungeonStr = dungeonStr .. Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true})
  end


  if string.len(dungeonStr) > 0 then table.insert(lineArray, dungeonStr) end
--Check if we can make it ourselves
  if string.len(killStr) > 0 then table.insert(lineArray, killStr) end
local skillIDs = {
['Gathering'] = {
['Woodcutting'] = { recipeKey = 'trees' },
['Fishing'] = { recipeKey = 'fish' },
['Mining'] = { recipeKey = 'rockData' },
['Farming'] = { recipeKey = 'recipes' }
},
['Artisan'] = {
['Cooking'] = { },
['Smithing'] = { },
['Fletching'] = { },
['Crafting'] = { },
['Runecrafting'] = { },
['Herblore'] = { },
['Summoning'] = { }
}
}


  --Next: Can we find it in a box?
-- Gathering skills
  --While we're here, check for upgrades, cooking, and growing
for localSkillID, dataProp in pairs(skillIDs.Gathering) do
  local lootStr = ''
local skillData = SkillData[localSkillID]
  local upgradeStr = ''
local skill = skillData.name
  local cookStr = ''
for i, recipe in ipairs(skillData[dataProp.recipeKey]) do
  local burnStr = ''
if recipe.productId == item.id then
  local growStr = ''
if localSkillID == 'Farming' and recipe.seedCost ~= nil then
  local count2 = 0
local seedItem = Items.getItemByID(recipe.seedCost.id)
  count1 = 0
if seedItem ~= nil then
  for i, item2 in pairs(ItemData.Items) do
table.insert(lineArray, 'Growing: ' .. Icons.Icon({seedItem.name, type='item', notext='true'}))
    if item2.dropTable ~= nil then
end
      for j, loot in pairs(item2.dropTable) do
else
        if loot[1] == item.id then
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
          count1 = count1 + 1
end
          if string.len(lootStr) > 0 then
break
            lootStr = lootStr..','
end
            --if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
end
            lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
end
          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.cookedItemID == item.id then
        if string.len(cookStr) > 0 then
          cookStr = cookStr..','..Icons.Icon({item2.name, type="item", notext="true"})
        else
          table.insert(categoryArray, '[[Category:Cooked Items]]')
          cookStr = cookStr..'Cooking: '..Icons.Icon({item2.name, type="item", notext="true"})
        end
    end
    if item2.burntItemID == item.id then
        if string.len(burnStr) > 0 then
          burnStr = burnStr..','..Icons.Icon({item2.name, type="item", notext="true"})
        else
          table.insert(categoryArray, '[[Category:Burnt Items]]')
          burnStr = burnStr..'Burning: '..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
  end
  if string.len(lootStr) > 0 then table.insert(lineArray, lootStr) end
  if string.len(upgradeStr) > 0 then table.insert(lineArray, upgradeStr) end
  if string.len(cookStr) > 0 then table.insert(lineArray, cookStr) end
  if string.len(burnStr) > 0 then table.insert(lineArray, burnStr) end
  if string.len(growStr) > 0 then table.insert(lineArray, growStr) end


  --Next: Can we take it from somebody else -without- killing them?
-- Artisan skills
  local thiefStr = ''
for localSkillID, dataProp in pairs(skillIDs.Artisan) do
  for i, npc in pairs(SkillData.Thieving) do
local skillData = SkillData[localSkillID]
    if npc.lootTable ~= nil then
local skill = skillData.name
      for j, loot in pairs(npc.lootTable) do
for i, recipe in ipairs(skillData.recipes) do
        if loot[1] == item.id then
if recipe.productID == item.id or
          if string.len(thiefStr) > 0 then
(localSkillID == 'Cooking' and recipe.perfectCookID == item.id) or
            thiefStr = thiefStr..','..Icons.Icon({npc.name, type="thieving", notext="true"})
(localSkillID == 'Herblore' and Shared.contains(recipe.potionIDs, item.id)) then
          else
table.insert(lineArray, Icons._SkillReq(skill, recipe.level))
            thiefStr = thiefStr..'Pickpocketing: '..Icons.Icon({npc.name, type="thieving", notext="true"})
break
          end
end
        end
end
      end
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
-- Township trading
  --SmithCheck:
for i, tsResource in ipairs(SkillData.Township.itemConversions.fromTownship) do
  if item.smithingLevel ~= nil then
local found = false
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
for j, tradeDef in ipairs(tsResource.items) do
  end
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


  --CraftCheck:
-- Archaeology sources
  if item.craftingLevel ~= nil then
-- Digsites
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
for i, digsite in ipairs(SkillData.Archaeology.digSites) do
  end
local found = false
for artefactType, artefactItems in pairs(digsite.artefacts) do
for j, itemDef in ipairs(artefactItems) do
if itemDef.itemID == item.id then
table.insert(lineArray, Icons._SkillReq(SkillData.Archaeology.name, digsite.level))
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


  --FletchCheck:
-- Cartography
  if item.fletchingLevel ~= nil then
-- Paper
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
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


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


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


  --FishCheck:
-- Fishing
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
-- Junk
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#'..item.type..'|'..item.type..']]')
if Shared.contains(SkillData.Fishing.junkItemIDs, item.id) then
  elseif item.fishingLevel ~= nil then
table.insert(lineArray, Icons.Icon({'Fishing', type='skill', notext=true}) .. ' [[Fishing#Junk|Junk]]')
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
-- 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


  --HerbCheck:
-- Firemaking: Coal
  if item.herbloreMasteryID ~= nil then
if Shared.contains({SkillData.Firemaking.coalItemID,
    local potionData = SkillData.Herblore.ItemData[item.herbloreMasteryID + 1].herbloreLevel
SkillData.Firemaking.ashItemID,
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
SkillData.Firemaking.charcoalItemID,
  end
SkillData.Firemaking.fireSpiritItemID,
SkillData.Firemaking.diamondItemID
}, item.id) then
table.insert(lineArray, Icons._SkillReq(SkillData.Firemaking.name, 1))
end


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


  --Finally there are some weird exceptions:
-- General rare drops for non-combat skills
  --Coal can be acquired via firemaking
-- Includes items like Circlet/Jewel of Rhaelyx, Mysterious stones, Signet ring half (a),
  if item.name == "Coal Ore" then
-- relics (for Ancient Relics mode)
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
local skillIconList, subText = {}, ''
  end
for i, skillDataAll in ipairs(GameData.rawData.skillData) do
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


  --Gems can be acquired from mining, fishing, and alt. magic
-- Supplementary stuff on top of general rare drops
  if item.type == 'Gem' then
if item.id == 'melvorD:Gold_Topaz_Ring' then
    table.insert(lineArray, Icons.Icon({"Fishing", type='skill', notext=true})..' [[Fishing#Special|Special]]')
table.insert(lineArray, 'Killing any monster if not worn (Instead of '..Icons.Icon({"Signet Ring Half (b)", type="item"})..')')
    table.insert(lineArray, Icons.Icon({"Mining", type='skill', notext=true})..' [[Mining#Gems|Gem]]')
elseif item.id == 'melvorD:Signet_Ring_Half_B' then
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
table.insert(lineArray, 'Killing any monster while wearing '..Icons.Icon({'Gold Topaz Ring', type='item'}))
  end
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


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


  --Chapeau Noir & Bobby's Pocket are special Thieving items
-- Golbin Raid exclusive items
  if item.name == "Chapeau Noir" or item.name == "Bobby&apos;s Pocket" then
if item.golbinRaidExclusive then
    table.insert(lineArray, Icons._SkillReq("Thieving", 1))
table.insert(lineArray, Icons.Icon({'Golbin Raid', type='pet', img='Golden Golbin'}))
  end
end


  --Rhaelyx pieces are also special
--Shop items (including special items like gloves that aren't otherwise listed)
  if item.name == 'Jewel of Rhaelyx' then
if not Shared.tableIsEmpty(Shop.getItemSourceArray(item.id)) then
    local rhaStr = 'Any action in: '
table.insert(lineArray, Icons.Icon({'Shop'}))
    rhaStr = rhaStr..Icons.Icon({'Firemaking', type = 'skill', notext = true})..', '..Icons.Icon({'Cooking', type = 'skill', notext = true})..', '..Icons.Icon({'Smithing', type = 'skill', notext = true})..',<br/>'
end
    rhaStr = rhaStr..Icons.Icon({'Fletching', type = 'skill', notext = true})..', '..Icons.Icon({'Crafting', type = 'skill', notext = true})..', '..Icons.Icon({'Runecrafting', type = 'skill', notext = true})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Herblore', type='skill'})
    table.insert(lineArray, rhaStr)
  elseif item.name == 'Circlet of Rhaelyx' then
    local rhaStr = 'Any action in: '
    rhaStr = rhaStr..Icons.Icon({'Woodcutting', type = 'skill', notext = true})..', '..Icons.Icon({'Fishing', type = 'skill', notext = true})..', '..Icons.Icon({'Mining', type = 'skill', notext = true})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Thieving', type = 'skill', notext = true})..', '..Icons.Icon({'Farming', type = 'skill', notext = true})..', '..Icons.Icon({'Agility', type = 'skill', notext = true})
    table.insert(lineArray, rhaStr)
  elseif item.name == 'Mysterious Stone' then
    local rhaStr = 'Any action in: '
    rhaStr = rhaStr..Icons.Icon({'Firemaking', type = 'skill', notext = true})..', '..Icons.Icon({'Cooking', type = 'skill', notext = true})..', '..Icons.Icon({'Smithing', type = 'skill', notext = true})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Fletching', type = 'skill', notext = true})..', '..Icons.Icon({'Crafting', type = 'skill', notext = true})..', '..Icons.Icon({'Runecrafting', type = 'skill', notext = true})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Herblore', type='skill', notext = true})..', '..Icons.Icon({'Woodcutting', type = 'skill', notext = true})..', '..Icons.Icon({'Fishing', type = 'skill', notext = true})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Mining', type = 'skill', notext = true})..', '..Icons.Icon({'Thieving', type = 'skill', notext = true})..', '..Icons.Icon({'Farming', type = 'skill', notext = true})..', '..Icons.Icon({'Agility', type = 'skill', notext = true})
    rhaStr = rhaStr..'<br/>after finding '..Icons.Icon({'Crown of Rhaelyx', type='item'})
    table.insert(lineArray, rhaStr)
  end
  --Tokens are from the appropriate skill
  if item.isToken then
    for skill, id in pairs(Constants.skill) do
      if id == item.skill then
        table.insert(lineArray, Icons._SkillReq(skill, 1))
      end
    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 result = ''
local resultPart = {}
  if asList then
if asList then
    result = '* '..table.concat(lineArray, "\r\n* ")
table.insert(resultPart, '* '..table.concat(lineArray, "\r\n* "))
  else
else
    result = table.concat(lineArray, "<br/>")
table.insert(resultPart, '<div style="max-width:180px;text-align:right">' .. table.concat(lineArray, "<br/>") .. '</div>')
    result = '<div style="max-width:180px;text-align:right">'..result..'</div>'
end
  end
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
  if addCategories then result = result..table.concat(categoryArray, '') end
return table.concat(resultPart)
  return result
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 result = '{| class="wikitable sortable stickyHeader"'
local resultPart = {}
  result = result..'\r\n|- class="headerRow-0"'
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  result = result..'\r\n!Source!!Source Type!!Quantity!!colspan="2"|Chance'
table.insert(resultPart, '\r\n|- class="headerRow-0"')
 
table.insert(resultPart, '\r\n!Source!!Level!!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 rowTxt = '\r\n|-'
    rowTxt = rowTxt..'\r\n|style="text-align: left;"|'..source
    rowTxt = rowTxt..'\r\n|style="text-align: left;"|'..type


    rowTxt = rowTxt..'\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..minqty
--Set up function for adding rows
    if qty ~= minqty then rowTxt = rowTxt..' - '..qty end
local buildRow = function(source, level, levelNum, minqty, qty, weight, totalWeight, expIcon)
    local chance = Shared.round(weight / totalWeight * 100, 2, 2)
if minqty == nil then minqty = 1 end
    if weight >= totalWeight then
if expIcon == nil then expIcon = '' end
      -- Fraction would be 1/1, so only show the percentage
if level == nil then level = 'N/A' end
      chance = 100
local rowPart = {}
      rowTxt = rowTxt .. '\r\n|colspan="2" '
table.insert(rowPart, '\r\n|-')
    else
table.insert(rowPart, '\r\n|style="text-align: left;"|'..source)
      rowTxt = rowTxt .. '\r\n|style="text-align: right;" data-sort-value="' .. chance .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|'
-- Retrieve numeric level value for sorting, or remove anything between [[]]
    end
local levelValue = ''
    rowTxt = rowTxt..'style="text-align: right;" data-sort-value="'.. chance .. '"|'..chance..'%'
if levelNum ~= nil then
    return rowTxt
levelValue = tostring(levelNum)
  end
else
  local dropRows = {}
levelValue = level:match('%[%[.-%]%]%s*(%w+)$') or ''
end
table.insert(rowPart, '\r\n|style="text-align: left;" data-sort-value="'..levelValue..'"|'..expIcon.. level)
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="'..qty..'"|'..Shared.formatnum(minqty))
if qty ~= minqty then table.insert(rowPart, ' - '..Shared.formatnum(qty)) end
local chance = weight / totalWeight * 100
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local fmt = (chance < 0.10 and '%.2g') or '%.2f'
local chanceStr = string.format(fmt, chance)
if weight >= totalWeight then
-- Fraction would be 1/1, so only show the percentage
chanceStr = '100'
table.insert(rowPart, '\r\n|colspan="2" ')
else
local fraction = Shared.fraction(weight, totalWeight)
if Shared.contains(fraction, '%.') then
--If fraction contains decimals, something screwy happened so just show only percentage
--(happens sometimes with the rare thieving items)
table.insert(rowPart, '\r\n|colspan="2" ')
else
table.insert(rowPart, '\r\n|style="text-align: right;" data-sort-value="' .. chanceStr .. '"| ' .. Shared.fraction(weight, totalWeight) .. '\r\n|')
end
end
if weight == -1 then
--Weight of -1 means this is a weird row that has a variable percentage
table.insert(rowPart, 'style="text-align: right;" data-sort-value="0"|Varies (see Thieving page)')
else
table.insert(rowPart, 'style="text-align: right;" data-sort-value="'.. chanceStr .. '"|'..chanceStr..'%')
end
return table.concat(rowPart)
end
local dropRows = {}


  --Alright, time to go through a few ways to get the item
--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 chance = 0
if SourceOverrides[drop.id] ~= nil then
    local wt = 0
iconName = SourceOverrides[drop.id]
    local totalWt = 0
end
    --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.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
      chance = 100
    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.lootChance or 100
    if wt > 0 and lootChance > 0 then
      local sourceTxt = nil
      local typeTxt = nil
      --If we're dealing with a boss, this is a Dungeon row instead
      if monster.isBoss and not Shared.contains(item.name, 'Shard') then
        local dung = Areas.getMonsterAreas(i - 1)[1]
        sourceTxt = Icons.Icon({dung.name, type='dungeon', notext=true})
        typeTxt = '[[Dungeon]]'
      else
        sourceTxt = Icons.Icon({monster.name, type='monster'})
        typeTxt = '[[Monster]]'
      end
      table.insert(dropRows, {source = sourceTxt, type = typeTxt, minqty = minqty, qty = qty, weight = wt * lootChance, totalWeight = totalWt * 100})
    end
  end


  --Special exception for the Fire/Infernal Cape and first two lore books as bonus dungeon drops
if monster ~= nil then
  if sourceOverrides['Dungeon'][item.id] ~= nil then
local monsterLevel = Monsters._getMonsterCombatLevel(monster)
    local sourceTxt = Icons.Icon({sourceOverrides['Dungeon'][item.id], type='dungeon', notext=true})
table.insert(dropRows, {
    table.insert(dropRows, {source=sourceTxt, type='[[Dungeon]]', minqty=1, qty=1, weight = 1, totalWeight = 1})
source = Icons.Icon({iconName, type='monster'}),
  end
level = Icons.Icon({'Monsters', img='Combat', notext=true}) .. ' Level ' .. Shared.formatnum(monsterLevel),
levelNum = monsterLevel,
minqty = drop.minQty,
qty = drop.maxQty,
weight = drop.dropWt,
totalWeight = drop.totalWt,
expIcon = Icons.getExpansionIcon(drop.id)})
end
end
--Patching in here because it uses the same format
--Can we find this in an Archaeology digsite?
for i, drop in ipairs(p._getItemArchSources(item)) do
if drop.name ~= nil then
table.insert(dropRows, {
source = Icons.Icon({drop.name, type='poi'}),
level = Icons._SkillReq('Archaeology', drop.level) .. ' ('..drop.size..')',
levelNum = drop.level,
minqty = drop.minQty,  
qty = drop.maxQty,  
weight = drop.dropWt,  
totalWeight = drop.totalWt,
expIcon = Icons.getExpansionIcon(drop.id)})
end
end


  --Next: Can we find it by rummaging around in another item?
-- Is the item dropped from any dungeon?
  for i, item2 in pairs(ItemData.Items) do
for i, dungeon in ipairs(GameData.rawData.dungeons) do
    if item2.dropTable ~= nil then
if (dungeon.oneTimeRewardID ~= nil and item.id == dungeon.oneTimeRewardID) or
      local qty = 1
(type(dungeon.rewardItemIDs) == 'table' and Shared.contains(dungeon.rewardItemIDs, item.id)) then
      local chance = 0
table.insert(dropRows, {
      local wt = 0
source = Icons.Icon({dungeon.name, type='dungeon'}),
      local totalWt = 0
level = '[[Dungeon]]',
      for j, loot in pairs(item2.dropTable) do
minqty = 1,
        totalWt = totalWt + loot[2]
qty = 1,
        if loot[1] == item.id then
weight = 1,
          wt = loot[2]
totalWeight = 1,
          if item2.dropQty ~= nil then qty = item2.dropQty[j] end
expIcon = Icons.getExpansionIcon(dungeon.id)})
        end
elseif dungeon.eventID ~= nil then
      end
-- 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


      if wt > 0 then
-- Can we find it in an openable item?
        local sourceTxt = Icons.Icon({item2.name, type='item'})
for i, item2 in ipairs(GameData.rawData.items) do
        table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, weight = wt, totalWeight = totalWt})
if item2.dropTable ~= nil then
      end
local minQty, maxQty, wt, totalWt = 1, 1, 0, 0
    end
for j, loot in ipairs(item2.dropTable) do
  end
totalWt = totalWt + loot.weight
if loot.itemID == item.id then
wt = loot.weight
minQty = loot.minQuantity
maxQty = loot.maxQuantity
end
end


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


  --Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches
-- Can it be obtained from Thieving?
  if item.type == 'Gem' then
local thiefItems = Skills.getThievingSourcesForItem(item.id)
    local mineType = Icons.Icon({'Mining', type='skill'})
for i, thiefRow in ipairs(thiefItems) do
    local thisGemChance = Items.GemTable[item.name].chance
local sourceTxt = ''
    local totalGemChance = 0
if thiefRow.npc == 'all' then
    for i, gem in pairs(Items.GemTable) do
sourceTxt = 'Thieving Rare Drop'
      totalGemChance = totalGemChance + gem.chance
else
    end
sourceTxt = Icons.Icon({thiefRow.npc, type='thieving'})
    table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
end
    local magicType = Icons.Icon({'Magic', type = 'skill'})
table.insert(dropRows, {
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
source = sourceTxt,
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, weight = thisGemChance, totalWeight = totalGemChance})
level = Icons._SkillReq("Thieving", thiefRow.level),
  end
levelNum = thiefRow.level,
minqty = thiefRow.minQty,  
qty = thiefRow.maxQty,  
weight = thiefRow.wt,  
totalWeight = thiefRow.totalWt,  
expIcon = Icons.getExpansionIcon(thiefRow.npcID)})
end


  if item.fishingCatchWeight ~= nil then
-- Fishing: Junk & Specials
    local fishSource = '[[Fishing#Special|Special]]'
if Shared.contains(SkillData.Fishing.junkItems, item.id) then
    local fishType = Icons.Icon({'Fishing', type='skill'})
local fishSource = '[[Fishing#Junk|Junk]]'
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = item.fishingCatchWeight, totalWeight = Items.specialFishWt})
local fishType = Icons.Icon({'Fishing', type='skill'})
  end
local fishTotWeight = Shared.tableCount(SkillData.Fishing.JunkItems)
table.insert(dropRows, {
source = fishSource,
level = Icons._SkillReq("Fishing", 1),
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


  if item.type == 'Junk' then
-- Mining: Gems, and also Alt. Magic spells producing random gems
    local fishSource = '[[Fishing#Junk|Junk]]'
if Shared.contains({'Gem', 'Superior Gem'}, item.type) then
    local fishType = Icons.Icon({'Fishing', type='skill'})
local gemKeys = { 'randomGems', 'randomSuperiorGems' }
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, weight = 1, totalWeight = Items.junkCount})
for i, gemKey in ipairs(gemKeys) do
  end
local thisGem, totalGemWeight = nil, 0
for j, gem in ipairs(GameData.rawData[gemKey]) do
totalGemWeight = totalGemWeight + gem.weight
if gem.itemID == item.id then
thisGem = gem
end
end
if thisGem ~= nil then
local expIcon = ''
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


  --Make sure to return nothing if there are no drop sources
--Make sure to return nothing if there are no drop sources
  if Shared.tableCount(dropRows) == 0 then return '' end
if Shared.tableIsEmpty(dropRows) then return '' end
table.sort(dropRows, function(a, b)
if a.weight / a.totalWeight == b.weight / b.totalWeight then
if a.minqty + a.qty == b.minqty + b.qty then
return (a.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


  table.sort(dropRows, function(a, b) return a.weight / a.totalWeight > b.weight / b.totalWeight end)
table.insert(resultPart, '\r\n|}')
  for i, data in pairs(dropRows) do
return table.concat(resultPart)
    result = result..buildRow(data.source, data.type, data.minqty, data.qty, data.weight, data.totalWeight)
  end
 
  result = result..'\r\n|}'
  return result
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 result = ''
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
    result = '{| 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/>'))
    result = result..'\r\n|-\r\n!style="text-align:right;"|Materials\r\n|'
table.insert(resultPart, '\r\n|}')
    result = result..table.concat(materials, '<br/>')
end
    result = result..'\r\n|}'
return table.concat(resultPart)
  end
  return result
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 = '{|class="wikitable"\r\n!colspan="2"|Spell'
table.insert(oreStringPart, Icons.Icon({matItem.name, type='item', notext='true', qty=mat.quantity}))
  superheatTable = superheatTable..'!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level'
end
  superheatTable = superheatTable..'!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level'
end
  superheatTable = superheatTable..'!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP'
 
  superheatTable = superheatTable..'!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars'
--Set up the header
  superheatTable = superheatTable..'!!Ore!!Runes'
local superheatTable = {}
  --Loop through all the variants
table.insert(superheatTable, '{|class="wikitable"\r\n!colspan="2"|Spell')
  local spellNames = {'Superheat I', 'Superheat II', 'Superheat III', 'Superheat IV'}
table.insert(superheatTable, '!!'..Icons.Icon({'Smithing', type='skill', notext='true'})..' Level')
  for i, sName in pairs(spellNames) do
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level')
    local spell = Magic.getSpell(sName, 'AltMagic')
table.insert(superheatTable, '!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' XP')
    local rowTxt = '\r\n|-\r\n|'..Icons.Icon({spell.name, type='spell', notext=true, size=50})
table.insert(superheatTable, '!!'..Icons.Icon({item.name, type='item', notext='true'})..' Bars')
    rowTxt = rowTxt..'||[['..spell.name..']]||'..item.smithingLevel
table.insert(superheatTable, '!!Ore!!Runes')
    rowTxt = rowTxt..'||'..spell.magicLevelRequired..'||'..spell.magicXP..'||'..spell.convertToQty
 
    rowTxt = rowTxt..'||'..oreString
--Loop through all the variants
    if spell.ignoreCoal ~= nil and not spell.ignoreCoal then rowTxt = rowTxt..coalString end
local spells = Magic.getSpellsProducingItem(item.id)
    rowTxt = rowTxt..'||style="text-align:center"|'
for i, spell in ipairs(spells) do
    for i, req in pairs(spell.runesRequired) do
if spell.specialCost ~= nil and Shared.contains({ 'BarIngredientsWithCoal', 'BarIngredientsWithoutCoal' }, spell.specialCost.type) then
      local rune = Items.getItemByID(req.id)
local imgType = Magic._getSpellIconType(spell)
      if i > 1 then rowTxt = rowTxt..', ' end
table.insert(superheatTable, '\r\n|-\r\n|'..Icons.Icon({spell.name, type=imgType, notext=true, size=50}))
      rowTxt = rowTxt..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
table.insert(superheatTable, '||'..Icons.Icon({spell.name, type=imgType, noicon=true})..'||style="text-align:right;"|'..smithRecipe.level)
    end
table.insert(superheatTable, '||style="text-align:right;"|'..spell.level..'||style="text-align:right;"|'..spell.baseExperience)
    rowTxt = rowTxt.."<br/>'''OR'''<br/>"
table.insert(superheatTable, '||style="text-align:right;"|'..spell.productionRatio)
    for i, req in pairs(spell.runesRequiredAlt) do
table.insert(superheatTable, '|| '..table.concat(oreStringPart, ', '))
      local rune = Items.getItemByID(req.id)
if spell.specialCost.type == 'BarIngredientsWithCoal' and coalString ~= '' then
      if i > 1 then rowTxt = rowTxt..', ' end
table.insert(superheatTable, (not Shared.tableIsEmpty(oreStringPart) and ', ' or '') .. coalString)
      rowTxt = rowTxt..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
end
    end
table.insert(superheatTable, '||style="text-align:center"| ' .. Magic._getSpellRunes(spell))
    superheatTable = superheatTable..rowTxt
end
  end
end
  --Add the table end and add the table to the result string
 
  superheatTable = superheatTable..'\r\n|}'
--Add the table end and add the table to the result string
  return superheatTable
table.insert(superheatTable, '\r\n|}')
return table.concat(superheatTable)
end
end


function p.getItemSuperheatTable(frame)
function p.getItemSuperheatTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module[[Category:Pages with script errors]]"
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end
 
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)


  return p._getItemSuperheatTable(item)
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 result = ''
local resultPart = {}
  local shopTable = Shop._getItemShopTable(item)
local shopTable = Shop._getItemShopTable(item)
  if string.len(shopTable) > 0 then
if shopTable ~= '' then
    result = result..'===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 string.len(result) > 0 then result = result..'\r\n' end
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
    result = result..'===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 string.len(result) > 0 then result = result..'\r\n' end
if not Shared.tableIsEmpty(resultPart) then table.insert(resultPart, '\r\n') end
    if string.len(creationTable) == 0 then result = result..'===Creation===\r\n' end
if creationTable ~= '' then table.insert(resultPart, '===Creation===\r\n') end
    result = result..upgradeTable
table.insert(resultPart, upgradeTable)
  end
end


  if item.type == 'Bar' then
local townshipTable = p._getTownshipTraderTable(item)
    result = result..'\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 string.len(result) > 0 then result = result..'\r\n' end
end
    result = result..'===Loot===\r\n'..lootTable
 
  end
local lootTable = p._getItemLootSourceTable(item)
  return result
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 table = '{| class="wikitable"\r\n'
local resultPart = {}
  table = table..'|-\r\n'
table.insert(resultPart, '{| class="wikitable"\r\n')
  table = table..'!colspan="2"|Item\r\n! Passive\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.isPassiveItem and true or false end)
local itemArray = Items.getItems(function(item) return item.validSlots ~= nil and (item.golbinRaidExclusive == nil or not item.golbinRaidExclusive) and Shared.contains(item.validSlots, 'Passive') end)


  for i, item in Shared.skpairs(itemArray) do
for i, item in ipairs(itemArray) do
    table = table..'|-\r\n'
local passiveDesc = item.customDescription or Constants.getModifiersText(item.modifiers, false)
    table = table..'! '..Icons.Icon({item.name, type='item', notext='true'})..'\r\n! [['..item.name..']]\r\n'
table.insert(resultPart, '|-\r\n')
    table = table..'| '..item.description..'\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')
  end
table.insert(resultPart, '| '..passiveDesc..'\r\n')
    
end
   table = table..'|}'
 
table.insert(resultPart, '|}')
 
return table.concat(resultPart)
end
 
function p._getItemMonsterSources(item)
local resultArray = {}
for i, monster in ipairs(GameData.rawData.monsters) do
local chance = 0
local weight = 0
local minQty = 1
local maxQty = 1
if monster.bones ~= nil and monster.bones.itemID == item.id and Monsters._getMonsterBones(monster) ~= nil then
-- Item is a bone, and is either a shard from God dungeons or dropped by a non-boss monster with a loot table
maxQty = (monster.bones.quantity ~= nil and monster.bones.quantity) or 1
minQty = maxQty
chance = 1
weight = 1
elseif monster.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
 
function p.getItemMonsterSources(itemName)
local item = Items.getItem(itemName)
return p._getItemMonsterSources(item)
end
 
function p._getItemArchSources(item)
local check = false
local itemID = item.id
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
 
function p.getItemArchSources(itemName)
local item = Items.getItem(itemName)
return p._getItemArchSources(item)
end


  return table
--[==[
-- 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