Module:Items: Difference between revisions

From Melvor Idle
(GemTable is now a module level variable)
(Add function for grabbing item.sellsFor value with mutliplier and rounding parameters)
 
(102 intermediate revisions by 5 users not shown)
Line 2: Line 2:
--Several functions related to use tables can be found at Module:Items/UseTables
--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
--Functions related to source tables can be found at Module:Items/SourceTables
--Other functions moved to Module:Items/ComparisonTables


local p = {}
local p = {}


local MonsterData = mw.loadData('Module:Monsters/data')
local GameData = require('Module:GameData')
local ItemData = mw.loadData('Module:Items/data')
local Constants = require('Module:Constants')
local SkillData = mw.loadData('Module:Skills/data')
local Constants = mw.loadData('Module:Constants/data')
 
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Num = require('Module:Number')


local EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon'}
p.EasterEggs = {'Amulet of Calculated Promotion', 'Clue Chasers Insignia', '8', 'Lemon', 'Easter Egg',
local OtherShopItems = {'Cooking Gloves', 'Mining Gloves', 'Gem Gloves', 'Smithing Gloves', 'Thieving Gloves'}
'Abnormal Log', 'Red Herring', 'Cool Glasses'}
--This is hardcoded, so there's no easy way to scrape it. Hopefully it doesn't change
p.EventItems = {'Christmas Cracker', 'Christmas Coal', 'Christmas Sweater',
p.GemTable = {["Topaz"] = {name = 'Topaz', id = 128, chance = 50},  
'Christmas Wreath', 'Candy Cane', 'Santa Hat',
                  ["Sapphire"] = {name = "Sapphire", id = 129, chance = 17.5},  
'Friendship Bracelet', 'Event Clue 1', 'Event Clue 2',
                  ["Ruby"] = {name = "Ruby", id = 130, chance = 17.5},  
'Event Clue 3', 'Event Clue 4', 'Candle', 'Cake Base',
                  ["Emerald"] = {name = "Emerald", id = 131, chance = 10},  
'Magical Flavouring', 'Magical Icing', 'Birthday Cake',
                  ["Diamond"] = {name = "Diamond", id = 132, chance = 5}}
'Purple Party Hat', 'Birthday Token', 'Christmas Present (Yellow)',
--The base chance to receive a gem while mining
'Christmas Present (Blue)', 'Christmas Present (Green)', 'Christmas Present (White)',
local GemChance = .01
'Christmas Present (Purple)', 'Christmas Present (Standard)', 'Event Token - Holiday 2021',
--The number of different fishing junk items
'Holiday Scarf', 'Gingerbread House', 'Gingerbread Man', 'Edible Candy Cane',
local junkCount = 8
'Locked Chest', 'Locked Chest Key', 'Event Token (Holiday 2021)'}
--Items (aside from bars & gems) which can be created via Alt Magic
local AltMagicProducts = {'Rune Essence', 'Bones', 'Holy Dust'}
--The kinds of gloves with cost & charges
p.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}}


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


p.specialFishWt = 6722
function p.getItem(name)
p.specialFishLoot = {{128, 2000}, {129, 1600}, {130, 1400}, {131, 1000}, {132, 400}, {667, 10}, {668, 10}, {902, 1}, {670, 1}, {669, 50}, {120, 250}}
name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "'", "'")
return GameData.getEntityByName('items', name)
end


function p.buildSpecialFishingTable()
function p.getItems(checkFunc)
  --This shouldn't ever be included in a page
return GameData.getEntities('items', checkFunc)
  --This is for generating the above 'specialFishLoot' variable if it ever needs to change
end
  --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
function p._canItemUseSlot(item, equipSlot)
  local lootArray = {}
--Function to easily check if an item can fit in a given equipment slot
  local totalWt = 0
--Ex: p._canItemUseSlot({Bronze Platebody}, 'Platebody') returns true
if type(item) == 'string' then
item = p.getItem(item)
end
return item.validSlots ~= nil and Shared.contains(item.validSlots, equipSlot)
end
 
function p._getItemEquipSlot(item)
--Function to return the (non-Passive) equipment slot that an item occupies
if type(item) == 'string' then
item = p.getItem(item)
end
if item == nil or item.validSlots == nil then
return 'Invalid'
end
for i, slot in pairs(item.validSlots) do
if slot ~= 'Passive' then
return slot
end
end
end
 
function p._getItemStat(item, StatName, ZeroIfNil)
local result = item[StatName]
--Special Overrides:
-- Equipment stats first
if item.equipmentStats ~= nil and item.equipmentStats[StatName] ~= nil then
result = item.equipmentStats[StatName]
elseif StatName == 'attackSpeed' and item.validSlots ~= nil and Shared.contains(item.validSlots, 'Weapon') then
-- Item can be equipped as a weapon but has no attack speed, so use default of 4000ms
result = 4000
elseif StatName == 'isTwoHanded' then
if item.validSlots ~= nil and item.occupiesSlots ~= nil then
result = Shared.contains(item.validSlots, 'Weapon') and Shared.contains(item.occupiesSlots, 'Shield')
else
result = false
end
elseif string.find(StatName, '^(.+)LevelRequired$') ~= nil and item.equipRequirements ~= nil then
local skillName = Shared.titleCase(string.match(StatName, '^(.+)LevelRequired$'))
if skillName ~= nil then
local skillID = Constants.getSkillID(skillName)
if skillID ~= nil then
for i, requirement in ipairs(item.equipRequirements) do
if requirement.type == "SkillLevel" and requirement.skillID == skillID then
result = requirement.level
break
end
end
end
end
elseif StatName == 'attackType' then
result = p._getWeaponAttackType(item)
elseif StatName == 'description' then
result = item.customDescription
if result == nil or result == '' then result = 'No Description' end
elseif StatName == 'completionReq' then
if item.ignoreCompletion == nil or not item.ignoreCompletion then
result = 'Yes'
else
result = 'No'
end
elseif StatName == 'slayerBonusXP' then
return p._getItemModifier(item, 'increasedSkillXP', 'Slayer', false)
elseif StatName == 'hasCombatStats' then
return tostring(p.hasCombatStats(item) or p._hasLevelRequirements(item))
elseif StatName == 'category' then
-- Some categories have a namespace for some reason, remove it
local _, localID = GameData.getLocalID(result)
return localID
end
if result == nil and ZeroIfNil then result = 0 end
return result
end
 
function p.getItemValue(item)
if type(item) == 'string' then
-- Specific check if the item is GP (value of 1)
if Shared.compareString('GP', item, true)
or Shared.compareString('Gold Pieces', item, true) then
return 1
end
 
item = p.getItem(item)
end
if item then
return item.sellsFor
end
return nil
end


  for i, item in pairs(ItemData.Items) do
-- Function already exists, but without fame.
    if item.fishingCatchWeight ~= nil then
-- Giving it a slightly different name since function overloading doesn't exist
      totalWt = totalWt + item.fishingCatchWeight
function p.getItemSellsFor(frame)
      table.insert(lootArray, '{'..(i - 1)..', '..item.fishingCatchWeight..'}')
local args = frame:getParent().args
    end
  end


  local result = 'p.specialFishWt = '..totalWt..'\r\n'
return p._getItemSellsFor(args[1], args[2], args.round)
  result = result..'p.specialFishLoot = {'..table.concat(lootArray, ', ')..'}'
  return result
end
end


function p.getSpecialAttackByID(ID)
function p._getItemSellsFor(itemName, multiplier, rounding)
  local result = Shared.clone(ItemData.SpecialAttacks[ID + 1])
local itemValue = p.getItemValue(itemName)
  if result ~= nil then
multiplier = tonumber(multiplier) or 1
    result.id = ID
rounding = tonumber(rounding) or 0
  end
  return result
if itemValue == nil then
error('No item named "' .. itemName .. '" exists in the data module')
end
 
return Num.round2(itemValue * multiplier, rounding)
end
end


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


function p.getItem(name)
--Gets the value of a given modifier for a given itemg
  local result = nil
--asString is false by default, when true it writes the full bonus text
  name = string.gsub(name, "%%27", "'")
function p._getItemModifier(item, modifier, skillID, asString)
  name = string.gsub(name, "'", "'")
if asString == nil then asString = false end
  name = string.gsub(name, "'", "'")
if skillID == '' then
  for i, item in pairs(ItemData.Items) do
skillID = nil
    local itemName = string.gsub(item.name, '#', '')
elseif string.find(skillID, ':') == nil then
    if(name == itemName) then
-- Try to find a skill ID if it looks like a skill name has been passed
      result = Shared.clone(item)
skillID = Constants.getSkillID(skillID)
      --Make sure every item has an id, and account for Lua being 1-index
end
      result.id = i - 1
 
      break
local result = 0
    end
 
  end
if item.modifiers ~= nil and item.modifiers[modifier] ~= nil then
  return result
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
end


function p.getItems(checkFunc)
function p.hasCombatStats(item)
  local result = {}
-- Checks if the combat stat is a valid, non-zero combat stat
  for i, item in pairs(ItemData.Items) do
-- Ensure that, only in the case where the item is a Familar AND
    if checkFunc(item) then
-- the checked stat is summoningMaxhit, the result is ignored.
      local newItem = Shared.clone(item)
function isNonZeroStat(statName, statVal)
      newItem.id = i - 1
if statName == 'summoningMaxhit' and (p._canItemUseSlot(item, 'Summon1') or p._canItemUseSlot(item, 'Summon2')) then
      table.insert(result, newItem)
return false
    end
end
  end
return statVal ~= 0
  return result
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
end


function p._getItemStat(item, StatName, ZeroIfNil)
function p._hasLevelRequirements(item)
  local result = item[StatName]
--Function true if an item has at least one level requirement to equip
  --Special Overrides:
if item.equipRequirements ~= nil then
  if StatName == 'stabAttackBonus' then
for idx, requirement in ipairs(item.equipRequirements) do
    if item.attackBonus == nil then
if requirement.type == 'SkillLevel' and requirement.level > 1 then
      result = nil
return true
    else
end
      result = item.attackBonus[1]
end
    end
end
  elseif StatName == 'slashAttackBonus' then
return false
    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.getItemModifier(frame)
  local args = frame.args ~= nil and frame.args or frame
local itemName = frame.args ~= nil and frame.args[1] or frame[1]
  local ItemName = args[1]
local modName = frame.args ~= nil and frame.args[2] or frame[2]
  local StatName = args[2]
local skillName = frame.args ~= nil and frame.args[3] or frame[3]
  local ZeroIfNil = args.ForceZero ~= nil and args.ForceZero ~= '' and args.ForceZero ~= 'false'
local asString = frame.args ~= nil and frame.args[4] or frame[4]
  local formatNum = args.formatNum ~= nil and args.formatNum ~= '' and args.formatNum ~= 'false'
if asString ~= nil then
  local item = p.getItem(ItemName)
asString = (string.upper(asString) ~= 'FALSE')
  if item == nil then
end
    return "ERROR: No item named "..ItemName.." exists in the data module"
 
  end
local item = p.getItem(itemName)
  local result = p._getItemStat(item, StatName, ZeroIfNil)
if item == nil then
  if formatNum then result = Shared.formatnum(result) end
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
  return result
end
 
return p._getItemModifier(item, modName, skillName, asString)
end
end


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


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


function p.getPotionTable(frame)
local statChangeDefs = {
  local potionName = frame.args ~= nil and frame.args[1] or frame
{
  local tiers = {'I', 'II', 'III', 'IV'}
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'
}
}


  local result = '{| class="wikitable"'
-- Produces a list of stat & modifier changes between two items of equipmednt
  result = result..'\r\n!Potion!!Tier!!Charges!!Effect'
function p.getStatChangeString(item1, item2)
local changeArray = {}


  local tier1potion = p.getItem(potionName..' I')
local equipStats = {
  for i, tier in pairs(tiers) do
type(item1.equipmentStats) == 'table' and item1.equipmentStats or {},
    local tierName = potionName..' '..tier
type(item2.equipmentStats) == 'table' and item2.equipmentStats or {}
    local potion = p.getItemByID(tier1potion.id + i - 1)
}
    if potion ~= nil then
for i, statDef in ipairs(statChangeDefs) do
      result = result..'\r\n|-'
local val1, val2 = 0, 0
      result = result..'\r\n|'..Icons.Icon({tierName, type='item', notext='true', size='60'})
if statDef.stat == 'levelRequired' then
      result = result..'||'..'[['..tierName..'|'..tier..']]'
-- Iterate over equipment stats for both items, determining level requirements
      result = result..'||'..potion.potionCharges..'||'..potion.description
local levelReqs = {}
    end
for itemNum, item in ipairs({item1, item2}) do
  end
levelReqs[itemNum] = {}
if item.equipRequirements ~= nil then
for j, req in ipairs(item.equipRequirements) do
if req.type == 'SkillLevel' then
levelReqs[itemNum][req.skillID] = req.level
end
end
end
end
-- Iterate over all skills, checking if there are requirements for these in either skill
for j, skillData in ipairs(GameData.rawData.skillData) do
local skillID = skillData.skillID
val1, val2 = levelReqs[1][skillID] or 0, levelReqs[2][skillID] or 0
if val1 ~= val2 then
table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. ' ' .. Icons.Icon({skillData.data.name, type='skill', notext=true}) .. (statDef.suffix or ''))
end
end
else
-- Equipment stats
val1, val2 = equipStats[1][statDef.stat] or 0, equipStats[2][statDef.stat] or 0
if val1 ~= val2 then
table.insert(changeArray, Shared.numStrWithSign(val1 - val2) .. (statDef.suffix or ''))
end
end
end


  result = result..'\r\n|}'
-- Include differences in modifiers
  return result
local modDiff = Constants.getModifiersText(Constants.getModifiersDifference(item2.modifiers, item1.modifiers))
end
if modDiff ~= nil and modDiff ~= '' then
table.insert(changeArray, modDiff)
end


function p.getEquipmentSlotName(id)
return table.concat(changeArray, '<br/>')
  for slotName, i in Shared.skpairs(Constants.equipmentSlot) do
    if i == id then
      return slotName
    end
  end
  return 'Invalid'
end
end


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


function p.getOtherItemBoxText(frame)
function p.getOtherItemBoxText(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = p.getItem(itemName)
local item = p.getItem(itemName)
  local 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._getOtherItemBoxText(item, asList)
return p._getOtherItemBoxText(item)
end
end


function p.getEquipmentTable(frame)
function p._getItemCategories(item)
  local args = frame.args ~= nil and frame.args or frame
local resultPart = {}
  local type = args.type
local isEquipment = item.validSlots ~= nil or item.occupiesSlots ~= nil or item.equipmentStats ~= nil
  local tier = args.tier
local category = p._getItemStat(item, 'category', false)
  local slotStr = args.slot
if category ~= nil and category ~= 'Skills' then
  local ammoTypeStr = args.ammoType
table.insert(resultPart, '[[Category:'..category..']]')
  local category = args.category ~= nil and args.category or 'Combat'
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


  --Find out what Ammo Type we're working with
function p.getItemCategories(frame)
  local ammoType = nil
local itemName = frame.args ~= nil and frame.args[1] or frame
  if ammoTypeStr ~= nil then
local item = p.getItem(itemName)
    if ammoTypeStr == "Arrows" then
if item == nil then
      ammoType = 0
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
    elseif ammoTypeStr == 'Bolts' then
end
      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
return p._getItemCategories(item)
  local slot = nil
end
  if slotStr ~= nil then
    slot = Constants.equipmentSlot[slotStr]
  end
 


  --Getting some lists set up here that will be used later
function p.getItemGrid(frame)
  --First, the list of columns used by both weapons & armour
local resultPart = {}
  local statColumns = {'stabAttackBonus', 'slashAttackBonus','blockAttackBonus','rangedAttackBonus', 'magicAttackBonus', 'strengthBonus', 'rangedStrengthBonus', 'magicDamageBonus', 'defenceBonus', 'rangedDefenceBonus', 'magicDefenceBonus', 'damageReduction', 'attackLevelRequired', 'defenceLevelRequired', 'rangedLevelRequired', 'magicLevelRequired'}
table.insert(resultPart, '{|')
  --Then the lists for just weapons/just armour
for i, item in ipairs(GameData.rawData.items) do
  --Then the list of weapon types
if i % 17 == 1 then
  local weaponTypes = {'Magic Staff', 'Magic Wand', 'Ranged Weapon', 'Weapon'}
table.insert(resultPart, '\r\n|-\r\n|')
else
table.insert(resultPart, '||')
end
table.insert(resultPart, 'style="padding:3px"|'..Icons.Icon({item.name, type='item', notext=true, size='40'}))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end


  local isWeaponType = Shared.contains(weaponTypes, type)
function p.getEquipRequirementRow(req)
local result = ""
if req.type == "SkillLevel" then
local skillName = Constants.getSkillName(req.skillID)
local skillIcon = Icons.Icon({skillName, type='skill', notext=true})
result = '\r\n!style="text-align:right;"| '..skillIcon..' Level Required'
result = result..'\r\n|style="text-align:right;"| '..req.level
elseif req.type == "DungeonCompletion" then
local dungeonName = GameData.getEntityByID("dungeons", req.dungeonID).name
local dungeonIcon = Icons.Icon({dungeonName, type="dungeon", notext=true})
result = '\r\n!style="text-align:right;"| '..dungeonIcon..' Completions'
result = result..'\r\n|style="text-align:right;"| '..req.count
elseif req.type == "Completion" then
local ns = GameData.getEntityByName('namespaces', req.namespace)
if ns == nil then
return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid namespace for completion requirement "' .. req.namespace .. '"')
else
result = '\r\n!style="text-align:right;"| ' .. ns.displayName .. ' Completion'
result = result .. '\r\n|style="text-align:right;"| ' .. req.percent .. '%'
end
else
return '\r\n!style="text-align:right;" colspan=2|' .. Shared.printError('Invalid equip requirement type "' .. req.type .. '"')
end
return result
end
 
function p.getWeaponStatsBox(frame)
local itemName = frame.args ~= nil and frame.args[1] or frame
local item = p.getItem(itemName)
if item == nil then
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
 
local ico = {
["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
["Combat"] = Icons.Icon({'Combat', notext=true}),
["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
}
local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
local emptyRow = '\r\n!colspan="2"|'
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Weapon Stats')
table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')


  --Now we need to figure out which items are in this list
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Speed')
  local itemList = {}
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. Shared.round(p._getItemStat(item, 'attackSpeed', true) / 1000, 3, 1) .. 's')
  for i, itemBase in pairs(ItemData.Items) do
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
    local item = Shared.clone(itemBase)
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))
    item.id = i - 1
    local listItem = false
    if isWeaponType then
    listItem = item.type == type and item.category == category
      if ammoType ~= nil then listItem = listItem and item.ammoTypeRequired == ammoType end
    else
      --Now for handling armour
      if type == "Armour" or type == "Melee" then
        listItem = item.defenceLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Armour')
      elseif type == "Ranged Armour" or type == "Ranged" then
        listItem = item.rangedLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Ranged Armour')
      elseif type == "Magic Armour" or type == "Magic" then
        listItem = item.magicLevelRequired ~= nil or (item.category == 'Combat' and item.type == 'Magic Armour')
      else
        listItem = item.type == type and item.category ~= 'Combat'
      end
      if ammoType ~= nil then listItem = listItem and item.ammoType == ammoType end
      if slot ~= nil then listItem = listItem and item.equipmentSlot == slot end
    end
    if listItem then
      table.insert(itemList, item)
    end
  end


  --Now that we have a preliminary list, let's figure out which columns are irrelevant (IE are zero for all items in the selection)
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Attack Type')
  local ignoreColumns = Shared.clone(statColumns)
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'attackType'))
  for i, item in pairs(itemList) do
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
    local ndx = 1
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')
    while Shared.tableCount(ignoreColumns) >= ndx do
      if p._getItemStat(item, ignoreColumns[ndx], true) ~= 0 then
        table.remove(ignoreColumns, ndx)
      else
        ndx = ndx + 1
      end
    end
  end


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


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


  result = result..'\r\n|}'
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
  return result
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'blockAttackBonus', true))
if reqCount > 0 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
else
table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
if reqCount > 1 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
else
table.insert(resultPart, emptyRow)
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
if reqCount > 2 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
else
table.insert(resultPart, emptyRow)
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' Attack Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicAttackBonus', true))
if reqCount > 3 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[4]))
else
table.insert(resultPart, emptyRow)
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
if reqCount > 4 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[5]))
else
table.insert(resultPart, emptyRow)
end
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| Two Handed?')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. (p._getItemStat(item, 'isTwoHanded') and 'Yes' or 'No'))
if reqCount > 5 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[6]))
else
table.insert(resultPart, emptyRow)
end
--Add extra rows at the end for items that have more than 3 different requirements
if reqCount > 6 then
for i = 7, reqCount, 1 do
table.insert(resultPart,"\r\n|-")
table.insert(resultPart, emptyRow)
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
end
end
 
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
end


function p._getItemCategories(item)
function p.getArmourStatsBox(frame)
  local result = ''
local itemName = frame.args ~= nil and frame.args[1] or frame
  if item.category ~= nil then result = result..'[[Category:'..item.category..']]' end
local item = p.getItem(itemName)
  if item.type ~= nil then result = result..'[[Category:'..item.type..']]' end
if item == nil then
  return result
return Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
 
local ico = {
["Attack"] = Icons.Icon({'Attack', type='skill', notext=true}),
["Combat"] = Icons.Icon({'Combat', notext=true}),
["Defence"] = Icons.Icon({'Defence', type='skill', notext=true}),
["Magic"] = Icons.Icon({'Magic', type='skill', notext=true}),
["Ranged"] = Icons.Icon({'Ranged', type='skill', notext=true}),
["Strength"] = Icons.Icon({'Strength', type='skill', notext=true}),
["Slayer"] = Icons.Icon({'Slayer', type='skill', notext=true})
}
local reqCount = item.equipRequirements ~= nil and Shared.tableCount(item.equipRequirements) or 0
local emptyRow = '\r\n!colspan="2"|'
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n|-\r\n!colspan="4" style="border-bottom:solid medium black;"| Armour Stats')
table.insert(resultPart, '\r\n|-\r\n!colspan="2" style="border-bottom:solid thin black;"| Offensive Stats')
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Defensive Stats')
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Strength'] .. ' Strength Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeStrengthBonus', true))
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Defence Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'meleeDefenceBonus', true))
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Stab Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'stabAttackBonus', 0))
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Defence'] .. ' Damage Reduction')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'damageReduction', true) .. '%')
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Slash Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'slashAttackBonus', true))
table.insert(resultPart, '\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Defence Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedDefenceBonus', true))
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Combat'] .. ' Block Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'blockAttackBonus', true))
table.insert(resultPart, '\r\n!style="text-align:right;border-bottom:solid thin black;"| ' .. ico['Magic'] .. ' Defence Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;border-bottom:solid thin black;"| ' .. p._getItemStat(item, 'magicDefenceBonus', true))
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Attack Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedAttackBonus', true))
table.insert(resultPart, '\r\n!colspan="2" style="border-bottom:solid thin black;"| Equip Requirements')
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Ranged'] .. ' Strength Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'rangedStrengthBonus', true))
if reqCount > 0 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[1]))
else
table.insert(resultPart, '\r\n|colspan=2 style="text-align:right"|None')
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' Attack Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicAttackBonus', true))
if reqCount > 1 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[2]))
else
table.insert(resultPart, emptyRow)
end
 
table.insert(resultPart, '\r\n|-\r\n!style="text-align:right;"| ' .. ico['Magic'] .. ' % Damage Bonus')
table.insert(resultPart, '\r\n|style="text-align:right;"| ' .. p._getItemStat(item, 'magicDamageBonus', true) .. '%')
if reqCount > 2 then
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[3]))
else
table.insert(resultPart, emptyRow)
end
--Add extra rows at the end for items that have more than 3 different requirements
if reqCount > 3 then
for i = 4, reqCount, 1 do
table.insert(resultPart, "\r\n|-")
table.insert(resultPart, emptyRow)
table.insert(resultPart, p.getEquipRequirementRow(item.equipRequirements[i]))
end
end
 
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
 
function p.getItemDataExport(frame)
local resultTable = mw.html.create('table')
resultTable:addClass('wikitable')
resultTable:tag('tr'):addClass('headerRow-0')
:tag('th'):wikitext('ItemID'):done()
:tag('th'):wikitext('ItemName'):done()
:tag('th'):wikitext('GPValue'):done()
 
for i, item in ipairs(GameData.rawData.items) do
resultTable:tag('tr')
:tag('td'):wikitext(item.id):done()
:tag('td'):wikitext(item.name):done()
:tag('td'):wikitext(item.sellsFor):done()
end
return tostring(resultTable)
end
end


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


function p.getSkillcapeTable(frame)
function p.buildSmithableArmourNav(frame)
  local skillName = frame.args ~= nil and frame.args[1] or frame
local resultPart = {}
  local cape = p.getItem(skillName..' Skillcape')
table.insert(resultPart, '{| class="wikitable mw-collapsible navigation-not-searchable" style="margin:auto; clear:both; width: 100%"')
  local result = '{| class="wikitable"\r\n'
table.insert(resultPart, '\r\n!colspan = 2 style="background-color:#275C87;color:#FFFFFF;min-width:730px;"|')
  result = result..'!Skillcape!!Name!!Effect'
table.insert(resultPart, Icons.Icon({'Smithing', type='skill', notext=true}))
  result = result..'\r\n|-\r\n|'..Icons.Icon({cape.name, type='item', size='60', notext=true})
table.insert(resultPart, ' Smithable Armour Sets')
  result = result..'||[['..cape.name..']]||'..cape.description
 
  result = result..'\r\n|}'
local metalTypes = {'Bronze', 'Iron', 'Steel', 'Mithril', {'Adamant', 'Adamantite'}, {'Rune', 'Runite'}, {'Dragon', 'Dragonite'},
  return result
{'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
end


function p.getShopSkillcapeTable()
function p.buildCraftableArmourNav(frame)
  local result = ''
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 capeList = {}
local leatherTypes = {'Leather', 'Hard Leather'}
  for i, item in pairs(ItemData.Items) do
local leatherPieces = {"Cowl", "Body", "Chaps", "Gloves", "Vambraces", "Boots"}
    if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then
table.insert(resultPart, '\r\n|-\r\n!')
      table.insert(capeList, item)
table.insert(resultPart, Icons.Icon({'Leather', type='item', notext=true}))
    end
table.insert(resultPart, ' Leather')
  end
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


  result = result..'\r\n{|class="wikitable sortable"'
local materialTypes = {{'Green D-hide', 'Green Dragonhide'}, {'Blue D-hide', 'Blue Dragonhide'}, {'Red D-hide', 'Red Dragonhide'}, {'Black D-hide', 'Black Dragonhide'},
  result = result..'\r\n!colspan="2" style="width:200px"|Cape'
{'Elderwood', 'Elderwood Logs', TotH = true}, {'Revenant', 'Revenant Logs', TotH = true}, {'Carrion', 'Carrion Logs', TotH = true}}
  result = result..'!!Description!!style="width:120px"|Price'
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|")


  --Sort the table by cost and then name
for j, piece in ipairs(pieces) do
  table.sort(capeList, function(a, b)  
if j > 1 then
                        if a.buysFor == b.buysFor then
table.insert(resultPart, ' • ')
                          return a.name < b.name
end
                        else
table.insert(resultPart, '<span style="display:inline-block">')
                          return a.sellsFor < b.buysFor
table.insert(resultPart, Icons.Icon({craftName..' '..piece, piece, type='item'}))
                        end
table.insert(resultPart, ' '..Icons.Icon({'(U) '..craftName..' '..piece, '(U)', type='item'}))
                      end)
table.insert(resultPart, '</span>')
  for i, thisItem in pairs(capeList) do
end
    result = result..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item', size='50', notext=true})
end
    result = result..'||[['..thisItem.name..']]'
    result = result..'\r\n||'..thisItem.description
    result = result..'||style="text-align:left" data-sort-value="'..thisItem.buysFor..'"'
    result = result..'|'..Icons.GP(thisItem.buysFor)
  end
  result = result..'\r\n|}'


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


function p.getItemGrid(frame)
function p.getLifestealWeapons()
  result = '{|'
local items = p.getItems(function(item)
  for i, item in Shared.skpairs(ItemData.Items) do
if item.specialAttacks ~= nil and not Shared.tableIsEmpty(item.specialAttacks) then
    if i % 17 == 1 then
for i, spAttID in ipairs(item.specialAttacks) do
      result = result..'\r\n|-\r\n|'
local spAtt = GameData.getEntityByID('attacks', spAttID)
    else
if spAtt ~= nil then
      result = result..'||'
return spAtt.lifesteal > 0
    end
end
    result = result..'style="padding:3px"|'..Icons.Icon({item.name, type='item', notext=true, size='40'})
end
  end
end
  result = result..'\r\n|}'
return false
  return result
end)
for i, item in ipairs(items) do
mw.log(item.name)
end
end
end


return p
return p

Latest revision as of 18:00, 4 April 2024

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


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

local p = {}

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

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

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

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

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

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

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

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

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

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

-- Function already exists, but without fame.
-- 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 = Shared.formatnum(result) end
	return result
end

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

	local result = 0

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

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

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

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

	return false
end

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

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

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

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

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

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

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

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

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

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

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

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

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

	return p._getOtherItemBoxText(item)
end

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

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

	return p._getItemCategories(item)
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return Icons.getExpansionIcon(item.id)
end

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

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

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

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

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

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

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

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

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

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

return p