Module:Items: Difference between revisions

From Melvor Idle
(Tweaks to skillcape table)
(Add gem link to gem equipment slot)
(134 intermediate revisions by 8 users not shown)
Line 1: Line 1:
--This module contains all sorts of functions for getting data on items
--Several functions related to use tables can be found at Module:Items/UseTables
--Functions related to source tables can be found at Module:Items/SourceTables
--Other functions moved to Module:Items/ComparisonTables
local p = {}
local p = {}


local MonsterData = mw.loadData('Module:Monsters/data')
local GameData = require('Module:GameData')
local ItemData = mw.loadData('Module:Items/data')
local Constants = require('Module:Constants')
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 Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')


local EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon'}
p.EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon', 'Easter Egg',
local OtherShopItems = {'Cooking Gloves', 'Mining Gloves', 'Gem Gloves', 'Smithing Gloves', 'Thieving Gloves'}
'Abnormal Log', 'Red Herring', 'Cool Glasses'}
--This is hardcoded, so there's no easy way to scrape it. Hopefully it doesn't change
p.EventItems = {'Christmas Cracker', 'Christmas Coal', 'Christmas Sweater',
local GemTable = {["Topaz"] = {name = 'Topaz', id = 128, chance = 50},  
'Christmas Wreath', 'Candy Cane', 'Santa Hat',
                  ["Sapphire"] = {name = "Sapphire", id = 129, chance = 17.5},  
'Friendship Bracelet', 'Event Clue 1', 'Event Clue 2',
                  ["Ruby"] = {name = "Ruby", id = 130, chance = 17.5},  
'Event Clue 3', 'Event Clue 4', 'Candle', 'Cake Base',
                  ["Emerald"] = {name = "Emerald", id = 131, chance = 10},  
'Magical Flavouring', 'Magical Icing', 'Birthday Cake',
                  ["Diamond"] = {name = "Diamond", id = 132, chance = 5}}
'Purple Party Hat', 'Birthday Token', 'Christmas Present (Yellow)',
--The base chance to receive a gem while mining
'Christmas Present (Blue)', 'Christmas Present (Green)', 'Christmas Present (White)',
local GemChance = .01
'Christmas Present (Purple)', 'Christmas Present (Standard)', 'Event Token - Holiday 2021',
--The number of different fishing junk items
'Holiday Scarf', 'Gingerbread House', 'Gingerbread Man', 'Edible Candy Cane',
local junkCount = 8
'Locked Chest', 'Locked Chest Key', 'Event Token (Holiday 2021)'}
--Items (aside from bars & gems) which can be created via Alt Magic
local AltMagicProducts = {'Rune Essence', 'Bones', 'Holy Dust'}
--The kinds of gloves with cost & charges
local GloveTable = {['Cooking Gloves'] = {cost=50000, charges=500},
                    ['Mining Gloves'] = {cost=75000, charges=500},
                    ['Smithing Gloves'] = {cost=100000, charges=500},
                    ['Thieving Gloves'] = {cost=100000, charges=500},
                    ['Gem Gloves'] = {cost=500000, charges=2000}}
 
 
local specialFishWt = 6722
local specialFishLoot = {{128, 2000}, {129, 1600}, {130, 1400}, {131, 1000}, {132, 400}, {667, 10}, {668, 10}, {902, 1}, {670, 1}, {669, 50}, {120, 250}}
 
function p.buildSpecialFishingTable()
  --This shouldn't ever be included in a page
  --This is for generating the above 'specialFishLoot' variable if it ever needs to change
  --To re-run, edit the module, type in "console.log(p.buildSpecialFishingTable())" and copy+paste the result as the new value of the variable
  --Also gives you the total fishing weight for saving time later
  local lootArray = {}
  local totalWt = 0
 
  for i, item in pairs(ItemData.Items) do
    if item.fishingCatchWeight ~= nil then
      totalWt = totalWt + item.fishingCatchWeight
      table.insert(lootArray, '{'..(i - 1)..', '..item.fishingCatchWeight..'}')
    end
  end
 
  local result = 'local specialFishWt = '..totalWt..'\r\n'
  result = result..'local specialFishLoot = {'..table.concat(lootArray, ', ')..'}'
  return result
end
 
function p.getSpecialAttackByID(ID)
  local result = Shared.clone(ItemData.SpecialAttacks[ID + 1])
  if result ~= nil then
    result.id = ID
  end
  return result
end


function p.getItemByID(ID)
function p.getItemByID(ID)
  local result = Shared.clone(ItemData.Items[ID + 1])
return GameData.getEntityByID('items', ID)
  if result ~= nil then
    result.id = ID
  end
  return result
end
end


function p.getItem(name)
function p.getItem(name)
  local result = nil
name = string.gsub(name, "%%27", "'")
  name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "'", "'")
  name = string.gsub(name, "'", "'")
return GameData.getEntityByName('items', name)
  for i, item in pairs(ItemData.Items) do
    if(item.name == name) then
      result = Shared.clone(item)
      --Make sure every item has an id, and account for Lua being 1-index
      result.id = i -1
      break
    end
  end
  return result
end
end


function p._getItemStat(item, StatName, ZeroIfNil)
function p.getItems(checkFunc)
  local result = item[StatName]
return GameData.getEntities('items', checkFunc)
  --Special Overrides:
  if StatName == 'stabAttackBonus' then
    if item.attackBonus == nil then
      result = nil
    else
      result = item.attackBonus[1]
    end
  elseif StatName == 'slashAttackBonus' then
    if item.attackBonus == nil then
      result = nil
    else
      result = item.attackBonus[2]
    end
  elseif StatName == 'blockAttackBonus' then
    if item.attackBonus == nil then
      result = nil
    else
      result = item.attackBonus[3]
    end
  elseif StatName == 'attackType' then
    result = p._getWeaponAttackType(item)
  elseif StatName == 'description' then
    result = item.description
    if result == nil or result == '' then result = 'No Description' end
  end
  if result == nil and ZeroIfNil then result = 0 end
  return result
end
end


function p.getItemStat(frame)
function p._canItemUseSlot(item, equipSlot)
  local args = frame.args ~= nil and frame.args or frame
--Function to easily check if an item can fit in a given equipment slot
  local ItemName = args[1]
--Ex: p._canItemUseSlot({Bronze Platebody}, 'Platebody') returns true
  local StatName = args[2]
if type(item) == 'string' then
  local ZeroIfNil = args.ForceZero ~= nil and args.ForceZero ~= '' and args.ForceZero ~= 'false'
item = p.getItem(item)
  local formatNum = args.formatNum ~= nil and args.formatNum ~= '' and args.formatNum ~= 'false'
end
  local item = p.getItem(ItemName)
return item.validSlots ~= nil and Shared.contains(item.validSlots, equipSlot)
  if item == nil then
    return "ERROR: No item named "..ItemName.." exists in the data module"
  end
  local result = p._getItemStat(item, StatName, ZeroIfNil)
  if formatNum then result = Shared.formatnum(result) end
  return result
end
end


function p._getWeaponAttackType(item)
function p._getItemEquipSlot(item)
  if item.type == 'Weapon' then
--Function to return the (non-Passive) equipment slot that an item occupies
    return Icons.Icon({'Melee', nolink='true'})
if type(item) == 'string' then
  elseif item.type == 'Ranged Weapon' then
item = p.getItem(item)
    return Icons.Icon({'Ranged', type='skill', nolink='true'})
end
  elseif item.type == 'Magic Staff' or item.type == 'Magic Wand' then
if item == nil or item.validSlots == nil then
    return Icons.Icon({'Magic', type='skill', nolink='true'})
return 'Invalid'
  else
end
    return "Invalid"
for i, slot in pairs(item.validSlots) do
  end
if slot ~= 'Passive' then
return slot
end
end
end
end


 
function p._getItemStat(item, StatName, ZeroIfNil)
function p.getWeaponAttackType(frame)
local result = item[StatName]
  local itemName = frame.args ~= nil and frame.args[1] or frame
--Special Overrides:
  local item = p.getItem(itemName)
-- Equipment stats first
  if item == nil then
if item.equipmentStats ~= nil and item.equipmentStats[StatName] ~= nil then
    return "ERROR: No item named "..ItemName.." exists in the data module"
result = item.equipmentStats[StatName]
  end
elseif StatName == 'attackSpeed' and item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon') then
  return p._getWeaponAttackType(item)
-- Item can be equipped as a weapon but has no attack speed, so use default of 4000ms
result = 4000
elseif StatName == 'isTwoHanded' then
if item.validSlots ~= nil and item.occupiesSlots ~= nil then
result = Shared.contains(item.validSlots, 'Weapon') and Shared.contains(item.occupiesSlots, 'Shield')
else
result = false
end
elseif string.find(StatName, '^(.+)LevelRequired$') ~= nil and item.equipRequirements ~= nil then
local skillName = Shared.titleCase(string.match(StatName, '^(.+)LevelRequired$'))
if skillName ~= nil then
local skillID = Constants.getSkillID(skillName)
if skillID ~= nil then
for i, requirement in ipairs(item.equipRequirements) do
if requirement.type == "SkillLevel" and requirement.skillID == skillID then
result = requirement.level
break
end
end
end
end
elseif StatName == 'attackType' then
result = p._getWeaponAttackType(item)
elseif StatName == 'description' then
result = item.customDescription
if result == nil or result == '' then result = 'No Description' end
elseif StatName == 'completionReq' then
if item.ignoreCompletion == nil or not item.ignoreCompletion then
result = 'Yes'
else
result = 'No'
end
elseif StatName == 'slayerBonusXP' then
return p._getItemModifier(item, 'increasedSkillXP', 'Slayer', false)
elseif StatName == 'hasCombatStats' then
return tostring(p.hasCombatStats(item) or p._hasLevelRequirements(item))
elseif StatName == 'category' then
-- Some categories have a namespace for some reason, remove it
local _, localID = GameData.getLocalID(result)
return localID
end
if result == nil and ZeroIfNil then result = 0 end
return result
end
end


function p.getPotionTable(frame)
function p.getItemValue(item)
  local potionName = frame.args ~= nil and frame.args[1] or frame
if type(item) == 'string' then
  local tiers = {'I', 'II', 'III', 'IV'}
-- Specific check if the item is GP (value of 1)
if Shared.compareString('GP', item, true)
or Shared.compareString('Gold Pieces', item, true) then
return 1
end


  local result = '{| class="wikitable"'
item = p.getItem(item)
  result = result..'\r\n!Potion!!Tier!!Charges!!Effect'
end
if item then
return item.sellsFor
end
return nil
end


  local tier1potion = p.getItem(potionName..' I')
function p.getItemStat(frame)
  for i, tier in pairs(tiers) do
local args = frame.args ~= nil and frame.args or frame
    local tierName = potionName..' '..tier
local ItemName = args[1]
    local potion = p.getItemByID(tier1potion.id + i - 1)
local StatName = args[2]
    if potion == nil then
local ZeroIfNil = args.ForceZero ~= nil and args.ForceZero ~= '' and args.ForceZero ~= 'false'
      mw.log("Failed to get tier "..tier)
local formatNum = args.formatNum ~= nil and args.formatNum ~= '' and args.formatNum ~= 'false'
    else
local item = p.getItem(ItemName)
      result = result..'\r\n|-'
if item == nil then
      result = result..'\r\n|'..Icons.Icon({tierName, type='item', notext='true', size='60'})
return Shared.printError('No item named "' .. ItemName .. '" exists in the data module')
      result = result..'||'..'[['..tierName..'|'..tier..']]'
end
      result = result..'||'..potion.potionCharges..'||'..potion.description
local result = p._getItemStat(item, StatName, ZeroIfNil)
    end
if formatNum then result = Shared.formatnum(result) end
  end
return result
 
  result = result..'\r\n|}'
  return result
end
end


function p._getCreationTable(item)
--Gets the value of a given modifier for a given itemg
  local skill = ''
--asString is false by default, when true it writes the full bonus text
  local specialReq = nil
function p._getItemModifier(item, modifier, skillID, asString)
  local time = 0
if asString == nil then asString = false end
  local maxTime = nil
if skillID == '' then
  local lvl = 0
skillID = nil
  local xp = 0
elseif string.find(skillID, ':') == nil then
  local qty = nil
-- Try to find a skill ID if it looks like a skill name has been passed
  local req = nil
skillID = Constants.getSkillID(skillID)
  local result = ''
end


  local tables = {}
local result = 0
  --First figure out what skill is used to make this...
  if item.smithingLevel ~= nil then
    skill = 'Smithing'
    lvl = item.smithingLevel
    xp = item.smithingXP
    req = item.smithReq
    qty = item.smithingQty
    time = 2
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  end
  if item.craftingLevel ~= nil then
    skill = 'Crafting'
    lvl = item.craftingLevel
    xp = item.craftingXP
    req = item.craftReq
    qty = item.craftQty
    time = 3
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time))
  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[item.miningID + 1].level
    time = 3
    xp = item.miningXP
    if item.name == 'Dragonite Ore' then
      specialReq = Icons.Icon({"Mastery", notext='true'})..' 271 total [[Mining]] [[Mastery]]'
    end
    table.insert(tables, p.buildCreationTable(skill, lvl, xp, req, qty, time, nil, specialReq))
  end
  if item.type == "Logs" then
    --Well this feels like cheating, but for as long as logs are the first items by ID it works
    local treeData = SkillData.Woodcutting[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 == "Havest" or item.type == "Herb" or item.type == "Logs" then
    --Havest/Herb means farming
    --Logs might mean farming or might not. Depends on the logs
    --Yes, Havest. The typos are coming from inside the source code
    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
        qty = 5
        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
    return ""
  else
    return table.concat(tables, '\r\n')
  end
end


function p.buildAltMagicTable(spellName)
if item.modifiers ~= nil and item.modifiers[modifier] ~= nil then
  local spell = Magic.getSpell(spellName, 'AltMagic')
if type(item.modifiers[modifier]) == 'table' then
  local result = '{|class="wikitable"\r\n|-'
for i, subVal in Shared.skpairs(item.modifiers[modifier]) do
  result = result..'\r\n!colspan="2"|'..Icons.Icon({spell.name, type='spell'})
if subVal[1] == skillID then
  result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements'
result = subVal[2]
  result = result..'\r\n|'..Icons._SkillReq('Magic', spell.magicLevelRequired)
break
  -- 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
else
    result = result..'\r\n|-\r\n!style="text-align:right;"|Materials'
result = item.modifiers[modifier]
    result = result..'\r\n|1 of any item'
end
  end
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 = p.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 = p.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)
if asString then
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Quantity\r\n|'..spell.convertToQty
if skillID ~= nil then
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base XP\r\n|'..spell.magicXP
return Constants._getModifierText(modifier, {skillID, result})
  result = result..'\r\n|-\r\n!style="text-align:right;"|Cast Time\r\n|2s'
else
  result = result..'\r\n|}'
return Constants._getModifierText(modifier, result)
  return result
end
else
return result
end
end
end


function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)
function p.hasCombatStats(item)
  if qty == nil then qty = 1 end
-- Checks if the combat stat is a valid, non-zero combat stat
  local result = '{|class="wikitable"'
-- Ensure that, only in the case where the item is a Familar AND
  if req ~= nil then
-- the checked stat is summoningMaxhit, the result is ignored.
    result = result..'\r\n!colspan="2"|Item Creation'
function isNonZeroStat(statName, statVal)
  else
if statName == 'summoningMaxhit' and (p._canItemUseSlot(item, 'Summon1') or p._canItemUseSlot(item, 'Summon2')) then
    result = result..'\r\n!colspan="2"|Item Production'
return false
  end
end
  result = result..'\r\n|-\r\n!style="text-align: right;"|Requirements'
return statVal ~= 0
  result = result..'\r\n|'..Icons._SkillReq(skill, lvl)
end
  if specialReq ~= nil then result = result..'<br/>'..specialReq end
 
  if req ~= nil then
    result = result..'\r\n|-\r\n!style="text-align: right;"|Materials\r\n|'
    if type(req) == 'table' then
      for i, mat in pairs(req) do
        if i > 1 then result = result..'<br/>' end
        local matItem = p.getItemByID(mat.id)
        if matItem == nil then
          result = result..mat.qty..'x ?????'
        else
          result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty})
        end
      end
    else
    result = result..req
    end
  end
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Quantity'
  result = result..'\r\n|'..qty
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Experience'
  result = result..'\r\n|'..Shared.formatnum(xp)..' XP'
  result = result..'\r\n|-\r\n!style="text-align:right;"|Base Creation Time'
  result = result..'\r\n|'..Shared.formatnum(Shared.round(time, 2, 0))..'s'
  if maxTime ~= nil then result = result..' - '..Shared.formatnum(Shared.round(maxTime, 2, 0))..'s' end
  result = result..'\r\n|}'


  return result
if item.equipmentStats ~= nil then
end
-- Ensure at least one stat has a non-zero value
for statName, statVal in pairs(item.equipmentStats) do
if isNonZeroStat(statName, statVal) then
return true
end
end
end


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


function p.getEquipmentSlotName(id)
function p._hasLevelRequirements(item)
  for slotName, i in Shared.skpairs(Constants.equipmentSlot) do
--Function true if an item has at least one level requirement to equip
    if i == id then
if item.equipRequirements ~= nil then
      return slotName
for idx, requirement in ipairs(item.equipRequirements) do
    end
if requirement.type == 'SkillLevel' and requirement.level > 1 then
  end
return true
  return 'Invalid'
end
end
end
return false
end
end


function p._getOtherItemBoxText(item)
function p.getItemModifier(frame)
  result = ''
local itemName = frame.args ~= nil and frame.args[1] or frame[1]
  --For equipment, show the slot they go in
local modName = frame.args ~= nil and frame.args[2] or frame[2]
  if item.equipmentSlot ~= nil then
local skillName = frame.args ~= nil and frame.args[3] or frame[3]
    result = result..'\r\n|-\r\n|Equipment Slot: '..p.getEquipmentSlotName(item.equipmentSlot)
local asString = frame.args ~= nil and frame.args[4] or frame[4]
  end
if asString ~= nil then
  --For weapons with a special attack, show the details
asString = (string.upper(asString) ~= 'FALSE')
  if item.hasSpecialAttack then
end
    local spAtt = p.getSpecialAttackByID(item.specialAttackID)
    result = result..'\r\n|-\r\n|Special Attack:'
    result = result..'\r\n* '..spAtt.chance..'% chance for '..spAtt.name..':'
    result = result..'\r\n** '..spAtt.description
  end
  --For potions, show the number of charges
  if item.potionCharges ~= nil then
    result = result..'\r\n|-\r\n|Charges: '..item.potionCharges
  end
  --For food, show how much it heals for
  if item.healsFor ~= nil then
    result = result..'\r\n|-\r\n|Heals for: '..Icons.Icon({"Hitpoints", type="skill", notext="true"})..' '..(item.healsFor * 10)
  end
  --For Prayer Points, show how many you get
  if item.prayerPoints ~= nil then
    result = result..'\r\n|-\r\n|'..Icons.Icon({'Prayer', type='skill'})..' Points: '..item.prayerPoints
  end
  return result
end


function p.getOtherItemBoxText(frame)
local item = p.getItem(itemName)
  local itemName = frame.args ~= nil and frame.args[1] or frame
if item == nil then
  local item = p.getItem(itemName)
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  local asList = false
end
  if frame.args ~= nil then
    asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
  end
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end


  return p._getOtherItemBoxText(item, asList)
return p._getItemModifier(item, modName, skillName, asString)
end
end


function p._getItemSources(item, asList, addCategories)
function p._getWeaponAttackType(item)
  local result = nil
if (item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon')) or
  local lineArray = {}
(item.occupiesSlots ~= nil and Shared.contains(item.occupiesSlots, 'Weapon')) then
  local categoryArray = {}
if Shared.contains({'melee', 'ranged', 'magic'}, item.attackType) then
 
local iconType = item.attackType ~= 'melee' and 'skill' or nil
  --Alright, time to go through all the ways you can get an item...
return Icons.Icon({Shared.titleCase(item.attackType), type=iconType, nolink='true'})
  --First up: Can we kill somebody and take theirs?
end
  local killStr = ''
end
  local dungeonStr = ''
return 'Invalid'
  local count1 = 0
  for i, monster in Shared.skpairs(MonsterData.Monsters) do
    local isDrop = false
    if monster.bones == item.id and ((monster.lootTable ~= nil and not monster.isBoss) or Shared.contains(item.name, "Shard")) then
      isDrop = true
    elseif monster.lootTable ~= nil then
      for j, loot in pairs(monster.lootTable) do
        if loot[1] == item.id then
          isDrop = true
        end
      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
  if string.len(dungeonStr) > 0 then table.insert(lineArray, dungeonStr) end
  if string.len(killStr) > 0 then table.insert(lineArray, killStr) end
 
  --Next: Can we find it in a box?
  --While we're here, check for upgrades, cooking, and growing
  local lootStr = ''
  local upgradeStr = ''
  local cookStr = ''
  local burnStr = ''
  local growStr = ''
  local count2 = 0
  count1 = 0
  for i, item2 in pairs(ItemData.Items) do
    if item2.dropTable ~= nil then
      for j, loot in pairs(item2.dropTable) do
        if loot[1] == item.id then
          count1 = count1 + 1
          if string.len(lootStr) > 0 then
            lootStr = lootStr..','
            if count1 % 3 == 1 and count1 > 1 then lootStr = lootStr..'<br/>' end
            lootStr = lootStr..Icons.Icon({item2.name, type="item", notext="true"})
          else
            lootStr = lootStr..'Opening: '..Icons.Icon({item2.name, type="item", notext="true"})
          end
        end
      end
    end
    if item2.trimmedItemID == item.id then
          count2 = count2 + 1
        if string.len(upgradeStr) > 0 then
          upgradeStr = upgradeStr..','
          if count2 % 3 == 1 and count2 > 1 then upgradeStr = upgradeStr..'<br/>' end
          upgradeStr = upgradeStr..Icons.Icon({item2.name, type="item", notext="true"})
        else
          table.insert(categoryArray, '[[Category:Upgraded Items]]')
          upgradeStr = upgradeStr..'Upgrading: '..Icons.Icon({item2.name, type="item", notext="true"})
        end
    end
    if item2.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?
  local thiefStr = ''
  for i, npc in pairs(SkillData.Thieving) do
    if npc.lootTable ~= nil then
      for j, loot in pairs(npc.lootTable) do
        if loot[1] == item.id then
          if string.len(thiefStr) > 0 then
            thiefStr = thiefStr..','..Icons.Icon({npc.name, type="thieving", notext="true"})
          else
            thiefStr = thiefStr..'Pickpocketing: '..Icons.Icon({npc.name, type="thieving", notext="true"})
          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
  --SmithCheck:
  if item.smithingLevel ~= nil then
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
  end
 
  --CraftCheck:
  if item.craftingLevel ~= nil then
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
  end
 
  --FletchCheck:
  if item.fletchingLevel ~= nil then
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
  end
 
  --RunecraftCheck:
  if item.runecraftingLevel ~= nil then
    table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
  end
 
  --MineCheck:
  if item.miningID ~= nil then
    table.insert(lineArray, Icons._SkillReq("Mining", SkillData.Mining[item.miningID + 1].level))
  end
 
  --FishCheck:
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
    table.insert(lineArray, Icons._SkillReq("Fishing", 1))
  elseif item.fishingLevel ~= nil then
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
  end
 
  --HerbCheck:
  if item.herbloreMasteryID ~= nil then
    local potionData = SkillData.Herblore.ItemData[item.herbloreMasteryID + 1].herbloreLevel
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
  end
 
  --WoodcuttingCheck
  if item.type == 'Logs' then
    local treeData = SkillData.Woodcutting[item.id + 1]
    local lvl = treeData.level
    table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
  end
 
  --Finally there are some weird exceptions:
  --Coal can be acquired via firemaking
  if item.name == "Coal Ore" then
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
  end
 
  --Gems can be acquired from mining, fishing, and alt. magic
  if item.type == 'Gem' then
    table.insert(lineArray, Icons._SkillReq("Fishing", 1))
    table.insert(lineArray, Icons._SkillReq("Mining", 1))
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
  end
 
  --Bars and some other stuff can also be acquired via Alt. Magic
  if type == 'Bar' or Shared.contains(AltMagicProducts, item.name) then
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
  end
 
  --Chapeau Noir & Bobby's Pocket are special Thieving items
  if item.name == "Chapeau Noir" or item.name == "Bobby&apos;s Pocket" then
    table.insert(lineArray, Icons._SkillReq("Thieving", 1))
  end
 
  --Rhaelyx pieces are also special
  if item.name == 'Jewel of Rhaelyx' 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'})
    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})
    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'})..', '..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})
    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)
  if item.slayerCost ~= nil or item.buysFor ~= nil or Shared.contains(OtherShopItems, item.name) then
    table.insert(lineArray, '[[Shop]]')
  end
 
  --Easter Eggs (manual list 'cause don't have a better way to do that)
  if Shared.contains(EasterEggs, item.name) then
    table.insert(lineArray, '[[Easter Eggs]]')
  end
 
  local result = ''
  if asList then
    result = '* '..table.concat(lineArray, "\r\n* ")
  else
    result = table.concat(lineArray, "<br/>")
    result = '<div style="max-width:180px;text-align:right">'..result..'</div>'
  end
  if addCategories then result = result..table.concat(categoryArray, '') end
  return result
end
end


 
function p.getWeaponAttackType(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 = p.getItem(itemName)
  local item = p.getItem(itemName)
if item == nil then
  local asList = false
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  local addCategories = false
end
  if frame.args ~= nil then
return p._getWeaponAttackType(item)
    asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
    addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
  end
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end
 
  return p._getItemSources(item, asList, addCategories)
end
end


--Brute forcing some item uses to make things easier
local statChangeDefs = {
local itemUseArray = {
{
  Combat = {},
stat = 'stabAttackBonus',
  Cooking = {'Cooking Gloves', 'Crown of Rhaelyx'},
suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Stab Bonus'
  Crafting = {'Crown of Rhaelyx'},
},
  Farming = {'Compost', 'Weird Gloop', 'Bob&apos;s Rake'},
{
  Firemaking = {'Crown of Rhaelyx'},
stat = 'slashAttackBonus',
  Fishing = {'Amulet of Fishing', 'Message in a Bottle'},
suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Slash Bonus'
  Fletching = {'Crown of Rhaelyx'},
},
  Herblore = {'Crown of Rhaelyx'},
{
  Mining = {'Mining Gloves', 'Gem Gloves'},
stat = 'blockAttackBonus',
  Prayer = {},
suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Block Bonus'
  Runecrafting = {'Crown of Rhaelyx'},
},
  Slayer = {},
{
  Smithing = {'Smithing Gloves', 'Crown of Rhaelyx'},
stat = 'meleeStrengthBonus',
  Thieving = {'Chapeau Noir', 'Thieving Gloves'},
suffix = ' ' .. Icons.Icon({'Strength', type='skill', notext=true}) .. ' Strength Bonus'
  Woodcutting = {},
},
  }
{
local potionUseArray = {
stat = 'rangedStrengthBonus',
  [0] = 'Combat',
suffix = ' ' .. Icons.Icon({'Ranged', type='skill', notext=true}) .. ' Strength Bonus'
  [1] = 'Combat',
},
  [2] = 'Combat',
{
  [3] = 'Combat',
stat = 'magicStrengthBonus',
  [4] = 'Combat',
suffix = '% ' .. Icons.Icon({'Magic', type='skill', notext=true}) .. ' Damage Bonus'
  [5] = 'Combat',
},
  [6] = 'Combat',
{
  [7] = 'Woodcutting',
stat = 'meleeDefenceBonus',
  [8] = 'Fishing',
suffix = ' ' .. Icons.Icon({'Defence', type='skill', notext=true}) .. ' Defence Bonus' },
  [9] = 'Firemaking',
{
  [10] = 'Cooking',
stat = 'rangedDefenceBonus',
  [11] = 'Mining',
suffix = ' ' .. Icons.Icon({'Ranged', type='skill', notext=true}) .. ' Defence Bonus'
  [12] = 'Smithing',
},
  [13] = 'Thieving',
{
  [14] = 'Farming',
stat = 'magicDefenceBonus',
  [15] = 'Fletching',
suffix = ' ' .. Icons.Icon({'Magic', type='skill', notext=true}) .. ' Defence Bonus'
  [16] = 'Crafting',
},
  [17] = 'Runecrafting',
{
  [18] = 'Herblore',
stat = 'damageReduction',
  [19] = 'Combat',
suffix = '% Damage Reduction'
  [20] = 'Combat',
},
  [21] = 'Combat',
{
  [22] = 'Combat',
stat = 'levelRequired',
  [23] = 'Combat',
suffix = ' Level Required'
}
}
}


function p._getItemUses(item, addCategories)
-- Produces a list of stat & modifier changes between two items of equipmednt
  local useArray = {}
function p.getStatChangeString(item1, item2)
  local categoryArray = {}
local changeArray = {}
  --Another fun one. This time getting all the different possible ways an item can be used


  --Before anything else, if this is a potion add it to the appropriate override section
local equipStats = {
  if item.herbloreMasteryID ~= nil then
type(item1.equipmentStats) == 'table' and item1.equipmentStats or {},
    table.insert(itemUseArray[potionUseArray[item.herbloreMasteryID]], item.name)
type(item2.equipmentStats) == 'table' and item2.equipmentStats or {}
  end
}
for i, statDef in ipairs(statChangeDefs) do
local val1, val2 = 0, 0
if statDef.stat == 'levelRequired' then
-- Iterate over equipment stats for both items, determining level requirements
local levelReqs = {}
for itemNum, item in ipairs({item1, item2}) do
levelReqs[itemNum] = {}
if item.equipRequirements ~= nil then
for j, req in ipairs(item.equipRequirements) do
if req.type == 'SkillLevel' then
levelReqs[itemNum][req.skillID] = req.level
end
end
end
end
-- Iterate over all skills, checking if there are requirements for these in either skill
for j, skillData in ipairs(GameData.rawData.skillData) do
local skillID = skillData.skillID
val1, val2 = levelReqs[1][skillID] or 0, levelReqs[2][skillID] or 0
if val1 ~= val2 then
table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. ' ' .. Icons.Icon({skillData.data.name, type='skill', notext=true}) .. (statDef.suffix or ''))
end
end
else
-- Equipment stats
val1, val2 = equipStats[1][statDef.stat] or 0, equipStats[2][statDef.stat] or 0
if val1 ~= val2 then
table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. (statDef.suffix or ''))
end
end
end


  --First things added to the list are non-skill things that are easy to check
-- Include differences in modifiers
  if item.equipmentSlot ~= nil or Shared.contains(itemUseArray.Combat, item.name) then
local modDiff = Constants.getModifiersText(Constants.getModifiersDifference(item2.modifiers, item1.modifiers))
    if item.equipmentSlot ~= nil then
if modDiff ~= nil and modDiff ~= '' then
      table.insert(categoryArray, '[[Category:'..p.getEquipmentSlotName(item.equipmentSlot)..' Slot Items]]')
table.insert(changeArray, modDiff)
    end
end
    table.insert(useArray, '* '..Icons.Icon({'Combat'}))
  end
  if item.healsFor ~= nil then
    table.insert(categoryArray, '[[Category:Food Items]]')
    table.insert(useArray, '* [[Food]]')
  end
  if item.dropTable ~= nil then
    table.insert(categoryArray, '[[Category:Openable Items]]')
    table.insert(useArray, '* [[Chest Drop Tables|Can Be Opened]]')  
  end


  --Next, upgrading, crafting, herblore, fletching, and runecrafting since we have to sift through other items for these
return table.concat(changeArray, '<br/>')
  local canUpgrade = false
end
  local canCraft = false
  local canFletch = false
  local canRunecraft = false
  local canHerblore = false
  if item.trimmedItemID ~= nil then
    canUpgrade = true
  else
    for i, item2 in pairs(ItemData.Items) do
      if item2.itemsRequired ~= nil then
        for j, req in pairs(item2.itemsRequired) do
          if req[1] == item.id then
            canUpgrade = true
            break
          end
        end
      end


      if item2.craftReq ~= nil then
function p._getOtherItemBoxText(item)
        for j, req in pairs(item2.craftReq) do
local resultPart = {}
          if req.id == item.id then
--For equipment, show the slot they go in
            canCraft = true
local isPassive = false
            break
if item.validSlots ~= nil then
          end
local slotLinkMap = {
        end
["Helmet"] = 'Helmets',
      end
["Platebody"] = 'Platebodies',
 
["Platelegs"] = 'Platelegs',
      if item2.fletchReq ~= nil then
["Boots"] = 'Boots',
        for j, req in pairs(item2.fletchReq) do
["Weapon"] = 'Weapons',
          if req.id == item.id then
["Shield"] = 'Shields',
            canFletch = true
["Amulet"] = 'Amulets',
            break
["Ring"] = 'Rings',
          end
["Gloves"] = 'Gloves',
        end
["Quiver"] = 'Ammunition',
      end
["Cape"] = 'Capes',
 
["Consumable"] = 'Consumables',
      if item2.runecraftReq ~= nil then
["Passive"] = 'Combat Passive Slot',
        for j, req in pairs(item2.runecraftReq) do
["Summon1"] = 'Summoning',
          if req.id == item.id then
["Summon2"] = 'Summoning',
            canRunecraft = true
["Gem"] = "Gems_(Equipment)"
            break
}
          end
local slotText = {}
        end
for i, slot in ipairs(item.validSlots) do
      end
local slotLink = slotLinkMap[slot]
 
if slotLink == nil then
      if item2.herbloreReq ~= nil then
table.insert(slotText, slot)
        for j, req in pairs(item2.herbloreReq) do
else
          if req.id == item.id then
table.insert(slotText, '[[' .. slotLink .. '|' .. slot .. ']]')
            canHerblore = true
end
            break
          end
if slot == 'Passive' then
        end
isPassive = true
      end
end
    end
end
  end
table.insert(resultPart, "\r\n|-\r\n|'''Equipment Slot:''' "..table.concat(slotText, ', '))
  if canUpgrade then
end
    if item.canUpgrade or (item.type == 'Armour' and item.canUpgrade == nil) then
--For weapons with a special attack, show the details
      table.insert(categoryArray, '[[Category:Upgradeable Items]]')
if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
    end
table.insert(resultPart, "\r\n|-\r\n|'''Special Attack:'''")
    table.insert(useArray, '* [[Upgrading Items]]')
for i, spAttID in ipairs(item.specialAttacks) do
  end
local spAtt = GameData.getEntityByID('attacks', spAttID)
   
if spAtt ~= nil then
  --Cooking
local spAttChance = spAtt.defaultChance
  if item.cookedItemID ~= nil or Shared.contains(itemUseArray.Cooking, item.name) then
if type(item.overrideSpecialChances) == 'table' and item.overrideSpecialChances[i] ~= nil then
    table.insert(useArray, '* '..Icons.Icon({'Cooking', type='skill'}))
spAttChance = item.overrideSpecialChances[i]
  end
end
  --Crafting
local spAttDesc = string.gsub(spAtt.description, '<Attack> ', '')
  if canCraft or Shared.contains(itemUseArray.Crafting, item.name) then
table.insert(resultPart, '\r\n* ' .. spAttChance .. '% chance for ' .. spAtt.name .. ':')
    table.insert(useArray, '* '..Icons.Icon({'Crafting', type='skill'}))
table.insert(resultPart, '\r\n** ' .. spAttDesc)
  end
end
  --Farming
end
  if item.grownItemID ~= nil or Shared.contains(itemUseArray.Farming, item.name) then
end
    table.insert(useArray, '* '..Icons.Icon({'Farming', type='skill'}))
-- For Summoning combat familiars, show the max hit
  end
if item.equipmentStats ~= nil and item.equipmentStats.summoningMaxhit ~= nil then
  --Firemaking
table.insert(resultPart, "\r\n|-\r\n|'''Max Hit:''' " .. Shared.formatnum(item.equipmentStats.summoningMaxhit * 10))
  if item.firemakingID ~= nil or Shared.contains(itemUseArray.Firemaking, item.name) then
end
    table.insert(useArray, '* '..Icons.Icon({'Firemaking', type='skill'}))
--For potions, show the number of charges
  end
if item.charges ~= nil then
  --Fishing
table.insert(resultPart, "\r\n|-\r\n|'''Charges:''' "..item.charges)
  if Shared.contains(itemUseArray.Fishing, item.name) then
end
    table.insert(useArray, '* '..Icons.Icon({'Fishing', type='skill'}))
--For food, show how much it heals for
  end
if item.healsFor ~= nil then
  --Fletching
table.insert(resultPart, "\r\n|-\r\n|'''Heals for:''' "..Icons.Icon({"Hitpoints", type="skill", notext="true"})..' '..(item.healsFor * 10))
  if canFletch or Shared.contains(itemUseArray.Fletching, item.name) then
end
    table.insert(useArray, '* '..Icons.Icon({'Fletching', type='skill'}))
--For Prayer Points, show how many you get
  end
if item.prayerPoints ~= nil then
  --Herblore
table.insert(resultPart, "\r\n|-\r\n|'''"..Icons.Icon({'Prayer', type='skill'}).." Points:''' "..item.prayerPoints)
  if canHerblore or Shared.contains(itemUseArray.Herblore, item.name) then
end
    table.insert(useArray, '* '..Icons.Icon({'Herblore', type='skill'}))
--For items that provide runes, show which runes are provided
  end
if item.providedRunes ~= nil then
  --Mining
table.insert(resultPart, "\r\n|-\r\n|'''Runes Provided:''' ")
  if Shared.contains(itemUseArray.Mining, item.name) then
local runeLines = {}
    table.insert(useArray, '* '..Icons.Icon({'Mining', type='skill'}))
local sortVal = ''
  end
for j, runePair in pairs(item.providedRunes) do
  --Prayer
local runeID = runePair.id
  if item.prayerPoints ~= nil or Shared.contains(itemUseArray.Prayer, item.name) then
local qty = runePair.quantity
    if item.prayerPoints ~= nil then table.insert(categoryArray, '[[Category:Buriable Items]]') end
local rune = p.getItemByID(runeID)
    table.insert(useArray, '* '..Icons.Icon({'Prayer', type='skill'}))
sortVal = sortVal..rune.name..qty
  end
table.insert(runeLines, Icons.Icon({rune.name, type='item', qty=qty}))
  --Runecrafting
end
  if canRunecraft or Shared.contains(itemUseArray.Runecrafting, item.name) then
table.insert(resultPart, table.concat(runeLines, ', '))
    table.insert(useArray, '* '..Icons.Icon({'Runecrafting', type='skill'}))
end
  end
--For items with modifiers, show what those are
  --Slayer
if item.modifiers ~= nil and not Shared.tableIsEmpty(item.modifiers) then
  if item.slayerCost ~= nil or Shared.contains(itemUseArray.Slayer, item.name) then
table.insert(resultPart, "\r\n|-\r\n|'''Modifiers:'''\r\n")
    table.insert(useArray, '* '..Icons.Icon({'Slayer', type='skill'}))
if isPassive then
  end
table.insert(resultPart, '<span style="color:green">Passive:</span><br/>')
  --Smithing
end
  if item.type == 'Bar' or item.type == 'Ore' or Shared.contains(itemUseArray.Smithing, item.name) then
table.insert(resultPart, Constants.getModifiersText(item.modifiers, true, false, 10))
    table.insert(useArray, '* '..Icons.Icon({'Smithing', type='skill'}))
end
  end
return table.concat(resultPart)
  --Thieving
  if Shared.contains(itemUseArray.Thieving, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Thieving', type='skill'}))
  end
  --Woodcutting
  if Shared.contains(itemUseArray.Woodcutting, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Woodcutting', type='skill'}))
  end
 
  --Other odds and ends:
  --Mastery Tokens are tied to 'Mastery'
  if item.type == 'Token' then
    table.insert(useArray, '* '..Icons.Icon({'Mastery'}))
  end
 
  --Skillcapes are tied to the appropriate skill
  --Except Max Skillcape, which is tied to all skills. (And so is the Signet Ring)
  --And combat skillcapes, since combat skills don't get special treatment
 
  local ignoreCapes = {'Ranged Skillcape', 'Attack Skillcape', 'Strength Skillcape', 'Hitpoints Skillcape', 'Defence Skillcape'}
  if item.name == 'Max Skillcape' or item.name == 'Aorpheat&apos;s Signet Ring' or item.name == 'Cape of Completion' then
    table.insert(useArray, '* All skills')
  elseif item.name == 'Magic Skillcape' then
    table.insert(useArray, '* '..Icons.Icon({'Magic', type='skill'}))
    table.insert(useArray, '* '..Icons.Icon({'Alt. Magic', type='skill'}))
  elseif Shared.contains(item.name, 'Skillcape') and not Shared.contains(ignoreCapes, item.name) then
    local skillName = Shared.splitString(item.name, ' ')[1]
    table.insert(useArray, '* '..Icons.Icon({skillName, type='skill'}))
  end
 
  if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then table.insert(categoryArray, '[[Category:Skillcapes]]') end
 
  --Special note for Charge Stone of Rhaelyx
  if item.name == 'Charge Stone of Rhaelyx' then
    table.insert(useArray, '* Powering '..Icons.Icon({'Crown of Rhaelyx', type='item'}))
  end
 
  local result = table.concat(useArray,'\r\n')
  if addCategories then result = result..table.concat(categoryArray, '') end
  return result
end
end


function p.getItemUses(frame)
function p.getOtherItemBoxText(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 = p.getItem(itemName)
local item = p.getItem(itemName)
  local addCategories = false
if item == nil then
  if frame.args ~= nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
    addCategories = frame.args.addCategories ~= nil and frame.args.addCategories ~= '' and frame.args.addCategories ~= 'false'
end
  end
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end


  return p._getItemUses(item, addCategories)
return p._getOtherItemBoxText(item)
end
end


function p._getItemUseTable(item)
function p._getItemCategories(item)
  local useArray = {}
local resultPart = {}
  local potTierMastery = {[0] = 0, [1] = 20, [2] = 50, [3] = 90}
local isEquipment = item.validSlots ~= nil or item.occupiesSlots ~= nil or item.equipmentStats ~= nil
 
local category = p._getItemStat(item, 'category', false)
  --First, loop through all items to find anything that can be made or upgraded into using our source
if category ~= nil and category ~= 'Skills' then
  for i, item2 in pairs(ItemData.Items) do
table.insert(resultPart, '[[Category:'..category..']]')
    if item2.itemsRequired ~= nil then
end
      for j, req in pairs(item2.itemsRequired) do
if item.type ~= nil then
        if req[1] == item.id then
table.insert(resultPart, '[[Category:'..item.type..']]')
          local mat = item2.itemsRequired
end
          local xp = 'N/A'
if isEquipment and item.tier ~= nil then
          local rowReq = 'None'
table.insert(resultPart, '[[Category:'..Shared.titleCase(item.tier)..' '..item.type..']]')
          --Potions do have upgrade requirements though
end
          if item2.potionTier ~= nil then
if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
            rowReq = Icons._MasteryReq(item2.name, potTierMastery[item2.potionTier])
table.insert(resultPart, '[[Category:Items With Special Attacks]]')
          end
end
          table.insert(useArray, {item = item2, qty = 1, mats = mat, skill = 'Upgrade', req = rowReq, xp = xp, gp = item2.trimmedGPCost})
if item.validSlots ~= nil then
          break
local slotRemap = {
        end
['Passive'] = 'Passive Items',
      end
['Summon1'] = 'Summoning Familiars',
    end
['Summon2'] = ''
    if item.name == 'Leather' and item2.buysForLeather ~= nil then
}
      local mat = {{id = item.id, qty = item2.buysForLeather}}
for i, slotName in ipairs(item.validSlots) do
      local xp = 'N/A'
local slotRemapName = slotName
      local rowReq = 'None'
if slotRemap[slotName] ~= nil then slotRemapName = slotRemap[slotName] end
      table.insert(useArray, {item = item2, qty = 1, mats = mat, skill = 'Shop', req = rowReq, xp = xp, gp = item2.buysFor})
if slotRemapName ~= '' then table.insert(resultPart, '[[Category:' .. slotRemapName .. ']]') end
    elseif item2.buysForItems ~= nil then
end
      for j, req in pairs(item2.buysForItems) do
end
        if req[1] == item.id then
if item.modifiers ~= nil then
          local mat = item2.buysForItems
local modsDL = {
          local xp = 'N/A'
'increasedChanceToDoubleLootCombat',
          local rowReq = 'None'
'decreasedChanceToDoubleLootCombat',
          table.insert(useArray, {item = item2, qty = 1, mats = mat, skill = 'Shop', req = rowReq, xp = xp, gp = item2.buysForGP})
'increasedChanceToDoubleLootThieving',
          break
'decreasedChanceToDoubleLootThieving',
        end
'increasedChanceToDoubleItemsGlobal',
      end
'decreasedChanceToDoubleItemsGlobal'
    end
}
    if item2.craftReq ~= nil then
for modName, val in pairs(item.modifiers) do
      for j, req in pairs(item2.craftReq) do
if Shared.contains(modsDL, modName) then
        if req.id == item.id then
table.insert(resultPart, '[[Category:Double Loot Chance Items]]')
          local mat = item2.craftReq
break
          local xp = item2.craftingXP
end
          local rowReq = item2.craftingLevel
end
          local qty = item2.craftQty
end
          table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Crafting', req = rowReq, xp = xp})
return table.concat(resultPart)
          break
        end
      end
    end
    if item2.fletchReq ~= nil then
      for j, req in pairs(item2.fletchReq) do
        if req.id == item.id then
          local xp = item2.fletchingXP
          local rowReq = item2.fletchingLevel
          --Arrow Shafts are special and have to be treated specially
          local qty = item2.fletchQty
          local mat = item2.fletchReq
          if item2.name == 'Arrow Shafts' then
            mat = {{id = item.id, qty = 1}}
            qty =  qty + (qty * item.id)
          end
          table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Fletching', req = rowReq, xp = xp})
          break
        end
      end
    end
    if item2.smithReq ~= nil then
      for j, req in pairs(item2.smithReq) do
        if req.id == item.id then
          local mat = item2.smithReq
          local xp = item2.smithingXP
          local rowReq = item2.smithingLevel
          local qty = item2.smithingQty
          table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Smithing', req = rowReq, xp = xp})
          break
        end
      end
    end
    if item2.runecraftReq ~= nil then
      for j, req in pairs(item2.runecraftReq) do
        if req.id == item.id then
          local mat = item2.runecraftReq
          local xp = item2.runecraftingXP
          local rowReq = item2.runecraftingLevel
          local qty = item2.runecraftQty
          table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Runecrafting', req = rowReq, xp = xp})
          break
        end
      end
    end
    if item2.herbloreReq ~= nil then
      for j, req in pairs(item2.herbloreReq) do
        if req.id == item.id then
          local potionData = SkillData.Herblore.ItemData[item2.herbloreMasteryID + 1]
          local mat = item2.herbloreReq
          local xp = potionData.herbloreXP
          --Potions do have upgrade requirements though
          local rowReq = Icons._SkillReq('Herblore', potionData.herbloreLevel)
          local masteryLvl = potTierMastery[item2.potionTier]
          if masteryLvl > 0 then
            rowReq = rowReq..'<br/>'..Icons._MasteryReq(item2.name, masteryLvl)
          end
          local reqVal = potionData.herbloreLevel + (masteryLvl * 0.01)
          table.insert(useArray, {item = item2, qty = 1, mats = mat, skill = 'Herblore', reqVal = reqVal, req = rowReq, xp = xp})
          break
        end
      end
    end
  end
  if item.grownItemID ~= nil then
    local item2 = p.getItemByID(item.grownItemID)
    local mat = {{id = item.id, qty = item.seedsRequired}}
    local xp = item.farmingXP
    local rowReq = item.farmingLevel
    local qty = 5
    table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Farming', req = rowReq, xp = xp})
  end
  if item.cookedItemID ~= nil then
    local item2 = p.getItemByID(item.cookedItemID)
    local mat = {{id = item.id, qty = 1}}
    local xp = item.cookingXP
    local rowReq = item.cookingLevel
    local qty = 1
    table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Cooking', req = rowReq, xp = xp})
  end
  if item.burntItemID ~= nil then
    local item2 = p.getItemByID(item.burntItemID)
    local mat = {{id = item.id, qty = 1}}
    local xp = 1
    local rowReq = item.cookingLevel
    local qty = 1
    table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Cooking', req = rowReq, xp = xp})
  end
 
  --Finally build the table using what we've learned
  table.sort(useArray, function(a, b)
                        local aReqVal = a.reqVal ~= nil and a.reqVal or a.req
                        local bReqVal = b.reqVal ~= nil and b.reqVal or b.req
                        if a.skill ~= b.skill then
                          return a.skill < b.skill
                        elseif type(aReqVal) ~= type(bReqVal) then
                          return tostring(aReqVal) < tostring(bReqVal)
                        elseif aReqVal ~= bReqVal then
                          return aReqVal < bReqVal
                        else
                          return a.item.name < b.item.name
                        end end)
 
  local spellUseTable = p._getSpellUseTable(item)
  local result = ''
  if Shared.tableCount(useArray) == 0 then
    if string.len(spellUseTable) > 0 then
      return '==Uses==\r\n==='..Icons.Icon({'Magic', type='skill', size='30'})..'===\r\n'..spellUseTable
    else
      return ''
    end
  end
  result = result..'{| class="wikitable sortable"'
  result = result..'\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients'
  for i, row in pairs(useArray) do
    local qty = row.qty ~= nil and row.qty or 1
    result = result..'\r\n|-\r\n|data-sort-value="'..row.item.name..'"|'
    result = result..Icons.Icon({row.item.name, type='item', notext=true, size=50})..'||'
    if qty > 1 then result = result.."'''"..qty.."x''' " end
    result = result..'[['..row.item.name..']]'
    if row.skill == 'Upgrade' then
      result = result..'||data-sort-value="Upgrade"|[[Upgrading Items|Upgrade]]'
    elseif row.skill == 'Shop' then
      result = result..'||data-sort-value="Shop"|[[Shop]]'
    else
      result = result..'||data-sort-value="'..row.skill..'"|'..Icons.Icon({row.skill, type='skill'})
    end
    if type(row.req) == 'number' then
      result = result..'|| data-sort-value="'..row.req..'"|'..Icons._SkillReq(row.skill, row.req)
    elseif row.reqVal ~= nil then
      result = result..'|| data-sort-value="'..row.reqVal..'"|'..row.req
    else
      result = result..'||'..row.req
    end
    if type(row.xp) == 'string' then
      result = result..'||data-sort-value="0"|'..row.xp
    else
      result = result..'||data-sort-value="'..row.xp..'"|'..row.xp..' '..Icons.Icon({row.skill, type='skill', notext=true})..' XP'
    end
    result = result..'||'
    for i, mat in pairs(row.mats) do
      local matID = mat.id ~= nil and mat.id or mat[1]
      local matQty = mat.qty ~= nil and mat.qty or mat[2]
      matItem = p.getItemByID(matID)
      if i > 1 then result = result..'<br/>' end
      result = result..Icons.Icon({matItem.name, type='item', qty=matQty})
    end
    if row.gp ~= nil then result = result..'<br/>'..Icons.GP(row.gp) end
  end
 
  result = result..'\r\n|}'
  if string.len(spellUseTable) > 0 then
    result = result..'\r\n==='..Icons.Icon({'Magic', type='skill', size='30'})..'===\r\n'..spellUseTable
  end
  return '==Uses==\r\n'..result
end
end


function p.getItemUseTable(frame)
function p.getItemCategories(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 = p.getItem(itemName)
local item = p.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


  return p._getItemUseTable(item)
return p._getItemCategories(item)
end
end


function p._getSpellUseTable(item)
function p.getItemGrid(frame)
  local spellList = Magic.getSpellsForRune(item.id)
local resultPart = {}
  --Bail immediately if no spells are found
table.insert(resultPart, '{|')
  if Shared.tableCount(spellList) == 0 then
for i, item in ipairs(GameData.rawData.items) do
    return ''
if i % 17 == 1 then
  end
table.insert(resultPart, '\r\n|-\r\n|')
 
else
  local result = '{|class="wikitable sortable"\r\n!colspan="2"|Spell'
table.insert(resultPart, '||')
  result = result..'!!'..Icons.Icon({'Magic', type='skill', notext='true'})..' Level'
end
  result = result..'!!Type!!style="width:275px"|Description'
table.insert(resultPart, 'style="padding:3px"|'..Icons.Icon({item.name, type='item', notext=true, size='40'}))
  result = result..'!!Runes'
end
  for i, spell in pairs(spellList) do
table.insert(resultPart, '\r\n|}')
    local rowTxt = '\r\n|-\r\n|data-sort-value="'..spell.name..'"|'
return table.concat(resultPart)
    if spell.type == 'Auroras' then
      rowTxt = rowTxt..Icons.Icon({spell.name, type='aurora', notext=true, size=50})
    elseif spell.type == 'Curses' then
      rowTxt = rowTxt..Icons.Icon({spell.name, type='curse', notext=true, size=50})
    else
      rowTxt = rowTxt..Icons.Icon({spell.name, type='spell', notext=true, size=50})
    end
    rowTxt = rowTxt..'||'..spell.name
    rowTxt = rowTxt..'||data-sort-value="'..spell.magicLevelRequired..'"|'..Icons._SkillReq('Magic', spell.magicLevelRequired)
    --Handle required items/dungeon clears
    if spell.requiredItem ~= nil and spell.requiredItem >= 0 then
      local reqItem = p.getItemByID(spell.requiredItem)
      rowTxt = rowTxt..'<br/>'..Icons.Icon({reqItem.name, type='item', notext=true})..' equipped'
    end
    if spell.requiredDungeonCompletion ~= nil then
      local dung = Areas.getAreaByID('dungeon', spell.requiredDungeonCompletion[1])
      rowTxt = rowTxt..'<br/>'..Icons.Icon({dung.name, type='dungeon', notext=true, qty=spell.requiredDungeonCompletion[2]})..' Clears'
    end
    rowTxt = rowTxt..'||data-sort-value="'..Magic.getSpellTypeIndex(spell.type)..'"|'
    rowTxt = rowTxt..Magic.getSpellTypeLink(spell.type)
    if spell.type == 'Spells' then
      rowTxt = rowTxt..'||Combat spell with a max hit of '..(spell.maxHit * 10)
    else
      rowTxt = rowTxt..'||'..spell.description
    end
    rowTxt = rowTxt..'||style="text-align:center"|'
    for i, req in pairs(spell.runesRequired) do
      local rune = p.getItemByID(req.id)
      if i > 1 then rowTxt = rowTxt..', ' end
      rowTxt = rowTxt..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
    end
    if spell.runesRequiredAlt ~= nil then
      rowTxt = rowTxt.."<br/>'''OR'''<br/>"
      for i, req in pairs(spell.runesRequiredAlt) do
        local rune = p.getItemByID(req.id)
        if i > 1 then rowTxt = rowTxt..', ' end
        rowTxt = rowTxt..Icons.Icon({rune.name, type='item', notext=true, qty=req.qty})
      end
    end
    result = result..rowTxt
  end
  --Add the table end and add the table to the result string
  result = result..'\r\n|}'
  return result
end
end


function p.getSpellUseTable(frame)
function p.getEquipRequirementRow(req)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local result = ""
  local item = p.getItem(itemName)
if req.type == "SkillLevel" then
  if item == nil then
local skillName = Constants.getSkillName(req.skillID)
    return "ERROR: No item named "..itemName.." exists in the data module"
local skillIcon = Icons.Icon({skillName, type='skill', notext=true})
  end
result = '\r\n!style="text-align:right;"| '..skillIcon..' Level Required'
 
result = result..'\r\n|style="text-align:right;"| '..req.level
  return p._getSpellUseTable(item)
elseif req.type == "DungeonCompletion" then
local dungeonName = GameData.getEntityByID("dungeons", req.dungeonID).name
local dungeonIcon = Icons.Icon({dungeonName, type="dungeon", notext=true})
result = '\r\n!style="text-align:right;"| '..dungeonIcon..' Completions'
result = result..'\r\n|style="text-align:right;"| '..req.count
elseif req.type == "Completion" then
local ns = GameData.getEntityByName('namespaces', req.namespace)
if ns == nil then
return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid namespace for completion requirement "' .. req.namespace .. '"')
else
result = '\r\n!style="text-align:right;"| ' .. ns.displayName .. ' Completion'
result = result .. '\r\n|style="text-align:right;"| ' .. req.percent .. '%'
end
else
return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid equip requirement type "' .. req.type .. '"')
end
return result
end
end


function p._getItemLootSourceTable(item)
function p.getWeaponStatsBox(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
local itemName = frame.args ~= nil and frame.args[1] or frame
  result = result..'\r\n|- class="headerRow-0"'
local item = p.getItem(itemName)
  result = result..'\r\n!Source!!Source Type!!Quantity!!Chance'
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end


  --Set up function for adding rows
local ico = {
  local buildRow = function(source, type, minqty, qty, chance)
["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
    if minqty == nil then minqty = 1 end
["Combat"] = Icons.Icon({'Combat', notext=true}),
    local rowTxt = '\r\n|-'
["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
    rowTxt = rowTxt..'\r\n|style ="text-align: left;"|'..source
["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
    rowTxt = rowTxt..'\r\n|style ="text-align: left;"|'..type
["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
}
local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
local emptyRow = '\r\n!colspan="2"|'
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Weapon Stats')
table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')


    rowTxt = rowTxt..'\r\n|style ="text-align: right;" data-sort-value:"'..qty..'"|'..minqty
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Speed')
    if qty ~= minqty then rowTxt = rowTxt..' - '..qty end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Shared.round(p._getItemStat(item, 'attackSpeed', true) / 1000, 3, 1) .. 's')
    rowTxt = rowTxt..'\r\n|style ="text-align: right;"|'..Shared.round(chance, 2, 2)..'%'
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
    return rowTxt
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))
  end
  local dropRows = {}
 
  --Alright, time to go through a few ways to get the item
  --First up: Can we kill somebody and take theirs?
  for i, monster in pairs(MonsterData.Monsters) do
    local minqty = 1
    local qty = 1
    local chance = 0
    local wt = 0
    local totalWt = 0
    --Only add bones if this monster has loot (ie appears outside a dungeon) and isn't a boss
    --... unless we're looking for Shards of course, at which point we'll take any monster with the right bones
    if ((monster.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
      if wt > 0 then
        local lootChance = monster.lootChance ~= nil and monster.lootChance or 100
        chance = ((wt * lootChance) / (totalWt * 100)) * 100
      end
    end
    if chance > 0 then
      local sourceTxt = Icons.Icon({monster.name, type='monster'})
      table.insert(dropRows, {source = sourceTxt, type = '[[Monster]]', minqty = minqty, qty = qty, chance = chance})
    end
  end


  --Next: Can we find it by rummaging around in another item?
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Type')
  for i, item2 in pairs(ItemData.Items) do
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'attackType'))
    if item2.dropTable ~= nil then
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
      local qty = 1
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')
      local chance = 0
      local wt = 0
      local totalWt = 0
      for j, loot in pairs(item2.dropTable) do
        totalWt = totalWt + loot[2]
        if loot[1] == item.id then
          wt = loot[2]
          if item2.dropQty ~= nil then qty = item2.dropQty[j] end
        end
      end


      if wt > 0 then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
        chance = (wt / totalWt) * 100
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeStrengthBonus', true))
        local sourceTxt = Icons.Icon({item2.name, type='item'})
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
        table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, chance = chance})
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))
      end
    end
  end


  --Finally, let's try just stealing it
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
  local thiefType = Icons.Icon({"Thieving", type='skill'})
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'stabAttackBonus', true))
  for i, npc in pairs(SkillData.Thieving) do
table.insert(resultPart, '\r\n!style="text-align:right;border-bottom:solid thin black;"| ' .. ico['Magic'] .. ' Defence Bonus')
    local qty = 1
table.insert(resultPart, '\r\n|style="text-align:right;border-bottom:solid thin black;"| ' .. p._getItemStat(item, 'magicDefenceBonus', true))
    local chance = 0
    local wt = 0
    local totalWt = 0
    if npc.lootTable ~= nil then
      for j, loot in pairs(npc.lootTable) do
        totalWt = totalWt + loot[2]
        if loot[1] == item.id then
          wt = loot[2]
        end
      end
      if wt > 0 then
        chance = (wt / totalWt) * 75
        local sourceTxt = Icons.Icon({npc.name, type='thieving'})
        table.insert(dropRows, {source = sourceTxt, type = thiefType, minqty = 1, qty = qty, chance = chance})
      end
    end
  end


  --Bonus overtime: Special Fishing table & mining gem table. Also Rags to Riches
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
  if item.type == 'Gem' then
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
    local mineType = Icons.Icon({'Mining', type='skill'})
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')
    local thisGemChance = GemTable[item.name].chance
    table.insert(dropRows, {source = '[[Mining#Gems|Gem]]', type = mineType, minqty = 1, qty = 1, chance = thisGemChance})
    local magicType = Icons.Icon({'Magic', type = 'skill'})
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches I", type="spell"}), type = magicType, minqty = 1, qty = 1, chance = thisGemChance})
    table.insert(dropRows, {source = Icons.Icon({"Rags to Riches II", type="spell"}), type = magicType, minqty = 1, qty = 1, chance = thisGemChance})
  end


  if item.fishingCatchWeight ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
    local fishSource = '[[Fishing#Special|Special]]'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'blockAttackBonus', true))
    local fishType = Icons.Icon({'Fishing', type='skill'})
if reqCount > 0 then
    local thisChance = (item.fishingCatchWeight / specialFishWt) * 100
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, chance = thisChance})
else
  end
table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
end


  if item.type == 'Junk' then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
    local fishSource = '[[Fishing#Junk|Junk]]'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
    local fishType = Icons.Icon({'Fishing', type='skill'})
if reqCount > 1 then
    local thisChance = 100 / junkCount
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, chance = thisChance})
else
  end
table.insert(resultPart, emptyRow)
end


  --Make sure to return nothing if there are no drop sources 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
  if Shared.tableCount(dropRows) == 0 then return '' end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
if reqCount > 2 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
else
table.insert(resultPart, emptyRow)
end


  table.sort(dropRows, function(a, b) return a.chance > b.chance end)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' Attack Bonus')
  for i, data in pairs(dropRows) do
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicAttackBonus', true))
    result = result..buildRow(data.source, data.type, data.minqty, data.qty, data.chance)
if reqCount > 3 then
  end
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[4]))
 
else
  result = result..'\r\n|}'
table.insert(resultPart, emptyRow)
  return result
end
end


function p.getItemLootSourceTable(frame)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
  local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
  local item = p.getItem(itemName)
if reqCount > 4 then
  if item == nil then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[5]))
    return "ERROR: No item named "..itemName.." exists in the data module"
else
  end
table.insert(resultPart, emptyRow)
end
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Two Handed?')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. (p._getItemStat(item, 'isTwoHanded') and 'Yes' or 'No'))
if reqCount > 5 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
else
table.insert(resultPart, emptyRow)
end
--Add extra rows at the end for items that have more than 3 different requirements
if reqCount > 6 then
for i = 7, reqCount, 1 do
table.insert(resultPart,"\r\n|-")
table.insert(resultPart, emptyRow)
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
end
end


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


function p._getItemShopTable(item)
function p.getArmourStatsBox(frame)
  local result = '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Shop]] Purchase'
local itemName = frame.args ~= nil and frame.args[1] or frame
  result = result..'\r\n|-\r\n!style="text-align:right;"|Cost\r\n|'
local item = p.getItem(itemName)
  local cost = {}
if item == nil then
  local qty = '1'
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  if item.buysFor ~= nil then
end
    if item.buysFor > 0 then table.insert(cost, Icons.GP(item.buysFor)) end
  elseif item.slayerCost ~= nil then
    table.insert(cost, Icons.SC(item.slayerCost))
  elseif GloveTable[item.name] ~= nil then
    table.insert(cost, Icons.GP(GloveTable[item.name].cost))
    qty = ' +'..Shared.formatnum(GloveTable[item.name].charges)..' Charges'
  end
  if item.buysForLeather ~= nil then
    table.insert(cost, Icons.Icon({'Leather', type='item', qty=item.buysForLeather}))
  end
  if item.buysForItems ~= nil then
    for i, row in pairs(item.buysForItems) do
      local mat = p.getItemByID(row[1])
      table.insert(cost, Icons.Icon({mat.name, type='item', qty=row[2]}))
    end
  end
  if Shared.tableCount(cost) == 0 then
    --If no cost is set, return an empty string
    return ''
  else
    result = result..table.concat(cost, '<br/>')
  end


  --For right now, only have requirements on Skillcapes
local ico = {
  result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements\r\n|'
["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
  if item.name == 'Cape of Completion' then
["Combat"] = Icons.Icon({'Combat', notext=true}),
    result = result..'100% Completion Log'
["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
  elseif item.name == 'Max Skillcape' then
["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
    result = result..'Level 99 in all [[Skills]]'
["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
  elseif Shared.contains(item.name, 'Skillcape') then
["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
    local skillName = Shared.splitString(item.name)[1]
["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
    result = result..Icons._SkillReq(skillName, 99)
}
  else
    result = result..'None'
local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
  end
local emptyRow = '\r\n!colspan="2"|'
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Armour Stats')
table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')


  result = result..'\r\n|-\r\n!style="text-align:right;"|Quantity\r\n|'..qty
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeStrengthBonus', true))
  return result
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))


function p.getItemShopTable(frame)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
  local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'stabAttackBonus', 0))
  local item = p.getItem(itemName)
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
  if item == nil then
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')
    return "ERROR: No item named "..itemName.." exists in the data module"
  end


  return p._getItemShopTable(item)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))


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


function p.getItemUpgradeTable(frame)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
  local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
  local item = p.getItem(itemName)
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end


  return p._getItemUpgradeTable(item)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
if reqCount > 0 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
else
table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
end


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


function p.getItemSuperheatTable(frame)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
  local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
  local item = p.getItem(itemName)
if reqCount > 2 then
  if item == nil then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
    return "ERROR: No item named "..itemName.." exists in the data module"
else
  end
table.insert(resultPart, emptyRow)
end
--Add extra rows at the end for items that have more than 3 different requirements
if reqCount > 3 then
for i = 4, reqCount, 1 do
table.insert(resultPart, "\r\n|-")
table.insert(resultPart, emptyRow)
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
end
end


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


function p._getItemSourceTables(item)
function p.getItemDataExport(frame)
  local result = ''
local resultTable = mw.html.create('table')
  local shopTable = p._getItemShopTable(item)
resultTable:addClass('wikitable')
  if string.len(shopTable) > 0 then
resultTable:tag('tr'):addClass('headerRow-0')
    result = result..'===Shop===\r\n'..shopTable
:tag('th'):wikitext('ItemID'):done()
  end
:tag('th'):wikitext('ItemName'):done()
 
:tag('th'):wikitext('GPValue'):done()
  local creationTable = p._getCreationTable(item)
  if string.len(creationTable) > 0 then
    if string.len(result) > 0 then result = result..'\r\n' end
    result = result..'===Creation===\r\n'..creationTable
  end
 
  local upgradeTable = p._getItemUpgradeTable(item)
  if string.len(upgradeTable) > 0 then
    if string.len(result) > 0 then result = result..'\r\n' end
    if string.len(creationTable) == 0 then result = result..'===Creation===\r\n' end
    result = result..upgradeTable
  end


  if item.type == 'Bar' then
for i, item in ipairs(GameData.rawData.items) do
    result = result..'\r\n==='..Icons.Icon({'Alt Magic', type='skill'})..'===\r\n'..p._getItemSuperheatTable(item)
resultTable:tag('tr')
  end
:tag('td'):wikitext(item.id):done()
 
:tag('td'):wikitext(item.name):done()
  local lootTable = p._getItemLootSourceTable(item)
:tag('td'):wikitext(item.sellsFor):done()
  if string.len(lootTable) > 0 then
end
    if string.len(result) > 0 then result = result..'\r\n' end
return tostring(resultTable)
    result = result..'===Loot===\r\n'..lootTable
  end
  return result
end
end


function p.getItemSourceTables(frame)
--Returns the expansion icon for the item if it has one
  local itemName = frame.args ~= nil and frame.args[1] or frame
function p.getExpansionIcon(frame)
  local item = p.getItem(itemName)
local itemName = frame.args ~= nil and frame.args[1] or frame
  if item == nil then
local item = p.getItem(itemName)
    return "ERROR: No item named "..itemName.." exists in the data module"
if item == nil then
  end
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end


  return p._getItemSourceTables(item)
return Icons.getExpansionIcon(item.id)
end
end


function p.getEquipmentTable(frame)
function p.buildSmithableArmourNav(frame)
  local args = frame.args ~= nil and frame.args or frame
local resultPart = {}
  local type = args.type
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
  local tier = args.tier
table.insert(resultPart, '\r\n!colspan = 2 style="background-color:#275C87;color:#FFFFFF;min-width:730px;"|')
  local slotStr = args.slot
table.insert(resultPart, Icons.Icon({'Smithing', type='skill', notext=true}))
  local ammoTypeStr = args.ammoType
table.insert(resultPart, ' Smithable Armour Sets')
  local category = args.category ~= nil and args.category or 'Combat'
 
  --Find out what Ammo Type we're working with
  local ammoType = nil
  if ammoTypeStr ~= nil then
    if ammoTypeStr == "Arrows" then
      ammoType = 0
    elseif ammoTypeStr == 'Bolts' then
      ammoType = 1
    elseif ammoTypeStr == 'Javelins' then
      ammoType = 2
    elseif ammoTypeStr == 'Throwing Knives' then
      ammoType = 3
    end
  end
 
  --Find out what slot we're working with
  local slot = nil
  if slotStr ~= nil then
    slot = Constants.equipmentSlot[slotStr]
  end
  mw.log("Type = "..(type ~= nil and type or '')..", Slot = "..(slot ~= nil and slot or '')..", AmmoType = "..(ammoType ~= nil and ammoType or ''))
 
 
  --Getting some lists set up here that will be used later
  --First, the list of columns used by both weapons & armour
  local statColumns = {'stabAttackBonus', 'slashAttackBonus','blockAttackBonus','rangedAttackBonus', 'magicAttackBonus', 'strengthBonus', 'rangedStrengthBonus', 'magicDamageBonus', 'defenceBonus', 'rangedDefenceBonus', 'magicDefenceBonus', 'damageReduction', 'attackLevelRequired', 'defenceLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'}
  --Then the lists for just weapons/just armour
  --Then the list of weapon types
  local weaponTypes = {'Magic Staff', 'Magic Wand', 'Ranged Weapon', 'Weapon'}
 
  local isWeaponType = Shared.contains(weaponTypes, type)
 
  --Now we need to figure out which items are in this list
  local itemList = {}
  for i, itemBase in pairs(ItemData.Items) do
    local item = Shared.clone(itemBase)
    item.id = i - 1
    local listItem = false
    if isWeaponType then
    listItem = item.type == type and item.category == category
      if ammoType ~= nil then listItem = listItem and item.ammoTypeRequired == ammoType end
    else
      --Now for handling armour
      if type == "Armour" or type == "Melee" then
        listItem = item.defenceLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Armour')
      elseif type == "Ranged Armour" or type == "Ranged" then
        listItem = item.rangedLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Ranged Armour')
      elseif type == "Magic Armour" or type == "Magic" then
        listItem = item.magicLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Magic Armour')
      else
        listItem = item.type == type and item.category ~= 'Combat'
      end
      if ammoType ~= nil then listItem = listItem and item.ammoType == ammoType end
      if slot ~= nil then listItem = listItem and item.equipmentSlot == slot end
    end
    if listItem then
      table.insert(itemList, item)
    end
  end


  --Now that we have a preliminary list, let's figure out which columns are irrelevant (IE are zero for all items in the selection)
local metalTypes = {'Bronze', 'Iron', 'Steel', 'Mithril', {'Adamant', 'Adamantite'}, {'Rune', 'Runite'}, {'Dragon', 'Dragonite'},
 
{'Corundum', 'Corundumite', TotH = true}, {'Augite', 'Augite', TotH = true}, {'Divine', 'Divinite', TotH = true}}
  mw.log(table.concat(statColumns, ', '))
local pieces = {"Helmet", "Platebody", "Platelegs", "Boots", "Shield"}
  local ignoreColumns = Shared.clone(statColumns)
for i, metal in ipairs(metalTypes) do
  for i, item in pairs(itemList) do
local metalName, barName
    local ndx = 1
local isTotH = false
    while Shared.tableCount(ignoreColumns) >= ndx do
if type(metal) == 'table' then
      if p._getItemStat(item, ignoreColumns[ndx], true) ~= 0 then
metalName = metal[1]
        table.remove(ignoreColumns, ndx)
barName = metal[2]..' Bar'
      else
isTotH = metal.TotH ~= nil and metal.TotH
        ndx = ndx + 1
else
      end
metalName = metal
    end
barName = metal..' Bar'
  end
end
  mw.log(table.concat(ignoreColumns, ', '))
table.insert(resultPart, '\r\n|-\r\n!')
if isTotH then
table.insert(resultPart, Icons.TotH())
end
table.insert(resultPart, Icons.Icon({barName, type="item", notext=true}))
table.insert(resultPart, " "..metalName)
table.insert(resultPart, "\r\n|")


  --Now to remove the ignored columns (and also we need to track groups like defence bonuses to see how many remain)
for j, piece in ipairs(pieces) do
  local attBonusCols = 5
if j > 1 then
  local strBonusCols = 2
table.insert(resultPart, ' ')
  local defBonusCols = 3
end
  local lvlReqCols = 4
table.insert(resultPart, '<span style="display:inline-block">')
  local ndx = 1
table.insert(resultPart, Icons.Icon({metalName..' '..piece, piece, type='item'}))
  while Shared.tableCount(statColumns) >= ndx do
if isTotH then
    local colName = statColumns[ndx]
table.insert(resultPart, ' '..Icons.Icon({'(I) '..metalName..' '..piece, '(I)', type='item'}))
    if Shared.contains(ignoreColumns, colName) then
table.insert(resultPart, ' '..Icons.Icon({'(P) '..metalName..' '..piece, '(P)', type='item'}))
      mw.log('Removing '..colName..' ('..ndx..')')
else
      if Shared.contains(colName, 'AttackBonus') then attBonusCols = attBonusCols - 1 end
table.insert(resultPart, ' '..Icons.Icon({'(S) '..metalName..' '..piece, '(S)', type='item'}))
      if Shared.contains(colName, 'trengthBonus') then strBonusCols = strBonusCols - 1 end
table.insert(resultPart, ' '..Icons.Icon({'(G) '..metalName..' '..piece, '(G)', type='item'}))
      if Shared.contains(colName, 'efenceBonus') then defBonusCols = defBonusCols - 1 end
end
      if Shared.contains(colName, 'LevelRequired') then lvlReqCols = lvlReqCols - 1 end
table.insert(resultPart, '</span>')
      table.remove(statColumns, ndx)
end
    else
end
      ndx = ndx + 1
    end
  end
  mw.log(table.concat(statColumns, ', '))
 
 
  --Alright, let's start the table by building the shared header
  local result = '{| class="wikitable sortable stickyHeader"\r\n|-class="headerRow-0"'
  if isWeaponType then
    --Weapons have extra columns here for Attack Speed and "Two Handed?"
    result = result..'\r\n!colspan="4"|'
  else
    result = result..'\r\n!colspan="2"|'
  end
  if attBonusCols > 0 then
    result = result..'\r\n!colspan="'..attBonusCols..'"style="padding:0 0.5em 0 0.5em;"|Attack Bonus'
  end
  if strBonusCols > 0 then
    result = result..'\r\n!colspan="'..strBonusCols..'"style="padding:0 0.5em 0 0.5em;"|Strength Bonus'
  end
  if Shared.contains(statColumns, 'magicDamageBonus') then
    result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|% Damage Bonus'
  end
  if defBonusCols > 0 then
    result = result..'\r\n!colspan="'..defBonusCols..'"style="padding:0 0.5em 0 0.5em;"|Defence Bonus'
  end
  if Shared.contains(statColumns, 'damageReduction') then
    result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|Damage Reduction'
  end
  if lvlReqCols > 0 then
    result = result..'\r\n!colspan="'..lvlReqCols..'"style="padding:0 0.5em 0 0.5em;"|Levels Required'
  end
  result = result..'\r\n!colspan="1"|'
  --One header row down, one to go
  result = result..'\r\n|-class="headerRow-1"'
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Item'
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Name'
  --Weapons have Attack Speed here
  if isWeaponType then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Attack Speed'
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Two Handed?'
  end
  --Attack bonuses
  if Shared.contains(statColumns, 'slashAttackBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'stabAttackBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'blockAttackBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'rangedAttackBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'magicAttackBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  end
  --Strength bonuses
  if Shared.contains(statColumns, 'strengthBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'rangedStrengthBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'magicDamageBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  end
  --Defence bonuses
  if Shared.contains(statColumns, 'defenceBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'rangedDefenceBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'magicDefenceBonus') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'damageReduction') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  --Level requirements
  if Shared.contains(statColumns, 'attackLevelRequired') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'defenceLevelRequired') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'rangedLevelRequired') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  end
  if Shared.contains(statColumns, 'magicLevelRequired') then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  end
  --And finally Sources
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Sources'


  table.sort(itemList, function(a, b) return a.id < b.id end)
table.insert(resultPart, '\r\n|}')
  for i, item in pairs(itemList) do
return table.concat(resultPart)
    if isWeaponType then
      --Building rows for weapons
      result = result..'\r\n|-'
      result = result..'\r\n|style ="text-align: left;padding: 0 0 0 0;"|'..Icons.Icon({item.name, type='item', size=50, notext=true})
      result = result..'\r\n|style ="text-align: left;padding: 0 0.5em 0 0.5em;"|[['..item.name..']]'
      result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;" |'..Shared.formatnum(item.attackSpeed)
      --That's the first list out of the way, now for 2-Handed
      result = result..'\r\n| style ="text-align: right;"|'
      if item.isTwoHanded then result = result..'Yes' else result = result..'No' end
      for j, statName in pairs(statColumns) do
        local statValue = p._getItemStat(item, statName, true)
        result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
        if not Shared.contains(statName, 'LevelRequired') then
          if statValue > 0 then
            result = result..'background-color:lightgreen;'
          elseif statValue < 0 then
            result = result..'background-color:lightpink;'
          end
        end
        result = result..'"|'..Shared.formatnum(statValue)
        if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
      end
      --Finally, the Sources
      result = result..'\r\n| style ="text-align: right;white-space: nowrap;padding: 0 0.5em 0 0.5em;" |'
      result = result..p._getItemSources(item)
    else
      --Building rows for armour
      result = result..'\r\n|-'
      result = result..'\r\n|style ="text-align: left;padding: 0 0 0 0;"|'..Icons.Icon({item.name, type='item', size=50, notext=true})
      result = result..'\r\n|style ="text-align: left;padding: 0 0.5em 0 0.5em;"|[['..item.name..']]'
      for j, statName in pairs(statColumns) do
        local statValue = p._getItemStat(item, statName, true)
        result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
        if statValue > 0 then
          result = result..'background-color:lightgreen;'
        elseif statValue < 0 then
          result = result..'background-color:lightpink;'
        end
        result = result..'"|'..Shared.formatnum(statValue)
        if statName == 'magicDamageBonus' or statName == 'damageReduction' then result = result..'%' end
      end
      --Finally, the Sources
      result = result..'\r\n| style ="text-align: right;white-space: nowrap;padding: 0 0.5em 0 0.5em;" |'
      result = result..p._getItemSources(item)
    end
  end
 
  result = result..'\r\n|}'
  return result
end
end


function p._getItemCategories(item)
function p.buildCraftableArmourNav(frame)
  local result = ''
local resultPart = {}
  if item.category ~= nil then result = result..'[[Category:'..item.category..']]' end
table.insert(resultPart, '{| class="wikitable mw-collapsible"')
  if item.type ~= nil then result = result..'[[Category:'..item.type..']]' end
table.insert(resultPart, '\r\n!colspan = 2 style="background-color:#275C87;color:#FFFFFF;min-width:730px;"|')
  return result
table.insert(resultPart, Icons.Icon({'Crafting', type='skill', notext=true}))
end
table.insert(resultPart, ' Craftable Armour Sets')
 
function p.getItemCategories(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = p.getItem(itemName)
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end
 
  return p._getItemCategories(item)
end


function p.getSpecialFishingTable(frame)
local leatherTypes = {'Leather', 'Hard Leather'}
  local lootValue = 0
local leatherPieces = {"Cowl", "Body", "Chaps", "Gloves", "Vambraces", "Boots"}
  local totalWt = specialFishWt
table.insert(resultPart, '\r\n|-\r\n!')
table.insert(resultPart, Icons.Icon({'Leather', type='item', notext=true}))
table.insert(resultPart, ' Leather')
for i, material in pairs(leatherTypes) do
if i > 1 then table.insert(resultPart, '\r\n|-\r\n!Hard Leather') end
table.insert(resultPart, '\r\n|')
for j, piece in ipairs(leatherPieces) do
if j > 1 then
table.insert(resultPart, ' • ')
end
table.insert(resultPart, Icons.Icon({material..' '..piece, piece, type='item'}))
end
end


  local result = ''
local materialTypes = {{'Green D-hide', 'Green Dragonhide'}, {'Blue D-hide', 'Blue Dragonhide'}, {'Red D-hide', 'Red Dragonhide'}, {'Black D-hide', 'Black Dragonhide'},
  result = result..'\r\n{|class="wikitable sortable"'
{'Elderwood', 'Elderwood Logs', TotH = true}, {'Revenant', 'Revenant Logs', TotH = true}, {'Carrion', 'Carrion Logs', TotH = true}}
  result = result..'\r\n!Item'
local pieces = {"Body", "Chaps", "Vambraces", "Shield"}
  result = result..'!!Price!!colspan="2"|Chance'
for i, material in ipairs(materialTypes) do
local isTotH = false
local craftName = material[1]
local matName = material[2]
isTotH = material.TotH ~= nil and material.TotH
table.insert(resultPart, '\r\n|-\r\n!')
if isTotH then
table.insert(resultPart, Icons.TotH())
end
table.insert(resultPart, Icons.Icon({matName, type="item", notext=true}))
table.insert(resultPart, " "..craftName)
table.insert(resultPart, "\r\n|")


  --Sort the loot table by weight in descending order
for j, piece in ipairs(pieces) do
  table.sort(specialFishLoot, function(a, b) return a[2] > b[2] end)
if j > 1 then
  for i, row in pairs(specialFishLoot) do
table.insert(resultPart, ' • ')
    local thisItem = p.getItemByID(row[1])
end
    result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
table.insert(resultPart, '<span style="display:inline-block">')
    result = result..'||style="text-align:left" data-sort-value="'..thisItem.sellsFor..'"'
table.insert(resultPart, Icons.Icon({craftName..' '..piece, piece, type='item'}))
    result = result..'|'..Icons.GP(thisItem.sellsFor)
table.insert(resultPart, ' '..Icons.Icon({'(U) '..craftName..' '..piece, '(U)', type='item'}))
table.insert(resultPart, '</span>')
end
end


    local dropChance = (row[2] / totalWt) * 100
table.insert(resultPart, '\r\n|}')
    result = result..'||style="text-align:right" data-sort-value="'..row[2]..'"'
return table.concat(resultPart)
    result = result..'|'..Shared.fraction(row[2], totalWt)
 
    result = result..'||style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%'
    lootValue = lootValue + (dropChance * 0.01 * thisItem.sellsFor)
  end
  result = result..'\r\n|}'
  result = result..'\r\nThe average value of a roll on the special fishing loot table is '..Icons.GP(Shared.round(lootValue, 2, 0))
 
  return result
end
end


function p.getShopSkillcapeTable()
function p.getLifestealWeapons()
  local result = ''
local items = p.getItems(function(item)  
 
if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
  local capeList = {}
for i, spAttID in ipairs(item.specialAttacks) do
  for i, item in pairs(ItemData.Items) do
local spAtt = GameData.getEntityByID('attacks', spAttID)
    if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then
if spAtt ~= nil then
      table.insert(capeList, item)
return spAtt.lifesteal > 0
    end
end
  end
end
 
end
  result = result..'\r\n{|class="wikitable sortable"'
return false
  result = result..'\r\n!colspan="2" style="width:200px"|Cape'
end)
  result = result..'!!Description!!Price'
 
for i, item in ipairs(items) do
  --Sort the table by cost and then name
mw.log(item.name)
  table.sort(capeList, function(a, b)  
end
                        if a.buysFor == b.buysFor then
                          return a.name < b.name
                        else
                          return a.sellsFor < b.buysFor
                        end
                      end)
  for i, thisItem in pairs(capeList) do
    result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item', size='50', notext=true})
    result = result..'||[['..thisItem.name..']]'
    result = result..'\r\n||'..thisItem.description
    result = result..'||style="text-align:left" data-sort-value="'..thisItem.buysFor..'"'
    result = result..'|'..Icons.GP(thisItem.buysFor)
  end
  result = result..'\r\n|}'
 
  return result
end
end


return p
return p

Revision as of 12:22, 27 March 2024

Lua module for generating various item tables. Pulls data from Module:GameData/data


--This module contains all sorts of functions for getting data on items
--Several functions related to use tables can be found at Module:Items/UseTables
--Functions related to source tables can be found at Module:Items/SourceTables
--Other functions moved to Module:Items/ComparisonTables

local p = {}

local GameData = require('Module:GameData')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')

p.EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon', 'Easter Egg',
				'Abnormal Log', 'Red Herring', 'Cool Glasses'}
p.EventItems = {'Christmas Cracker', 'Christmas Coal', 'Christmas Sweater',
				'Christmas Wreath', 'Candy Cane', 'Santa Hat',
				'Friendship Bracelet', 'Event Clue 1', 'Event Clue 2',
				'Event Clue 3', 'Event Clue 4', 'Candle', 'Cake Base',
				'Magical Flavouring', 'Magical Icing', 'Birthday Cake',
				'Purple Party Hat', 'Birthday Token', 'Christmas Present (Yellow)',
				'Christmas Present (Blue)', 'Christmas Present (Green)', 'Christmas Present (White)',
				'Christmas Present (Purple)', 'Christmas Present (Standard)', 'Event Token - Holiday 2021',
				'Holiday Scarf', 'Gingerbread House', 'Gingerbread Man', 'Edible Candy Cane',
				'Locked Chest', 'Locked Chest Key', 'Event Token (Holiday 2021)'}

function p.getItemByID(ID)
	return GameData.getEntityByID('items', ID)
end

function p.getItem(name)
	name = string.gsub(name, "%%27", "'")
	name = string.gsub(name, "&#39;", "'")
	return GameData.getEntityByName('items', name)
end

function p.getItems(checkFunc)
	return GameData.getEntities('items', checkFunc)
end

function p._canItemUseSlot(item, equipSlot)
	--Function to easily check if an item can fit in a given equipment slot
	--Ex: p._canItemUseSlot({Bronze Platebody}, 'Platebody') returns true
	if type(item) == 'string' then
		item = p.getItem(item)
	end
	return item.validSlots ~= nil and Shared.contains(item.validSlots, equipSlot)
end

function p._getItemEquipSlot(item)
	--Function to return the (non-Passive) equipment slot that an item occupies
	if type(item) == 'string' then
		item = p.getItem(item)
	end
	if item == nil or item.validSlots == nil then
		return 'Invalid'
	end
	for i, slot in pairs(item.validSlots) do
		if slot ~= 'Passive' then
			return slot
		end
	end
end

function p._getItemStat(item, StatName, ZeroIfNil)
	local result = item[StatName]
	--Special Overrides:
	-- Equipment stats first
	if item.equipmentStats ~= nil and item.equipmentStats[StatName] ~= nil then
		result = item.equipmentStats[StatName]
	elseif StatName == 'attackSpeed' and item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon') then
		-- Item can be equipped as a weapon but has no attack speed, so use default of 4000ms
		result = 4000
	elseif StatName == 'isTwoHanded' then
		if item.validSlots ~= nil and item.occupiesSlots ~= nil then
			result = Shared.contains(item.validSlots, 'Weapon') and Shared.contains(item.occupiesSlots, 'Shield')
		else
			result = false
		end
	elseif string.find(StatName, '^(.+)LevelRequired$') ~= nil and item.equipRequirements ~= nil then
		local skillName = Shared.titleCase(string.match(StatName, '^(.+)LevelRequired$'))
		if skillName ~= nil then
			local skillID = Constants.getSkillID(skillName)
			if skillID ~= nil then
				for i, requirement in ipairs(item.equipRequirements) do
					if requirement.type == "SkillLevel" and requirement.skillID == skillID then
						result = requirement.level
						break
					end
				end
			end
		end
	elseif StatName == 'attackType' then
		result = p._getWeaponAttackType(item)
	elseif StatName == 'description' then
		result = item.customDescription
		if result == nil or result == '' then result = 'No Description' end
	elseif StatName == 'completionReq' then
		if item.ignoreCompletion == nil or not item.ignoreCompletion then
			result = 'Yes'
		else
			result = 'No'
		end
	elseif StatName == 'slayerBonusXP' then
		return p._getItemModifier(item, 'increasedSkillXP', 'Slayer', false)
	elseif StatName == 'hasCombatStats' then
		return tostring(p.hasCombatStats(item) or p._hasLevelRequirements(item))
	elseif StatName == 'category' then
		-- Some categories have a namespace for some reason, remove it
		local _, localID = GameData.getLocalID(result)
		return localID
	end
	if result == nil and ZeroIfNil then result = 0 end
	return result
end

function p.getItemValue(item)
	if type(item) == 'string' then
		-- Specific check if the item is GP (value of 1)
		if Shared.compareString('GP', item, true) 
		or Shared.compareString('Gold Pieces', item, true) then
			return 1
		end

		item = p.getItem(item)
	end
	
	if item then
		return item.sellsFor
	end
	
	return nil
end

function p.getItemStat(frame)
	local args = frame.args ~= nil and frame.args or frame
	local ItemName = args[1]
	local StatName = args[2]
	local ZeroIfNil = args.ForceZero ~= nil and args.ForceZero ~= '' and args.ForceZero ~= 'false'
	local formatNum = args.formatNum ~= nil and args.formatNum ~= '' and args.formatNum ~= 'false'
	local item = p.getItem(ItemName)
	if item == nil then
		return Shared.printError('No item named "' .. ItemName .. '" exists in the data module')
	end
	local result = p._getItemStat(item, StatName, ZeroIfNil)
	if formatNum then result = Shared.formatnum(result) end
	return result
end

--Gets the value of a given modifier for a given itemg
--asString is false by default, when true it writes the full bonus text
function p._getItemModifier(item, modifier, skillID, asString)
	if asString == nil then asString = false end
	if skillID == '' then
		skillID = nil
	elseif string.find(skillID, ':') == nil then
		-- Try to find a skill ID if it looks like a skill name has been passed
		skillID = Constants.getSkillID(skillID)
	end

	local result = 0

	if item.modifiers ~= nil and item.modifiers[modifier] ~= nil then
		if type(item.modifiers[modifier]) == 'table' then
			for i, subVal in Shared.skpairs(item.modifiers[modifier]) do
				if subVal[1] == skillID then
					result = subVal[2]
					break
				end
			end
		else
			result = item.modifiers[modifier]
		end
	end

	if asString then
		if skillID ~= nil then
			return Constants._getModifierText(modifier, {skillID, result})
		else
			return Constants._getModifierText(modifier, result)
		end
	else
		return result
	end
end

function p.hasCombatStats(item)
	-- Checks if the combat stat is a valid, non-zero combat stat
	-- Ensure that, only in the case where the item is a Familar AND
	-- the checked stat is summoningMaxhit, the result is ignored.
	function isNonZeroStat(statName, statVal)
		if statName == 'summoningMaxhit' and (p._canItemUseSlot(item, 'Summon1') or p._canItemUseSlot(item, 'Summon2')) then
			return false
		end
		return statVal ~= 0
	end

	if item.equipmentStats ~= nil then
		-- Ensure at least one stat has a non-zero value
		for statName, statVal in pairs(item.equipmentStats) do
			if isNonZeroStat(statName, statVal) then
				return true
			end
		end
	end

	return false
end

function p._hasLevelRequirements(item)
	--Function true if an item has at least one level requirement to equip
	if item.equipRequirements ~= nil then
		for idx, requirement in ipairs(item.equipRequirements) do
			if requirement.type == 'SkillLevel' and requirement.level > 1 then
				return true
			end
		end
	end
	return false
end

function p.getItemModifier(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame[1]
	local modName = frame.args ~= nil and frame.args[2] or frame[2]
	local skillName = frame.args ~= nil and frame.args[3] or frame[3]
	local asString = frame.args ~= nil and frame.args[4] or frame[4]
	if asString ~= nil then
		asString = (string.upper(asString) ~= 'FALSE')
	end

	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	return p._getItemModifier(item, modName, skillName, asString)
end

function p._getWeaponAttackType(item)
	if (item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon')) or
		(item.occupiesSlots ~= nil and Shared.contains(item.occupiesSlots, 'Weapon')) then
		if Shared.contains({'melee', 'ranged', 'magic'}, item.attackType) then
			local iconType = item.attackType ~= 'melee' and 'skill' or nil
			return Icons.Icon({Shared.titleCase(item.attackType), type=iconType, nolink='true'})
		end
	end
	return 'Invalid'
end

function p.getWeaponAttackType(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end
	return p._getWeaponAttackType(item)
end

local statChangeDefs = {
	{
		stat = 'stabAttackBonus',
		suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Stab Bonus'
	},
	{
		stat = 'slashAttackBonus',
		suffix =  ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Slash Bonus'
	},
	{
		stat = 'blockAttackBonus',
		suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Block Bonus'
	},
	{
		stat = 'meleeStrengthBonus',
		suffix = ' ' .. Icons.Icon({'Strength', type='skill', notext=true}) .. ' Strength Bonus'
	},
	{
		stat = 'rangedStrengthBonus',
		suffix =  ' ' .. Icons.Icon({'Ranged', type='skill', notext=true}) .. ' Strength Bonus'
	},
	{
		stat = 'magicStrengthBonus',
		suffix = '% ' .. Icons.Icon({'Magic', type='skill', notext=true}) .. ' Damage Bonus'
	},
	{
		stat = 'meleeDefenceBonus',
		suffix = ' ' .. Icons.Icon({'Defence', type='skill', notext=true}) .. ' Defence Bonus' },
	{
		stat = 'rangedDefenceBonus',
		suffix = ' ' .. Icons.Icon({'Ranged', type='skill', notext=true}) .. ' Defence Bonus'
	},
	{
		stat = 'magicDefenceBonus',
		suffix = ' ' .. Icons.Icon({'Magic', type='skill', notext=true}) .. ' Defence Bonus'
	},
	{
		stat = 'damageReduction',
		suffix = '% Damage Reduction'
	},
	{
		stat = 'levelRequired',
		suffix = ' Level Required'
	}
}

-- Produces a list of stat & modifier changes between two items of equipmednt
function p.getStatChangeString(item1, item2)
	local changeArray = {}

	local equipStats = {
		type(item1.equipmentStats) == 'table' and item1.equipmentStats or {},
		type(item2.equipmentStats) == 'table' and item2.equipmentStats or {}
	}
	for i, statDef in ipairs(statChangeDefs) do
		local val1, val2 = 0, 0
		if statDef.stat == 'levelRequired' then
			-- Iterate over equipment stats for both items, determining level requirements
			local levelReqs = {}
			for itemNum, item in ipairs({item1, item2}) do
				levelReqs[itemNum] = {}
				if item.equipRequirements ~= nil then
					for j, req in ipairs(item.equipRequirements) do
						if req.type == 'SkillLevel' then
							levelReqs[itemNum][req.skillID] = req.level
						end
					end
				end
			end
			-- Iterate over all skills, checking if there are requirements for these in either skill
			for j, skillData in ipairs(GameData.rawData.skillData) do
				local skillID = skillData.skillID
				val1, val2 = levelReqs[1][skillID] or 0, levelReqs[2][skillID] or 0
				if val1 ~= val2 then
					table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. ' ' .. Icons.Icon({skillData.data.name, type='skill', notext=true}) .. (statDef.suffix or ''))
				end
			end
		else
			-- Equipment stats
			val1, val2 = equipStats[1][statDef.stat] or 0, equipStats[2][statDef.stat] or 0
			if val1 ~= val2 then
				table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. (statDef.suffix or ''))
			end
		end
	end

	-- Include differences in modifiers
	local modDiff = Constants.getModifiersText(Constants.getModifiersDifference(item2.modifiers, item1.modifiers))
	if modDiff ~= nil and modDiff ~= '' then
		table.insert(changeArray, modDiff)
	end

	return table.concat(changeArray, '<br/>')
end

function p._getOtherItemBoxText(item)
	local resultPart = {}
	--For equipment, show the slot they go in
	local isPassive = false
	if item.validSlots ~= nil then
		local slotLinkMap = {
			["Helmet"] = 'Helmets',
			["Platebody"] = 'Platebodies',
			["Platelegs"] = 'Platelegs',
			["Boots"] = 'Boots',
			["Weapon"] = 'Weapons',
			["Shield"] = 'Shields',
			["Amulet"] = 'Amulets',
			["Ring"] = 'Rings',
			["Gloves"] = 'Gloves',
			["Quiver"] = 'Ammunition',
			["Cape"] = 'Capes',
			["Consumable"] = 'Consumables',
			["Passive"] = 'Combat Passive Slot',
			["Summon1"] = 'Summoning',
			["Summon2"] = 'Summoning',
			["Gem"] = "Gems_(Equipment)"
		}
		local slotText = {}
		for i, slot in ipairs(item.validSlots) do
			local slotLink = slotLinkMap[slot]
			if slotLink == nil then
				table.insert(slotText, slot)
			else
				table.insert(slotText, '[[' .. slotLink .. '|' .. slot .. ']]')
			end
			
			if slot == 'Passive' then
				isPassive = true
			end
		end
		table.insert(resultPart, "\r\n|-\r\n|'''Equipment Slot:''' "..table.concat(slotText, ', '))
	end
	--For weapons with a special attack, show the details
	if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
		table.insert(resultPart, "\r\n|-\r\n|'''Special Attack:'''")
		for i, spAttID in ipairs(item.specialAttacks) do
			local spAtt = GameData.getEntityByID('attacks', spAttID)
			if spAtt ~= nil then
				local spAttChance = spAtt.defaultChance
				if type(item.overrideSpecialChances) == 'table' and item.overrideSpecialChances[i] ~= nil then
					spAttChance = item.overrideSpecialChances[i]
				end
				local spAttDesc = string.gsub(spAtt.description, '<Attack> ', '')
				table.insert(resultPart, '\r\n* ' .. spAttChance .. '% chance for ' .. spAtt.name .. ':')
				table.insert(resultPart, '\r\n** ' .. spAttDesc)
			end
		end
	end
	-- For Summoning combat familiars, show the max hit
	if item.equipmentStats ~= nil and item.equipmentStats.summoningMaxhit ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''Max Hit:''' " .. Shared.formatnum(item.equipmentStats.summoningMaxhit * 10))
	end
	--For potions, show the number of charges
	if item.charges ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''Charges:''' "..item.charges)
	end
	--For food, show how much it heals for
	if item.healsFor ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''Heals for:''' "..Icons.Icon({"Hitpoints", type="skill", notext="true"})..' '..(item.healsFor * 10))
	end
	--For Prayer Points, show how many you get
	if item.prayerPoints ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''"..Icons.Icon({'Prayer', type='skill'}).." Points:''' "..item.prayerPoints)
	end
	--For items that provide runes, show which runes are provided
	if item.providedRunes ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''Runes Provided:''' ")
		local runeLines = {}
		local sortVal = ''
		for j, runePair in pairs(item.providedRunes) do
			local runeID = runePair.id
			local qty = runePair.quantity
			local rune = p.getItemByID(runeID)
			sortVal = sortVal..rune.name..qty
			table.insert(runeLines, Icons.Icon({rune.name, type='item', qty=qty}))
		end
		table.insert(resultPart, table.concat(runeLines, ', '))
	end
	--For items with modifiers, show what those are
	if item.modifiers ~= nil and not Shared.tableIsEmpty(item.modifiers) then
		table.insert(resultPart, "\r\n|-\r\n|'''Modifiers:'''\r\n")
		if isPassive then
			table.insert(resultPart, '<span style="color:green">Passive:</span><br/>')
		end
		table.insert(resultPart, Constants.getModifiersText(item.modifiers, true, false, 10))
	end
	return table.concat(resultPart)
end

function p.getOtherItemBoxText(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	return p._getOtherItemBoxText(item)
end

function p._getItemCategories(item)
	local resultPart = {}
	local isEquipment = item.validSlots ~= nil or item.occupiesSlots ~= nil or item.equipmentStats ~= nil
	local category = p._getItemStat(item, 'category', false)
	if category ~= nil and category ~= 'Skills' then
		table.insert(resultPart, '[[Category:'..category..']]')
	end
	if item.type ~= nil then
		table.insert(resultPart, '[[Category:'..item.type..']]')
	end
	if isEquipment and item.tier ~= nil then
		table.insert(resultPart, '[[Category:'..Shared.titleCase(item.tier)..' '..item.type..']]')
	end
	if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
		table.insert(resultPart, '[[Category:Items With Special Attacks]]')
	end
	if item.validSlots ~= nil then
		local slotRemap = {
			['Passive'] = 'Passive Items',
			['Summon1'] = 'Summoning Familiars',
			['Summon2'] = ''
		}
		for i, slotName in ipairs(item.validSlots) do
			local slotRemapName = slotName
			if slotRemap[slotName] ~= nil then slotRemapName = slotRemap[slotName] end
			if slotRemapName ~= '' then table.insert(resultPart, '[[Category:' .. slotRemapName .. ']]') end
		end
	end
	if item.modifiers ~= nil then
		local modsDL = {
			'increasedChanceToDoubleLootCombat',
			'decreasedChanceToDoubleLootCombat',
			'increasedChanceToDoubleLootThieving',
			'decreasedChanceToDoubleLootThieving',
			'increasedChanceToDoubleItemsGlobal',
			'decreasedChanceToDoubleItemsGlobal'
		}
		for modName, val in pairs(item.modifiers) do
			if Shared.contains(modsDL, modName) then
				table.insert(resultPart, '[[Category:Double Loot Chance Items]]')
				break
			end
		end
	end
	return table.concat(resultPart)
end

function p.getItemCategories(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	return p._getItemCategories(item)
end

function p.getItemGrid(frame)
	local resultPart = {}
	table.insert(resultPart, '{|')
	for i, item in ipairs(GameData.rawData.items) do
		if i % 17 == 1 then
			table.insert(resultPart, '\r\n|-\r\n|')
		else
			table.insert(resultPart, '||')
		end
		table.insert(resultPart, 'style="padding:3px"|'..Icons.Icon({item.name, type='item', notext=true, size='40'}))
	end
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

function p.getEquipRequirementRow(req)
	local result = ""
	if req.type == "SkillLevel" then
		local skillName = Constants.getSkillName(req.skillID)
		local skillIcon = Icons.Icon({skillName, type='skill', notext=true})
		result = '\r\n!style="text-align:right;"| '..skillIcon..' Level Required'
		result = result..'\r\n|style="text-align:right;"| '..req.level
	elseif req.type == "DungeonCompletion" then
		local dungeonName = GameData.getEntityByID("dungeons", req.dungeonID).name
		local dungeonIcon = Icons.Icon({dungeonName, type="dungeon", notext=true})
		result = '\r\n!style="text-align:right;"| '..dungeonIcon..' Completions'
		result = result..'\r\n|style="text-align:right;"| '..req.count
	elseif req.type == "Completion" then
		local ns = GameData.getEntityByName('namespaces', req.namespace)
		if ns == nil then
			return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid namespace for completion requirement "' .. req.namespace .. '"')
		else
			result = '\r\n!style="text-align:right;"| ' .. ns.displayName .. ' Completion'
			result = result .. '\r\n|style="text-align:right;"| ' .. req.percent .. '%'
		end
	else
		return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid equip requirement type "' .. req.type .. '"')
	end
		return result
end

function p.getWeaponStatsBox(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	local ico = {
		["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
		["Combat"] = Icons.Icon({'Combat', notext=true}),
		["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
		["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
		["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
		["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
		["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
	}
	
	local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
	local emptyRow = '\r\n!colspan="2"|'
	
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Weapon Stats')
	table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
	table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Speed')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Shared.round(p._getItemStat(item, 'attackSpeed', true) / 1000, 3, 1) .. 's')
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Type')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'attackType'))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeStrengthBonus', true))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'stabAttackBonus', true))
	table.insert(resultPart, '\r\n!style="text-align:right;border-bottom:solid thin black;"| ' .. ico['Magic'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;border-bottom:solid thin black;"| ' .. p._getItemStat(item, 'magicDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
	table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'blockAttackBonus', true))
	if reqCount > 0 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
	else
		table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
	if reqCount > 1 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
	else
		table.insert(resultPart, emptyRow)
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
	if reqCount > 2 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
	else
		table.insert(resultPart, emptyRow)
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicAttackBonus', true))
	if reqCount > 3 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[4]))
	else
		table.insert(resultPart, emptyRow)
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
	if reqCount > 4 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[5]))
	else
		table.insert(resultPart, emptyRow)
	end
	
	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Two Handed?')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. (p._getItemStat(item, 'isTwoHanded') and 'Yes' or 'No'))
	if reqCount > 5 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
	else
		table.insert(resultPart, emptyRow)
	end
	
	--Add extra rows at the end for items that have more than 3 different requirements
	if reqCount > 6 then
		for i = 7, reqCount, 1 do
			table.insert(resultPart,"\r\n|-")
			table.insert(resultPart, emptyRow)
			table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
		end
	end

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

function p.getArmourStatsBox(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	local ico = {
		["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
		["Combat"] = Icons.Icon({'Combat', notext=true}),
		["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
		["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
		["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
		["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
		["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
	}
	
	local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
	local emptyRow = '\r\n!colspan="2"|'
	
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Armour Stats')
	table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
	table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeStrengthBonus', true))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'stabAttackBonus', 0))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'blockAttackBonus', true))
	table.insert(resultPart, '\r\n!style="text-align:right;border-bottom:solid thin black;"| ' .. ico['Magic'] .. ' Defence Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;border-bottom:solid thin black;"| ' .. p._getItemStat(item, 'magicDefenceBonus', true))

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
	table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
	if reqCount > 0 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
	else
		table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicAttackBonus', true))
	if reqCount > 1 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
	else
		table.insert(resultPart, emptyRow)
	end

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
	if reqCount > 2 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
	else
		table.insert(resultPart, emptyRow)
	end
	
	--Add extra rows at the end for items that have more than 3 different requirements
	if reqCount > 3 then
		for i = 4, reqCount, 1 do
			table.insert(resultPart, "\r\n|-")
			table.insert(resultPart, emptyRow)
			table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
		end
	end

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

function p.getItemDataExport(frame)
	local resultTable = mw.html.create('table')
	resultTable:addClass('wikitable')
	resultTable:tag('tr'):addClass('headerRow-0')
		:tag('th'):wikitext('ItemID'):done()
		:tag('th'):wikitext('ItemName'):done()
		:tag('th'):wikitext('GPValue'):done()

	for i, item in ipairs(GameData.rawData.items) do
		resultTable:tag('tr')
			:tag('td'):wikitext(item.id):done()
			:tag('td'):wikitext(item.name):done()
			:tag('td'):wikitext(item.sellsFor):done()
	end
	return tostring(resultTable)
end

--Returns the expansion icon for the item if it has one
function p.getExpansionIcon(frame)
	local itemName = frame.args ~= nil and frame.args[1] or frame
	local item = p.getItem(itemName)
	if item == nil then
		return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
	end

	return Icons.getExpansionIcon(item.id)
end

function p.buildSmithableArmourNav(frame)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
	table.insert(resultPart, '\r\n!colspan = 2 style="background-color:#275C87;color:#FFFFFF;min-width:730px;"|')
	table.insert(resultPart, Icons.Icon({'Smithing', type='skill', notext=true}))
	table.insert(resultPart, ' Smithable Armour Sets')

	local metalTypes = {'Bronze', 'Iron', 'Steel', 'Mithril', {'Adamant', 'Adamantite'}, {'Rune', 'Runite'}, {'Dragon', 'Dragonite'},
						{'Corundum', 'Corundumite', TotH = true}, {'Augite', 'Augite', TotH = true}, {'Divine', 'Divinite', TotH = true}}
	local pieces = {"Helmet", "Platebody", "Platelegs", "Boots", "Shield"}
	for i, metal in ipairs(metalTypes) do
		local metalName, barName
		local isTotH = false
		if type(metal) == 'table' then
			metalName = metal[1]
			barName = metal[2]..' Bar'
			isTotH = metal.TotH ~= nil and metal.TotH
		else
			metalName = metal
			barName = metal..' Bar'
		end
		table.insert(resultPart, '\r\n|-\r\n!')
		if isTotH then
			table.insert(resultPart, Icons.TotH())
		end
		table.insert(resultPart, Icons.Icon({barName, type="item", notext=true}))
		table.insert(resultPart, " "..metalName)
		table.insert(resultPart, "\r\n|")

		for j, piece in ipairs(pieces) do
			if j > 1 then
				table.insert(resultPart, ' • ')
			end
			table.insert(resultPart, '<span style="display:inline-block">')
			table.insert(resultPart, Icons.Icon({metalName..' '..piece, piece, type='item'}))
			if isTotH then
				table.insert(resultPart, ' '..Icons.Icon({'(I) '..metalName..' '..piece, '(I)', type='item'}))
				table.insert(resultPart, ' '..Icons.Icon({'(P) '..metalName..' '..piece, '(P)', type='item'}))
			else
				table.insert(resultPart, ' '..Icons.Icon({'(S) '..metalName..' '..piece, '(S)', type='item'}))
				table.insert(resultPart, ' '..Icons.Icon({'(G) '..metalName..' '..piece, '(G)', type='item'}))
			end
			table.insert(resultPart, '</span>')
		end
	end

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

function p.buildCraftableArmourNav(frame)
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable mw-collapsible"')
	table.insert(resultPart, '\r\n!colspan = 2 style="background-color:#275C87;color:#FFFFFF;min-width:730px;"|')
	table.insert(resultPart, Icons.Icon({'Crafting', type='skill', notext=true}))
	table.insert(resultPart, ' Craftable Armour Sets')

	local leatherTypes = {'Leather', 'Hard Leather'}
	local leatherPieces = {"Cowl", "Body", "Chaps", "Gloves", "Vambraces", "Boots"}
	table.insert(resultPart, '\r\n|-\r\n!')
	table.insert(resultPart, Icons.Icon({'Leather', type='item', notext=true}))
	table.insert(resultPart, ' Leather')
	for i, material in pairs(leatherTypes) do
		if i > 1 then table.insert(resultPart, '\r\n|-\r\n!Hard Leather') end
		table.insert(resultPart, '\r\n|')
		for j, piece in ipairs(leatherPieces) do
			if j > 1 then
				table.insert(resultPart, ' • ')
			end
			table.insert(resultPart, Icons.Icon({material..' '..piece, piece, type='item'}))
		end
	end

	local materialTypes = {{'Green D-hide', 'Green Dragonhide'}, {'Blue D-hide', 'Blue Dragonhide'}, {'Red D-hide', 'Red Dragonhide'}, {'Black D-hide', 'Black Dragonhide'},
						{'Elderwood', 'Elderwood Logs', TotH = true}, {'Revenant', 'Revenant Logs', TotH = true}, {'Carrion', 'Carrion Logs', TotH = true}}
	local pieces = {"Body", "Chaps", "Vambraces", "Shield"}
	for i, material in ipairs(materialTypes) do
		local isTotH = false
		local craftName = material[1]
		local matName = material[2]
		isTotH = material.TotH ~= nil and material.TotH
		table.insert(resultPart, '\r\n|-\r\n!')
		if isTotH then
			table.insert(resultPart, Icons.TotH())
		end
		table.insert(resultPart, Icons.Icon({matName, type="item", notext=true}))
		table.insert(resultPart, " "..craftName)
		table.insert(resultPart, "\r\n|")

		for j, piece in ipairs(pieces) do
			if j > 1 then
				table.insert(resultPart, ' • ')
			end
			table.insert(resultPart, '<span style="display:inline-block">')
			table.insert(resultPart, Icons.Icon({craftName..' '..piece, piece, type='item'}))
			table.insert(resultPart, ' '..Icons.Icon({'(U) '..craftName..' '..piece, '(U)', type='item'}))
			table.insert(resultPart, '</span>')
		end
	end

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

function p.getLifestealWeapons()
	local items = p.getItems(function(item) 
		if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
			for i, spAttID in ipairs(item.specialAttacks) do
				local spAtt = GameData.getEntityByID('attacks', spAttID)
				if spAtt ~= nil then
					return spAtt.lifesteal > 0
				end
			end
		end
		return false
	end)
	
	for i, item in ipairs(items) do
		mw.log(item.name)
	end
end

return p