Module:Items/UseTables: Difference between revisions

From Melvor Idle
m (Undo revision 51304 - This change made many pages unhappy (error 1))
Tag: Undo
(Update for v1.0.3)
Line 12: Line 12:
local Agility = require('Module:Skills/Agility')
local Agility = require('Module:Skills/Agility')
local Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local SkillEnum = mw.loadData('Module:Constants/data').skill


--Brute forcing some item uses to make things easier
--Brute forcing some item uses to make things easier
Line 72: Line 74:
[29] = 'Astrology'
[29] = 'Astrology'
}
}
local SkillEnum = {}
for i, skill in pairs(SkillData.Skills) do
SkillEnum[skill.name] = Constants.getSkillID(skill.name)
end


function p._getItemUses(item, asList, addCategories)
function p._getItemUses(item, asList, addCategories)
local useArray = {}
-- Another fun one. This time getting all the different possible ways an item can be used
local categoryArray = {}
local categoryArray = {}
local chr = asList and '* ' or ''
local skillUses = {}
--Another fun one. This time getting all the different possible ways an item can be used
local otherUses = {}
local otherUseText = {
["Combat"] = Icons.Icon({'Combat'}),
["Upgrade"] = '[[Upgrading Items]]',
["Food"] = '[[Food]]',
["Chest"] = '[[Chest Drop Tables|Can Be Opened]]',
["Mastery"] = Icons.Icon({'Mastery'}),
["AllSkills"] = 'All skills',
["AltMagic"] = Icons.Icon({'Alt. Magic', type='skill'}),
["ChargeStone"] = 'Powering ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}),
["Shop"] = Icons.Icon({'Shop'})
}


--Before anything else, if this is a potion add it to the appropriate override section
local addUse = function(useName)
if item.masteryID ~= nil and item.masteryID[1] == SkillEnum.Herblore then
local skillID = (type(useName) == 'number' and useName) or SkillEnum[useName]
if potionUseArray[item.masteryID[2]] ~= nil then
if type(skillID) == 'number' and skillUses[skillID] == nil then
table.insert(itemUseArray[potionUseArray[item.masteryID[2]]], item.name)
skillUses[skillID] = Constants.getSkillName(skillID)
elseif not otherUses[useName] then
otherUses[useName] = true
end
end
local hasUse = function(useName)
local skillID = (type(useName) == 'number' and useName) or SkillEnum[useName]
if type(skillID) == 'number' then
return (skillUses[skillID] ~= nil) or false
else
else
table.insert(itemUseArray["Combat"], item.name)
return otherUses[useName] or false
end
end
end
end


--If the item has any modifiers that affect a given skill, add it to those lists
-- Check for any overrides within itemUseArray
--Added an exception for Mastery Tokens since they were being incorrectly flagged as usable for all skills
for useName, itemList in pairs(itemUseArray) do
if item.modifiers ~= nil and (item.isToken == nil or not item.isToken) then
if Shared.contains(itemList, item.name) then
local skillArray = Constants.getModifierSkills(item.modifiers)
addUse(useName)
for i, skillName in Shared.skpairs(skillArray) do
table.insert(itemUseArray[skillName], item.name)
end
end
end
end


--First things added to the list are non-skill things that are easy to check
-- If this is a potion add it to the appropriate uses table
if Items.hasCombatStats(item) or item.specialAttacks ~= nil or Shared.contains(itemUseArray.Combat, item.name) then
if type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Herblore then
table.insert(useArray, chr..Icons.Icon({'Combat'}))
-- Default to 'Combat' if unknown
local potionUse = potionUseArray[item.masteryID[2]] or 'Combat'
addUse(potionUseArray[item.masteryID[2]] or 'Combat')
end
end


if item.healsFor ~= nil then
-- If the item has any modifiers that affect a given skill, add it to those tables
table.insert(categoryArray, '[[Category:Food Items]]')
-- Added an exception for Mastery Tokens since they were being incorrectly flagged as usable for all skills
table.insert(useArray, chr..'[[Food]]')
if item.modifiers ~= nil and (item.isToken == nil or not item.isToken) then
local skillArray = Constants.getModifierSkills(item.modifiers)
for i, skillName in ipairs(skillArray) do
addUse(skillName)
end
end
end


if item.dropTable ~= nil then
--First things added to the list are non-skill things that are easy to check
table.insert(categoryArray, '[[Category:Openable Items]]')
if not hasUse('Combat') and (Items.hasCombatStats(item) or item.specialAttacks ~= nil) then
table.insert(useArray, chr..'[[Chest Drop Tables|Can Be Opened]]')
addUse('Combat')
end
end


-- Check if the item is an entry requirement for any Slayer area
-- Check if the item is an entry requirement for any Slayer area
local isSlayerAreaReq = false
if not hasUse(SkillEnum.Slayer) and item.isEquipment then
if item.isEquipment then
local slayerAreas = Areas.getAreas(function(area) return area.type == 'slayer' and type(area.entryRequirements) == 'table' end)
local slayerAreas = Areas.getAreas(function(area) return area.type == 'slayer' end)
for i, area in pairs(slayerAreas) do
for i, area in pairs(slayerAreas) do
if area.entryRequirements ~= nil and type(area.entryRequirements) == 'table' then
for j, req in pairs(area.entryRequirements) do
for j, req in pairs(area.entryRequirements) do
if req.type == "SlayerItem" and req.itemID == item.id then
if req.type == "SlayerItem" and req.itemID == item.id then
addUse(SkillEnum.Slayer)
isSlayerAreaReq = true
break
end
end
if hasUse(SkillEnum.Slayer) then
break
end
end
end
 
-- Can the item be upgraded, or is it part of an upgrade recipe?
if item.canUpgrade then
addUse('Upgrade')
else
for i, item2 in pairs(ItemData.Items) do
if item2.itemsRequired ~= nil then
for j, req in ipairs(item2.itemsRequired) do
if req[1] == item.id then
addUse('Upgrade')
break
break
end
end
end
end
if isSlayerAreaReq then break end
if hasUse('Upgrade') then
break
end
end
end
end
end
end
if hasUse('Upgrade') then
table.insert(categoryArray, '[[Category:Upgradeable Items]]')
end
end


--Next, upgrading, crafting, herblore, fletching, and runecrafting since we have to sift through other items for these
if item.healsFor ~= nil then
local canUpgrade = false
table.insert(categoryArray, '[[Category:Food Items]]')
local canCraft = false
addUse('Food')
local canFletch = false
end
local canRunecraft = false
local canHerblore = false
local canAgile = false
local canSummon = false
local canCook = false
local canSmith = false


if item.trimmedItemID ~= nil then
if item.canOpen then
canUpgrade = true
table.insert(categoryArray, '[[Category:Openable Items]]')
addUse('Chest')
end
end


for i, item2 in pairs(ItemData.Items) do
-- Cooking, Smithing, Fletching, Crafting, Runecrafting, Herblore
if not canUpgrade and item2.itemsRequired ~= nil then
-- All have somewhat consistent recipe data structures
for j, req in pairs(item2.itemsRequired) do
local recipeSkillIDs = {
if req[1] == item.id then
SkillEnum.Cooking,
canUpgrade = true
SkillEnum.Smithing,
break
SkillEnum.Fletching,
end
SkillEnum.Crafting,
end
SkillEnum.Runecrafting,
end
SkillEnum.Herblore
if not canCraft and item2.craftReq ~= nil then
}
for j, req in pairs(item2.craftReq) do
for i, recipeSkillID in ipairs(recipeSkillIDs) do
if req.id == item.id then
if not hasUse(recipeSkillID) then
canCraft = true
local recipeKey = (recipeSkillID == SkillEnum.Herblore and 'Potions') or 'Recipes'
break
local skillName = Constants.getSkillName(recipeSkillID)
end
-- Iterate over all recipes for the current skill
end
for j, recipe in ipairs(SkillData[skillName][recipeKey]) do
end
for k, itemCost in ipairs(recipe.itemCosts) do
if not canFletch and item2.fletchReq ~= nil then
if itemCost.id == item.id then
for j, req in pairs(item2.fletchReq) do
addUse(recipeSkillID)
if req.id == item.id then
canFletch = true
break
end
end
end
if not canRunecraft and item2.runecraftReq ~= nil then
for j, req in pairs(item2.runecraftReq) do
if req.id == item.id then
canRunecraft = true
break
end
end
end
if not canSummon and item2.summoningReq ~= nil then
for j, reqSet in pairs(item2.summoningReq) do
for k, req in pairs(reqSet) do
if req.id == item.id then
canSummon = true
break
break
end
end
end
end
if canSummon then
-- Some items (such as Arrow shafts) have multiple recipes
break
if not hasUse(recipeSkillID) and type(recipe.alternativeCosts) == 'table' then
end
for k, altCost in ipairs(recipe.alternativeCosts) do
end
for m, itemCost in ipairs(altCost.itemCosts) do
end
if itemCost.id == item.id then
--Handling for new Cooking method
addUse(recipeSkillID)
if not canCook and item2.recipeRequirements ~= nil and item2.recipeRequirements[1] ~= nil then
break
for j, reqSet in pairs(item2.recipeRequirements) do
end
for k, req in pairs(reqSet) do
end
if req.id == item.id then
if hasUse(recipeSkillID) then
canCook = true
break
break
end
end
end
end
end
if canCook then
if hasUse(recipeSkillID) then
break
break
end
end
Line 211: Line 228:
end
end


-- Check potions for Herblore
-- Firemaking
for i, potion in ipairs(SkillData.Herblore.Potions) do
if not hasUse(SkillEnum.Firemaking) and type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Firemaking then
for j, itemCost in ipairs(potion.itemCosts) do
addUse(SkillEnum.Firemaking)
if itemCost.id == item.id then
end
canHerblore = true
 
break
-- Farming
end
if not hasUse(SkillEnum.Farming) and item.grownItemID ~= nil then
end
addUse(SkillEnum.Farming)
if canHerblore then
end
break
 
end
-- Agility
if not hasUse(SkillEnum.Agility) and Shared.tableCount(Agility.getObstaclesForItem(item.id)) > 0 then
addUse(SkillEnum.Agility)
end
end


-- Check recipes for Smithing
-- Summoning
for i, recipe in ipairs(SkillData.Smithing.Recipes) do
if not hasUse(SkillEnum.Summoning) then
for j, itemCost in ipairs(recipe.itemCosts) do
for i, recipe in ipairs(SkillData.Summoning.Marks) do
if itemCost.id == item.id then
-- Tablets & Non-shard items
canSmith = true
if recipe.itemID == item.id or Shared.contains(recipe.nonShardItemCosts, item.id) then
addUse(SkillEnum.Summoning)
break
break
else
-- Shards
for j, itemCost in ipairs(recipe.itemCosts) do
if itemCost.id == item.id then
addUse(SkillEnum.Summoning)
break
end
end
if hasUse(SkillEnum.Summoning) then
break
end
end
end
end
if canSmith then
break
end
end
end
end


--Check if Agility applies here
-- Prayer
canAgile = Shared.tableCount(Agility.getObstaclesForItem(item.id)) > 0
if item.prayerPoints ~= nil then
 
table.insert(categoryArray, '[[Category:Buriable Items]]')
--Agility
if not hasUse(SkillEnum.Prayer) then
if canAgile or Shared.contains(itemUseArray.Agility, item.name) then
addUse(SkillEnum.Prayer)
table.insert(useArray, chr..Icons.Icon({'Agility', type='skill'}))
end
end
--Astrology
if Shared.contains(itemUseArray.Astrology, item.name) then
table.insert(useArray, chr..Icons.Icon({'Astrology', type='skill'}))
end
--Cooking
if canCook or Shared.contains(itemUseArray.Cooking, item.name) then
table.insert(useArray, chr..Icons.Icon({'Cooking', type='skill'}))
end
--Crafting
if canCraft or Shared.contains(itemUseArray.Crafting, item.name) then
table.insert(useArray, chr..Icons.Icon({'Crafting', type='skill'}))
end
--Farming
if item.grownItemID ~= nil or Shared.contains(itemUseArray.Farming, item.name) then
table.insert(useArray, chr..Icons.Icon({'Farming', type='skill'}))
end
--Firemaking
if (type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Firemaking) or Shared.contains(itemUseArray.Firemaking, item.name) then
table.insert(useArray, chr..Icons.Icon({'Firemaking', type='skill'}))
end
--Fishing
if Shared.contains(itemUseArray.Fishing, item.name) then
table.insert(useArray, chr..Icons.Icon({'Fishing', type='skill'}))
end
--Fletching
if canFletch or Shared.contains(itemUseArray.Fletching, item.name) then
table.insert(useArray, chr..Icons.Icon({'Fletching', type='skill'}))
end
--Herblore
if canHerblore or Shared.contains(itemUseArray.Herblore, item.name) then
table.insert(useArray, chr..Icons.Icon({'Herblore', type='skill'}))
end
--Mining
if Shared.contains(itemUseArray.Mining, item.name) then
table.insert(useArray, chr..Icons.Icon({'Mining', type='skill'}))
end
--Prayer
if item.prayerPoints ~= nil or (Shared.contains(itemUseArray.Prayer, item.name) and not Shared.contains(itemUseArray.Combat, item.name)) then
if item.prayerPoints ~= nil then table.insert(categoryArray, '[[Category:Buriable Items]]') end
table.insert(useArray, chr..Icons.Icon({'Prayer', type='skill'}))
end
--Runecrafting
if canRunecraft or Shared.contains(itemUseArray.Runecrafting, item.name) then
table.insert(useArray, chr..Icons.Icon({'Runecrafting', type='skill'}))
end
--Slayer
if isSlayerAreaReq or Shared.contains(itemUseArray.Slayer, item.name) then
table.insert(useArray, chr..Icons.Icon({'Slayer', type='skill'}))
end
--Smithing
if canSmith or Shared.contains(itemUseArray.Smithing, item.name) then
table.insert(useArray, chr..Icons.Icon({'Smithing', type='skill'}))
end
--Summoning
if canSummon or (item.type == 'Shard' and item.category == 'Summoning') or item.type == 'Familiar' or Shared.contains(itemUseArray.Summoning, item.name) then
table.insert(useArray, chr..Icons.Icon({'Summoning', type='skill'}))
end
--Thieving
if Shared.contains(itemUseArray.Thieving, item.name) then
table.insert(useArray, chr..Icons.Icon({'Thieving', type='skill'}))
end
--Woodcutting
if Shared.contains(itemUseArray.Woodcutting, item.name) then
table.insert(useArray, chr..Icons.Icon({'Woodcutting', type='skill'}))
end
end


--Other odds and ends:
-- Other odds and ends:


--Mastery Tokens are tied to 'Mastery'
-- Mastery Tokens are tied to 'Mastery'
if item.isToken and item.skill ~= nil then
if item.isToken and item.skill ~= nil then
table.insert(useArray, chr..Icons.Icon({'Mastery'}))
addUse('Mastery')
end
end


--Skillcapes are tied to the appropriate skill
-- Skillcapes are tied to the appropriate skill
--Except Maximum Skillcape, which is tied to all skills. (And so is the Signet Ring)
-- Except Maximum Skillcape, which is tied to all skills. (And so is the Signet Ring)
--And combat skillcapes, since combat skills don't get special treatment
-- And combat skillcapes, since combat skills don't get special treatment
 
local ignoreCapes = {'Ranged Skillcape', 'Attack Skillcape', 'Strength Skillcape', 'HP Skillcape', 'Defence Skillcape'}
local ignoreCapes = {'Ranged Skillcape', 'Attack Skillcape', 'Strength Skillcape', 'HP Skillcape', 'Defence Skillcape'}
if Shared.contains({'Maximum Skillcape', 'Aorpheat's Signet Ring', 'Cape of Completion'}, item.name) then
if Shared.contains({'Maximum Skillcape', 'Aorpheat's Signet Ring', 'Cape of Completion'}, item.name) then
table.insert(useArray, chr..'All skills')
addUse('AllSkills')
elseif item.name == 'Magic Skillcape' then
elseif item.name == 'Magic Skillcape' then
table.insert(useArray, chr..Icons.Icon({'Magic', type='skill'}))
addUse(SkillEnum.Magic)
table.insert(useArray, chr..Icons.Icon({'Alt. Magic', type='skill'}))
addUse('AltMagic')
elseif Shared.contains(item.name, 'Skillcape') and not Shared.contains(ignoreCapes, item.name) then
elseif Shared.contains(item.name, 'Skillcape') and not Shared.contains(ignoreCapes, item.name) then
local skillName = Shared.splitString(item.name, ' ')[1]
local skillName = Shared.splitString(item.name, ' ')[1]
--Avoiding double-listing the same skill twice
addUse(skillName)
if not Shared.contains(itemUseArray[skillName], item.name) then
table.insert(useArray, chr..Icons.Icon({skillName, type='skill'}))
end
end
end


if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then table.insert(categoryArray, '[[Category:Skillcapes]]') end
if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then
table.insert(categoryArray, '[[Category:Skillcapes]]')
end


--Special note for Charge Stone of Rhaelyx
--Special note for Charge Stone of Rhaelyx
if item.name == 'Charge Stone of Rhaelyx' then
if item.name == 'Charge Stone of Rhaelyx' then
table.insert(useArray, chr..'Powering '..Icons.Icon({'Crown of Rhaelyx', type='item'}))
addUse('ChargeStone')
end
end


Line 345: Line 306:
local shopArray = Shop.getItemCostArray(item.id)
local shopArray = Shop.getItemCostArray(item.id)
if Shared.tableCount(shopArray) > 0 then
if Shared.tableCount(shopArray) > 0 then
table.insert(useArray, chr..Icons.Icon({'Shop'}))
addUse('Shop')
end
end


if canUpgrade then
-- Generate result text
if item.canUpgrade or (item.type == 'Armour' and item.canUpgrade == nil) then
local useArray = {}
table.insert(categoryArray, '[[Category:Upgradeable Items]]')
local prefix, delim = asList and '* ' or '', asList and '\r\n' or '<br/>'
end
for use, _ in Shared.skpairs(otherUses) do
table.insert(useArray, chr..'[[Upgrading Items]]')
table.insert(useArray, prefix .. (otherUseText[use] or use))
end
for skillID, skillName in Shared.spairs(skillUses, function(t, a, b) return t[a] < t[b] end) do
table.insert(useArray, prefix .. Icons.Icon({skillName, type='skill'}))
end
end


local resultPart = {}
return table.concat(useArray, delim) .. (addCategories and table.concat(categoryArray, '') or '')
table.insert(resultPart, asList and table.concat(useArray,'\r\n') or table.concat(useArray, '<br/>'))
if addCategories then table.insert(resultPart, table.concat(categoryArray, '')) end
return table.concat(resultPart)
end
end


Line 379: Line 340:
function p._getItemUseTable(item)
function p._getItemUseTable(item)
local useArray = {}
local useArray = {}
local potTierMastery = {[0] = 0, [1] = 20, [2] = 50, [3] = 90}


--First, loop through all items to find anything that can be made or upgraded into using our source
-- Loop through all items to find anything that can be upgraded using our source
for i, item2 in pairs(ItemData.Items) do
for i, item2 in ipairs(ItemData.Items) do
if item2.itemsRequired ~= nil then
if item2.itemsRequired ~= nil then
for j, req in pairs(item2.itemsRequired) do
for j, req in pairs(item2.itemsRequired) do
Line 388: Line 348:
local mat = item2.itemsRequired
local mat = item2.itemsRequired
local xp = 'N/A'
local xp = 'N/A'
local rowReq = 'None'
local rowReq = nil
--Potions do have upgrade requirements though
--Potions do have upgrade requirements though
if item2.potionTier ~= nil then
if item2.potionTier ~= nil then
rowReq = Icons._MasteryReq(item2.name, potTierMastery[item2.potionTier])
rowReq = Icons._MasteryReq(item2.name, SkillData.Herblore.TierMasteryLevels[item2.potionTier + 1])
end
end
table.insert(useArray, {item = item2, qty = 1, mats = mat, skill = 'Upgrade', req = rowReq, xp = xp, gp = item2.trimmedGPCost})
table.insert(useArray, {item = {id = item2.id, name = item2.name}, qty = 1, mats = mat, skill = 'Upgrade', req = rowReq, xp = xp, gp = item2.trimmedGPCost})
break
break
end
end
end
end
end
end
if item2.craftReq ~= nil then
end
for j, req in pairs(item2.craftReq) do
if req.id == item.id then
local mat = item2.craftReq
local xp = item2.craftingXP
local rowReq = item2.craftingLevel
local qty = item2.craftQty
table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Crafting', req = rowReq, xp = xp, gp = item2.craftGPCost})
break
end
end
end
if item2.fletchReq ~= nil then
for j, req in pairs(item2.fletchReq) do
if req.id == item.id then
local xp = item2.fletchingXP
local rowReq = item2.fletchingLevel
--Arrow Shafts are special and have to be treated specially
local qty = item2.fletchQty
local mat = item2.fletchReq
if item2.name == 'Arrow Shafts' then
mat = {{id = item.id, qty = 1}}
qty =  qty + (qty * item.id)
end
table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Fletching', req = rowReq, xp = xp})
break
end
end
end
--Handling for new Cooking method
if item2.recipeRequirements ~= nil then
for j, reqSet in pairs(item2.recipeRequirements) do
for k, req in pairs(reqSet) do
if req.id == item.id then
local mat = reqSet
local xp = item2.cookingXP
local rowReq = item2.cookingLevel
local qty = item2.cookingQty
table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Cooking', req = rowReq, xp = xp})


if item2.perfectItem ~= nil then
-- Cooking, Smithing, Fletching, Crafting, Runecrafting, Herblore
local perfectItem = Items.getItemByID(item2.perfectItem)
-- All have somewhat consistent recipe data structures
table.insert(useArray, {item = perfectItem, qty = qty, mats = mat, skill = 'Cooking', req = rowReq, xp = xp})
local recipeSkillIDs = {
SkillEnum.Cooking,
SkillEnum.Smithing,
SkillEnum.Fletching,
SkillEnum.Crafting,
SkillEnum.Runecrafting,
SkillEnum.Herblore
}
for i, recipeSkillID in ipairs(recipeSkillIDs) do
local skillName = Constants.getSkillName(recipeSkillID)
local recipeKey = (recipeSkillID == SkillEnum.Herblore and 'Potions') or 'Recipes'
-- Iterate over all recipes for the current skill
for j, recipe in ipairs(SkillData[skillName][recipeKey]) do
local costLists = {recipe.alternativeCosts or {}, {{["itemCosts"] = recipe.itemCosts}}}
for k, costList in pairs(costLists) do
for m, costDef in pairs(costList) do
for n, itemCost in ipairs(costDef.itemCosts) do
if itemCost.id == item.id then
local recipeItemIDs = nil
if recipeSkillID == SkillEnum.Herblore then
recipeItemIDs = recipe.potionIDs
elseif recipeSkillID == SkillEnum.Cooking then
recipeItemIDs = {recipe.itemID, recipe.perfectCookID}
else
recipeItemIDs = {recipe.itemID}
end
for o, recipeItemID in ipairs(recipeItemIDs) do
local recipeItem = Items.getItemByID(recipeItemID)
if recipeItem ~= nil then
local itemDef = {id = recipe.itemID, name = recipeItem.name}
local qty = (recipe.baseQuantity or 1) * (costDef.quantityMultiplier or 1)
local rowReq = recipe.level
local reqVal = nil
if recipeSkillID == SkillEnum.Herblore then
-- Herblore may also have a mastery requirement
local masteryLvl = SkillData.Herblore.TierMasteryLevels[o]
if masteryLvl ~= nil and masteryLvl > 1 then
local masteryReq = Icons._MasteryReq(recipeItem.name, masteryLvl)
reqVal = rowReq + masteryLvl * 0.01
rowReq = Icons._SkillReq(skillName, rowReq) .. '<br/>' .. masteryReq
end
end
table.insert(useArray, {item = itemDef, qty = qty, mats = costDef.itemCosts, skill = skillName, reqVal = reqVal, req = rowReq, xp = recipe.baseXP})
end
end
break
end
end
end
end
end
end
if item2.runecraftReq ~= nil then
for j, req in pairs(item2.runecraftReq) do
if req.id == item.id then
local mat = item2.runecraftReq
local xp = item2.runecraftingXP
local rowReq = item2.runecraftingLevel
local qty = item2.runecraftQty
table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Runecrafting', req = rowReq, xp = xp})
break
end
end
end
if item2.summoningReq ~= nil then
for j, reqSet in pairs(item2.summoningReq) do
for k, req in pairs(reqSet) do
if req.id == item.id then
local mat = Shared.clone(reqSet)
mat[k].qty = math.max(math.floor(1000 / math.max(item.sellsFor, 20)), 1)
local xp = 5 + 2 * math.floor(item2.summoningLevel * 0.2)
local rowReq = item2.summoningLevel
table.insert(useArray, {item = item2, qty = 25, mats = mat, skill = 'Summoning', req = rowReq, xp = xp})
end
end
end
end
Line 472: Line 414:
end
end
end
end
-- Farming
if item.grownItemID ~= nil then
if item.grownItemID ~= nil then
local item2 = Items.getItemByID(item.grownItemID)
local item2 = Items.getItemByID(item.grownItemID)
Line 478: Line 422:
local rowReq = item.farmingLevel
local rowReq = item.farmingLevel
local qty = (item.tier ~= nil and item.tier == 'Tree' and 35 or 15)
local qty = (item.tier ~= nil and item.tier == 'Tree' and 35 or 15)
table.insert(useArray, {item = item2, qty = qty, mats = mat, skill = 'Farming', req = rowReq, xp = xp})
table.insert(useArray, {item = {id = item2.id, name = item2.name}, qty = qty, mats = mat, skill = 'Farming', req = rowReq, xp = xp})
end
end


-- Check potions for Herblore
-- Agility
for i, potion in ipairs(SkillData.Herblore.Potions) do
local obstacles = Agility.getObstaclesForItem(item.id)
for j, itemCost in ipairs(potion.itemCosts) do
for i, obstacle in ipairs(obstacles) do
if itemCost.id == item.id then
local itemCosts = {}
local mat = potion.itemCosts
for j, itemDef in ipairs(obstacle.cost.items) do
local xp = potion.baseXP
table.insert(itemCosts, {id = itemDef[1], qty = itemDef[2]})
local baseRowReq = Icons._SkillReq('Herblore', potion.level)
-- Iterate over each potion tier
for k, potItemID in ipairs(potion.potionIDs) do
local potItem = Items.getItemByID(potItemID)
if potItem ~= nil then
local rowReq = baseRowReq
local masteryLvl = potTierMastery[potItem.potionTier]
if masteryLvl > 0 then
rowReq = rowReq .. '<br/>' .. Icons._MasteryReq(potItem.name, masteryLvl)
end
local reqVal = potion.level + masteryLvl * 0.01
table.insert(useArray, {item = potItem, qty = 1, mats = mat, skill = 'Herblore', reqVal = reqVal, req = rowReq, xp = xp})
end
end
break
end
end
end
local req = Agility._getObstacleRequirements(obstacle)
--local objType = (obstacle.category == nil and 'Pillar') or 'Obstacle'
table.insert(useArray, {item = {id = obstacle.id, name = obstacle.name}, qty = 1, mats = itemCosts, gp = obstacle.cost.gp, sc = obstacle.cost.slayerCoins, skill = 'Agility', req = req, type = 'skill'})
end
end


-- Check recipes for Smithing
-- Summoning
for i, recipe in ipairs(SkillData.Smithing.Recipes) do
for i, recipe in ipairs(SkillData.Summoning.Marks) do
local recipeGPCost = SkillData.Summoning.RecipeGPCost
local useShards = false
local recipeItem = nil
for j, itemCost in ipairs(recipe.itemCosts) do
for j, itemCost in ipairs(recipe.itemCosts) do
if itemCost.id == item.id then
if itemCost.id == item.id then
local smithItem = Items.getItemByID(recipe.itemID)
useShards = true
if smithItem ~= nil then
break
local mat = recipe.itemCosts
end
local xp = recipe.baseXP
end
local rowReq = recipe.level
-- Non-shard items
local qty = recipe.baseQuantity
for j, nonShardItemID in ipairs(recipe.nonShardItemCosts) do
table.insert(useArray, {item = smithItem, qty = qty, mats = mat, skill = 'Smithing', req = rowReq, xp = xp})
if useShards or nonShardItemID == item.id then
break
-- Item is used in this particular synergy recipe
if recipeItem == nil then
recipeItem = Items.getItemByID(recipe.itemID)
end
end
local nonShardItem = Items.getItemByID(nonShardItemID)
local itemValue = math.max(item.sellsFor, 20)
local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
local recipeCosts = Shared.clone(recipe.itemCosts)
table.insert(recipeCosts, {id = nonShardItemID, qty = nonShardQty})
table.insert(useArray, {item = {id = recipe.itemID, name = recipeItem.name}, qty = recipe.baseQuantity, mats = recipeCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = 'Summoning', req = recipe.level, xp = recipe.baseXP})
end
end
end
end
Line 525: Line 467:
--Handle shop purchases using Module:Shop
--Handle shop purchases using Module:Shop
local shopUses = Shop.getItemCostArray(item.id)
local shopUses = Shop.getItemCostArray(item.id)
for i, purchase in Shared.skpairs(shopUses) do
for i, purchase in ipairs(shopUses) do
local rowReq = Shop.getRequirementString(purchase.unlockRequirements)
local rowReq = Shop.getRequirementString(purchase.unlockRequirements)
local iconType = (purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0) and 'item' or 'upgrade'
local iconType = (purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0) and 'item' or 'upgrade'
Line 533: Line 475:
--Finally build the table using what we've learned
--Finally build the table using what we've learned
table.sort(useArray, function(a, b)
table.sort(useArray, function(a, b)
          local aReqVal = a.reqVal ~= nil and a.reqVal or a.req
local aReqVal = a.reqVal ~= nil and a.reqVal or a.req
          local bReqVal = b.reqVal ~= nil and b.reqVal or b.req
local bReqVal = b.reqVal ~= nil and b.reqVal or b.req
          if a.skill ~= b.skill then
if a.skill ~= b.skill then
            return a.skill < b.skill
return a.skill < b.skill
          elseif type(aReqVal) ~= type(bReqVal) then
elseif type(aReqVal) ~= type(bReqVal) then
            return tostring(aReqVal) < tostring(bReqVal)
return tostring(aReqVal) < tostring(bReqVal)
          elseif aReqVal ~= bReqVal then
elseif aReqVal ~= bReqVal then
            return aReqVal < bReqVal
return aReqVal < bReqVal
          else
else
            return a.item.name < b.item.name
return a.item.name < b.item.name
          end end)
end
end)


local obstacles = Agility.getObstaclesForItem(item.id)


local spellUseTable = p._getSpellUseTable(item)
local resultPart = {}
local resultPart = {}
if Shared.tableCount(useArray) == 0 and Shared.tableCount(obstacles) == 0 then
if Shared.tableCount(useArray) > 0 then
if string.len(spellUseTable) > 0 then
local typeTextList = {
return '==Uses==\r\n==='..Icons.Icon({'Magic', type='skill', size='30'})..'===\r\n'..spellUseTable
["Shop"] = Icons.Icon({'Shop'}),
else
["Upgrade"] = '[[Upgrading Items|Upgrade]]'
return ''
}
end
 
end
-- Header
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '{| class="wikitable stickyHeader sortable"')
table.insert(resultPart, '\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
for i, row in pairs(useArray) do
table.insert(resultPart, '\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients')
local qty = row.qty ~= nil and row.qty or 1
local iconType = row.type ~= nil and row.type or 'item'
table.insert(resultPart, '\r\n|-\r\n|data-sort-value="'..row.item.name..'"|')
table.insert(resultPart, Icons.Icon({row.item.name, type=iconType, notext=true, size=50})..'||')
if qty > 1 then table.insert(resultPart, "'''"..qty.."x''' ") end
table.insert(resultPart, Icons.Icon({row.item.name, type='item', noicon=true}))
if row.skill == 'Upgrade' then
table.insert(resultPart, '||data-sort-value="Upgrade"|[[Upgrading Items|Upgrade]]')
elseif row.skill == 'Shop' then
table.insert(resultPart, '||data-sort-value="Shop"|'..Icons.Icon({'Shop'}))
else
table.insert(resultPart, '||data-sort-value="'..row.skill..'"|'..Icons.Icon({row.skill, type='skill'}))
end
if type(row.req) == 'number' then
table.insert(resultPart, '|| style="text-align:right" data-sort-value="'..row.req..'"|'..Icons._SkillReq(row.skill, row.req))
elseif row.reqVal ~= nil then
table.insert(resultPart, '|| style="text-align:right" data-sort-value="'..row.reqVal..'"|'..row.req)
else
table.insert(resultPart, '||'..row.req)
end
if type(row.xp) == 'string' then
table.insert(resultPart, '||style="text-align:right" data-sort-value="0"|'..row.xp)
else
table.insert(resultPart, '||style="text-align:right" data-sort-value="'..row.xp..'"|'..row.xp..' '..Icons.Icon({row.skill, type='skill', notext=true})..' XP')
end
table.insert(resultPart, '||')
for i, mat in Shared.skpairs(row.mats) do
local matID = mat.id ~= nil and mat.id or mat[1]
local matQty = mat.qty ~= nil and mat.qty or mat[2]
local matText = ''


if i > 1 then table.insert(resultPart, '<br/>') end
-- Rows
if matID >= 0 then
for i, row in ipairs(useArray) do
-- Item
local qty = row.qty ~= nil and row.qty or 1
local matItem = Items.getItemByID(matID)
local iconType = row.type ~= nil and row.type or 'item'
if matItem == nil then
local iconName = row.item.name
matText = 'ERROR: Failed to find item with ID ' .. matID .. '[[Category:Pages with Script Errors]]'
if row.skill == 'Agility' then
else
iconName = 'Agility'
matText = Icons.Icon({matItem.name, type='item', qty=matQty})
end
local typeName = row.skill ~= nil and row.skill or ''
local typeText = typeTextList[typeName] or Icons.Icon({typeName, type='skill'}) or ''
local reqVal, reqText = row.reqVal, 'None'
if type(row.req) == 'number' then
reqVal = row.req
reqText = Icons._SkillReq(typeName, row.req)
elseif type(row.req) == 'string' then
reqText = row.req
end
local xpVal, xpText = 0, 'N/A'
if type(row.xp) == 'string' then
xpText = row.xp
elseif type(row.xp) == 'number' then
xpVal = row.xp
xpText = Shared.formatnum(row.xp) .. ' ' .. Icons.Icon({typeName, type='skill', notext=true}) .. ' XP'
end
local matRow = {}
if type(row.mats) == 'table' then
for j, itemCost in ipairs(row.mats) do
local matItemID = itemCost.id or itemCost[1] or -1
local matItem = Items.getItemByID(matItemID)
local matQty = itemCost.qty or itemCost[2] or 1
if matItem == nil then
table.insert(matRow, 'ERROR: Failed to find item with ID ' .. itemCost.id .. '[[Category:Pages with script errors]]')
elseif type(matQty) == 'number' then
table.insert(matRow, Icons.Icon({matItem.name, type='item', qty=matQty}))
else
table.insert(matRow, Icons.Icon({matItem.name, type='item'}))
end
end
end
elseif matID == -4 then
-- GP
matText = Icons.GP(SkillData.Summoning.Settings.recipeGPCost)
elseif matID == -5 then
-- Slayer Coins
matText = Icons.SC(SkillData.Summoning.Settings.recipeGPCost)
else
matText = 'ERROR: Unknown item ID: ' .. matID .. ' [[Category:Pages with Script Errors]]'
end
end
table.insert(resultPart, matText)
if row.gp ~= nil and row.gp > 0 then
table.insert(matRow, Icons.GP(row.gp))
end
if row.sc ~= nil and row.sc > 0 then
table.insert(matRow, Icons.SC(row.sc))
end
-- Item created
table.insert(resultPart, '\r\n|-\r\n|data-sort-value="' .. row.item.name .. '"| ')
table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, notext=true, size=50}))
table.insert(resultPart, '\r\n| ')
if qty > 1 then
table.insert(resultPart, "'''" .. Shared.formatnum(qty) .. "x''' ")
end
table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, noicon=true}))
-- Type
table.insert(resultPart, '\r\n|data-sort-value="' .. typeName .. '"| ' .. typeText)
-- Requirements
table.insert(resultPart, '\r\n|style="text-align:right;"')
if row.reqVal ~= nil then
table.insert(resultPart, ' data-sort-value="' .. reqVal .. '"')
end
table.insert(resultPart, '| ' .. reqText)
-- XP
table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. xpVal .. '"| ' .. xpText)
-- Ingredients
table.insert(resultPart, '\r\n| ' .. table.concat(matRow, '<br/>'))
end
end
if row.gp ~= nil then table.insert(resultPart, '<br/>'..Icons.GP(row.gp)) end
table.insert(resultPart, '\r\n|}')
end
end
 
local spellUseTable = p._getSpellUseTable(item)
--Agility obstacles are weird and get their own section
if spellUseTable ~= nil and spellUseTable ~= '' then
for i, obst in Shared.skpairs(obstacles) do
table.insert(resultPart, '\r\n===' .. Icons.Icon({'Magic', type='skill', size=30}) .. '===\r\n' .. spellUseTable)
table.insert(resultPart, '\r\n|-\r\n|')
table.insert(resultPart, Icons.Icon({"Agility", type="skill", size="50", notext=true}))
table.insert(resultPart, '||[[Agility#Obstacles|'..obst.name..']]')
table.insert(resultPart, '||'..Icons.Icon({"Agility", type="skill"}))
 
--Adding the requirements for the Agility Obstacle
local reqArray = {}
if obst.category == nil then --nil category means this is a passive pillar
table.insert(reqArray, Icons._SkillReq('Agility', 99))
elseif obst.category > 0 then --Otherwise it's category * 10
table.insert(reqArray, Icons._SkillReq('Agility', obst.category * 10))
end
--Then the other skill levels if any are added
if obst.requirements ~= nil and obst.requirements.skillLevel ~= nil then
for j, req in Shared.skpairs(obst.requirements.skillLevel) do
table.insert(reqArray, Icons._SkillReq(Constants.getSkillName(req[1]), req[2]))
end
end
table.insert(resultPart, '||style="text-align:right"|'..table.concat(reqArray, '<br/>'))
 
--Just including 'N/A' for XP since it doesn't really apply for Agility Obstacles
table.insert(resultPart, '||style="text-align:right"|N/A')
 
--Finally the cost
local cost = obst.cost
local costArray = {}
if cost.gp ~= nil and cost.gp > 0 then
table.insert(costArray, Icons.GP(cost.gp))
end
if cost.slayerCoins ~= nil and cost.slayerCoins > 0 then
table.insert(costArray, Icons.SC(cost.slayerCoins))
end
for j, mat in Shared.skpairs(cost.items) do
local item = Items.getItemByID(mat[1])
table.insert(costArray, Icons.Icon({item.name, type="item", qty=mat[2]}))
end
 
table.insert(resultPart, '||'..table.concat(costArray, '<br/>'))
 
end
end
 
if Shared.tableCount(resultPart) == 0 then
table.insert(resultPart, '\r\n|}')
return ''
if string.len(spellUseTable) > 0 then
else
table.insert(resultPart, '\r\n==='..Icons.Icon({'Magic', type='skill', size='30'})..'===\r\n'..spellUseTable)
return '==Uses==\r\n' .. table.concat(resultPart)
end
end
return '==Uses==\r\n'..table.concat(resultPart)
end
end


Line 673: Line 590:
end
end


-- TODO Doesn't include Alt. Magic spells that use particular classes of items, e.g. Junk/Coal for Rags to Riches
-- Tempory Template calling Items/SourceTables|getAltMagicTable for Junk/Coal added to respective pages. Remove after TODO is completed.
function p._getSpellUseTable(item)
function p._getSpellUseTable(item)
local spellList = Magic.getSpellsForRune(item.id)
local spellList = Magic.getSpellsForItem(item.id, true)
--Bail immediately if no spells are found
--Bail immediately if no spells are found
if Shared.tableCount(spellList) == 0 then
if Shared.tableCount(spellList) == 0 then
Line 724: Line 639:
return p._getSpellUseTable(item)
return p._getSpellUseTable(item)
end
end
--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
local checkItems = {
'Gold Bar',
'Raw Shrimp',
'Coal Ore',
'Rune Platebody',
'Arrow Shafts',
'Garum Seeds',
'Rune Essence',
'Runite Bar',
'Water Rune',
'Steam Rune',
'Controlled Heat Potion II',
'Wolf',
'Cyclops',
'Leprechaun',
'Redwood Logs',
'Carrot Cake',
'Carrot Cake (Perfect)',
'Mantalyme Herb',
'Carrot',
'Topaz',
'Rune Essence',
'Infernal Claw',
'Chapeau Noir',
'Stardust',
'Rope',
'Ancient Ring of Mastery',
'Mysterious Stone',
'Mastery Token (Cooking)',
'Gem Gloves'
}
local checkFuncs = {
p.getItemUses,
p.getItemUseTable
}
local errCount = 0
for i, item in ipairs(checkItems) do
local param = {args={item}}
mw.log('=' .. item .. '=')
for j, func in ipairs(checkFuncs) do
local callSuccess, retVal = pcall(func, param)
if not callSuccess then
errCount = errCount + 1
mw.log('Error with item "' .. item .. '": ' .. retVal)
else
mw.log(retVal)
end
end
end
if errCount == 0 then
mw.log('Test successful')
else
mw.log('Test failed with ' .. errCount .. ' failures')
end
end
--]==]


return p
return p

Revision as of 12:25, 5 March 2022

Documentation for this module may be created at Module:Items/UseTables/doc

local p = {}

local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/data')

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Magic = require('Module:Magic')
local Areas = require('Module:CombatAreas')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Agility = require('Module:Skills/Agility')
local Shop = require('Module:Shop')

local SkillEnum = mw.loadData('Module:Constants/data').skill

--Brute forcing some item uses to make things easier
local itemUseArray = {
	Agility = {},
	Astrology = {'Stardust', 'Golden Stardust'},
	Attack = {},
	Combat = {'Gold Emerald Ring'},
	Cooking = {'Cooking Gloves', 'Crown of Rhaelyx'},
	Crafting = {'Crown of Rhaelyx'},
	Defence = {},
	Farming = {'Compost', 'Weird Gloop', 'Bob&apos;s Rake'},
	Firemaking = {'Crown of Rhaelyx'},
	Fishing = {'Amulet of Fishing', 'Message in a Bottle'},
	Fletching = {'Crown of Rhaelyx'},
	Herblore = {'Crown of Rhaelyx'},
	Hitpoints = {},
	Magic = {},
	Mining = {'Mining Gloves', 'Gem Gloves'},
	Prayer = {},
	Ranged = {},
	Runecrafting = {'Crown of Rhaelyx'},
	Slayer = {},
	Smithing = {'Smithing Gloves', 'Crown of Rhaelyx'},
	Strength = {},
	Summoning = {'Crown of Rhaelyx'},
	Thieving = {'Chapeau Noir', 'Thieving Gloves', 'Gloves of Silence'},
	Woodcutting = {},
	}
local potionUseArray = {
	[0] = 'Combat',
	[1] = 'Combat',
	[2] = 'Combat',
	[3] = 'Combat',
	[4] = 'Combat',
	[5] = 'Combat',
	[6] = 'Combat',
	[7] = 'Woodcutting',
	[8] = 'Fishing',
	[9] = 'Firemaking',
	[10] = 'Cooking',
	[11] = 'Mining',
	[12] = 'Smithing',
	[13] = 'Thieving',
	[14] = 'Farming',
	[15] = 'Fletching',
	[16] = 'Crafting',
	[17] = 'Runecrafting',
	[18] = 'Herblore',
	[19] = 'Combat',
	[20] = 'Combat',
	[21] = 'Combat',
	[22] = 'Combat',
	[23] = 'Combat',
	[24] = 'Agility',
	[25] = 'Summoning',
	[26] = 'Combat',
	[27] = 'Combat',
	[28] = 'Combat',
	[29] = 'Astrology'
}

function p._getItemUses(item, asList, addCategories)
	-- Another fun one. This time getting all the different possible ways an item can be used
	local categoryArray = {}
	local skillUses = {}
	local otherUses = {}
	local otherUseText = {
		["Combat"] = Icons.Icon({'Combat'}),
		["Upgrade"] = '[[Upgrading Items]]',
		["Food"] = '[[Food]]',
		["Chest"] = '[[Chest Drop Tables|Can Be Opened]]',
		["Mastery"] = Icons.Icon({'Mastery'}),
		["AllSkills"] = 'All skills',
		["AltMagic"] = Icons.Icon({'Alt. Magic', type='skill'}),
		["ChargeStone"] = 'Powering ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}),
		["Shop"] = Icons.Icon({'Shop'})
	}

	local addUse = function(useName)
		local skillID = (type(useName) == 'number' and useName) or SkillEnum[useName]
		if type(skillID) == 'number' and skillUses[skillID] == nil then
			skillUses[skillID] = Constants.getSkillName(skillID)
		elseif not otherUses[useName] then
			otherUses[useName] = true
		end
	end
	local hasUse = function(useName)
		local skillID = (type(useName) == 'number' and useName) or SkillEnum[useName]
		if type(skillID) == 'number' then
			return (skillUses[skillID] ~= nil) or false
		else
			return otherUses[useName] or false
		end
	end

	-- Check for any overrides within itemUseArray
	for useName, itemList in pairs(itemUseArray) do
		if Shared.contains(itemList, item.name) then
			addUse(useName)
		end
	end

	-- If this is a potion add it to the appropriate uses table
	if type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Herblore then
		-- Default to 'Combat' if unknown
		local potionUse = potionUseArray[item.masteryID[2]] or 'Combat'
		addUse(potionUseArray[item.masteryID[2]] or 'Combat')
	end

	-- If the item has any modifiers that affect a given skill, add it to those tables
	-- Added an exception for Mastery Tokens since they were being incorrectly flagged as usable for all skills
	if item.modifiers ~= nil and (item.isToken == nil or not item.isToken) then
		local skillArray = Constants.getModifierSkills(item.modifiers)
		for i, skillName in ipairs(skillArray) do
			addUse(skillName)
		end
	end

	--First things added to the list are non-skill things that are easy to check
	if not hasUse('Combat') and (Items.hasCombatStats(item) or item.specialAttacks ~= nil) then
		addUse('Combat')
	end

	-- Check if the item is an entry requirement for any Slayer area
	if not hasUse(SkillEnum.Slayer) and item.isEquipment then
		local slayerAreas = Areas.getAreas(function(area) return area.type == 'slayer' and type(area.entryRequirements) == 'table' end)
		for i, area in pairs(slayerAreas) do
			for j, req in pairs(area.entryRequirements) do
				if req.type == "SlayerItem" and req.itemID == item.id then
					addUse(SkillEnum.Slayer)
					break
				end
			end
			if hasUse(SkillEnum.Slayer) then
				break
			end
		end
	end

	-- Can the item be upgraded, or is it part of an upgrade recipe?
	if item.canUpgrade then
		addUse('Upgrade')
	else
		for i, item2 in pairs(ItemData.Items) do
			if item2.itemsRequired ~= nil then
				for j, req in ipairs(item2.itemsRequired) do
					if req[1] == item.id then
						addUse('Upgrade')
						break
					end
				end
				if hasUse('Upgrade') then
					break
				end
			end
		end
	end
	if hasUse('Upgrade') then
		table.insert(categoryArray, '[[Category:Upgradeable Items]]')
	end

	if item.healsFor ~= nil then
		table.insert(categoryArray, '[[Category:Food Items]]')
		addUse('Food')
	end

	if item.canOpen then
		table.insert(categoryArray, '[[Category:Openable Items]]')
		addUse('Chest')
	end

	-- Cooking, Smithing, Fletching, Crafting, Runecrafting, Herblore
	-- All have somewhat consistent recipe data structures
	local recipeSkillIDs = {
		SkillEnum.Cooking,
		SkillEnum.Smithing,
		SkillEnum.Fletching,
		SkillEnum.Crafting,
		SkillEnum.Runecrafting,
		SkillEnum.Herblore
	}
	for i, recipeSkillID in ipairs(recipeSkillIDs) do
		if not hasUse(recipeSkillID) then
			local recipeKey = (recipeSkillID == SkillEnum.Herblore and 'Potions') or 'Recipes'
			local skillName = Constants.getSkillName(recipeSkillID)
			-- Iterate over all recipes for the current skill
			for j, recipe in ipairs(SkillData[skillName][recipeKey]) do
				for k, itemCost in ipairs(recipe.itemCosts) do
					if itemCost.id == item.id then
						addUse(recipeSkillID)
						break
					end
				end
				-- Some items (such as Arrow shafts) have multiple recipes
				if not hasUse(recipeSkillID) and type(recipe.alternativeCosts) == 'table' then
					for k, altCost in ipairs(recipe.alternativeCosts) do
						for m, itemCost in ipairs(altCost.itemCosts) do
							if itemCost.id == item.id then
								addUse(recipeSkillID)
								break
							end
						end
						if hasUse(recipeSkillID) then
							break
						end
					end
				end
				if hasUse(recipeSkillID) then
					break
				end
			end
		end
	end

	-- Firemaking
	if not hasUse(SkillEnum.Firemaking) and type(item.masteryID) == 'table' and item.masteryID[1] == SkillEnum.Firemaking then
		addUse(SkillEnum.Firemaking)
	end

	-- Farming
	if not hasUse(SkillEnum.Farming) and item.grownItemID ~= nil then
		addUse(SkillEnum.Farming)
	end

	-- Agility
	if not hasUse(SkillEnum.Agility) and Shared.tableCount(Agility.getObstaclesForItem(item.id)) > 0 then
		addUse(SkillEnum.Agility)
	end

	-- Summoning
	if not hasUse(SkillEnum.Summoning) then
		for i, recipe in ipairs(SkillData.Summoning.Marks) do
			-- Tablets & Non-shard items
			if recipe.itemID == item.id or Shared.contains(recipe.nonShardItemCosts, item.id) then
				addUse(SkillEnum.Summoning)
				break
			else
				-- Shards
				for j, itemCost in ipairs(recipe.itemCosts) do
					if itemCost.id == item.id then
						addUse(SkillEnum.Summoning)
						break
					end
				end
				if hasUse(SkillEnum.Summoning) then
					break
				end
			end
		end
	end

	-- Prayer
	if item.prayerPoints ~= nil then
		table.insert(categoryArray, '[[Category:Buriable Items]]')
		if not hasUse(SkillEnum.Prayer) then
			addUse(SkillEnum.Prayer)
		end
	end

	-- Other odds and ends:

	-- Mastery Tokens are tied to 'Mastery'
	if item.isToken and item.skill ~= nil then
		addUse('Mastery')
	end

	-- Skillcapes are tied to the appropriate skill
	-- Except Maximum Skillcape, which is tied to all skills. (And so is the Signet Ring)
	-- And combat skillcapes, since combat skills don't get special treatment
	local ignoreCapes = {'Ranged Skillcape', 'Attack Skillcape', 'Strength Skillcape', 'HP Skillcape', 'Defence Skillcape'}
	if Shared.contains({'Maximum Skillcape', 'Aorpheat&apos;s Signet Ring', 'Cape of Completion'}, item.name) then
		addUse('AllSkills')
	elseif item.name == 'Magic Skillcape' then
		addUse(SkillEnum.Magic)
		addUse('AltMagic')
	elseif Shared.contains(item.name, 'Skillcape') and not Shared.contains(ignoreCapes, item.name) then
		local skillName = Shared.splitString(item.name, ' ')[1]
		addUse(skillName)
	end

	if Shared.contains(item.name, 'Skillcape') or item.name == 'Cape of Completion' then
		table.insert(categoryArray, '[[Category:Skillcapes]]')
	end

	--Special note for Charge Stone of Rhaelyx
	if item.name == 'Charge Stone of Rhaelyx' then
		addUse('ChargeStone')
	end

	--Some items are needed to make shop purchases
	local shopArray = Shop.getItemCostArray(item.id)
	if Shared.tableCount(shopArray) > 0 then
		addUse('Shop')
	end

	-- Generate result text
	local useArray = {}
	local prefix, delim = asList and '* ' or '', asList and '\r\n' or '<br/>'
	for use, _ in Shared.skpairs(otherUses) do
		table.insert(useArray, prefix .. (otherUseText[use] or use))
	end
	for skillID, skillName in Shared.spairs(skillUses, function(t, a, b) return t[a] < t[b] end) do
		table.insert(useArray, prefix .. Icons.Icon({skillName, type='skill'}))
	end

	return table.concat(useArray, delim) .. (addCategories and table.concat(categoryArray, '') or '')
end

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

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

function p._getItemUseTable(item)
	local useArray = {}

	-- Loop through all items to find anything that can be upgraded using our source
	for i, item2 in ipairs(ItemData.Items) do
		if item2.itemsRequired ~= nil then
			for j, req in pairs(item2.itemsRequired) do
				if req[1] == item.id then
					local mat = item2.itemsRequired
					local xp = 'N/A'
					local rowReq = nil
					--Potions do have upgrade requirements though
					if item2.potionTier ~= nil then
						rowReq = Icons._MasteryReq(item2.name, SkillData.Herblore.TierMasteryLevels[item2.potionTier + 1])
					end
					table.insert(useArray, {item = {id = item2.id, name = item2.name}, qty = 1, mats = mat, skill = 'Upgrade', req = rowReq, xp = xp, gp = item2.trimmedGPCost})
					break
				end
			end
		end
	end

	-- Cooking, Smithing, Fletching, Crafting, Runecrafting, Herblore
	-- All have somewhat consistent recipe data structures
	local recipeSkillIDs = {
		SkillEnum.Cooking,
		SkillEnum.Smithing,
		SkillEnum.Fletching,
		SkillEnum.Crafting,
		SkillEnum.Runecrafting,
		SkillEnum.Herblore
	}
	for i, recipeSkillID in ipairs(recipeSkillIDs) do
		local skillName = Constants.getSkillName(recipeSkillID)
		local recipeKey = (recipeSkillID == SkillEnum.Herblore and 'Potions') or 'Recipes'
		-- Iterate over all recipes for the current skill
		for j, recipe in ipairs(SkillData[skillName][recipeKey]) do
			local costLists = {recipe.alternativeCosts or {}, {{["itemCosts"] = recipe.itemCosts}}}
			for k, costList in pairs(costLists) do
				for m, costDef in pairs(costList) do
					for n, itemCost in ipairs(costDef.itemCosts) do
						if itemCost.id == item.id then
							local recipeItemIDs = nil
							if recipeSkillID == SkillEnum.Herblore then
								recipeItemIDs = recipe.potionIDs
							elseif recipeSkillID == SkillEnum.Cooking then
								recipeItemIDs = {recipe.itemID, recipe.perfectCookID}
							else
								recipeItemIDs = {recipe.itemID}
							end
							for o, recipeItemID in ipairs(recipeItemIDs) do
								local recipeItem = Items.getItemByID(recipeItemID)
								if recipeItem ~= nil then
									local itemDef = {id = recipe.itemID, name = recipeItem.name}
									local qty = (recipe.baseQuantity or 1) * (costDef.quantityMultiplier or 1)
									local rowReq = recipe.level
									local reqVal = nil
									if recipeSkillID == SkillEnum.Herblore then
										-- Herblore may also have a mastery requirement
										local masteryLvl = SkillData.Herblore.TierMasteryLevels[o]
										if masteryLvl ~= nil and masteryLvl > 1 then
											local masteryReq = Icons._MasteryReq(recipeItem.name, masteryLvl)
											reqVal = rowReq + masteryLvl * 0.01
											rowReq = Icons._SkillReq(skillName, rowReq) .. '<br/>' .. masteryReq
										end
									end
									table.insert(useArray, {item = itemDef, qty = qty, mats = costDef.itemCosts, skill = skillName, reqVal = reqVal, req = rowReq, xp = recipe.baseXP})
								end
							end
							break
						end
					end
				end
			end
		end
	end

	-- Farming
	if item.grownItemID ~= nil then
		local item2 = Items.getItemByID(item.grownItemID)
		local mat = {{id = item.id, qty = item.seedsRequired}}
		local xp = item.farmingXP
		local rowReq = item.farmingLevel
		local qty = (item.tier ~= nil and item.tier == 'Tree' and 35 or 15)
		table.insert(useArray, {item = {id = item2.id, name = item2.name}, qty = qty, mats = mat, skill = 'Farming', req = rowReq, xp = xp})
	end

	-- Agility
	local obstacles = Agility.getObstaclesForItem(item.id)
	for i, obstacle in ipairs(obstacles) do
		local itemCosts = {}
		for j, itemDef in ipairs(obstacle.cost.items) do
			table.insert(itemCosts, {id = itemDef[1], qty = itemDef[2]})
		end
		local req = Agility._getObstacleRequirements(obstacle)
		--local objType = (obstacle.category == nil and 'Pillar') or 'Obstacle'
		table.insert(useArray, {item = {id = obstacle.id, name = obstacle.name}, qty = 1, mats = itemCosts, gp = obstacle.cost.gp, sc = obstacle.cost.slayerCoins, skill = 'Agility', req = req, type = 'skill'})
	end

	-- Summoning
	for i, recipe in ipairs(SkillData.Summoning.Marks) do
		local recipeGPCost = SkillData.Summoning.RecipeGPCost
		local useShards = false
		local recipeItem = nil
		for j, itemCost in ipairs(recipe.itemCosts) do
			if itemCost.id == item.id then
				useShards = true
				break
			end
		end
		-- Non-shard items
		for j, nonShardItemID in ipairs(recipe.nonShardItemCosts) do
			if useShards or nonShardItemID == item.id then
				-- Item is used in this particular synergy recipe
				if recipeItem == nil then
					recipeItem = Items.getItemByID(recipe.itemID)
				end
				local nonShardItem = Items.getItemByID(nonShardItemID)
				local itemValue = math.max(item.sellsFor, 20)
				local nonShardQty = math.max(1, math.floor(recipeGPCost / itemValue))
				local recipeCosts = Shared.clone(recipe.itemCosts)
				table.insert(recipeCosts, {id = nonShardItemID, qty = nonShardQty})
				table.insert(useArray, {item = {id = recipe.itemID, name = recipeItem.name}, qty = recipe.baseQuantity, mats = recipeCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = 'Summoning', req = recipe.level, xp = recipe.baseXP})
			end
		end
	end

	--Handle shop purchases using Module:Shop
	local shopUses = Shop.getItemCostArray(item.id)
	for i, purchase in ipairs(shopUses) do
		local rowReq = Shop.getRequirementString(purchase.unlockRequirements)
		local iconType = (purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0) and 'item' or 'upgrade'
		table.insert(useArray, {item = {name = purchase.name}, qty = 1, mats = purchase.cost.items, skill = 'Shop', req = rowReq, xp = 'N/A', gp = purchase.cost.gp, type = iconType})
	end

	--Finally build the table using what we've learned
	table.sort(useArray, function(a, b)
		local aReqVal = a.reqVal ~= nil and a.reqVal or a.req
		local bReqVal = b.reqVal ~= nil and b.reqVal or b.req
		if a.skill ~= b.skill then
			return a.skill < b.skill
		elseif type(aReqVal) ~= type(bReqVal) then
			return tostring(aReqVal) < tostring(bReqVal)
		elseif aReqVal ~= bReqVal then
			return aReqVal < bReqVal
		else
			return a.item.name < b.item.name
		end
	end)


	local resultPart = {}
	if Shared.tableCount(useArray) > 0 then
		local typeTextList = {
			["Shop"] = Icons.Icon({'Shop'}),
			["Upgrade"] = '[[Upgrading Items|Upgrade]]'
		}

		-- Header
		table.insert(resultPart, '{| class="wikitable stickyHeader sortable"')
		table.insert(resultPart, '\r\n|- class="headerRow-0"')
		table.insert(resultPart, '\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients')

		-- Rows
		for i, row in ipairs(useArray) do
			local qty = row.qty ~= nil and row.qty or 1
			local iconType = row.type ~= nil and row.type or 'item'
			local iconName = row.item.name
			if row.skill == 'Agility' then
				iconName = 'Agility'
			end
			local typeName = row.skill ~= nil and row.skill or ''
			local typeText = typeTextList[typeName] or Icons.Icon({typeName, type='skill'}) or ''
			local reqVal, reqText = row.reqVal, 'None'
			if type(row.req) == 'number' then
				reqVal = row.req
				reqText = Icons._SkillReq(typeName, row.req)
			elseif type(row.req) == 'string' then
				reqText = row.req
			end
			local xpVal, xpText = 0, 'N/A'
			if type(row.xp) == 'string' then
				xpText = row.xp
			elseif type(row.xp) == 'number' then
				xpVal = row.xp
				xpText = Shared.formatnum(row.xp) .. ' ' .. Icons.Icon({typeName, type='skill', notext=true}) .. ' XP'
			end
			local matRow = {}
			if type(row.mats) == 'table' then
				for j, itemCost in ipairs(row.mats) do
					local matItemID = itemCost.id or itemCost[1] or -1
					local matItem = Items.getItemByID(matItemID)
					local matQty = itemCost.qty or itemCost[2] or 1
					if matItem == nil then
						table.insert(matRow, 'ERROR: Failed to find item with ID ' .. itemCost.id .. '[[Category:Pages with script errors]]')
					elseif type(matQty) == 'number' then
						table.insert(matRow, Icons.Icon({matItem.name, type='item', qty=matQty}))
					else
						table.insert(matRow, Icons.Icon({matItem.name, type='item'}))
					end
				end
			end
			if row.gp ~= nil and row.gp > 0 then
				table.insert(matRow, Icons.GP(row.gp))
			end
			if row.sc ~= nil and row.sc > 0 then
				table.insert(matRow, Icons.SC(row.sc))
			end
			-- Item created
			table.insert(resultPart, '\r\n|-\r\n|data-sort-value="' .. row.item.name .. '"| ')
			table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, notext=true, size=50}))
			table.insert(resultPart, '\r\n| ')
			if qty > 1 then
				table.insert(resultPart, "'''" .. Shared.formatnum(qty) .. "x''' ")
			end
			table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, noicon=true}))
			-- Type
			table.insert(resultPart, '\r\n|data-sort-value="' .. typeName .. '"| ' .. typeText)
			-- Requirements
			table.insert(resultPart, '\r\n|style="text-align:right;"')
			if row.reqVal ~= nil then
				table.insert(resultPart, ' data-sort-value="' .. reqVal .. '"')
			end
			table.insert(resultPart, '| ' .. reqText)
			-- XP
			table.insert(resultPart, '\r\n|style="text-align:right;" data-sort-value="' .. xpVal .. '"| ' .. xpText)
			-- Ingredients
			table.insert(resultPart, '\r\n| ' .. table.concat(matRow, '<br/>'))
		end
		table.insert(resultPart, '\r\n|}')
	end
	local spellUseTable = p._getSpellUseTable(item)
	if spellUseTable ~= nil and spellUseTable ~= '' then
		table.insert(resultPart, '\r\n===' .. Icons.Icon({'Magic', type='skill', size=30}) .. '===\r\n' .. spellUseTable)
	end
	if Shared.tableCount(resultPart) == 0 then
		return ''
	else
		return '==Uses==\r\n' .. table.concat(resultPart)
	end
end

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

	return p._getItemUseTable(item)
end

function p._getSpellUseTable(item)
	local spellList = Magic.getSpellsForItem(item.id, true)
	--Bail immediately if no spells are found
	if Shared.tableCount(spellList) == 0 then
		return ''
	end

	local resultPart = {}
	table.insert(resultPart, '{|class="wikitable sortable"\r\n!colspan="2"|Spell')
	table.insert(resultPart, '!!Requirements')
	table.insert(resultPart, '!!Type!!style="width:275px"|Description')
	table.insert(resultPart, '!!Runes')
	for i, spell in pairs(spellList) do
		local rowPart = {}
		table.insert(rowPart, '\r\n|-\r\n|data-sort-value="'..spell.name..'"|')
		local iconType = (spell.type == 'Auroras' and 'aurora') or (spell.type == 'Curses' and 'curse') or 'spell'
		table.insert(rowPart, Icons.Icon({spell.name, type=iconType, notext=true, size=50}))
		table.insert(rowPart, '||'..Icons.Icon({spell.name, type=iconType, noicon=true}))
		table.insert(rowPart, '||data-sort-value="'..spell.level..'"|'..Icons._SkillReq('Magic', spell.level))
		--Handle required items/dungeon clears
		if spell.requiredItem ~= nil and spell.requiredItem >= 0 then
			local reqItem = Items.getItemByID(spell.requiredItem)
			table.insert(rowPart, '<br/>'..Icons.Icon({reqItem.name, type='item', notext=true})..' equipped')
		end
		if spell.requiredDungeonCompletion ~= nil then
			local dung = Areas.getAreaByID('dungeon', spell.requiredDungeonCompletion[1])
			table.insert(rowPart, '<br/>'..Icons.Icon({dung.name, type='dungeon', notext=true, qty=spell.requiredDungeonCompletion[2]})..' Clears')
		end
		table.insert(rowPart, '||data-sort-value="'..Magic.getSpellTypeIndex(spell.type)..'"|')
		table.insert(rowPart, Magic.getSpellTypeLink(spell.type))
		table.insert(rowPart, '||'..Magic._getSpellStat(spell, 'description'))
		table.insert(rowPart, '||style="text-align:center"|')
		table.insert(rowPart, Magic._getSpellRunes(spell))
		table.insert(resultPart, table.concat(rowPart))
	end
	 --Add the table end and add the table to the result string
	table.insert(resultPart, '\r\n|}')
	return table.concat(resultPart)
end

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

	return p._getSpellUseTable(item)
end

--[==[
-- Uncomment this block and execute 'p.test()' within the debug console
-- to test after making changes
function p.test()
	local checkItems = {
		'Gold Bar',
		'Raw Shrimp',
		'Coal Ore',
		'Rune Platebody',
		'Arrow Shafts',
		'Garum Seeds',
		'Rune Essence',
		'Runite Bar',
		'Water Rune',
		'Steam Rune',
		'Controlled Heat Potion II',
		'Wolf',
		'Cyclops',
		'Leprechaun',
		'Redwood Logs',
		'Carrot Cake',
		'Carrot Cake (Perfect)',
		'Mantalyme Herb',
		'Carrot',
		'Topaz',
		'Rune Essence',
		'Infernal Claw',
		'Chapeau Noir',
		'Stardust',
		'Rope',
		'Ancient Ring of Mastery',
		'Mysterious Stone',
		'Mastery Token (Cooking)',
		'Gem Gloves'
	}
	local checkFuncs = {
		p.getItemUses,
		p.getItemUseTable
	}
	local errCount = 0
	for i, item in ipairs(checkItems) do
		local param = {args={item}}
		mw.log('=' .. item .. '=')
		for j, func in ipairs(checkFuncs) do
			local callSuccess, retVal = pcall(func, param)
			if not callSuccess then
				errCount = errCount + 1
				mw.log('Error with item "' .. item .. '": ' .. retVal)
			else
				mw.log(retVal)
			end
		end
	end
	if errCount == 0 then
		mw.log('Test successful')
	else
		mw.log('Test failed with ' .. errCount .. ' failures')
	end
end
--]==]

return p