Module:Items/UseTables: Difference between revisions
From Melvor Idle
m (Fix Summoning non-shard item cost quantity) |
(_getItemUses: Ensure 'All skills' is displayed before any skills) |
||
(37 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local Common = require('Module:Common') | |||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local GameData = require('Module:GameData') | |||
local SkillData = GameData.skillData | |||
local Modifiers = require('Module:Modifiers') | |||
local Skills = require('Module:Skills') | |||
local Magic = require('Module:Magic') | local Magic = require('Module:Magic') | ||
local Areas = require('Module:CombatAreas') | local Areas = require('Module:CombatAreas') | ||
Line 12: | Line 14: | ||
local Agility = require('Module:Skills/Agility') | local Agility = require('Module:Skills/Agility') | ||
local Shop = require('Module:Shop') | local Shop = require('Module:Shop') | ||
local Num = require('Module:Number') | |||
local | |||
--Brute forcing some item uses to make things easier | --Brute forcing some item uses to make things easier | ||
local itemUseArray = { | local itemUseArray = { | ||
Agility = {}, | Agility = {}, | ||
Astrology = {'Stardust', ' | Astrology = {'melvorF:Stardust', 'melvorF:Golden_Stardust', 'melvorItA:Abyssal_Stardust'}, | ||
Archaeology = {}, | |||
Attack = {}, | Attack = {}, | ||
Combat = {' | Cartography = {}, | ||
Cooking = {' | Combat = {'melvorF:Gold_Emerald_Ring', 'melvorD:Obsidian_Cape', 'melvorF:Throwing_Power_Gloves'}, | ||
Crafting = {' | Cooking = {'melvorD:Cooking_Gloves'}, | ||
Crafting = {'melvorItA:Abyssal_Crafting_Gloves'}, | |||
Defence = {}, | Defence = {}, | ||
Farming = {'Compost', ' | Farming = {'melvorD:Compost', 'melvorD:Weird_Gloop', 'melvorItA:Abyssal_Compost', 'melvorD:Bobs_Rake'}, | ||
Firemaking = {' | Firemaking = {'melvorItA:Abyssal_Firemaking_Gloves'}, | ||
Fishing = {' | Fishing = {'melvorD:Message_In_A_Bottle', 'melvorD:Barbarian_Gloves'}, | ||
Fletching = {' | Fletching = {'melvorItA:Abyssal_Fletching_Gloves'}, | ||
Herblore = {' | Harvesting = {'melvorItA:Abyssal_Harvesting_Gloves'}, | ||
Herblore = {'melvorItA:Abyssal_Herblore_Gloves'}, | |||
Hitpoints = {}, | Hitpoints = {}, | ||
Magic = {}, | Magic = {}, | ||
Mining = {' | Mining = {'melvorD:Mining_Gloves', 'melvorD:Gem_Gloves', 'melvorItA:Abyssal_Mining_Gloves'}, | ||
Prayer = {}, | Prayer = {}, | ||
Ranged = {}, | Ranged = {}, | ||
Runecrafting = {' | Runecrafting = {'melvorItA:Abyssal_Runecrafting_Gloves'}, | ||
Slayer = {}, | Slayer = {}, | ||
Smithing = {' | Smithing = {'melvorD:Smithing_Gloves', 'melvorItA:Abyssal_Smithing_Gloves'}, | ||
Smithing = {}, | |||
Strength = {}, | Strength = {}, | ||
Summoning = { | Summoning = {}, | ||
Thieving = {' | Thieving = {'melvorF:Thieving_Gloves'}, | ||
Township = {}, | |||
Woodcutting = {}, | Woodcutting = {}, | ||
} | } | ||
-- List of modifier IDs which, if present on an item and not specific to any | |||
-- particular skill, indicate that the item applies to all skills | |||
local allSkillModIDs = { | |||
-- Skill XP | |||
'skillXP', | |||
'nonCombatSkillXP', | |||
'abyssalSkillXP', | |||
-- Mastery | |||
'flatMasteryTokens', | |||
'xpFromMasteryTokens', | |||
'masteryPoolCap', | |||
'masteryXP', | |||
'masteryPoolProgress', | |||
-- Resource preservation | |||
'bypassGlobalPreservationChance', | |||
'skillPreservationChance', | |||
'skillPreservationCap', | |||
-- Other preservation (consumables, summons, potion charges) | |||
'consumablePreservationChance', | |||
'summoningChargePreservationChance', | |||
'potionChargePreservationChance', | |||
-- Item doubling | |||
'globalItemDoublingChance', | |||
-- Resource quantity | |||
'flatBasePrimaryProductQuantity', | |||
'basePrimaryProductQuantity', | |||
'flatBaseRandomProductQuantity', | |||
'flatAdditionalSkillItem', | |||
'flatAdditionalPrimaryProductQuantity', | |||
-- Cost reduction | |||
'skillCostReduction', | |||
-- Off item chance | |||
'offItemChance' | |||
} | |||
local function allSkillModCriteriaFromIDs(modIDs) | |||
local modIDsWithProps = {} | |||
for i, modID in ipairs(allSkillModIDs) do | |||
table.insert(modIDsWithProps, { | |||
["id"] = modID, | |||
["type"] = 'id', | |||
["props"] = { | |||
["skillID"] = 'nil', | |||
["modType"] = 'pos' | |||
} | |||
}) | |||
end | |||
return Modifiers.getMatchCriteriaFromIDs(modIDsWithProps) | |||
end | |||
local allSkillModCriteria = allSkillModCriteriaFromIDs(allSkillModIDs) | |||
function p._getItemUses(item, asList, addCategories) | function p._getItemUses(item, asList, addCategories) | ||
Line 84: | Line 110: | ||
["Upgrade"] = '[[Upgrading Items]]', | ["Upgrade"] = '[[Upgrading Items]]', | ||
["Food"] = '[[Food]]', | ["Food"] = '[[Food]]', | ||
["Chest"] = '[[Chest | ["Chest"] = '[[Chest|Can Be Opened]]', | ||
["Mastery"] = Icons.Icon({'Mastery'}), | ["Mastery"] = Icons.Icon({'Mastery'}), | ||
["AllSkills"] = 'All skills', | ["AllSkills"] = 'All skills', | ||
["AltMagic"] = Icons.Icon({'Alt. Magic', type='skill'}), | ["AltMagic"] = Icons.Icon({'Alt. Magic', type='skill'}), | ||
["ChargeStone"] = 'Powering ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}), | ["ChargeStone"] = 'Powering ' .. Icons.Icon({'Crown of Rhaelyx', type='item'}), | ||
["Shop"] = Icons.Icon({'Shop'}) | ["Shop"] = Icons.Icon({'Shop'}), | ||
["TownshipTask"] = Icons.Icon({'Tasks', type='township'}) | |||
} | } | ||
local addUse = function(useName) | local addUse = function(useName) | ||
local skillID = | local skillID = Constants.getSkillID(useName) | ||
if skillID == nil then | |||
skillUses[skillID] = | -- May have been passed a skill ID instead | ||
local skillName = Constants.getSkillName(useName) | |||
if skillName ~= nil then | |||
skillID = useName | |||
skillUses[skillID] = skillName | |||
end | |||
else | |||
skillUses[skillID] = useName | |||
end | |||
if skillID == nil and not otherUses[useName] then | |||
otherUses[useName] = true | otherUses[useName] = true | ||
end | end | ||
end | end | ||
local hasUse = function(useName) | local hasUse = function(useName) | ||
local skillID = | local skillID = Constants.getSkillID(useName) | ||
if | if skillID == nil and Constants.getSkillName(useName) ~= nil then | ||
skillID = useName | |||
end | |||
if skillID ~= nil then | |||
return (skillUses[skillID] ~= nil) or false | return (skillUses[skillID] ~= nil) or false | ||
else | else | ||
Line 111: | Line 150: | ||
-- Check for any overrides within itemUseArray | -- Check for any overrides within itemUseArray | ||
for useName, itemList in pairs(itemUseArray) do | for useName, itemList in pairs(itemUseArray) do | ||
if Shared.contains(itemList, item. | if Shared.contains(itemList, item.id) then | ||
addUse(useName) | addUse(useName) | ||
end | end | ||
end | end | ||
-- If the item has any modifiers that affect a given skill, add it to those tables | -- If the item has any modifiers that affect a given skill, add it to those tables | ||
-- Added | -- Added special handling for Mastery Tokens since they were being incorrectly flagged as usable for all skills | ||
if item.modifiers ~= nil | if item.modifiers ~= nil then | ||
if item.modifiers.masteryToken ~= nil then | |||
-- Mastery tokens | |||
addUse( | addUse('Mastery') | ||
else | |||
local modsAllSkills = Modifiers.getMatchingModifiers(item.modifiers, allSkillModCriteria) | |||
if not Shared.tableIsEmpty(modsAllSkills.matched) then | |||
addUse('AllSkills') | |||
else | |||
local skillArray = Modifiers.getModifierSkills(item.modifiers) | |||
for i, skillName in ipairs(skillArray) do | |||
addUse(skillName) | |||
end | |||
end | |||
end | |||
if item.modifiers.thievingStealth ~= nil then | |||
addUse('melvorD:Thieving') | |||
end | end | ||
end | end | ||
Line 138: | Line 183: | ||
-- Check if the item is an entry requirement for any Slayer area | -- Check if the item is an entry requirement for any Slayer area | ||
if not hasUse( | if not hasUse('Slayer') and (item.validSlots ~= nil or item.occupiesSlots ~= nil or item.equipmentStats ~= nil) then | ||
local slayerAreas = Areas.getAreas(function(area) return area.type == ' | local slayerAreas = Areas.getAreas(function(area) return area.type == 'slayerArea' and type(area.entryRequirements) == 'table' end) | ||
for i, area in | for i, area in ipairs(slayerAreas) do | ||
for j, req in | for j, req in ipairs(area.entryRequirements) do | ||
if req.type == "SlayerItem" and req.itemID == item.id then | if req.type == "SlayerItem" and req.itemID == item.id then | ||
addUse( | addUse('Slayer') | ||
break | break | ||
end | end | ||
end | end | ||
if hasUse( | if hasUse('Slayer') then | ||
break | break | ||
end | end | ||
Line 153: | Line 198: | ||
end | end | ||
-- | -- Is the item a cost in an upgrade? | ||
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do | |||
for j, itemCost in ipairs(upgrade.itemCosts) do | |||
if itemCost.id == item.id then | |||
addUse('Upgrade') | |||
table.insert(categoryArray, '[[Category:Upgradeable Items]]') | |||
break | |||
end | end | ||
end | end | ||
if hasUse('Upgrade') then | |||
break | |||
end | |||
end | end | ||
Line 180: | Line 217: | ||
end | end | ||
if item. | if item.dropTable ~= nil then | ||
table.insert(categoryArray, '[[Category:Openable Items]]') | table.insert(categoryArray, '[[Category:Openable Items]]') | ||
addUse('Chest') | addUse('Chest') | ||
Line 188: | Line 225: | ||
-- All have somewhat consistent recipe data structures | -- All have somewhat consistent recipe data structures | ||
local recipeSkillIDs = { | local recipeSkillIDs = { | ||
'melvorD:Cooking', | |||
'melvorD:Smithing', | |||
'melvorD:Fletching', | |||
'melvorD:Crafting', | |||
'melvorD:Runecrafting', | |||
'melvorD:Herblore' | |||
} | } | ||
for i, recipeSkillID in ipairs(recipeSkillIDs) do | for i, recipeSkillID in ipairs(recipeSkillIDs) do | ||
if not hasUse(recipeSkillID) then | if not hasUse(recipeSkillID) then | ||
local | local _, localSkillID = GameData.getLocalID(recipeSkillID) | ||
-- Iterate over all recipes for the current skill | -- Iterate over all recipes for the current skill | ||
for j, recipe in ipairs(SkillData[ | for j, recipe in ipairs(SkillData[localSkillID].recipes) do | ||
for k, itemCost in ipairs(recipe.itemCosts) do | for k, itemCost in ipairs(recipe.itemCosts) do | ||
if itemCost.id == item.id then | if itemCost.id == item.id then | ||
Line 229: | Line 265: | ||
-- Firemaking | -- Firemaking | ||
if not hasUse( | if not hasUse('melvorD:Firemaking') then | ||
for i, recipe in ipairs(SkillData.Firemaking.logs) do | |||
if recipe.logID == item.id then | |||
addUse('melvorD:Firemaking') | |||
break | |||
end | |||
end | |||
end | end | ||
-- Farming | -- Farming | ||
if not hasUse( | if not hasUse('melvorD:Farming') then | ||
for i, recipe in ipairs(SkillData.Farming.recipes) do | |||
if recipe.seedCost.id == item.id then | |||
addUse('melvorD:Farming') | |||
break | |||
end | |||
end | |||
end | end | ||
-- Agility | -- Agility | ||
if not hasUse( | if not hasUse('melvorD:Agility') and not Shared.tableIsEmpty(Agility.getObstaclesForItem(item.id)) then | ||
addUse( | addUse('melvorD:Agility') | ||
end | end | ||
-- Summoning | -- Summoning | ||
if not hasUse( | if not hasUse('melvorD:Summoning') then | ||
for i, recipe in ipairs(SkillData.Summoning. | for i, recipe in ipairs(SkillData.Summoning.recipes) do | ||
-- Tablets & Non-shard items | -- Tablets & Non-shard items | ||
if recipe. | if recipe.productID == item.id or Shared.contains(recipe.nonShardItemCosts, item.id) then | ||
addUse( | addUse('melvorD:Summoning') | ||
break | break | ||
else | else | ||
Line 254: | Line 300: | ||
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 | ||
addUse( | addUse('melvorD:Summoning') | ||
break | break | ||
end | end | ||
end | end | ||
end | end | ||
Line 268: | Line 311: | ||
if item.prayerPoints ~= nil then | if item.prayerPoints ~= nil then | ||
table.insert(categoryArray, '[[Category:Buriable Items]]') | table.insert(categoryArray, '[[Category:Buriable Items]]') | ||
if not hasUse( | if not hasUse('melvorD:Prayer') then | ||
addUse( | addUse('melvorD:Prayer') | ||
end | |||
elseif item.soulPoints ~= nil then | |||
table.insert(categoryArray, '[[Category:Releasable Items]]') | |||
if not hasUse('melvorD:Prayer') then | |||
addUse('melvorD:Prayer') | |||
end | |||
end | |||
-- Magic | |||
if not (hasUse('Magic') and hasUse('AltMagic')) then | |||
-- First check if the item its self is used in any spells | |||
local spellList = Magic.getSpellsUsingItem(item.id, true) | |||
for i, spell in ipairs(spellList) do | |||
local useKey = (spell.type == 'altMagic' and 'AltMagic' or 'Magic') | |||
if not hasUse(useKey) then | |||
addUse(useKey) | |||
end | |||
end | |||
-- Check if the item provides runes, if it does check where they are used also | |||
if item.providedRunes ~= nil then | |||
for i, rune in ipairs(item.providedRunes) do | |||
if hasUse('Magic') and hasUse('AltMagic') then | |||
break | |||
else | |||
local spellList = Magic.getSpellsUsingItem(rune.id, false) | |||
for j, spell in ipairs(spellList) do | |||
local useKey = (spell.type == 'altMagic' and 'AltMagic' or 'Magic') | |||
if not hasUse(useKey) then | |||
addUse(useKey) | |||
end | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
-- Other odds and ends: | -- Other odds and ends: | ||
-- 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 = {' | if item.tier == 'Skillcape' then | ||
local ignoreCapes = { | |||
'melvorD:Attack_Skillcape', | |||
'melvorD:Strength_Skillcape', | |||
'melvorD:Defence_Skillcape', | |||
'melvorD:Hitpoints_Skillcape', | |||
'melvorF:Ranged_Skillcape', | |||
'melvorTotH:Superior_Attack_Skillcape', | |||
'melvorTotH:Superior_Strength_Skillcape', | |||
'melvorTotH:Superior_Defence_Skillcape', | |||
'melvorTotH:Superior_Hitpoints_Skillcape', | |||
'melvorTotH:Superior_Ranged_Skillcape', | |||
} | |||
local allCapes = { | |||
'melvorF:Max_Skillcape', | |||
'melvorF:Cape_of_Completion', | |||
'melvorTotH:Superior_Max_Skillcape', | |||
'melvorTotH:Superior_Cape_Of_Completion', | |||
} | |||
if Shared.contains(allCapes, item.id) then | |||
addUse('AllSkills') | |||
elseif Shared.contains({'melvorF:Magic_Skillcape', 'melvorTotH:Superior_Magic_Skillcape'}, item.id) then | |||
addUse('melvorD:Magic') | |||
addUse('AltMagic') | |||
elseif not Shared.contains(ignoreCapes, item.id) then | |||
local splitName = Shared.splitString(item.name, ' ') | |||
local skillName = (splitName[1] == 'Superior' and splitName[2]) or splitName[1] | |||
addUse(skillName) | |||
end | |||
table.insert(categoryArray, '[[Category:Skillcapes]]') | table.insert(categoryArray, '[[Category:Skillcapes]]') | ||
end | end | ||
--Special note for Charge Stone of Rhaelyx | --Special note for Charge Stone of Rhaelyx | ||
if item. | if item.id == 'melvorD:Charge_Stone_of_Rhaelyx' then | ||
addUse('ChargeStone') | addUse('ChargeStone') | ||
end | end | ||
Line 305: | Line 393: | ||
--Some items are needed to make shop purchases | --Some items are needed to make shop purchases | ||
local shopArray = Shop.getItemCostArray(item.id) | local shopArray = Shop.getItemCostArray(item.id) | ||
if Shared. | if not Shared.tableIsEmpty(shopArray) then | ||
addUse('Shop') | addUse('Shop') | ||
end | end | ||
-- Township Tasks | |||
for _, task in ipairs(SkillData.Township.tasks) do | |||
if task.goals.items ~= nil then -- Skip tasks with no items | |||
if GameData.getEntityByID(task.goals.items, item.id) then | |||
addUse('TownshipTask') | |||
break | |||
end | |||
end | |||
end | |||
-- Generate result text | -- Generate result text | ||
local useArray = {} | local useArray = {} | ||
local prefix, delim = asList and '* ' or '', asList and '\r\n' or '<br/>' | local prefix, delim = asList and '* ' or '', asList and '\r\n' or '<br/>' | ||
-- Always place 'All skills' use before any skills | |||
table.insert(useArray, prefix .. (otherUseText | if hasUse('AllSkills') then | ||
table.insert(useArray, prefix .. (otherUseText.AllSkills or 'AllSkills')) | |||
otherUses.AllSkills = nil | |||
end | end | ||
for skillID, skillName in Shared.spairs(skillUses, function(t, a, b) return t[a] < t[b] end) do | 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'})) | table.insert(useArray, prefix .. Icons.Icon({skillName, type='skill'})) | ||
end | |||
for use, _ in Shared.skpairs(otherUses) do | |||
table.insert(useArray, prefix .. (otherUseText[use] or use)) | |||
end | end | ||
Line 332: | Line 435: | ||
end | end | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 341: | Line 444: | ||
local useArray = {} | local useArray = {} | ||
-- Loop through all | -- Loop through all upgrades to find anything that can be upgraded using our source | ||
for i, | for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do | ||
for j, itemCost in ipairs(upgrade.itemCosts) do | |||
if itemCost.id == item.id then | |||
local rowReq = nil | |||
-- Potions do have upgrade requirements though | |||
local upgradeItem = Items.getItemByID(upgrade.upgradedItemID) | |||
if upgradeItem ~= nil and upgradeItem.charges ~= nil and upgradeItem.tier ~= nil then | |||
local levelUnlock = GameData.getEntityByProperty(SkillData.Herblore.masteryLevelUnlocks, 'descriptionID', upgradeItem.tier + 1) | |||
if | if levelUnlock ~= nil then | ||
rowReq = Icons._MasteryReq( | rowReq = Icons._MasteryReq(upgradeItem.name, levelUnlock.level) | ||
end | end | ||
end | end | ||
table.insert(useArray, {item = {id = upgradeItem.id, name = upgradeItem.name}, qty = (upgrade.quantity or 1), mats = upgrade.itemCosts, skill = 'Upgrade', req = rowReq, xp = 'N/A', gp = upgrade.gpCost, sc = upgrade.scCost}) | |||
end | end | ||
end | end | ||
Line 363: | Line 465: | ||
-- All have somewhat consistent recipe data structures | -- All have somewhat consistent recipe data structures | ||
local recipeSkillIDs = { | local recipeSkillIDs = { | ||
'melvorD:Cooking', | |||
'melvorD:Smithing', | |||
'melvorD:Fletching', | |||
'melvorD:Crafting', | |||
'melvorD:Runecrafting', | |||
'melvorD:Herblore' | |||
} | } | ||
for i, recipeSkillID in ipairs(recipeSkillIDs) do | for i, recipeSkillID in ipairs(recipeSkillIDs) do | ||
local skillName = Constants.getSkillName(recipeSkillID) | local skillName = Constants.getSkillName(recipeSkillID) | ||
local | local _, localSkillID = GameData.getLocalID(recipeSkillID) | ||
-- Iterate over all recipes for the current skill | -- Iterate over all recipes for the current skill | ||
for j, recipe in ipairs(SkillData[ | for j, recipe in ipairs(SkillData[localSkillID].recipes) do | ||
local costLists = {recipe.alternativeCosts or {}, {{["itemCosts"] = recipe.itemCosts}}} | local costLists = {recipe.alternativeCosts or {}, {{["itemCosts"] = recipe.itemCosts}}} | ||
for k, costList in pairs(costLists) do | for k, costList in pairs(costLists) do | ||
Line 381: | Line 484: | ||
if itemCost.id == item.id then | if itemCost.id == item.id then | ||
local recipeItemIDs = nil | local recipeItemIDs = nil | ||
if recipeSkillID == | if recipeSkillID == 'melvorD:Herblore' then | ||
recipeItemIDs = recipe.potionIDs | recipeItemIDs = recipe.potionIDs | ||
elseif recipeSkillID == | elseif recipeSkillID == 'melvorD:Cooking' then | ||
recipeItemIDs = {recipe. | recipeItemIDs = {recipe.productID, recipe.perfectCookID} | ||
else | else | ||
recipeItemIDs = {recipe. | recipeItemIDs = {recipe.productID} | ||
end | end | ||
for o, recipeItemID in ipairs(recipeItemIDs) do | for o, recipeItemID in ipairs(recipeItemIDs) do | ||
Line 393: | Line 496: | ||
local itemDef = {id = recipe.itemID, name = recipeItem.name} | local itemDef = {id = recipe.itemID, name = recipeItem.name} | ||
local qty = (recipe.baseQuantity or 1) * (costDef.quantityMultiplier or 1) | local qty = (recipe.baseQuantity or 1) * (costDef.quantityMultiplier or 1) | ||
local rowReq = recipe | local rowReq, isAbyssal = Skills.getRecipeLevelRealm(recipeSkillID, recipe) | ||
local reqVal = nil | local reqVal = nil | ||
if recipeSkillID == | local xp = recipe.baseAbyssalExperience or recipe.baseExperience | ||
if recipeSkillID == 'melvorD:Herblore' then | |||
-- Herblore may also have a mastery requirement | -- Herblore may also have a mastery requirement | ||
local | local levelUnlock = GameData.getEntityByProperty(SkillData.Herblore.masteryLevelUnlocks, 'descriptionID', recipeItem.tier + 1) | ||
if | if levelUnlock ~= nil and levelUnlock.level > 1 then | ||
local masteryReq = Icons._MasteryReq(recipeItem.name, | local masteryReq = Icons._MasteryReq(recipeItem.name, levelUnlock.level) | ||
reqVal = rowReq + | reqVal = rowReq + levelUnlock.level * 0.01 | ||
rowReq = Icons._SkillReq(skillName, rowReq) .. '<br/>' .. masteryReq | rowReq = Icons._SkillReq(skillName, rowReq, false, (isAbyssal and "melvorItA:Abyssal" or nil)) .. '<br/>' .. masteryReq | ||
end | end | ||
end | end | ||
table.insert(useArray, {item = itemDef, qty = qty, mats = costDef.itemCosts, skill = skillName, reqVal = reqVal, req = rowReq, xp = | table.insert(useArray, {item = itemDef, qty = qty, mats = costDef.itemCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = skillName, reqVal = reqVal, req = rowReq, isAbyssal = isAbyssal, xp = xp}) | ||
end | end | ||
end | end | ||
Line 416: | Line 520: | ||
-- Farming | -- Farming | ||
if item. | for i, recipe in ipairs(SkillData.Farming.recipes) do | ||
if recipe.seedCost.id == item.id then | |||
local product = Items.getItemByID(recipe.productId) | |||
local mat = {{id = recipe.seedCost.id, quantity = recipe.seedCost.quantity}} | |||
local rowReq, isAbyssal = Skills.getRecipeLevelRealm('melvorD:Farming', recipe) | |||
local category = GameData.getEntityByID(SkillData.Farming.categories, recipe.categoryID) | |||
local qty = 5 * category.harvestMultiplier | |||
local xp = recipe.baseAbyssalExperience or recipe.baseExperience | |||
table.insert(useArray, {item = {id = product.id, name = product.name}, qty = qty, mats = mat, skill = 'Farming', req = rowReq, isAbyssal = isAbyssal, xp = xp}) | |||
end | |||
end | end | ||
Line 429: | Line 536: | ||
for i, obstacle in ipairs(obstacles) do | for i, obstacle in ipairs(obstacles) do | ||
local itemCosts = {} | local itemCosts = {} | ||
for j, itemDef in ipairs(obstacle. | for j, itemDef in ipairs(obstacle.itemCosts) do | ||
table.insert(itemCosts, {id = itemDef | table.insert(itemCosts, {id = itemDef.id, quantity = itemDef.quantity}) | ||
end | end | ||
local req = Agility._getObstacleRequirements(obstacle) | local req = Agility._getObstacleRequirements(obstacle) | ||
--local objType = (obstacle.category == nil and 'Pillar') or '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. | table.insert(useArray, {item = {id = obstacle.id, name = obstacle.name}, qty = 1, mats = itemCosts, gp = obstacle.gpCost, sc = obstacle.scCost, skill = 'Agility', req = req, type = 'skill'}) | ||
end | end | ||
-- Summoning | -- Summoning | ||
for i, recipe in ipairs(SkillData.Summoning. | for i, recipe in ipairs(SkillData.Summoning.recipes) do | ||
local | local recipeCost = 0 | ||
if recipe.abyssalLevel ~= nil then | |||
recipeCost = SkillData.Summoning.recipeAPCost | |||
else | |||
recipeCost = SkillData.Summoning.recipeGPCost | |||
end | |||
local useShards = false | local useShards = false | ||
local recipeItem = nil | local recipeItem = nil | ||
Line 449: | Line 561: | ||
end | end | ||
-- Non-shard items | -- Non-shard items | ||
for j, nonShardItemID in ipairs( | -- Familiar recipes may also have a currency cost without any non-shard | ||
-- items, so account for this with a dummy ID such that one iteration | |||
-- of the below loop always occurs | |||
local nonShardItemIDs = (Shared.tableIsEmpty(recipe.nonShardItemCosts) and {''} or recipe.nonShardItemCosts) | |||
for j, nonShardItemID in ipairs(nonShardItemIDs) do | |||
if useShards or nonShardItemID == item.id then | if useShards or nonShardItemID == item.id then | ||
-- Item is used in this particular synergy recipe | -- Item is used in this particular synergy recipe | ||
if recipeItem == nil then | if recipeItem == nil then | ||
recipeItem = Items.getItemByID(recipe. | recipeItem = Items.getItemByID(recipe.productID) | ||
end | end | ||
local nonShardItem = Items.getItemByID(nonShardItemID) | local nonShardItem = Items.getItemByID(nonShardItemID) | ||
local recipeCosts = Shared.clone(recipe.itemCosts) | local recipeCosts = Shared.clone(recipe.itemCosts) | ||
table.insert(recipeCosts, {id = nonShardItemID, | local xp = recipe.baseAbyssalExperience or recipe.baseExperience | ||
table.insert(useArray, {item = {id = | local req, isAbyssal = Skills.getRecipeLevelRealm('melvorD:Summoning', recipe) | ||
local recipeCosts = {} | |||
for k, itemCost in ipairs(recipe.itemCosts) do | |||
table.insert(recipeCosts, {id = itemCost.id, quantity = itemCost.quantity}) | |||
end | |||
if nonShardItem ~= nil then | |||
-- Item ID can be nil for recipes such as Leprechaun or Cyclops | |||
local itemValue = math.max(nonShardItem.sellsFor, 20) | |||
local nonShardQty = math.max(1, math.ceil(recipeCost / itemValue)) | |||
table.insert(recipeCosts, {id = nonShardItemID, quantity = nonShardQty}) | |||
end | |||
table.insert(useArray, {item = {id = recipeItem.id, name = recipeItem.name}, qty = recipe.baseQuantity, mats = recipeCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = 'Summoning', req = req, isAbyssal = isAbyssal, xp = xp}) | |||
end | end | ||
end | end | ||
Line 467: | Line 592: | ||
--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, | for i, shopUse in ipairs(shopUses) do | ||
local rowReq = Shop.getRequirementString(purchase. | local purchase = shopUse.purchase | ||
local iconType = (purchase. | local rowReq = Shop.getRequirementString(purchase.purchaseRequirements) | ||
table.insert(useArray, {item = {name = purchase | local iconType = Common.getPurchaseIconType(purchase) | ||
local gpCost = Shop.getCurrencyCostString(purchase.cost, 'gp') | |||
local scCost = Shop.getCurrencyCostString(purchase.cost, 'slayerCoins') | |||
local rcCost = Shop.getCurrencyCostString(purchase.cost, 'raidCoins') | |||
table.insert(useArray, {item = {name = Shop._getPurchaseName(purchase)}, qty = 1, mats = purchase.cost.items, skill = 'Shop', req = rowReq, xp = 'N/A', gp = gpCost, sc = scCost, rc = rcCost, type = iconType}) | |||
end | end | ||
Line 490: | Line 619: | ||
local resultPart = {} | local resultPart = {} | ||
if Shared. | if not Shared.tableIsEmpty(useArray) then | ||
local typeTextList = { | local typeTextList = { | ||
["Shop"] = Icons.Icon({'Shop'}), | ["Shop"] = Icons.Icon({'Shop'}), | ||
Line 497: | Line 626: | ||
-- Header | -- Header | ||
table.insert(resultPart, '{| class="wikitable stickyHeader sortable"') | table.insert(resultPart, '{| class="wikitable stickyHeader sortable col-1-img"') | ||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | table.insert(resultPart, '\r\n|- class="headerRow-0"') | ||
table.insert(resultPart, '\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients') | table.insert(resultPart, '\r\n!colspan=2|Item Created!!Type!!Requirements!!XP!!Ingredients') | ||
Line 507: | Line 636: | ||
local iconName = row.item.name | local iconName = row.item.name | ||
if row.skill == 'Agility' then | if row.skill == 'Agility' then | ||
iconType = 'agility' | |||
end | end | ||
local typeName = row.skill ~= nil and row.skill or '' | local typeName = row.skill ~= nil and row.skill or '' | ||
Line 514: | Line 643: | ||
if type(row.req) == 'number' then | if type(row.req) == 'number' then | ||
reqVal = row.req | reqVal = row.req | ||
reqText = Icons._SkillReq(typeName, row.req) | reqText = Icons._SkillReq(typeName, row.req, false, (row.isAbyssal and "melvorItA:Abyssal" or nil)) | ||
elseif type(row.req) == 'string' then | elseif type(row.req) == 'string' then | ||
reqText = row.req | reqText = row.req | ||
Line 523: | Line 652: | ||
elseif type(row.xp) == 'number' then | elseif type(row.xp) == 'number' then | ||
xpVal = row.xp | xpVal = row.xp | ||
xpText = | xpText = Num.formatnum(row.xp) .. ' ' .. Icons.Icon({typeName, type='skill', notext=true}) .. ' XP' | ||
end | end | ||
local matRow = {} | local matRow = {} | ||
Line 530: | Line 659: | ||
local matItemID = itemCost.id or itemCost[1] or -1 | local matItemID = itemCost.id or itemCost[1] or -1 | ||
local matItem = Items.getItemByID(matItemID) | local matItem = Items.getItemByID(matItemID) | ||
local matQty = itemCost. | local matQty = itemCost.quantity or itemCost[2] or 1 | ||
if matItem == nil then | if matItem == nil then | ||
table.insert(matRow, ' | table.insert(matRow, Shared.printError('Failed to find item with ID "' .. itemCost.id .. '"')) | ||
elseif type(matQty) == 'number' then | elseif type(matQty) == 'number' then | ||
table.insert(matRow, Icons.Icon({matItem.name, type='item', qty=matQty})) | table.insert(matRow, Icons.Icon({matItem.name, type='item', qty=matQty})) | ||
Line 540: | Line 669: | ||
end | end | ||
end | end | ||
if row.gp ~= nil and row.gp > 0 then | if row.gp ~= nil then | ||
table.insert(matRow, Icons. | local gpText = nil | ||
if type(row.gp) == 'number' and row.gp > 0 then | |||
gpText = Icons._Currency('melvorD:GP', row.gp) | |||
elseif type(row.gp) == 'string' then | |||
gpText = row.gp | |||
end | |||
table.insert(matRow, gpText) | |||
end | |||
if row.sc ~= nil then | |||
local scText = nil | |||
if type(row.sc) == 'number' and row.sc > 0 then | |||
scText = Icons._Currency('melvorD:SlayerCoins', row.sc) | |||
elseif type(row.sc) == 'string' then | |||
scText = row.sc | |||
end | |||
table.insert(matRow, scText) | |||
end | end | ||
if row. | if row.rc ~= nil then | ||
local rcText = nil | |||
if type(row.rc) == 'number' and row.rc > 0 then | |||
rcText = Icons._Currency('melvorD:RaidCoins', row.rc) | |||
elseif type(row.rc) == 'string' then | |||
rcText = row.rc | |||
end | |||
table.insert(matRow, rcText) | |||
end | end | ||
-- Item created | -- Item created | ||
Line 551: | Line 701: | ||
table.insert(resultPart, '\r\n| ') | table.insert(resultPart, '\r\n| ') | ||
if qty > 1 then | if qty > 1 then | ||
table.insert(resultPart, "'''" .. | table.insert(resultPart, "'''" .. Num.formatnum(qty) .. "x''' ") | ||
end | end | ||
table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, noicon=true})) | table.insert(resultPart, Icons.Icon({iconName, row.item.name, type=iconType, noicon=true})) | ||
Line 573: | Line 723: | ||
table.insert(resultPart, '\r\n===' .. Icons.Icon({'Magic', type='skill', size=30}) .. '===\r\n' .. spellUseTable) | table.insert(resultPart, '\r\n===' .. Icons.Icon({'Magic', type='skill', size=30}) .. '===\r\n' .. spellUseTable) | ||
end | end | ||
if Shared. | if Shared.tableIsEmpty(resultPart) then | ||
return '' | return '' | ||
else | else | ||
Line 584: | Line 734: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 591: | Line 741: | ||
function p._getSpellUseTable(item) | function p._getSpellUseTable(item) | ||
local spellList = Magic. | local spellList = Magic.getSpellsUsingItem(item.id, true) | ||
--Bail immediately if no spells are found | --Bail immediately if no spells are found | ||
if Shared. | if Shared.tableIsEmpty(spellList) then | ||
return '' | return '' | ||
end | |||
--Adding a check for if the Items column is needed | |||
local hasItems = false | |||
for i, spell in pairs(spellList) do | |||
if Magic._getSpellItems(spell) ~= '' then | |||
hasItems = true | |||
break | |||
end | |||
end | end | ||
Line 602: | Line 761: | ||
table.insert(resultPart, '!!Type!!style="width:275px"|Description') | table.insert(resultPart, '!!Type!!style="width:275px"|Description') | ||
table.insert(resultPart, '!!Runes') | table.insert(resultPart, '!!Runes') | ||
if hasItems then | |||
table.insert(resultPart, '!!Item Cost') | |||
end | |||
for i, spell in pairs(spellList) do | for i, spell in pairs(spellList) do | ||
local spellBook = Magic.getSpellBookFromSpell(spell) | |||
local rowPart = {} | local rowPart = {} | ||
table.insert(rowPart, '\r\n|-\r\n|data-sort-value="'..spell.name..'"|') | table.insert(rowPart, '\r\n|-\r\n|data-sort-value="'..spell.name..'"|') | ||
local iconType = | local iconType = Magic._getSpellIconType(spell) | ||
table.insert(rowPart, Icons.Icon({spell.name, type=iconType, notext=true, size=50})) | 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, '||'..Icons.Icon({spell.name, type=iconType, noicon=true})) | ||
table.insert(rowPart, '||data-sort-value="'.. | table.insert(rowPart, '||data-sort-value="'..Skills.getRecipeLevel('melvorD:Summoning', spell)..'"|'..Magic._getSpellRequirements(spell)) | ||
table.insert(rowPart, '||data-sort-value="'.. spellBook.id ..'"|') | |||
table.insert(rowPart, Magic.getSpellTypeLink(spellBook.id)) | |||
table.insert(rowPart, '||data-sort-value="'.. | |||
table.insert(rowPart, Magic.getSpellTypeLink( | |||
table.insert(rowPart, '||'..Magic._getSpellStat(spell, 'description')) | table.insert(rowPart, '||'..Magic._getSpellStat(spell, 'description')) | ||
table.insert(rowPart, '||style="text-align:center"|') | table.insert(rowPart, '||style="text-align:center"|') | ||
table.insert(rowPart, Magic._getSpellRunes(spell)) | table.insert(rowPart, Magic._getSpellRunes(spell)) | ||
if hasItems then | |||
table.insert(rowPart, '||style="text-align:right"|') | |||
table.insert(rowPart, Magic._getSpellItems(spell)) | |||
end | |||
table.insert(resultPart, table.concat(rowPart)) | table.insert(resultPart, table.concat(rowPart)) | ||
end | end | ||
Line 634: | Line 794: | ||
local item = Items.getItem(itemName) | local item = Items.getItem(itemName) | ||
if item == nil then | if item == nil then | ||
return | return Shared.printError('No item named "' .. itemName .. '" exists in the data module') | ||
end | end | ||
Line 673: | Line 833: | ||
'Mysterious Stone', | 'Mysterious Stone', | ||
'Mastery Token (Cooking)', | 'Mastery Token (Cooking)', | ||
'Gem Gloves' | 'Gem Gloves', | ||
'Basic Bag', | |||
'Bird Nest', | |||
'Abyssal Stone', | |||
'Withered Ash', | |||
'Wrath Rune' | |||
} | } | ||
local checkFuncs = { | local checkFuncs = { |
Latest revision as of 19:20, 6 September 2024
Documentation for this module may be created at Module:Items/UseTables/doc
local p = {}
local Constants = require('Module:Constants')
local Common = require('Module:Common')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Skills = require('Module:Skills')
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 Num = require('Module:Number')
--Brute forcing some item uses to make things easier
local itemUseArray = {
Agility = {},
Astrology = {'melvorF:Stardust', 'melvorF:Golden_Stardust', 'melvorItA:Abyssal_Stardust'},
Archaeology = {},
Attack = {},
Cartography = {},
Combat = {'melvorF:Gold_Emerald_Ring', 'melvorD:Obsidian_Cape', 'melvorF:Throwing_Power_Gloves'},
Cooking = {'melvorD:Cooking_Gloves'},
Crafting = {'melvorItA:Abyssal_Crafting_Gloves'},
Defence = {},
Farming = {'melvorD:Compost', 'melvorD:Weird_Gloop', 'melvorItA:Abyssal_Compost', 'melvorD:Bobs_Rake'},
Firemaking = {'melvorItA:Abyssal_Firemaking_Gloves'},
Fishing = {'melvorD:Message_In_A_Bottle', 'melvorD:Barbarian_Gloves'},
Fletching = {'melvorItA:Abyssal_Fletching_Gloves'},
Harvesting = {'melvorItA:Abyssal_Harvesting_Gloves'},
Herblore = {'melvorItA:Abyssal_Herblore_Gloves'},
Hitpoints = {},
Magic = {},
Mining = {'melvorD:Mining_Gloves', 'melvorD:Gem_Gloves', 'melvorItA:Abyssal_Mining_Gloves'},
Prayer = {},
Ranged = {},
Runecrafting = {'melvorItA:Abyssal_Runecrafting_Gloves'},
Slayer = {},
Smithing = {'melvorD:Smithing_Gloves', 'melvorItA:Abyssal_Smithing_Gloves'},
Smithing = {},
Strength = {},
Summoning = {},
Thieving = {'melvorF:Thieving_Gloves'},
Township = {},
Woodcutting = {},
}
-- List of modifier IDs which, if present on an item and not specific to any
-- particular skill, indicate that the item applies to all skills
local allSkillModIDs = {
-- Skill XP
'skillXP',
'nonCombatSkillXP',
'abyssalSkillXP',
-- Mastery
'flatMasteryTokens',
'xpFromMasteryTokens',
'masteryPoolCap',
'masteryXP',
'masteryPoolProgress',
-- Resource preservation
'bypassGlobalPreservationChance',
'skillPreservationChance',
'skillPreservationCap',
-- Other preservation (consumables, summons, potion charges)
'consumablePreservationChance',
'summoningChargePreservationChance',
'potionChargePreservationChance',
-- Item doubling
'globalItemDoublingChance',
-- Resource quantity
'flatBasePrimaryProductQuantity',
'basePrimaryProductQuantity',
'flatBaseRandomProductQuantity',
'flatAdditionalSkillItem',
'flatAdditionalPrimaryProductQuantity',
-- Cost reduction
'skillCostReduction',
-- Off item chance
'offItemChance'
}
local function allSkillModCriteriaFromIDs(modIDs)
local modIDsWithProps = {}
for i, modID in ipairs(allSkillModIDs) do
table.insert(modIDsWithProps, {
["id"] = modID,
["type"] = 'id',
["props"] = {
["skillID"] = 'nil',
["modType"] = 'pos'
}
})
end
return Modifiers.getMatchCriteriaFromIDs(modIDsWithProps)
end
local allSkillModCriteria = allSkillModCriteriaFromIDs(allSkillModIDs)
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|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'}),
["TownshipTask"] = Icons.Icon({'Tasks', type='township'})
}
local addUse = function(useName)
local skillID = Constants.getSkillID(useName)
if skillID == nil then
-- May have been passed a skill ID instead
local skillName = Constants.getSkillName(useName)
if skillName ~= nil then
skillID = useName
skillUses[skillID] = skillName
end
else
skillUses[skillID] = useName
end
if skillID == nil and not otherUses[useName] then
otherUses[useName] = true
end
end
local hasUse = function(useName)
local skillID = Constants.getSkillID(useName)
if skillID == nil and Constants.getSkillName(useName) ~= nil then
skillID = useName
end
if skillID ~= nil 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.id) then
addUse(useName)
end
end
-- If the item has any modifiers that affect a given skill, add it to those tables
-- Added special handling for Mastery Tokens since they were being incorrectly flagged as usable for all skills
if item.modifiers ~= nil then
if item.modifiers.masteryToken ~= nil then
-- Mastery tokens
addUse('Mastery')
else
local modsAllSkills = Modifiers.getMatchingModifiers(item.modifiers, allSkillModCriteria)
if not Shared.tableIsEmpty(modsAllSkills.matched) then
addUse('AllSkills')
else
local skillArray = Modifiers.getModifierSkills(item.modifiers)
for i, skillName in ipairs(skillArray) do
addUse(skillName)
end
end
end
if item.modifiers.thievingStealth ~= nil then
addUse('melvorD:Thieving')
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('Slayer') and (item.validSlots ~= nil or item.occupiesSlots ~= nil or item.equipmentStats ~= nil) then
local slayerAreas = Areas.getAreas(function(area) return area.type == 'slayerArea' and type(area.entryRequirements) == 'table' end)
for i, area in ipairs(slayerAreas) do
for j, req in ipairs(area.entryRequirements) do
if req.type == "SlayerItem" and req.itemID == item.id then
addUse('Slayer')
break
end
end
if hasUse('Slayer') then
break
end
end
end
-- Is the item a cost in an upgrade?
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
for j, itemCost in ipairs(upgrade.itemCosts) do
if itemCost.id == item.id then
addUse('Upgrade')
table.insert(categoryArray, '[[Category:Upgradeable Items]]')
break
end
end
if hasUse('Upgrade') then
break
end
end
if item.healsFor ~= nil then
table.insert(categoryArray, '[[Category:Food Items]]')
addUse('Food')
end
if item.dropTable ~= nil 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 = {
'melvorD:Cooking',
'melvorD:Smithing',
'melvorD:Fletching',
'melvorD:Crafting',
'melvorD:Runecrafting',
'melvorD:Herblore'
}
for i, recipeSkillID in ipairs(recipeSkillIDs) do
if not hasUse(recipeSkillID) then
local _, localSkillID = GameData.getLocalID(recipeSkillID)
-- Iterate over all recipes for the current skill
for j, recipe in ipairs(SkillData[localSkillID].recipes) 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('melvorD:Firemaking') then
for i, recipe in ipairs(SkillData.Firemaking.logs) do
if recipe.logID == item.id then
addUse('melvorD:Firemaking')
break
end
end
end
-- Farming
if not hasUse('melvorD:Farming') then
for i, recipe in ipairs(SkillData.Farming.recipes) do
if recipe.seedCost.id == item.id then
addUse('melvorD:Farming')
break
end
end
end
-- Agility
if not hasUse('melvorD:Agility') and not Shared.tableIsEmpty(Agility.getObstaclesForItem(item.id)) then
addUse('melvorD:Agility')
end
-- Summoning
if not hasUse('melvorD:Summoning') then
for i, recipe in ipairs(SkillData.Summoning.recipes) do
-- Tablets & Non-shard items
if recipe.productID == item.id or Shared.contains(recipe.nonShardItemCosts, item.id) then
addUse('melvorD:Summoning')
break
else
-- Shards
for j, itemCost in ipairs(recipe.itemCosts) do
if itemCost.id == item.id then
addUse('melvorD:Summoning')
break
end
end
end
end
end
-- Prayer
if item.prayerPoints ~= nil then
table.insert(categoryArray, '[[Category:Buriable Items]]')
if not hasUse('melvorD:Prayer') then
addUse('melvorD:Prayer')
end
elseif item.soulPoints ~= nil then
table.insert(categoryArray, '[[Category:Releasable Items]]')
if not hasUse('melvorD:Prayer') then
addUse('melvorD:Prayer')
end
end
-- Magic
if not (hasUse('Magic') and hasUse('AltMagic')) then
-- First check if the item its self is used in any spells
local spellList = Magic.getSpellsUsingItem(item.id, true)
for i, spell in ipairs(spellList) do
local useKey = (spell.type == 'altMagic' and 'AltMagic' or 'Magic')
if not hasUse(useKey) then
addUse(useKey)
end
end
-- Check if the item provides runes, if it does check where they are used also
if item.providedRunes ~= nil then
for i, rune in ipairs(item.providedRunes) do
if hasUse('Magic') and hasUse('AltMagic') then
break
else
local spellList = Magic.getSpellsUsingItem(rune.id, false)
for j, spell in ipairs(spellList) do
local useKey = (spell.type == 'altMagic' and 'AltMagic' or 'Magic')
if not hasUse(useKey) then
addUse(useKey)
end
end
end
end
end
end
-- Other odds and ends:
-- 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
if item.tier == 'Skillcape' then
local ignoreCapes = {
'melvorD:Attack_Skillcape',
'melvorD:Strength_Skillcape',
'melvorD:Defence_Skillcape',
'melvorD:Hitpoints_Skillcape',
'melvorF:Ranged_Skillcape',
'melvorTotH:Superior_Attack_Skillcape',
'melvorTotH:Superior_Strength_Skillcape',
'melvorTotH:Superior_Defence_Skillcape',
'melvorTotH:Superior_Hitpoints_Skillcape',
'melvorTotH:Superior_Ranged_Skillcape',
}
local allCapes = {
'melvorF:Max_Skillcape',
'melvorF:Cape_of_Completion',
'melvorTotH:Superior_Max_Skillcape',
'melvorTotH:Superior_Cape_Of_Completion',
}
if Shared.contains(allCapes, item.id) then
addUse('AllSkills')
elseif Shared.contains({'melvorF:Magic_Skillcape', 'melvorTotH:Superior_Magic_Skillcape'}, item.id) then
addUse('melvorD:Magic')
addUse('AltMagic')
elseif not Shared.contains(ignoreCapes, item.id) then
local splitName = Shared.splitString(item.name, ' ')
local skillName = (splitName[1] == 'Superior' and splitName[2]) or splitName[1]
addUse(skillName)
end
table.insert(categoryArray, '[[Category:Skillcapes]]')
end
--Special note for Charge Stone of Rhaelyx
if item.id == 'melvorD:Charge_Stone_of_Rhaelyx' then
addUse('ChargeStone')
end
--Some items are needed to make shop purchases
local shopArray = Shop.getItemCostArray(item.id)
if not Shared.tableIsEmpty(shopArray) then
addUse('Shop')
end
-- Township Tasks
for _, task in ipairs(SkillData.Township.tasks) do
if task.goals.items ~= nil then -- Skip tasks with no items
if GameData.getEntityByID(task.goals.items, item.id) then
addUse('TownshipTask')
break
end
end
end
-- Generate result text
local useArray = {}
local prefix, delim = asList and '* ' or '', asList and '\r\n' or '<br/>'
-- Always place 'All skills' use before any skills
if hasUse('AllSkills') then
table.insert(useArray, prefix .. (otherUseText.AllSkills or 'AllSkills'))
otherUses.AllSkills = nil
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
for use, _ in Shared.skpairs(otherUses) do
table.insert(useArray, prefix .. (otherUseText[use] or use))
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 Shared.printError('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 upgrades to find anything that can be upgraded using our source
for i, upgrade in ipairs(GameData.rawData.itemUpgrades) do
for j, itemCost in ipairs(upgrade.itemCosts) do
if itemCost.id == item.id then
local rowReq = nil
-- Potions do have upgrade requirements though
local upgradeItem = Items.getItemByID(upgrade.upgradedItemID)
if upgradeItem ~= nil and upgradeItem.charges ~= nil and upgradeItem.tier ~= nil then
local levelUnlock = GameData.getEntityByProperty(SkillData.Herblore.masteryLevelUnlocks, 'descriptionID', upgradeItem.tier + 1)
if levelUnlock ~= nil then
rowReq = Icons._MasteryReq(upgradeItem.name, levelUnlock.level)
end
end
table.insert(useArray, {item = {id = upgradeItem.id, name = upgradeItem.name}, qty = (upgrade.quantity or 1), mats = upgrade.itemCosts, skill = 'Upgrade', req = rowReq, xp = 'N/A', gp = upgrade.gpCost, sc = upgrade.scCost})
end
end
end
-- Cooking, Smithing, Fletching, Crafting, Runecrafting, Herblore
-- All have somewhat consistent recipe data structures
local recipeSkillIDs = {
'melvorD:Cooking',
'melvorD:Smithing',
'melvorD:Fletching',
'melvorD:Crafting',
'melvorD:Runecrafting',
'melvorD:Herblore'
}
for i, recipeSkillID in ipairs(recipeSkillIDs) do
local skillName = Constants.getSkillName(recipeSkillID)
local _, localSkillID = GameData.getLocalID(recipeSkillID)
-- Iterate over all recipes for the current skill
for j, recipe in ipairs(SkillData[localSkillID].recipes) 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 == 'melvorD:Herblore' then
recipeItemIDs = recipe.potionIDs
elseif recipeSkillID == 'melvorD:Cooking' then
recipeItemIDs = {recipe.productID, recipe.perfectCookID}
else
recipeItemIDs = {recipe.productID}
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, isAbyssal = Skills.getRecipeLevelRealm(recipeSkillID, recipe)
local reqVal = nil
local xp = recipe.baseAbyssalExperience or recipe.baseExperience
if recipeSkillID == 'melvorD:Herblore' then
-- Herblore may also have a mastery requirement
local levelUnlock = GameData.getEntityByProperty(SkillData.Herblore.masteryLevelUnlocks, 'descriptionID', recipeItem.tier + 1)
if levelUnlock ~= nil and levelUnlock.level > 1 then
local masteryReq = Icons._MasteryReq(recipeItem.name, levelUnlock.level)
reqVal = rowReq + levelUnlock.level * 0.01
rowReq = Icons._SkillReq(skillName, rowReq, false, (isAbyssal and "melvorItA:Abyssal" or nil)) .. '<br/>' .. masteryReq
end
end
table.insert(useArray, {item = itemDef, qty = qty, mats = costDef.itemCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = skillName, reqVal = reqVal, req = rowReq, isAbyssal = isAbyssal, xp = xp})
end
end
break
end
end
end
end
end
end
-- Farming
for i, recipe in ipairs(SkillData.Farming.recipes) do
if recipe.seedCost.id == item.id then
local product = Items.getItemByID(recipe.productId)
local mat = {{id = recipe.seedCost.id, quantity = recipe.seedCost.quantity}}
local rowReq, isAbyssal = Skills.getRecipeLevelRealm('melvorD:Farming', recipe)
local category = GameData.getEntityByID(SkillData.Farming.categories, recipe.categoryID)
local qty = 5 * category.harvestMultiplier
local xp = recipe.baseAbyssalExperience or recipe.baseExperience
table.insert(useArray, {item = {id = product.id, name = product.name}, qty = qty, mats = mat, skill = 'Farming', req = rowReq, isAbyssal = isAbyssal, xp = xp})
end
end
-- Agility
local obstacles = Agility.getObstaclesForItem(item.id)
for i, obstacle in ipairs(obstacles) do
local itemCosts = {}
for j, itemDef in ipairs(obstacle.itemCosts) do
table.insert(itemCosts, {id = itemDef.id, quantity = itemDef.quantity})
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.gpCost, sc = obstacle.scCost, skill = 'Agility', req = req, type = 'skill'})
end
-- Summoning
for i, recipe in ipairs(SkillData.Summoning.recipes) do
local recipeCost = 0
if recipe.abyssalLevel ~= nil then
recipeCost = SkillData.Summoning.recipeAPCost
else
recipeCost = SkillData.Summoning.recipeGPCost
end
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
-- Familiar recipes may also have a currency cost without any non-shard
-- items, so account for this with a dummy ID such that one iteration
-- of the below loop always occurs
local nonShardItemIDs = (Shared.tableIsEmpty(recipe.nonShardItemCosts) and {''} or recipe.nonShardItemCosts)
for j, nonShardItemID in ipairs(nonShardItemIDs) do
if useShards or nonShardItemID == item.id then
-- Item is used in this particular synergy recipe
if recipeItem == nil then
recipeItem = Items.getItemByID(recipe.productID)
end
local nonShardItem = Items.getItemByID(nonShardItemID)
local recipeCosts = Shared.clone(recipe.itemCosts)
local xp = recipe.baseAbyssalExperience or recipe.baseExperience
local req, isAbyssal = Skills.getRecipeLevelRealm('melvorD:Summoning', recipe)
local recipeCosts = {}
for k, itemCost in ipairs(recipe.itemCosts) do
table.insert(recipeCosts, {id = itemCost.id, quantity = itemCost.quantity})
end
if nonShardItem ~= nil then
-- Item ID can be nil for recipes such as Leprechaun or Cyclops
local itemValue = math.max(nonShardItem.sellsFor, 20)
local nonShardQty = math.max(1, math.ceil(recipeCost / itemValue))
table.insert(recipeCosts, {id = nonShardItemID, quantity = nonShardQty})
end
table.insert(useArray, {item = {id = recipeItem.id, name = recipeItem.name}, qty = recipe.baseQuantity, mats = recipeCosts, gp = recipe.gpCost, sc = recipe.scCost, skill = 'Summoning', req = req, isAbyssal = isAbyssal, xp = xp})
end
end
end
--Handle shop purchases using Module:Shop
local shopUses = Shop.getItemCostArray(item.id)
for i, shopUse in ipairs(shopUses) do
local purchase = shopUse.purchase
local rowReq = Shop.getRequirementString(purchase.purchaseRequirements)
local iconType = Common.getPurchaseIconType(purchase)
local gpCost = Shop.getCurrencyCostString(purchase.cost, 'gp')
local scCost = Shop.getCurrencyCostString(purchase.cost, 'slayerCoins')
local rcCost = Shop.getCurrencyCostString(purchase.cost, 'raidCoins')
table.insert(useArray, {item = {name = Shop._getPurchaseName(purchase)}, qty = 1, mats = purchase.cost.items, skill = 'Shop', req = rowReq, xp = 'N/A', gp = gpCost, sc = scCost, rc = rcCost, 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 not Shared.tableIsEmpty(useArray) then
local typeTextList = {
["Shop"] = Icons.Icon({'Shop'}),
["Upgrade"] = '[[Upgrading Items|Upgrade]]'
}
-- Header
table.insert(resultPart, '{| class="wikitable stickyHeader sortable col-1-img"')
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
iconType = '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, false, (row.isAbyssal and "melvorItA:Abyssal" or nil))
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 = Num.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.quantity or itemCost[2] or 1
if matItem == nil then
table.insert(matRow, Shared.printError('Failed to find item with ID "' .. itemCost.id .. '"'))
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 then
local gpText = nil
if type(row.gp) == 'number' and row.gp > 0 then
gpText = Icons._Currency('melvorD:GP', row.gp)
elseif type(row.gp) == 'string' then
gpText = row.gp
end
table.insert(matRow, gpText)
end
if row.sc ~= nil then
local scText = nil
if type(row.sc) == 'number' and row.sc > 0 then
scText = Icons._Currency('melvorD:SlayerCoins', row.sc)
elseif type(row.sc) == 'string' then
scText = row.sc
end
table.insert(matRow, scText)
end
if row.rc ~= nil then
local rcText = nil
if type(row.rc) == 'number' and row.rc > 0 then
rcText = Icons._Currency('melvorD:RaidCoins', row.rc)
elseif type(row.rc) == 'string' then
rcText = row.rc
end
table.insert(matRow, rcText)
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, "'''" .. Num.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.tableIsEmpty(resultPart) 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 Shared.printError('No item named "' .. itemName .. '" exists in the data module')
end
return p._getItemUseTable(item)
end
function p._getSpellUseTable(item)
local spellList = Magic.getSpellsUsingItem(item.id, true)
--Bail immediately if no spells are found
if Shared.tableIsEmpty(spellList) then
return ''
end
--Adding a check for if the Items column is needed
local hasItems = false
for i, spell in pairs(spellList) do
if Magic._getSpellItems(spell) ~= '' then
hasItems = true
break
end
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')
if hasItems then
table.insert(resultPart, '!!Item Cost')
end
for i, spell in pairs(spellList) do
local spellBook = Magic.getSpellBookFromSpell(spell)
local rowPart = {}
table.insert(rowPart, '\r\n|-\r\n|data-sort-value="'..spell.name..'"|')
local iconType = Magic._getSpellIconType(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="'..Skills.getRecipeLevel('melvorD:Summoning', spell)..'"|'..Magic._getSpellRequirements(spell))
table.insert(rowPart, '||data-sort-value="'.. spellBook.id ..'"|')
table.insert(rowPart, Magic.getSpellTypeLink(spellBook.id))
table.insert(rowPart, '||'..Magic._getSpellStat(spell, 'description'))
table.insert(rowPart, '||style="text-align:center"|')
table.insert(rowPart, Magic._getSpellRunes(spell))
if hasItems then
table.insert(rowPart, '||style="text-align:right"|')
table.insert(rowPart, Magic._getSpellItems(spell))
end
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 Shared.printError('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',
'Basic Bag',
'Bird Nest',
'Abyssal Stone',
'Withered Ash',
'Wrath Rune'
}
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