Module:Items: Difference between revisions

From Melvor Idle
(Added Shop table for item sources)
(getItemGrid: Amend to use new CSS class rather than per-cell styles)
(211 intermediate revisions by 9 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 Modifiers = require('Module:Modifiers')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Num = require('Module:Number')


local EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon'}
-- Used by p._getItemLevelReqs()
local OtherShopItems = {'Cooking Gloves', 'Mining Gloves', 'Gem Gloves', 'Smithing Gloves', 'Thieving Gloves'}
local LevelReqCache = {}
--This is hardcoded, so there's no easy way to scrape it. Hopefully it doesn't change
local GemTable = {["Topaz"] = {name = 'Topaz', id = 128, chance = 50},
                  ["Sapphire"] = {name = "Sapphire", id = 129, chance = 17.5},
                  ["Ruby"] = {name = "Ruby", id = 130, chance = 17.5},
                  ["Emerald"] = {name = "Emerald", id = 131, chance = 10},
                  ["Diamond"] = {name = "Diamond", id = 132, chance = 5}}
--The base chance to receive a gem while mining
local GemChance = .01
--The number of different fishing junk items
local junkCount = 8
--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 = 6721
local specialFishLoot = {{129, 2000}, {130, 1600}, {131, 1400}, {132, 1000}, {133, 400}, {668, 10}, {669, 10}, {671, 1}, {670, 50}, {121, 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..', '..item.fishingCatchWeight..'}')
    end
  end


  local result = 'local specialFishWt = '..totalWt..'\r\n'
p.EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon', 'Easter Egg',
  result = result..'local specialFishLoot = {'..table.concat(lootArray, ', ')..'}'
'Abnormal Log', 'Red Herring', 'Cool Glasses'}
  return result
p.EventItems = {'Christmas Cracker', 'Christmas Coal', 'Christmas Sweater',
end
'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)'}
-- List of item IDs that should typically not be included within outputs, usually
-- because they are not fully implemented despite existing within the game data
p.HiddenItems = {}


function p.getSpecialAttackByID(ID)
local function populateHiddenItems()
  local result = Shared.clone(ItemData.SpecialAttacks[ID + 1])
local hiddenItems = GameData.getEntities('items',
  if result ~= nil then
function(item)
    result.id = ID
return item.name == nil or Shared.contains({'melvorTotH:Meteorite_Dust'}, item.id)
  end
end
  return result
)
for _, item in ipairs(hiddenItems) do
table.insert(p.HiddenItems, item.id)
end
end
end
populateHiddenItems()


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 = Shared.fixPagename(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',
  --Special Overrides:
function(obj)
  if StatName == 'stabAttackBonus' then
return not Shared.contains(p.HiddenItems, obj.id) and checkFunc(obj)
    if item.attackBonus == nil then
end
      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


-- Given an item, returns a table containing the item's level requirements (if any)
-- The table structure is as follows:
-- { ['normal'] = { SkillID = SkillLevel, ... }, ['abyssal'] = { SkillID = SkillLevel, ... } }
-- If optional parameter 'condense' is specified as true, any normal requirements are excluded
-- from the output if there should exist an abyssal level requirement for the same skill
function p._getItemLevelReqs(item, condense)
local condenseResult = (type(condense) == 'boolean' and condense) or false
local reqTypeMap = {
['SkillLevel'] = 'normal',
['AbyssalLevel'] = 'abyssal'
}
local levelReqs = LevelReqCache[item.id]
if levelReqs == nil then
-- Item not in cache - obtain level requirements data
if item.equipRequirements == nil then
return nil
end
levelReqs = { ['normal'] = {}, ['abyssal'] = {} }
for _, requirement in ipairs(item.equipRequirements) do
local reqTypeKey = reqTypeMap[requirement.type]
if reqTypeKey ~= nil then
-- Requirement is a level requirement
local reqSkillID, reqLevel = requirement.skillID, requirement.level
local reqCurrentLevel = levelReqs[reqTypeKey][reqSkillID]
if reqCurrentLevel == nil or reqLevel > reqCurrentLevel then
levelReqs[reqTypeKey][reqSkillID] = reqLevel
end
end
end
-- Cache level requirements data for any subsequent lookups
LevelReqCache[item.id] = levelReqs
end


function p.getWeaponAttackType(frame)
if not condenseResult then
  local itemName = frame.args ~= nil and frame.args[1] or frame
-- Return requirements as is
  local item = p.getItem(itemName)
return levelReqs
  if item == nil then
else
    return "ERROR: No item named "..ItemName.." exists in the data module"
-- Exclude any normal level requirements should an abyssal requirement exist for
  end
-- the same skill
  return p._getWeaponAttackType(item)
local levelReqsCondensed = { ['normal'] = {}, ['abyssal'] = levelReqs.abyssal }
for skillID, reqLevel in pairs(levelReqs.normal) do
if levelReqs.abyssal[skillID] == nil then
levelReqsCondensed.normal[skillID] = reqLevel
end
end
return levelReqsCondensed
end
end
end


function p.getPotionTable(frame)
function p._getItemStat(item, StatName, ZeroIfNil)
  local potionName = frame.args ~= nil and frame.args[1] or frame
local result = item[StatName]
  local tiers = {'I', 'II', 'III', 'IV'}
--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
or string.find(StatName, '^(.+)AbyssalLevel$') ~= nil
) and item.equipRequirements ~= nil then
local levelType = 'normal'
local skillName = string.match(StatName, '^(.+)LevelRequired$')
if skillName == nil then
levelType = 'abyssal'
skillName = string.match(StatName, '^(.+)AbyssalLevel$')
end
if skillName ~= nil then
local skillID = Constants.getSkillID(Shared.titleCase(skillName))
if skillID ~= nil then
local levelReqs = p._getItemLevelReqs(item)
result = levelReqs ~= nil and levelReqs[levelType][skillID]
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


  local result = '{| class="wikitable"'
function p.getItemValueByID(itemID)
  result = result..'\r\n!Potion!!Tier!!Charges!!Effect'
local item = p.getItemByID(itemID)
if item == nil then
return 0
end
return p.getItemValue(item['name'])
end


  local tier1potion = p.getItem(potionName..' I')
function p.getItemValue(item)
  for i, tier in pairs(tiers) do
if type(item) == 'string' then
    local tierName = potionName..' '..tier
-- Specific check if the item is GP (value of 1)
    local potion = p.getItemByID(tier1potion.id + i - 1)
if Shared.compareString('GP', item, true)
    if potion == nil then
or Shared.compareString('Gold Pieces', item, true) then
      mw.log("Failed to get tier "..tier)
return 1
    else
end
      result = result..'\r\n|-'
      result = result..'\r\n|'..Icons.Icon({tierName, type='item', notext='true', size='60'})
      result = result..'||'..'[['..tierName..'|'..tier..']]'
      result = result..'||'..potion.potionCharges..'||'..potion.description
    end
  end


  result = result..'\r\n|}'
item = p.getItem(item)
  return result
end
if item then
return item.sellsFor
end
return nil
end
end


function p._getCreationTable(item)
function p.getValueText(item, minQuantity, maxQuantity)
  local skill = ''
local minQ, maxQ = 1, 1
  local specialReq = nil
if type(minQuantity) == 'number' then
  local time = 0
minQ = minQuantity
  local maxTime = nil
end
  local lvl = 0
if type(maxQuantity) == 'number' then
  local xp = 0
maxQ = maxQuantity
  local qty = nil
else
  local req = nil
maxQ = minQ
  local result = ''
end
local amt = item.sellsFor or 0
local currID = item.sellsForCurrency or 'melvorD:GP'
return Icons._Currency(currID, amt * minQ, amt * maxQ)
end


  local tables = {}
-- Function already exists, but without frame.
  --First figure out what skill is used to make this...
-- Giving it a slightly different name since function overloading doesn't exist
  if item.smithingLevel ~= nil then
function p.getItemSellsFor(frame)
    skill = 'Smithing'
local args = frame:getParent().args
    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
    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.miningLevel ~= nil then
    skill = 'Mining'
    lvl = item.miningLevel
    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


  if Shared.tableCount(tables) == 0 then
return p._getItemSellsFor(args[1], args[2], args.round)
    return ""
  else
    return table.concat(tables, '\r\n')
  end
end
end


function p.buildCreationTable(skill, lvl, xp, req, qty, time, maxTime, specialReq)
function p._getItemSellsFor(itemName, multiplier, rounding)
  if qty == nil then qty = 1 end
local itemValue = p.getItemValue(itemName)
  local result = '{|class="wikitable"'
multiplier = tonumber(multiplier) or 1
  if req ~= nil then
rounding = tonumber(rounding) or 0
    result = result..'\r\n!colspan="2"|Item Creation'
  else
if itemValue == nil then
    result = result..'\r\n!colspan="2"|Item Production'
error('No item named "' .. itemName .. '" exists in the data module')
  end
end
  result = result..'\r\n|-\r\n!style="text-align: right;"|Requirements'
  result = result..'\r\n|'..Icons._SkillReq(skill, lvl)
  if specialReq ~= nil then result = result..'<br/>'..specialReq end


  if req ~= nil then
return Num.round2(itemValue * multiplier, rounding)
    result = result..'\r\n|-\r\n!style="text-align: right;"|Materials\r\n|'
end
    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
  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
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 = Num.formatnum(result) end
return result
end
end


function p.getCreationTable(frame)
--Gets the value of a given modifier for a given item
  local itemName = frame.args ~= nil and frame.args[1] or frame
--asString is false by default, when true it writes the full bonus text
  local item = p.getItem(itemName)
function p._getItemModifier(item, modifier, skillID, asString)
  if item == nil then
if asString == nil then asString = false end
    return "ERROR: No item named "..itemName.." exists in the data module"
if skillID == '' then
  end
skillID = nil
 
elseif string.find(skillID, ':') == nil then
  return p._getCreationTable(item)
-- Try to find a skill ID if it looks like a skill name has been passed
end
skillID = Constants.getSkillID(skillID)
end


function p._getOtherItemBoxText(item)
local result = 0
  result = ''
  --For equipment, show the slot they go in
  if item.equipmentSlot ~= nil then
    for slotName, i in Shared.skpairs(Constants.equipmentSlot) do
      if i == item.equipmentSlot then
        result = result..'\r\n|-\r\n|Equipment Slot: '..slotName
        break
      end
    end
  end
  --For weapons with a special attack, show the details
  if item.hasSpecialAttack then
    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
  return result
end


function p.getOtherItemBoxText(frame)
if item.modifiers ~= nil and item.modifiers[modifier] ~= nil then
  local itemName = frame.args ~= nil and frame.args[1] or frame
if type(item.modifiers[modifier]) == 'table' then
  local item = p.getItem(itemName)
for i, subVal in Shared.skpairs(item.modifiers[modifier]) do
  local asList = false
if subVal[1] == skillID then
  if frame.args ~= nil then  
result = subVal[2]
    asList = frame.args.asList ~= nil and frame.args.asList ~= '' and frame.args.asList ~= 'false'
break
  end
end
  if item == nil then
end
    return "ERROR: No item named "..itemName.." exists in the data module"
else
  end
result = item.modifiers[modifier]
end
end


  return p._getOtherItemBoxText(item, asList)
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
end


function p._getItemSources(item, asList)
function p.hasCombatStats(item)
  local result = nil
-- Checks if the combat stat is a valid, non-zero combat stat
  local lineArray = {}
-- Ensure that, only in the case where the item is a Familar AND
-- the checked stat is summoningMaxhit, the result is ignored.
local 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


  --Alright, time to go through all the ways you can get an item...
if item.equipmentStats ~= nil then
  --First up: Can we kill somebody and take theirs?
-- Ensure at least one stat has a non-zero value
  local killStr = ''
for statName, statVal in pairs(item.equipmentStats) do
  local count1 = 0
if isNonZeroStat(statName, statVal) then
  for i, monster in pairs(MonsterData.Monsters) do
return true
    local isDrop = false
end
    if monster.bones == item.id then
end
      isDrop = true
end
    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
      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
  if string.len(killStr) > 0 then table.insert(lineArray, killStr) end


  --Next: Can we find it in a box?
return false
  --While we're here, check for upgrades, cooking, and growing
end
  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
          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
          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
          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
          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?
function p._hasLevelRequirements(item)
  local thiefStr = ''
--Function true if an item has at least one level requirement to equip
  for i, npc in pairs(SkillData.Thieving) do
local levelReqs = p._getItemLevelReqs(item)
    if npc.lootTable ~= nil then
if levelReqs ~= nil then
      for j, loot in pairs(npc.lootTable) do
for levelType, reqs in pairs(levelReqs) do
        if loot[1] == item.id then
if not Shared.tableIsEmpty(reqs) then
          if string.len(thiefStr) > 0 then
return true
            thiefStr = thiefStr..','..Icons.Icon({npc.name, type="thieving", notext="true"})
end
          else
end
            thiefStr = thiefStr..'Pickpocketing: '..Icons.Icon({npc.name, type="thieving", notext="true"})
end
          end
return false
        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
function p.getItemModifier(frame)
  --SmithCheck:
local itemName = frame.args ~= nil and frame.args[1] or frame[1]
  if item.smithingLevel ~= nil then
local modName = frame.args ~= nil and frame.args[2] or frame[2]
    table.insert(lineArray, Icons._SkillReq("Smithing", item.smithingLevel))
local skillName = frame.args ~= nil and frame.args[3] or frame[3]
  end
local asString = frame.args ~= nil and frame.args[4] or frame[4]
if asString ~= nil then
asString = (string.upper(asString) ~= 'FALSE')
end


  --CraftCheck:
local item = p.getItem(itemName)
  if item.craftingLevel ~= nil then
if item == nil then
    table.insert(lineArray, Icons._SkillReq("Crafting", item.craftingLevel))
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


  --FletchCheck:
return p._getItemModifier(item, modName, skillName, asString)
  if item.fletchingLevel ~= nil then
end
    table.insert(lineArray, Icons._SkillReq("Fletching", item.fletchingLevel))
  end


  --RunecraftCheck:
function p._getWeaponAttackType(item)
  if item.runecraftingLevel ~= nil then
if (item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon')) or
    table.insert(lineArray, Icons._SkillReq("Runecrafting", item.runecraftingLevel))
(item.occupiesSlots ~= nil and Shared.contains(item.occupiesSlots, 'Weapon')) then
  end
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


  --MineCheck:
function p.getWeaponAttackType(frame)
  if item.miningLevel ~= nil then
local itemName = frame.args ~= nil and frame.args[1] or frame
    table.insert(lineArray, Icons._SkillReq("Mining", item.miningLevel))
local item = p.getItem(itemName)
  end
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
return p._getWeaponAttackType(item)
end


  --FishCheck:
local statChangeDefs = {
  if (item.category == "Fishing" and (item.type == "Junk" or item.type == "Special")) then
{
    table.insert(lineArray, Icons._SkillReq("Fishing", 1))
stat = 'stabAttackBonus',
  elseif item.fishingLevel ~= nil then
suffix = ' ' .. Icons.Icon({'Melee', notext=true}) .. ' Stab Bonus'
    table.insert(lineArray, Icons._SkillReq("Fishing", item.fishingLevel))
},
  end
{
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'
}
}


  --HerbCheck:
-- Produces a list of stat & modifier changes between two items of equipmednt
  if item.herbloreMasteryID ~= nil then
function p.getStatChangeString(item1, item2)
    local potionData = SkillData.Herblore.ItemData[item.herbloreMasteryID + 1].herbloreLevel
local changeArray = {}
    table.insert(lineArray, Icons._SkillReq("Herblore", potionData))
  end


  --WoodcuttingCheck
local equipStats = {
  if item.type == 'Logs' then
type(item1.equipmentStats) == 'table' and item1.equipmentStats or {},
    local treeData = SkillData.Woodcutting[item.id + 1]
type(item2.equipmentStats) == 'table' and item2.equipmentStats or {}
    local lvl = treeData.level
}
    table.insert(lineArray, Icons._SkillReq("Woodcutting", lvl))
for i, statDef in ipairs(statChangeDefs) do
  end
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, Num.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, Num.numStrWithSign(val1 - val2) .. (statDef.suffix or ''))
end
end
end


  --Finally there are some weird exceptions:
-- Include differences in modifiers
  --Coal can be acquired via firemaking
-- TODO Implement getModifiersDifference
  if item.name == "Coal Ore" then
--local modDiff = Constants.getModifiersText(Constants.getModifiersDifference(item2.modifiers, item1.modifiers))
    table.insert(lineArray, Icons._SkillReq("Firemaking", 1))
local modDiff = nil
  end
if modDiff ~= nil and modDiff ~= '' then
table.insert(changeArray, modDiff)
end


  --Gems can be acquired from mining, fishing, and alt. magic
return table.concat(changeArray, '<br/>')
  if item.type == 'Gem' then
end
    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
function p._getOtherItemBoxText(item)
  if type == 'Bar' or Shared.contains(AltMagicProducts, item.name) then
local resultPart = {}
    table.insert(lineArray, Icons.Icon({"Alt. Magic", type='skill'}))
--For equipment, show the slot they go in
  end
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:''' " .. Num.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
if item.soulPoints ~= nil then
table.insert(resultPart, "\r\n|-\r\n|'''"..Icons.Icon({'Prayer', 'Soul', type='item', img='Lesser Soul'}).." Points:''' "..item.soulPoints)
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, Modifiers.getModifiersText(item.modifiers, true, false, 10))
end
return table.concat(resultPart)
end


  --Chapeau Noir & Bobby's Pocket are special Thieving items
function p.getOtherItemBoxText(frame)
  if item.name == "Chapeau Noir" or item.name == "Bobby&apos;s Pocket" then
local itemName = frame.args ~= nil and frame.args[1] or frame
    table.insert(lineArray, Icons._SkillReq("Thieving", 1))
local item = p.getItem(itemName)
  end
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end


  --Rhaelyx pieces are also special
return p._getOtherItemBoxText(item)
  if item.name == 'Jewel of Rhaelyx' then
end
    local rhaStr = 'Any action in: '
    rhaStr = rhaStr..Icons.Icon({'Firemaking', type = 'skill'})..', '..Icons.Icon({'Cooking', type = 'skill'})..', '..Icons.Icon({'Smithing', type = 'skill'})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Fletching', type = 'skill'})..', '..Icons.Icon({'Crafting', type = 'skill'})..', '..Icons.Icon({'Runecrafting', type = 'skill'})..',<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'})..', '..Icons.Icon({'Fishing', type = 'skill'})..', '..Icons.Icon({'Mining', type = 'skill'})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Thieving', type = 'skill'})..', '..Icons.Icon({'Farming', type = 'skill'})
    table.insert(lineArray, rhaStr)
  elseif item.name == 'Mysterious Stone' then
    local rhaStr = 'Any action in: '
    rhaStr = rhaStr..Icons.Icon({'Firemaking', type = 'skill'})..', '..Icons.Icon({'Cooking', type = 'skill'})..', '..Icons.Icon({'Smithing', type = 'skill'})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Fletching', type = 'skill'})..', '..Icons.Icon({'Crafting', type = 'skill'})..', '..Icons.Icon({'Runecrafting', type = 'skill'})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Herblore', type='skill'})..', '..Icons.Icon({'Woodcutting', type = 'skill'})..', '..Icons.Icon({'Fishing', type = 'skill'})..',<br/>'
    rhaStr = rhaStr..Icons.Icon({'Mining', type = 'skill'})..', '..Icons.Icon({'Thieving', type = 'skill'})..', '..Icons.Icon({'Farming', type = 'skill'})
    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)
function p._getCurrencyItemBoxText(item)
  if item.slayerCost ~= nil or item.buysFor ~= nil or Shared.contains(OtherShopItems, item.name) then
local span = mw.html.create('span')
    table.insert(lineArray, '[[Shop]]')
:wikitext("'''Sells For:''' ")
  end
:wikitext(Icons._Currency((item.sellsForCurrency or 'melvorD:GP'), item.sellsFor))
return span
end


  --Easter Eggs (manual list 'cause don't have a better way to do that)
function p.getCurrencyItemBoxText(frame)
  if Shared.contains(EasterEggs, item.name) then
local itemName = frame.args ~= nil and frame.args[1] or frame
    table.insert(lineArray, '[[Easter Eggs]]')
local item = p.getItem(itemName)
  end
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end


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


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


--Brute forcing some item uses to make things easier
function p.getItemGrid(frame)
local itemUseArray = {
--melvorF, melvorD, melvorTotH, melvorAoD
  Combat = {},
local dlcFunc = function(item, dlc)
  Cooking = {'Cooking Gloves', 'Crown of Rhaelyx'},
local itemDLC = Shared.getLocalID(item.id)
  Crafting = {'Crown of Rhaelyx'},
if dlc == nil then
  Farming = {'Compost', 'Weird Gloop', 'Bob&apos;s Rake'},
return true
  Firemaking = {'Crown of Rhaelyx'},
end
  Fishing = {'Amulet of Fishing', 'Message in a Bottle'},
  Fletching = {'Crown of Rhaelyx'},
if dlc == 'base' then
  Herblore = {'Crown of Rhaelyx'},
return itemDLC == 'melvorD' or itemDLC == 'melvorF'
  Mining = {'Mining Gloves', 'Gem Gloves'},
end
  Prayer = {},
  Runecrafting = {'Crown of Rhaelyx'},
if itemDLC == dlc then
  Slayer = {},
return true
  Smithing = {'Smithing Gloves', 'Crown of Rhaelyx'},
end
  Thieving = {'Chapeau Noir', 'Thieving Gloves'},
return false
  Woodcutting = {},
end
  }
local potionUseArray = {
-- Convert list of hidden items into a key/value structure, such that
  [0] = 'Combat',
-- lookups are more efficient than repeated calls to Shared.contains()
  [1] = 'Combat',
local hiddenItemIDs = {}
  [2] = 'Combat',
for i, itemID in ipairs(p.HiddenItems) do
  [3] = 'Combat',
hiddenItemIDs[itemID] = 1
  [4] = 'Combat',
end
  [5] = 'Combat',
  [6] = 'Combat',
  [7] = 'Woodcutting',
  [8] = 'Fishing',
  [9] = 'Firemaking',
  [10] = 'Cooking',
  [11] = 'Mining',
  [12] = 'Smithing',
  [13] = 'Thieving',
  [14] = 'Farming',
  [15] = 'Fletching',
  [16] = 'Crafting',
  [17] = 'Runecrafting',
  [18] = 'Herblore',
  [19] = 'Combat',
  [20] = 'Combat',
  [21] = 'Combat',
  [22] = 'Combat',
  [23] = 'Combat',
}


function p._getItemUses(item)
local args = frame:getParent().args
  local useArray = {}
local dlc = args[1] or args.DLC or args.dlc or nil
  --Another fun one. This time getting all the different possible ways an item can be used
local columns = tonumber(args[2] or args.Columns or args.columns) or 17
local html = mw.html.create('table')
:addClass('wikitable lighttable individual table-img-grid')


  --Before anything else, if this is a potion add it to the appropriate override section
local curRow = html:tag('tr')
  if item.herbloreMasteryID ~= nil then
local i = 0
    table.insert(itemUseArray[potionUseArray[item.herbloreMasteryID]], item.name)
for _, v in pairs(GameData.rawData.items) do
  end
if hiddenItemIDs[v.id] == nil and dlcFunc(v, dlc) == true then
if i >= columns then
curRow = html:tag('tr')
i = 0
end
local cell = curRow:tag('td')
:wikitext(Icons.Icon({v.name, type='item', notext=true, size='32'}))
-- Mod operator is slow. We use this instead
i = i + 1
end
end
return tostring(html)
end


  --First things added to the list are non-skill things that are easy to check
function p.getEquipRequirementRow(req)
  if item.equipmentSlot ~= nil or Shared.contains(itemUseArray.Combat, item.name) then
local result = ""
    table.insert(useArray, '* '..Icons.Icon({'Combat'}))
if (req.type == "SkillLevel" or req.type == "AbyssalLevel") then
  end
local pre = (req.type == "AbyssalLevel" and ' Abyssal') or ''
  if item.healsFor ~= nil then
local skillName = Constants.getSkillName(req.skillID)
    table.insert(useArray, '* [[Food]]')
local skillIcon = Icons.Icon({skillName, type='skill', notext=true})
  end
result = '\r\n!style="text-align:right;"| '..skillIcon..pre..' Level Required'
  if item.dropTable ~= nil then
result = result..'\r\n|style="text-align:right;"| '..req.level
    table.insert(useArray, '* [[Chest Drop Tables|Can Be Opened]]')  
elseif (req.type == "DungeonCompletion" or req.type == "AbyssDepthCompletion") then
  end
local reqDefns = {
["DungeonCompletion"] = {
["dataKey"] = 'dungeons',
["IDKey"] = 'dungeonID',
["imgType"] = 'dungeon'
},
["AbyssDepthCompletion"] = {
["dataKey"] = 'abyssDepths',
["IDKey"] = 'depthID',
["imgType"] = 'combatArea'
}
}
local reqDefn = reqDefns[req.type]
if reqDefn ~= nil then
local area = GameData.getEntityByID(reqDefn.dataKey, req[reqDefn.IDKey])
if area == nil then
result = '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid area for requirement type "' .. req.type .. '"')
else
local areaIcon = Icons.Icon({area.name, type=reqDefn.imgType, notext=true})
result = '\r\n!style="text-align:right;"| '..areaIcon..' Completions'
result = result..'\r\n|style="text-align:right;"| '..Num.formatnum(req.count)
end
end
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


  --Next, upgrading, crafting, herblore, fletching, and runecrafting since we have to sift through other items for these
function p._getItemResistance(item)
  local canUpgrade = false
local namespace = Shared.getLocalID(item.id)
  local canCraft = false
if namespace == 'melvorItA' then
  local canFletch = false
local eternalResistance = p._getItemStat(item, 'resistanceEternal', false)
  local canRunecraft = false
if eternalResistance ~= nil or item.damageType ~= nil and item.damageType == 'melvorItA:Eternal' then
  local canHerblore = false
return eternalResistance or 0, 'Eternal Resistance'
  if item.trimmedItemID ~= nil then
else
    canUpgrade = true
return p._getItemStat(item, 'resistanceAbyssal', true), 'Abyssal Resistance'
  else
end
    for i, item2 in pairs(ItemData.Items) do
else
      if item2.itemsRequired ~= nil then
return p._getItemStat(item, 'damageReduction', true), 'Damage Reduction'
        for j, req in pairs(item2.itemsRequired) do
end
          if req[1] == item.id then
end
            canUpgrade = true
            break
          end
        end
      end


      if item2.craftReq ~= nil then
function p.getWeaponStatsBox(frame)
        for j, req in pairs(item2.craftReq) do
local itemName = frame.args ~= nil and frame.args[1] or frame
          if req.id == item.id then
local item = p.getItem(itemName)
            canCraft = true
if item == nil then
            break
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
          end
end
        end
      end


      if item2.fletchReq ~= nil then
local resistance, resistanceText = p._getItemResistance(item)
        for j, req in pairs(item2.fletchReq) do
local ns, damageType = nil, nil
          if req.id == item.id then
if item.damageType ~= nil then
            canFletch = true
ns, damageType = Shared.getLocalID(p._getItemStat(item, 'damageType'))
            break
else
          end
damageType = 'Normal'
        end
end
      end


      if item2.runecraftReq ~= nil then
local ico = {
        for j, req in pairs(item2.runecraftReq) do
["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
          if req.id == item.id then
["Combat"] = Icons.Icon({'Combat', notext=true}),
            canRunecraft = true
["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
            break
["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
          end
["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
        end
["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
      end
["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true}),
["Resistance"] = Icons.Icon({resistanceText, notext=true}),
["DamageType"] = Icons.Icon({(damageType .. ' Damage'), type="damage", 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')


      if item2.herbloreReq ~= nil then
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Speed')
        for j, req in pairs(item2.herbloreReq) do
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.round(p._getItemStat(item, 'attackSpeed', true) / 1000, 3, 1) .. 's')
          if req.id == item.id then
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
            canHerblore = true
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))
            break
          end
        end
      end
    end
  end
  if canUpgrade then
    table.insert(useArray, '* [[Upgrading Items]]')
  end
   
  --Cooking
  if item.cookedItemID ~= nil or Shared.contains(itemUseArray.Cooking, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Cooking', type='skill'}))
  end
  --Crafting
  if canCraft or Shared.contains(itemUseArray.Crafting, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Crafting', type='skill'}))
  end
  --Farming
  if item.grownItemID ~= nil or Shared.contains(itemUseArray.Farming, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Farming', type='skill'}))
  end
  --Firemaking
  if item.firemakingID ~= nil or Shared.contains(itemUseArray.Firemaking, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Firemaking', type='skill'}))
  end
  --Fishing
  if Shared.contains(itemUseArray.Fishing, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Fishing', type='skill'}))
  end
  --Fletching
  if canFletch or Shared.contains(itemUseArray.Fletching, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Fletching', type='skill'}))
  end
  --Herblore
  if canHerblore or Shared.contains(itemUseArray.Herblore, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Herblore', type='skill'}))
  end
  --Mining
  if Shared.contains(itemUseArray.Mining, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Mining', type='skill'}))
  end
  --Prayer
  if item.prayerPoints ~= nil or Shared.contains(itemUseArray.Prayer, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Prayer', type='skill'}))
  end
  --Runecrafting
  if canRunecraft or Shared.contains(itemUseArray.Runecrafting, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Runecrafting', type='skill'}))
  end
  --Slayer
  if item.slayerCost ~= nil or Shared.contains(itemUseArray.Slayer, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Slayer', type='skill'}))
  end
  --Smithing
  if item.type == 'Bar' or item.type == 'Ore' or Shared.contains(itemUseArray.Smithing, item.name) then
    table.insert(useArray, '* '..Icons.Icon({'Smithing', type='skill'}))
  end
  --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
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Damage Type')
  --Except Max Skillcape, which is tied to all skills. (And so is the Signet Ring)
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. ico['DamageType'] .. ' ' .. damageType)
  --And combat skillcapes, since combat skills don't get special treatment
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Resistance'] .. ' ' .. resistanceText)
 
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. resistance .. '%')
  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' 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


  --Special note for Charge Stone of Rhaelyx
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Type')
  if item.name == 'Charge Stone of Rhaelyx' then
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'attackType'))
    table.insert(useArray, '* Powering '..Icons.Icon({'Crown of Rhaelyx', type='item'}))
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
  end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))


  return table.concat(useArray,'\r\n')
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'meleeStrengthBonus', 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))


function p.getItemUses(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;"| ' .. Num.formatnum(p._getItemStat(item, 'stabAttackBonus', 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._getItemUses(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;"| ' .. Num.formatnum(p._getItemStat(item, 'slashAttackBonus', 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._getItemLootSourceTable(item)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
  local result = '{| class="wikitable sortable stickyHeader"'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'blockAttackBonus', true)))
  result = result..'\r\n|- class="headerRow-0"'
if reqCount > 1 then
  result = result..'\r\n!Source!!Source Type!!Quantity!!Chance'
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
else
table.insert(resultPart, emptyRow)
end


  --Set up function for adding rows
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
  local buildRow = function(source, type, minqty, qty, chance)
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedAttackBonus', true)))
    if minqty == nil then minqty = 1 end
if reqCount > 2 then
    local rowTxt = '\r\n|-'
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
    rowTxt = rowTxt..'\r\n|style ="text-align: left;"|'..source
else
    rowTxt = rowTxt..'\r\n|style ="text-align: left;"|'..type
table.insert(resultPart, emptyRow)
end


    rowTxt = rowTxt..'\r\n|style ="text-align: right;" data-sort-value:"'..qty..'"|'..minqty
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
    if qty ~= minqty then rowTxt = rowTxt..' - '..qty end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedStrengthBonus', true)))
    rowTxt = rowTxt..'\r\n|style ="text-align: right;"|'..Shared.round(chance, 2, 2)..'%'
if reqCount > 3 then
    return rowTxt
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[4]))
  end
else
  local dropRows = {}
table.insert(resultPart, emptyRow)
 
end
  --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;"| ' .. ico['Magic'] .. ' Attack Bonus')
  for i, item2 in pairs(ItemData.Items) do
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'magicAttackBonus', true)))
    if item2.dropTable ~= nil then
if reqCount > 4 then
      local qty = 1
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[5]))
      local chance = 0
else
      local wt = 0
table.insert(resultPart, emptyRow)
      local totalWt = 0
end
      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['Magic'] .. ' % Damage Bonus')
        chance = (wt / totalWt) * 100
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'magicDamageBonus', true)) .. '%')
        local sourceTxt = Icons.Icon({item2.name, type='item'})
if reqCount > 5 then
        table.insert(dropRows, {source = sourceTxt, type = '[[Chest]]', minqty = 1, qty = qty, chance = chance})
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
      end
else
    end
table.insert(resultPart, emptyRow)
  end
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 > 6 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
else
table.insert(resultPart, emptyRow)
end


  --Finally, let's try just stealing it
--Add extra rows at the end for items that have more than 3 different requirements
  local thiefType = Icons.Icon({"Thieving", type='skill'})
if reqCount > 7 then
  for i, npc in pairs(SkillData.Thieving) do
for i = 8, reqCount, 1 do
    local qty = 1
table.insert(resultPart,"\r\n|-")
    local chance = 0
table.insert(resultPart, emptyRow)
    local wt = 0
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
    local totalWt = 0
end
    if npc.lootTable ~= nil then
end
      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|}')
  if item.type == 'Gem' then
return table.concat(resultPart)
    local mineType = Icons.Icon({'Mining', type='skill'})
end
    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
function p.getArmourStatsBox(frame)
    local fishSource = '[[Fishing#Special|Special]]'
local itemName = frame.args ~= nil and frame.args[1] or frame
    local fishType = Icons.Icon({'Fishing', type='skill'})
local item = p.getItem(itemName)
    local thisChance = (item.fishingCatchWeight / specialFishWt) * 100
if item == nil then
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, chance = thisChance})
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  end
end


  if item.type == 'Junk' then
local resistance, resistanceText = p._getItemResistance(item)
    local fishSource = '[[Fishing#Junk|Junk]]'
    local fishType = Icons.Icon({'Fishing', type='skill'})
    local thisChance = 100 / junkCount
    table.insert(dropRows, {source = fishSource, type = fishType, minqty = 1, qty = 1, chance = thisChance})
  end


  --Make sure to return nothing if there are no drop sources  
local ico = {
  if Shared.tableCount(dropRows) == 0 then return '' end
["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}),
["Resistance"] = Icons.Icon({resistanceText, 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.sort(dropRows, function(a, b) return a.qty * a.chance > b.qty * b.chance end)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
  for i, data in pairs(dropRows) do
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'meleeStrengthBonus', true)))
    result = result..buildRow(data.source, data.type, data.minqty, data.qty, data.chance)
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
  end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'meleeDefenceBonus', true)))


  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
  return result
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'stabAttackBonus', true)))
end
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Resistance'] .. ' ' .. resistanceText)
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. resistance .. '%')


function p.getItemLootSourceTable(frame)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
  local itemName = frame.args ~= nil and frame.args[1] or frame
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'slashAttackBonus', true)))
  local item = p.getItem(itemName)
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
  if item == nil then
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedDefenceBonus', true)))
    return "ERROR: No item named "..itemName.." exists in the data module"
  end


  return p._getItemLootSourceTable(item)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
end
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(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))


function p._getItemShopTable(item)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
  local result = '{| class="wikitable"\r\n|-\r\n!colspan="2"|[[Shop]] Purchase'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedAttackBonus', true)))
  result = result..'\r\n|-\r\n!style="text-align:right;"|Cost\r\n|'
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')
  local cost = {}
  local qty = '1'
  if item.buysFor ~= nil then
    if item.buysFor > 0 then table.insert(cost, Icons.GP(item.buysFor)) end
  elseif item.slayerCost ~= nil then
    table.insert(cost, Icons.Icon({'Slayer Coins', qty=item.slayerCost, notext='true'}))
  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
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
  result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements\r\n|'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedStrengthBonus', true)))
  if item.name == 'Max Skillcape' then
if reqCount > 0 then
    result = result..'Level 99 in all [[Skills]]'
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
  elseif Shared.contains(item.name, 'Skillcape') then
else
    local skillName = Shared.splitString(item.name)[1]
table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
    result = result..Icons._SkillReq(skillName, 99)
end
  else
    result = result..'None'
  end


  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['Magic'] .. ' Attack Bonus')
  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'magicAttackBonus', true)))
  return result
if reqCount > 1 then
end
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
else
table.insert(resultPart, emptyRow)
end


function p.getItemShopTable(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._getItemShopTable(item)
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
end


function p._getItemUpgradeTable(item)
function p.getItemDataExport(frame)
  local result = ''
local resultTable = mw.html.create('table')
  if item.itemsRequired ~= nil then
resultTable:addClass('wikitable')
    --First, get details on all the required materials
resultTable:tag('tr'):addClass('headerRow-0')
    local upgradeFrom = {}
:tag('th'):wikitext('ItemID'):done()
    local materials = {}
:tag('th'):wikitext('ItemName'):done()
    for i, row in pairs(item.itemsRequired) do
:tag('th'):wikitext('GPValue'):done()
      local mat = p.getItemByID(row[1])
 
      --Check to see if the source item can trigger the upgrade
for i, item in ipairs(GameData.rawData.items) do
      if mat.canUpgrade or (mat.type == 'Armour' and mat.canUpgrade == nil) then
resultTable:tag('tr')
        table.insert(upgradeFrom, Icons.Icon({mat.name, type='item'}))
:tag('td'):wikitext(item.id):done()
      end
:tag('td'):wikitext(item.name):done()
      table.insert(materials, Icons.Icon({mat.name, type='item', qty=row[2]}))
:tag('td'):wikitext(item.sellsFor):done()
    end
end
    if item.trimmedGPCost ~= nil then
return tostring(resultTable)
      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
end


function p.getItemUpgradeTable(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._getItemUpgradeTable(item)
return Icons.getExpansionIcon(item.id)
end
end


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


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


function p.getItemSourceTables(frame)
for j, piece in ipairs(pieces) do
  local itemName = frame.args ~= nil and frame.args[1] or frame
if j > 1 then
  local item = p.getItem(itemName)
table.insert(resultPart, ' • ')
  if item == nil then
end
    return "ERROR: No item named "..itemName.." exists in the data module"
table.insert(resultPart, '<span style="display:inline-block">')
  end
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


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


function p.getEquipmentTable(frame)
function p.buildCraftableArmourNav(frame)
  local args = frame.args ~= nil and frame.args or frame
local resultPart = {}
  local type = args.type
table.insert(resultPart, '{| class="wikitable mw-collapsible"')
  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({'Crafting', type='skill', notext=true}))
  local ammoTypeStr = args.ammoType
table.insert(resultPart, ' Craftable 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 = {'slashAttackBonus', 'stabAttackBonus','blockAttackBonus','rangedAttackBonus', 'magicAttackBonus', 'strengthBonus', 'rangedStrengthBonus', 'magicDamageBonus', 'defenceBonus', 'rangedDefenceBonus', 'magicDefenceBonus'}
  --Then the lists for just weapons/just armour
  local weaponStatColumns = {'attackLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'}
  local armourStatColumns = {'damageReduction', 'defenceLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'}
  --Then the list of weapon types
  local weaponTypes = {'Magic Staff', 'Magic Wand', 'Ranged Weapon', 'Weapon'}


  local isWeaponType = Shared.contains(weaponTypes, type)
local leatherTypes = {'Leather', 'Hard Leather'}
 
local leatherPieces = {"Cowl", "Body", "Chaps", "Gloves", "Vambraces", "Boots"}
  --Alright, let's start the table by building the shared header
table.insert(resultPart, '\r\n|-\r\n!')
  local result = '{| class="wikitable sortable stickyHeader"\r\n|-class="headerRow-0"'
table.insert(resultPart, Icons.Icon({'Leather', type='item', notext=true}))
  if isWeaponType then
table.insert(resultPart, ' Leather')
    --Weapons have an extra column here for Attack Speed
for i, material in pairs(leatherTypes) do
    result = result..'\r\n!colspan="3"|'
if i > 1 then table.insert(resultPart, '\r\n|-\r\n!Hard Leather') end
  else
table.insert(resultPart, '\r\n|')
    result = result..'\r\n!colspan="2"|'
for j, piece in ipairs(leatherPieces) do
  end
if j > 1 then
  result = result..'\r\n!colspan="5"style="padding:0 0.5em 0 0.5em;"|Attack Bonus'
table.insert(resultPart, ' ')
  result = result..'\r\n!colspan="2"style="padding:0 0.5em 0 0.5em;"|Strength Bonus'
end
  result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|% Damage Bonus'
table.insert(resultPart, Icons.Icon({material..' '..piece, piece, type='item'}))
  result = result..'\r\n!colspan="3"style="padding:0 0.5em 0 0.5em;"|Defence Bonus'
end
  if isWeaponType then
end
    --Weapons have an extra columns here for "Two Handed?"
    result = result..'\r\n!colspan="1"|'
  else
    --Only armour pieces have DR right now, so ignore that column for weapons
    result = result..'\r\n!colspan="1"style="padding:0 0.5em 0 0.5em;"|Damage Reduction'
  end
  result = result..'\r\n!colspan="3"style="padding:0 0.5em 0 0.5em;"|Levels Required'
  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'
  end
  --Attack bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Strength bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Strength', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Defence bonuses
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --Damage Reduction/Defence Req for armour, 2-handed/Attack Req for weapons
  if isWeaponType then
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Two Handed?'
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Attack', type='skill', notext='true'})
  else
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
    result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Defence', type='skill', notext='true'})
  end
  --Then Ranged/Magic requirements
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Ranged', type='skill', notext='true'})
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|'..Icons.Icon({'Magic', type='skill', notext='true'})
  --And finally Sources
  result = result..'\r\n!style="padding:0 1em 0 0.5em;"|Sources'


  --And with all the header out of the way, finally time to actually build the table itself.
local materialTypes = {{'Green D-hide', 'Green Dragonhide'}, {'Blue D-hide', 'Blue Dragonhide'}, {'Red D-hide', 'Red Dragonhide'}, {'Black D-hide', 'Black Dragonhide'},
  local itemList = {}
{'Elderwood', 'Elderwood Logs', TotH = true}, {'Revenant', 'Revenant Logs', TotH = true}, {'Carrion', 'Carrion Logs', TotH = true}}
  for i, itemBase in pairs(ItemData.Items) do
local pieces = {"Body", "Chaps", "Vambraces", "Shield"}
    local item = Shared.clone(itemBase)
for i, material in ipairs(materialTypes) do
    item.id = i - 1
local isTotH = false
    local listItem = false
local craftName = material[1]
    if isWeaponType then
local matName = material[2]
    listItem = item.type == type and item.category == category
isTotH = material.TotH ~= nil and material.TotH
      if ammoType ~= nil then listItem = listItem and item.ammoTypeRequired == ammoType end
table.insert(resultPart, '\r\n|-\r\n!')
    else
if isTotH then
      --Now for handling armour
table.insert(resultPart, Icons.TotH())
      if type == "Armour" or type == "Melee" then
end
        listItem = item.defenceLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Armour')
table.insert(resultPart, Icons.Icon({matName, type="item", notext=true}))
      elseif type == "Ranged Armour" or type == "Ranged" then
table.insert(resultPart, " "..craftName)
        listItem = item.rangedLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Ranged Armour')
table.insert(resultPart, "\r\n|")
      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


  table.sort(itemList, function(a, b) return a.id < b.id end)
for j, piece in ipairs(pieces) do
  for i, item in pairs(itemList) do
if j > 1 then
    if isWeaponType then
table.insert(resultPart, ' ')
      --Building rows for weapons
end
      result = result..'\r\n|-'
table.insert(resultPart, '<span style="display:inline-block">')
      result = result..'\r\n|style ="text-align: left;padding: 0 0 0 0;"|'..Icons.Icon({item.name, type='item', size=50, notext=true})
table.insert(resultPart, Icons.Icon({craftName..' '..piece, piece, type='item'}))
      result = result..'\r\n|style ="text-align: left;padding: 0 0.5em 0 0.5em;"|[['..item.name..']]'
table.insert(resultPart, ' '..Icons.Icon({'(U) '..craftName..' '..piece, '(U)', type='item'}))
      result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;" |'..Shared.formatnum(item.attackSpeed)
table.insert(resultPart, '</span>')
      for j, statName in pairs(statColumns) do
end
        local statValue = p._getItemStat(item, statName, true)
end
        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
      --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
      --Now the weapon exclusive columns
      for j, statName in pairs(weaponStatColumns) do
        local statValue = p._getItemStat(item, statName, true)
        result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
        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
      --That's the first list out of the way, now for armour specific things
      for j, statName in pairs(armourStatColumns) do
        local statValue = p._getItemStat(item, statName, true)
        result = result..'\r\n| style ="text-align: right;padding: 0 0.5em 0 0;'
        if j == 1 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)
    end
  end


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


function p._getItemCategories(item)
function p.getLifestealWeapons()
  local result = ''
local items = p.getItems(function(item)
  if item.category ~= nil then result = result..'[[Category:'..item.category..']]' end
if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
  if item.type ~= nil then result = result..'[[Category:'..item.type..']]' end
for i, spAttID in ipairs(item.specialAttacks) do
  return result
local spAtt = GameData.getEntityByID('attacks', spAttID)
end
if spAtt ~= nil then
 
return spAtt.lifesteal > 0
function p.getItemCategories(frame)
end
  local itemName = frame.args ~= nil and frame.args[1] or frame
end
  local item = p.getItem(itemName)
end
  if item == nil then
return false
    return "ERROR: No item named "..itemName.." exists in the data module"
end)
  end
 
for i, item in ipairs(items) do
  return p._getItemCategories(item)
mw.log(item.name)
end
end
end


return p
return p

Revision as of 21:27, 11 August 2024

Lua module containing all sorts of functions for getting data on items. Pulls data from Module:GameData/data.

Some functions were split to submodules:


--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 Modifiers = require('Module:Modifiers')
local Icons = require('Module:Icons')
local Num = require('Module:Number')

-- Used by p._getItemLevelReqs()
local LevelReqCache = {}

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)'}
-- List of item IDs that should typically not be included within outputs, usually
-- because they are not fully implemented despite existing within the game data
p.HiddenItems = {}

local function populateHiddenItems()
	local hiddenItems = GameData.getEntities('items',
		function(item)
			return item.name == nil or Shared.contains({'melvorTotH:Meteorite_Dust'}, item.id)
		end
	)
	for _, item in ipairs(hiddenItems) do
		table.insert(p.HiddenItems, item.id)
	end
end
populateHiddenItems()

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

function p.getItem(name)
	name = Shared.fixPagename(name)
	return GameData.getEntityByName('items', name)
end

function p.getItems(checkFunc)
	return GameData.getEntities('items',
		function(obj)
			return not Shared.contains(p.HiddenItems, obj.id) and checkFunc(obj)
		end
	)
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

-- Given an item, returns a table containing the item's level requirements (if any)
-- The table structure is as follows: 
-- { ['normal'] = { SkillID = SkillLevel, ... }, ['abyssal'] = { SkillID = SkillLevel, ... } }
-- If optional parameter 'condense' is specified as true, any normal requirements are excluded
-- from the output if there should exist an abyssal level requirement for the same skill
function p._getItemLevelReqs(item, condense)
	local condenseResult = (type(condense) == 'boolean' and condense) or false
	local reqTypeMap = {
		['SkillLevel'] = 'normal',
		['AbyssalLevel'] = 'abyssal'
	}
	local levelReqs = LevelReqCache[item.id]
	if levelReqs == nil then
		-- Item not in cache - obtain level requirements data
		if item.equipRequirements == nil then
			return nil
		end
		levelReqs = { ['normal'] = {}, ['abyssal'] = {} }
		for _, requirement in ipairs(item.equipRequirements) do
			local reqTypeKey = reqTypeMap[requirement.type]
			if reqTypeKey ~= nil then
				-- Requirement is a level requirement
				local reqSkillID, reqLevel = requirement.skillID, requirement.level
				local reqCurrentLevel = levelReqs[reqTypeKey][reqSkillID]
				if reqCurrentLevel == nil or reqLevel > reqCurrentLevel then
					levelReqs[reqTypeKey][reqSkillID] = reqLevel
				end
			end
		end
		-- Cache level requirements data for any subsequent lookups
		LevelReqCache[item.id] = levelReqs
	end

	if not condenseResult then
		-- Return requirements as is
		return levelReqs
	else
		-- Exclude any normal level requirements should an abyssal requirement exist for
		-- the same skill
		local levelReqsCondensed = { ['normal'] = {}, ['abyssal'] = levelReqs.abyssal }
		for skillID, reqLevel in pairs(levelReqs.normal) do
			if levelReqs.abyssal[skillID] == nil then
				levelReqsCondensed.normal[skillID] = reqLevel
			end
		end
		return levelReqsCondensed
	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
			or string.find(StatName, '^(.+)AbyssalLevel$') ~= nil
		) and item.equipRequirements ~= nil then
		local levelType = 'normal'
		local skillName = string.match(StatName, '^(.+)LevelRequired$')
		if skillName == nil then
			levelType = 'abyssal'
			skillName = string.match(StatName, '^(.+)AbyssalLevel$')
		end
		if skillName ~= nil then
			local skillID = Constants.getSkillID(Shared.titleCase(skillName))
			if skillID ~= nil then
				local levelReqs = p._getItemLevelReqs(item)
				result = levelReqs ~= nil and levelReqs[levelType][skillID]
			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.getItemValueByID(itemID)
	local item = p.getItemByID(itemID)
	if item == nil then
		return 0
	end
	
	return p.getItemValue(item['name'])
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.getValueText(item, minQuantity, maxQuantity)
	local minQ, maxQ = 1, 1
	if type(minQuantity) == 'number' then
		minQ = minQuantity
	end
	if type(maxQuantity) == 'number' then
		maxQ = maxQuantity
	else
		maxQ = minQ
	end
	local amt = item.sellsFor or 0
	local currID = item.sellsForCurrency or 'melvorD:GP'
	return Icons._Currency(currID, amt * minQ, amt * maxQ)
end

-- Function already exists, but without frame.
-- Giving it a slightly different name since function overloading doesn't exist
function p.getItemSellsFor(frame)
	local args = frame:getParent().args

	return p._getItemSellsFor(args[1], args[2], args.round)
end

function p._getItemSellsFor(itemName, multiplier, rounding)
	local itemValue = p.getItemValue(itemName)
	multiplier = tonumber(multiplier) or 1
	rounding = tonumber(rounding) or 0
	
	if itemValue == nil then
		error('No item named "' .. itemName .. '" exists in the data module')
	end

	return Num.round2(itemValue * multiplier, rounding)
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 = Num.formatnum(result) end
	return result
end

--Gets the value of a given modifier for a given item
--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.
	local 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
	local levelReqs = p._getItemLevelReqs(item)
	if levelReqs ~= nil then
		for levelType, reqs in pairs(levelReqs) do
			if not Shared.tableIsEmpty(reqs) 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, Num.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, Num.numStrWithSign(val1 - val2) .. (statDef.suffix or ''))
			end
		end
	end

	-- Include differences in modifiers
	-- TODO Implement getModifiersDifference
	--local modDiff = Constants.getModifiersText(Constants.getModifiersDifference(item2.modifiers, item1.modifiers))
	local modDiff = nil
	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:''' " .. Num.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
	if item.soulPoints ~= nil then
		table.insert(resultPart, "\r\n|-\r\n|'''"..Icons.Icon({'Prayer', 'Soul', type='item', img='Lesser Soul'}).." Points:''' "..item.soulPoints)
	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, Modifiers.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._getCurrencyItemBoxText(item)
	local span = mw.html.create('span')
		:wikitext("'''Sells For:''' ")
		:wikitext(Icons._Currency((item.sellsForCurrency or 'melvorD:GP'), item.sellsFor))
	return span
end

function p.getCurrencyItemBoxText(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._getCurrencyItemBoxText(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)
	--melvorF, melvorD, melvorTotH, melvorAoD
	local dlcFunc = function(item, dlc) 
		local itemDLC = Shared.getLocalID(item.id)
		if dlc == nil then
			return true
		end
		
		if dlc == 'base' then
			return itemDLC == 'melvorD' or itemDLC == 'melvorF'
		end
		
		if itemDLC == dlc then
			return true
		end
		return false
	end
	
	-- Convert list of hidden items into a key/value structure, such that
	-- lookups are more efficient than repeated calls to Shared.contains()
	local hiddenItemIDs = {}
	for i, itemID in ipairs(p.HiddenItems) do
		hiddenItemIDs[itemID] = 1
	end

	local args = frame:getParent().args
	local dlc = args[1] or args.DLC or args.dlc or nil
	local columns = tonumber(args[2] or args.Columns or args.columns) or 17
	
	local html = mw.html.create('table')
		:addClass('wikitable lighttable individual table-img-grid')

	local curRow = html:tag('tr')
	local i = 0
	for _, v in pairs(GameData.rawData.items) do
		if hiddenItemIDs[v.id] == nil and dlcFunc(v, dlc) == true then
			if i >= columns then
				curRow = html:tag('tr')
				i = 0
			end
			
			local cell = curRow:tag('td')
				:wikitext(Icons.Icon({v.name, type='item', notext=true, size='32'}))
				
			-- Mod operator is slow. We use this instead	
			i = i + 1
		end
	end
	
	return tostring(html)
end

function p.getEquipRequirementRow(req)
	local result = ""
	if (req.type == "SkillLevel" or req.type == "AbyssalLevel") then
		local pre = (req.type == "AbyssalLevel" and ' Abyssal') or ''
		local skillName = Constants.getSkillName(req.skillID)
		local skillIcon = Icons.Icon({skillName, type='skill', notext=true})
		result = '\r\n!style="text-align:right;"| '..skillIcon..pre..' Level Required'
		result = result..'\r\n|style="text-align:right;"| '..req.level
	elseif (req.type == "DungeonCompletion" or req.type == "AbyssDepthCompletion") then
		local reqDefns = {
			["DungeonCompletion"] = {
				["dataKey"] = 'dungeons',
				["IDKey"] = 'dungeonID',
				["imgType"] = 'dungeon'
			},
			["AbyssDepthCompletion"] = {
				["dataKey"] = 'abyssDepths',
				["IDKey"] = 'depthID',
				["imgType"] = 'combatArea'
			}
		}
		local reqDefn = reqDefns[req.type]
		if reqDefn ~= nil then
			local area = GameData.getEntityByID(reqDefn.dataKey, req[reqDefn.IDKey])
			if area == nil then
				result = '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid area for requirement type "' .. req.type .. '"')
			else
				local areaIcon = Icons.Icon({area.name, type=reqDefn.imgType, notext=true})
				result = '\r\n!style="text-align:right;"| '..areaIcon..' Completions'
				result = result..'\r\n|style="text-align:right;"| '..Num.formatnum(req.count)
			end
		end
	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._getItemResistance(item)
	local namespace = Shared.getLocalID(item.id)
	if namespace == 'melvorItA' then
		local eternalResistance = p._getItemStat(item, 'resistanceEternal', false)
		if eternalResistance ~= nil or item.damageType ~= nil and item.damageType == 'melvorItA:Eternal' then
			return eternalResistance or 0, 'Eternal Resistance'
		else	
			return p._getItemStat(item, 'resistanceAbyssal', true), 'Abyssal Resistance'
		end
	else
		return p._getItemStat(item, 'damageReduction', true), 'Damage Reduction'
	end
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 resistance, resistanceText = p._getItemResistance(item)
	local ns, damageType = nil, nil
	if item.damageType ~= nil then
		ns, damageType = Shared.getLocalID(p._getItemStat(item, 'damageType'))
	else
		damageType = 'Normal'
	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}),
		["Resistance"] = Icons.Icon({resistanceText, notext=true}),
		["DamageType"] = Icons.Icon({(damageType .. ' Damage'), type="damage", 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;"| ' .. Num.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;"| Damage Type')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. ico['DamageType'] .. ' ' .. damageType)
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Resistance'] .. ' ' .. resistanceText)
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. resistance .. '%')

	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['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['Strength'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'meleeStrengthBonus', 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'] .. ' Stab Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'stabAttackBonus', 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'] .. ' Slash Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'slashAttackBonus', 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['Combat'] .. ' Block Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'blockAttackBonus', 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'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedAttackBonus', 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['Ranged'] .. ' Strength Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'rangedStrengthBonus', 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'] .. ' Attack Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'magicAttackBonus', 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;"| ' .. ico['Magic'] .. ' % Damage Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(p._getItemStat(item, 'magicDamageBonus', true)) .. '%')
	if reqCount > 5 then
		table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
	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 > 6 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 > 7 then
		for i = 8, 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 resistance, resistanceText = p._getItemResistance(item)

	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}),
		["Resistance"] = Icons.Icon({resistanceText,  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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(p._getItemStat(item, 'stabAttackBonus', true)))
	table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Resistance'] .. ' ' .. resistanceText)
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. resistance .. '%')

	table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
	table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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;"| ' .. Num.formatnum(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