Module:SkillUnlocks: Difference between revisions

From Melvor Idle
No edit summary
(Fix Abyssal depth & stronghold handling)
 
(31 intermediate revisions by 3 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


--This module polls various game data sources to produce a full list of skill
-- This module polls various game data sources to produce a full list of skill
--level unlocks for each skill. The game has a hard-coded set of "milestones"
-- level unlocks for each skill. The game has a hard-coded set of "milestones"
--for each skill that does the same thing, but it is not exhaustive and not
-- for each skill that does the same thing, but it is not exhaustive and not
--permanently visible for most combat skills.
-- permanently visible for most combat skills.
 
-- TODO: Args to filter by level range and unlock type


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
Line 11: Line 13:
local Items = require('Module:Items')
local Items = require('Module:Items')
local CombatAreas = require('Module:CombatAreas')
local CombatAreas = require('Module:CombatAreas')
local Shop = require('Module:Shop')
local Magic = require('Module:Magic')
local Township = require('Module:Township')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Num = require('Module:Number')


local CHECK_ITEMS = {'Attack', 'Strength', 'Defence', 'Ranged', 'Magic',  
-- This excludes certain checks pertinent to all skills, like equipment
'Slayer'}
local SKILL_CHECK_MAP = {
local CHECK_AREAS = {'Slayer'}
['Attack'] = {},
['Strength'] = {},
['Defence'] = {},
['Hitpoints'] = {},
['Ranged'] = {},
['Magic'] = {'spells', 'altmagic'},
['Prayer'] = {'prayers'},
['Slayer'] = {'areas'},
['Farming'] = {'mastery', 'gatheringitems', 'farmingplots'},
['Township'] = {'townshipunlocks'},
['Woodcutting'] = {'mastery', 'gatheringitems'},
['Fishing'] = {'mastery', 'gatheringitems'},
['Firemaking'] = {'mastery', 'firemaking'},
['Cooking'] = {'mastery', 'artisanitems'},
['Mining'] = {'mastery', 'gatheringitems'},
['Smithing'] = {'mastery', 'artisanitems'},
['Thieving'] = {'mastery', 'thieving'},
['Fletching'] = {'mastery', 'artisanitems'},
['Crafting'] = {'mastery', 'artisanitems'},
['Runecrafting'] = {'mastery', 'artisanitems'},
['Herblore'] = {'mastery', 'artisanitems'},
['Agility'] = {'mastery', 'agilityslots'},
['Summoning'] = {'mastery', 'artisanitems'},
['Astrology'] = {'mastery', 'constellations'},
['Alt. Magic'] = {'altmagic'},
}
local TYPE_SORT_ORDER = {
local TYPE_SORT_ORDER = {
['item'] = 1,  
['special'] = 1,
['combatArea'] = 2,
['biome'] = 2,
['slayerArea'] = 3,
['building'] = 3,
['dungeon'] = 4
['plot'] = 4,
['obstacleslot'] = 5,
['pillar'] = 6,
['spell'] = 7,
['prayer'] = 8,
['thieving'] = 9,
['constellation'] = 10,
['gathering'] = 11,
['artisan'] = 12,
['item'] = 13,  
['combatArea'] = 14,
['slayerArea'] = 15,
['dungeon'] = 16,
['depth'] = 17,
['stronghold'] = 18,
['shop'] = 19,
['trader'] = 20,
['obstacle'] = 21
}
}
local VERBS_PER_SUBTYPE = {
local VERBS_PER_SUBTYPE = {
['Weapon'] = 'Wield',
['Weapon'] = 'Wield',
['Magic Wand'] = 'Wield',
['Magic Staff'] = 'Wield',
['Magic Book'] = 'Wield',
['Ranged Weapon'] = 'Wield',
['Ammo'] = 'Wield',
['Equipment'] = 'Wear',
['Armour'] = 'Wear',
['Armour'] = 'Wear',
['Trimmed Armour'] = 'Wear',
['Magic Armour'] = 'Wear',
['Ranged Armour'] = 'Wear',
['Ranged Armour'] = 'Wear',
['Slayer Armour'] = 'Wear',
['Slayer Armour'] = 'Wear',
Line 30: Line 88:
['combatArea'] = 'Access',
['combatArea'] = 'Access',
['slayerArea'] = 'Access',
['slayerArea'] = 'Access',
['dungeon'] = 'Access'
['dungeon'] = 'Access',
['standard'] = 'Cast',
['aurora'] = 'Cast',
['curse'] = 'Cast',
['ancient'] = 'Cast',
['archaic'] = 'Cast',
['prayer'] = 'Use',
['altMagic'] = 'Cast',
['tree'] = 'Cut',
['fish'] = 'Catch',
['Essence'] = 'Mine',
['Ore'] = 'Mine',
['Gem'] = 'Mine',
['artisan'] = 'Create',
['melvorD:Fire'] = 'Cook',
['melvorD:Furnace'] = 'Bake',
['melvorD:Pot'] = 'Boil',
['melvorD:Bars'] = 'Smelt',
['melvorF:SkillPotions'] = 'Brew',
['melvorF:CombatPotions'] = 'Brew',
['log'] = 'Burn',
['npc'] = 'Pickpocket',
['constellation'] = 'Study',
['shop'] = 'Purchase',
['plot'] = 'New',
['biome'] = 'Access',
['basicBuilding'] = 'Build',
['trader'] = 'Trade for',
['obstacle'] = 'Build',
['obstacleslot'] = 'Build',
['pillar'] = 'Build'
}
local SUBTYPE_OVERRIDES = {
['Agile Wings Rapier'] = 'Weapon',
['Bundled Protection Body'] = 'Armour',
['Ethereal Greataxe'] = 'Weapon',
['Ethereal Longbow'] = 'Ranged Weapon',
['Ethereal Staff'] = 'Magic Staff',
['Feather Storm Crossbow'] = 'Ranged Weapon',
['FrostSpark 1H Sword'] = 'Weapon',
['Lightning Coil 2H Staff'] = 'Magic Staff',
['Lightning Strike 1H Sword'] = 'Weapon',
['Perfect Sight Legs'] = 'Armour',
['Royal Toxins Spear'] = 'Weapon',
['Slicing Maelstrom Wand'] = 'Magic Wand',
['Spectral Ice Sword'] = 'Weapon',
['Torrential Blast Crossbow'] = 'Ranged Weapon'
}
}
local SUBTYPE_SORT_OVERRIDES = {
['melvorD:Bars'] = '1',
['melvorF:StandardRunes'] = '1',
['log'] = '1'
}
local GEAR_SETS = {
{
['verb'] = 'Wear',
['name'] = 'Adamant Armour',
['level'] = 30,
['entities'] = {'Adamant Boots', '(S) Adamant Boots', '(G) Adamant Boots', 'Adamant Gloves', 'Adamant Helmet', '(S) Adamant Helmet', '(G) Adamant Helmet', 'Adamant Platebody', '(S) Adamant Platebody', '(G) Adamant Platebody', 'Adamant Platelegs', '(S) Adamant Platelegs', '(G) Adamant Platelegs', 'Adamant Shield', '(S) Adamant Shield', '(G) Adamant Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Adamant Ranged Weapons',
['level'] = 30,
['entities'] = {'Adamant Crossbow', 'Adamant Javelin', 'Adamant Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Adamant Weapons',
['level'] = 30,
['entities'] = {'Adamant 2H Sword', 'Adamant Battleaxe', 'Adamant Dagger', 'Adamant Scimitar', 'Adamant Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Aeris God Armour',
['level'] = 85,
['entities'] = {'Aeris God Boots', 'Aeris God Gloves', 'Aeris God Helmet', 'Aeris God Platebody', 'Aeris God Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Air Acolyte Wizard Gear',
['level'] = 1,
['entities'] = {'Air Acolyte Wizard Boots', 'Air Acolyte Wizard Bottoms', 'Air Acolyte Wizard Hat', 'Air Acolyte Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Air Adept Wizard Gear',
['level'] = 35,
['entities'] = {'Air Adept Wizard Boots', 'Air Adept Wizard Bottoms', 'Air Adept Wizard Hat', 'Air Adept Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Air Expert Wizard Gear',
['level'] = 65,
['entities'] = {'Air Expert Wizard Boots', 'Air Expert Wizard Bottoms', 'Air Expert Wizard Hat', 'Air Expert Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Ancient Armour',
['level'] = 70,
['entities'] = {'Ancient Helmet', '(S) Ancient Helmet', '(G) Ancient Helmet', 'Ancient Platebody', '(S) Ancient Platebody', '(G) Ancient Platebody', 'Ancient Platelegs', '(S) Ancient Platelegs', '(G) Ancient Platelegs', 'Ancient Shield', '(S) Ancient Shield', '(G) Ancient Shield'}
},
{
['verb'] = 'Wear',
['name'] = 'Ancient Dragonhide Armour',
['level'] = 80,
['entities'] = {'Ancient D-hide Body', 'Ancient D-hide Chaps', 'Ancient D-hide Shield', 'Ancient D-hide Vambraces', '(U) Ancient D-hide Body', '(U) Ancient D-hide Chaps', '(U) Ancient D-hide Shield', '(U) Ancient D-hide Vambraces'}
},
{
['verb'] = 'Wield',
['name'] = 'Ancient Ranged Weapons',
['level'] = 70,
['entities'] = {'Ancient Crossbow', 'Ancient Javelin', 'Ancient Throwing Knife', 'Ancient Longbow'}
},
{
['verb'] = 'Wield',
['name'] = 'Ancient Weapons',
['level'] = 70,
['entities'] = {'Ancient 2H Sword', 'Ancient Claw', 'Ancient Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Ancient Wizard Gear',
['level'] = 70,
['entities'] = {'Ancient Wizard Boots', 'Ancient Wizard Bottoms', 'Ancient Wizard Hat', 'Ancient Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Augite Armour',
['level'] = 105,
['entities'] = {'Augite Boots', '(I) Augite Boots', '(P) Augite Boots', 'Augite Gloves', 'Augite Helmet', '(I) Augite Helmet', '(P) Augite Helmet', 'Augite Platebody', '(I) Augite Platebody', '(P) Augite Platebody', 'Augite Platelegs', '(I) Augite Platelegs', '(P) Augite Platelegs', 'Augite Shield', '(I) Augite Shield', '(P) Augite Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Augite Ranged Weapons',
['level'] = 105,
['entities'] = {'Augite Crossbow', 'Augite Javelin', 'Augite Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Augite Weapons',
['level'] = 105,
['entities'] = {'Augite 2H Sword', 'Augite Battleaxe', 'Augite Dagger', 'Augite Scimitar', 'Augite Sword'}
},
{
['verb'] = 'Wield',
['name'] = 'Battlestaves',
['level'] = 30,
['entities'] = {'Air Battlestaff', 'Earth Battlestaff', 'Fire Battlestaff', 'Water Battlestaff'}
},
{
['verb'] = 'Wear',
['name'] = 'Black Armour',
['level'] = 10,
['entities'] = {'Black Boots', '(S) Black Boots', '(G) Black Boots', 'Black Helmet', '(S) Black Helmet', '(G) Black Helmet', 'Black Platebody', '(S) Black Platebody', '(G) Black Platebody', 'Black Platelegs', '(S) Black Platelegs', '(G) Black Platelegs', 'Black Shield', '(S) Black Shield', '(G) Black Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Black Weapons',
['level'] = 10,
['entities'] = {'Black 2H Sword', 'Black Battleaxe', 'Black Dagger', 'Black Scimitar', 'Black Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Black Dragonhide Armour',
['level'] = 70,
['entities'] = {'Black D-hide Body', 'Black D-hide Chaps', 'Black D-hide Shield', 'Black D-hide Vambraces', '(U) Black D-hide Body', '(U) Black D-hide Chaps', '(U) Black D-hide Shield', '(U) Black D-hide Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Basic Black Wizard Gear',
['level'] = 50,
['entities'] = {'Black Wizard Boots', 'Black Wizard Bottoms', 'Black Wizard Hat', 'Black Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Blue Dragonhide Armour',
['level'] = 50,
['entities'] = {'Blue D-hide Body', 'Blue D-hide Chaps', 'Blue D-hide Shield', 'Blue D-hide Vambraces', '(U) Blue D-hide Body', '(U) Blue D-hide Chaps', '(U) Blue D-hide Shield', '(U) Blue D-hide Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Basic Blue Wizard Gear',
['level'] = 10,
['entities'] = {'Blue Wizard Boots', 'Blue Wizard Bottoms', 'Blue Wizard Hat', 'Blue Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Bronze Armour',
['level'] = 1,
['entities'] = {'Bronze Boots', '(S) Bronze Boots', '(G) Bronze Boots', 'Bronze Gloves', 'Bronze Helmet', '(S) Bronze Helmet', '(G) Bronze Helmet', 'Bronze Platebody', '(S) Bronze Platebody', '(G) Bronze Platebody', 'Bronze Platelegs', '(S) Bronze Platelegs', '(G) Bronze Platelegs', 'Bronze Shield', '(S) Bronze Shield', '(G) Bronze Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Bronze Ranged Weapons',
['level'] = 1,
['entities'] = {'Bronze Crossbow', 'Bronze Javelin', 'Bronze Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Bronze Weapons',
['level'] = 1,
['entities'] = {'Bronze 2H Sword', 'Bronze Battleaxe', 'Bronze Dagger', 'Bronze Scimitar', 'Bronze Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Carrion Armour',
['level'] = 110,
['entities'] = {'Carrion Body', 'Carrion Chaps', 'Carrion Shield', 'Carrion Vambraces', '(U) Carrion Body', '(U) Carrion Chaps', '(U) Carrion Shield', '(U) Carrion Vambraces'}
},
{
['verb'] = 'Wield',
['name'] = 'Carrion Bows',
['level'] = 110,
['entities'] = {'Carrion Longbow', 'Carrion Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Corundum Armour',
['level'] = 100,
['entities'] = {'Corundum Boots', '(I) Corundum Boots', '(P) Corundum Boots', 'Corundum Gloves', 'Corundum Helmet', '(I) Corundum Helmet', '(P) Corundum Helmet', 'Corundum Platebody', '(I) Corundum Platebody', '(P) Corundum Platebody', 'Corundum Platelegs', '(I) Corundum Platelegs', '(P) Corundum Platelegs', 'Corundum Shield', '(I) Corundum Shield', '(P) Corundum Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Corundum Ranged Weapons',
['level'] = 100,
['entities'] = {'Corundum Crossbow', 'Corundum Javelin', 'Corundum Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Corundum Weapons',
['level'] = 100,
['entities'] = {'Corundum 2H Sword', 'Corundum Battleaxe', 'Corundum Dagger', 'Corundum Scimitar', 'Corundum Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Divine Armour',
['level'] = 110,
['entities'] = {'Divine Boots', '(I) Divine Boots', '(P) Divine Boots', 'Divine Gloves', 'Divine Helmet', '(I) Divine Helmet', '(P) Divine Helmet', 'Divine Platebody', '(I) Divine Platebody', '(P) Divine Platebody', 'Divine Platelegs', '(I) Divine Platelegs', '(P) Divine Platelegs', 'Divine Shield', '(I) Divine Shield', '(P) Divine Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Divine Ranged Weapons',
['level'] = 110,
['entities'] = {'Divine Crossbow', 'Divine Javelin', 'Divine Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Divine Weapons',
['level'] = 110,
['entities'] = {'Divine 2H Sword', 'Divine Battleaxe', 'Divine Dagger', 'Divine Scimitar', 'Divine Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Dragon Armour',
['level'] = 60,
['entities'] = {'Dragon Boots', '(S) Dragon Boots', '(G) Dragon Boots', 'Dragon Gloves', 'Dragon Helmet', '(S) Dragon Helmet', '(G) Dragon Helmet', 'Dragon Platebody', '(S) Dragon Platebody', '(G) Dragon Platebody', 'Dragon Platelegs', '(S) Dragon Platelegs', '(G) Dragon Platelegs', 'Dragon Shield', '(S) Dragon Shield', '(G) Dragon Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Dragon Ranged Weapons',
['level'] = 60,
['entities'] = {'Dragon Crossbow', 'Dragon Javelin', 'Dragon Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Dragon Weapons',
['level'] = 60,
['entities'] = {'Dragon 2H Sword', 'Dragon Battleaxe', 'Dragon Dagger', 'Dragon Scimitar', 'Dragon Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Earth Acolyte Wizard Gear',
['level'] = 9,
['entities'] = {'Earth Acolyte Wizard Boots', 'Earth Acolyte Wizard Bottoms', 'Earth Acolyte Wizard Hat', 'Earth Acolyte Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Earth Adept Wizard Gear',
['level'] = 43,
['entities'] = {'Earth Adept Wizard Boots', 'Earth Adept Wizard Bottoms', 'Earth Adept Wizard Hat', 'Earth Adept Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Earth Expert Wizard Gear',
['level'] = 73,
['entities'] = {'Earth Expert Wizard Boots', 'Earth Expert Wizard Bottoms', 'Earth Expert Wizard Hat', 'Earth Expert Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Elderwood Armour',
['level'] = 100,
['entities'] = {'Elderwood Body', 'Elderwood Chaps', 'Elderwood Shield', 'Elderwood Vambraces', '(U) Elderwood Body', '(U) Elderwood Chaps', '(U) Elderwood Shield', '(U) Elderwood Vambraces'}
},
{
['verb'] = 'Wield',
['name'] = 'Elderwood Bows',
['level'] = 100,
['entities'] = {'Elderwood Longbow', 'Elderwood Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Fire Acolyte Wizard Gear',
['level'] = 14,
['entities'] = {'Fire Acolyte Wizard Boots', 'Fire Acolyte Wizard Bottoms', 'Fire Acolyte Wizard Hat', 'Fire Acolyte Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Fire Adept Wizard Gear',
['level'] = 48,
['entities'] = {'Fire Adept Wizard Boots', 'Fire Adept Wizard Bottoms', 'Fire Adept Wizard Hat', 'Fire Adept Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Fire Expert Wizard Gear',
['level'] = 78,
['entities'] = {'Fire Expert Wizard Boots', 'Fire Expert Wizard Bottoms', 'Fire Expert Wizard Hat', 'Fire Expert Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Glacia God Gear',
['level'] = 85,
['entities'] = {'Glacia God Boots', 'Glacia God Gloves', 'Glacia God Helmet', 'Glacia God Platebody', 'Glacia God Platelegs'}
},
{
['verb'] = 'Wield',
['name'] = 'Godswords',
['level'] = 85,
['entities'] = {'Aeris Godsword', 'Glacia Godsword', 'Ragnar Godsword', 'Terran Godsword'}
},
{
['verb'] = 'Wear',
['name'] = 'Green Dragonhide Armour',
['level'] = 40,
['entities'] = {'Green D-hide Body', 'Green D-hide Chaps', 'Green D-hide Shield', 'Green D-hide Vambraces', '(U) Green D-hide Body', '(U) Green D-hide Chaps', '(U) Green D-hide Shield', '(U) Green D-hide Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Basic Green Wizard Gear',
['level'] = 1,
['entities'] = {'Green Wizard Boots', 'Green Wizard Bottoms', 'Green Wizard Hat', 'Green Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Hard Leather Armour',
['level'] = 10,
['entities'] = {'Hard Leather Body', 'Hard Leather Boots', 'Hard Leather Chaps', 'Hard Leather Cowl', 'Hard Leather Gloves', 'Hard Leather Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Ice Armor',
['level'] = 40,
['entities'] = {'Ice Boots', 'Ice Helmet', 'Ice Platebody', 'Ice Platelegs', 'Ice Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Ice Bows',
['level'] = 40,
['entities'] = {'Ice Longbow', 'Ice Shortbow'}
},
{
['verb'] = 'Wield',
['name'] = 'Ice Weapons',
['level'] = 40,
['entities'] = {'Ice 2H Sword', 'Ice Battleaxe', 'Ice Dagger', 'Ice Sword'}
},
{
['verb'] = 'Wield',
['name'] = 'Imbued Magic Wands',
['level'] = 70,
['entities'] = {'Air Imbued Wand', 'Earth Imbued Wand', 'Fire Imbued Wand', 'Water Imbued Wand'}
},
{
['verb'] = 'Wear',
['name'] = 'Infernal Legendary Wizard Gear',
['level'] = 105,
['entities'] = {'Infernal Legendary Wizard Boots', 'Infernal Legendary Wizard Bottoms', 'Infernal Legendary Wizard Hat', 'Infernal Legendary Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Infernal Master Wizard Gear',
['level'] = 100,
['entities'] = {'Infernal Master Wizard Boots', 'Infernal Master Wizard Bottoms', 'Infernal Master Wizard Hat', 'Infernal Master Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Infernal Mythical Wizard Gear',
['level'] = 110,
['entities'] = {'Infernal Mythical Wizard Boots', 'Infernal Mythical Wizard Bottoms', 'Infernal Mythical Wizard Hat', 'Infernal Mythical Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Iron Armour',
['level'] = 1,
['entities'] = {'Iron Boots', '(S) Iron Boots', '(G) Iron Boots', 'Iron Gloves', 'Iron Helmet', '(S) Iron Helmet', '(G) Iron Helmet', 'Iron Platebody', '(S) Iron Platebody', '(G) Iron Platebody', 'Iron Platelegs', '(S) Iron Platelegs', '(G) Iron Platelegs', 'Iron Shield', '(S) Iron Shield', '(G) Iron Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Iron Ranged Weapons',
['level'] = 1,
['entities'] = {'Iron Crossbow', 'Iron Javelin', 'Iron Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Iron Weapons',
['level'] = 1,
['entities'] = {'Iron 2H Sword', 'Iron Battleaxe', 'Iron Dagger', 'Iron Scimitar', 'Iron Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Leather Armour',
['level'] = 1,
['entities'] = {'Leather Body', 'Leather Boots', 'Leather Chaps', 'Leather Cowl', 'Leather Gloves', 'Leather Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Lightning Legendary Wizard Gear',
['level'] = 105,
['entities'] = {'Lightning Legendary Wizard Boots', 'Lightning Legendary Wizard Bottoms', 'Lightning Legendary Wizard Hat', 'Lightning Legendary Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Lightning Master Wizard Gear',
['level'] = 100,
['entities'] = {'Lightning Master Wizard Boots', 'Lightning Master Wizard Bottoms', 'Lightning Master Wizard Hat', 'Lightning Master Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Lightning Mythical Wizard Gear',
['level'] = 110,
['entities'] = {'Lightning Mythical Wizard Boots', 'Lightning Mythical Wizard Bottoms', 'Lightning Mythical Wizard Hat', 'Lightning Mythical Wizard Robes'}
},
{
['verb'] = 'Wield',
['name'] = 'Magic Bows',
['level'] = 50,
['entities'] = {'Magic Longbow', 'Magic Shortbow'}
},
{
['verb'] = 'Wield',
['name'] = 'Maple Bows',
['level'] = 30,
['entities'] = {'Maple Longbow', 'Maple Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Meteorite Armour',
['level'] = 108,
['entities'] = {'Meteorite Helmet', 'Meteorite Platebody', 'Meteorite Platelegs'}
},
{
['verb'] = 'Wield',
['name'] = 'Meteorite Ranged Weapons',
['level'] = 1,
['entities'] = {'Meteorite Crossbow', 'Meteorite Javelin'}
},
{
['verb'] = 'Wear',
['name'] = 'Miolite Armour',
['level'] = 40,
['entities'] = {'Miolite Boots', 'Miolite Helmet', 'Miolite Platebody', 'Miolite Platelegs', 'Miolite Shield'}
},
{
['verb'] = 'Wear',
['name'] = 'Mithril Armour',
['level'] = 20,
['entities'] = {'Mithril Boots', '(S) Mithril Boots', '(G) Mithril Boots', 'Mithril Gloves', 'Mithril Helmet', '(S) Mithril Helmet', '(G) Mithril Helmet', 'Mithril Platebody', '(S) Mithril Platebody', '(G) Mithril Platebody', 'Mithril Platelegs', '(S) Mithril Platelegs', '(G) Mithril Platelegs', 'Mithril Shield', '(S) Mithril Shield', '(G) Mithril Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Mithril Ranged Weapons',
['level'] = 20,
['entities'] = {'Mithril Crossbow', 'Mithril Javelin', 'Mithril Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Mithril Weapons',
['level'] = 20,
['entities'] = {'Mithril 2H Sword', 'Mithril Battleaxe', 'Mithril Dagger', 'Mithril Scimitar', 'Mithril Sword'}
},
{
['verb'] = 'Wield',
['name'] = 'Mystic Magic Staves',
['level'] = 40,
['entities'] = {'Mystic Air Staff', 'Mystic Earth Staff', 'Mystic Fire Staff', 'Mystic Water Staff'}
},
{
['verb'] = 'Wield',
['name'] = 'Normal Bows',
['level'] = 1,
['entities'] = {'Normal Longbow', 'Normal Shortbow'}
},
{
['verb'] = 'Wield',
['name'] = 'Oak Bows',
['level'] = 5,
['entities'] = {'Oak Longbow', 'Oak Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Poison Legendary Wizard Gear',
['level'] = 105,
['entities'] = {'Poison Legendary Wizard Boots', 'Poison Legendary Wizard Bottoms', 'Poison Legendary Wizard Hat', 'Poison Legendary Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Poison Master Wizard Gear',
['level'] = 100,
['entities'] = {'Poison Master Wizard Boots', 'Poison Master Wizard Bottoms', 'Poison Master Wizard Hat', 'Poison Master Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Poison Mythical Wizard Gear',
['level'] = 110,
['entities'] = {'Poison Mythical Wizard Boots', 'Poison Mythical Wizard Bottoms', 'Poison Mythical Wizard Hat', 'Poison Mythical Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Ragnar God Armour',
['level'] = 85,
['entities'] = {'Ragnar God Boots', 'Ragnar God Gloves', 'Ragnar God Helmet', 'Ragnar God Platebody', 'Ragnar God Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Red Dragonhide Armour',
['level'] = 60,
['entities'] = {'Red D-hide Body', 'Red D-hide Chaps', 'Red D-hide Shield', 'Red D-hide Vambraces', '(U) Red D-hide Body', '(U) Red D-hide Chaps', '(U) Red D-hide Shield', '(U) Red D-hide Vambraces'}
},
{
['verb'] = 'Wear',
['name'] = 'Basic Red Wizard Gear',
['level'] = 30,
['entities'] = {'Red Wizard Boots', 'Red Wizard Bottoms', 'Red Wizard Hat', 'Red Wizard Robes'}
},
{
['verb'] = 'Wield',
['name'] = 'Redwood Bows',
['level'] = 60,
['entities'] = {'Redwood Longbow', 'Redwood Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Revenant Armour',
['level'] = 105,
['entities'] = {'Revenant Body', 'Revenant Chaps', 'Revenant Shield', 'Revenant Vambraces', '(U) Revenant Body', '(U) Revenant Chaps', '(U) Revenant Shield', '(U) Revenant Vambraces'}
},
{
['verb'] = 'Wield',
['name'] = 'Revenant Bows',
['level'] = 105,
['entities'] = {'Revenant Longbow', 'Revenant Shortbow'}
},
{
['verb'] = 'Wear',
['name'] = 'Rune Armour',
['level'] = 40,
['entities'] = {'Rune Boots', '(S) Rune Boots', '(G) Rune Boots', 'Rune Gloves', 'Rune Helmet', '(S) Rune Helmet', '(G) Rune Helmet', 'Rune Platebody', '(S) Rune Platebody', '(G) Rune Platebody', 'Rune Platelegs', '(S) Rune Platelegs', '(G) Rune Platelegs', 'Rune Shield', '(S) Rune Shield', '(G) Rune Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Rune Ranged Weapons',
['level'] = 40,
['entities'] = {'Rune Crossbow', 'Rune Javelin', 'Rune Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Rune Weapons',
['level'] = 40,
['entities'] = {'Rune 2H Sword', 'Rune Battleaxe', 'Rune Dagger', 'Rune Scimitar', 'Rune Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Basic)',
['level'] = 1,
['entities'] = {'Slayer Helmet (Basic)', 'Slayer Platebody (Basic)'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Strong)',
['level'] = 30,
['entities'] = {'Slayer Helmet (Strong)', 'Slayer Platebody (Strong)'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Elite)',
['level'] = 60,
['entities'] = {'Slayer Helmet (Elite)', 'Slayer Platebody (Elite)'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Master)',
['level'] = 80,
['entities'] = {'Slayer Helmet (Master)', 'Slayer Platebody (Master)'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Legendary)',
['level'] = 100,
['entities'] = {'Slayer Helmet (Legendary)', 'Slayer Platebody (Legendary)'}
},
{
['verb'] = 'Wear',
['name'] = 'Melee Slayer Gear (Mythical)',
['level'] = 110,
['entities'] = {'Slayer Helmet (Mythical)', 'Slayer Platebody (Mythical)'}
},
{
['verb'] = 'Wear',
['name'] = 'Ranged Slayer Gear (Strong)',
['level'] = 30,
['entities'] = {'Slayer Cowl (Strong)', 'Slayer Leather Body (Strong)'}
},
{
['verb'] = 'Wear',
['name'] = 'Ranged Slayer Gear (Elite)',
['level'] = 60,
['entities'] = {'Slayer Cowl (Elite)', 'Slayer Leather Body (Elite)'}
},
{
['verb'] = 'Wear',
['name'] = 'Ranged Slayer Gear (Master)',
['level'] = 80,
['entities'] = {'Slayer Cowl (Master)', 'Slayer Leather Body (Master)'}
},
{
['verb'] = 'Wear',
['name'] = 'Ranged Slayer Gear (Legendary)',
['level'] = 100,
['entities'] = {'Slayer Cowl (Legendary)', 'Slayer Leather Body (Legendary)'}
},
{
['verb'] = 'Wear',
['name'] = 'Ranged Slayer Gear (Mythical)',
['level'] = 110,
['entities'] = {'Slayer Cowl (Mythical)', 'Slayer Leather Body (Mythical)'}
},
{
['verb'] = 'Wear',
['name'] = 'Magic Slayer Gear (Strong)',
['level'] = 30,
['entities'] = {'Slayer Wizard Hat (Strong)', 'Slayer Wizard Robes (Strong)'}
},
{
['verb'] = 'Wear',
['name'] = 'Magic Slayer Gear (Elite)',
['level'] = 60,
['entities'] = {'Slayer Wizard Hat (Elite)', 'Slayer Wizard Robes (Elite)'}
},
{
['verb'] = 'Wear',
['name'] = 'Magic Slayer Gear (Master)',
['level'] = 80,
['entities'] = {'Slayer Wizard Hat (Master)', 'Slayer Wizard Robes (Master)'}
},
{
['verb'] = 'Wear',
['name'] = 'Magic Slayer Gear (Legendary)',
['level'] = 100,
['entities'] = {'Slayer Wizard Hat (Legendary)', 'Slayer Wizard Robes (Legendary)'}
},
{
['verb'] = 'Wear',
['name'] = 'Magic Slayer Gear (Mythical)',
['level'] = 110,
['entities'] = {'Slayer Wizard Hat (Mythical)', 'Slayer Wizard Robes (Mythical)'}
},
{
['verb'] = 'Wield',
['name'] = 'Basic Magic Staves',
['level'] = 1,
['entities'] = {'Staff of Air', 'Staff of Earth', 'Staff of Fire', 'Staff of Water'}
},
{
['verb'] = 'Wear',
['name'] = 'Steel Armour',
['level'] = 5,
['entities'] = {'Steel Boots', '(S) Steel Boots', '(G) Steel Boots', 'Steel Gloves', 'Steel Helmet', '(S) Steel Helmet', '(G) Steel Helmet', 'Steel Platebody', '(S) Steel Platebody', '(G) Steel Platebody', 'Steel Platelegs', '(S) Steel Platelegs', '(G) Steel Platelegs', 'Steel Shield', '(S) Steel Shield', '(G) Steel Shield'}
},
{
['verb'] = 'Wield',
['name'] = 'Steel Ranged Weapons',
['level'] = 5,
['entities'] = {'Steel Crossbow', 'Steel Javelin', 'Steel Throwing Knife'}
},
{
['verb'] = 'Wield',
['name'] = 'Steel Weapons',
['level'] = 5,
['entities'] = {'Steel 2H Sword', 'Steel Battleaxe', 'Steel Dagger', 'Steel Scimitar', 'Steel Sword'}
},
{
['verb'] = 'Wear',
['name'] = 'Terran God Armour',
['level'] = 85,
['entities'] = {'Terran God Boots', 'Terran God Gloves', 'Terran God Helmet', 'Terran God Platebody', 'Terran God Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Vorloran Devastator Armour',
['level'] = 120,
['entities'] = {'Vorloran Devastator Boots', 'Vorloran Devastator Gauntlets', 'Vorloran Devastator Helmet', 'Vorloran Devastator Platebody', 'Vorloran Devastator Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Vorloran Protector Armour',
['level'] = 120,
['entities'] = {'Vorloran Protector Boots', 'Vorloran Protector Gauntlets', 'Vorloran Protector Helmet', 'Vorloran Protector Platebody', 'Vorloran Protector Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Vorloran Watcher Armour',
['level'] = 120,
['entities'] = {'Vorloran Watcher Boots', 'Vorloran Watcher Gauntlets', 'Vorloran Watcher Helmet', 'Vorloran Watcher Platebody', 'Vorloran Watcher Platelegs'}
},
{
['verb'] = 'Wear',
['name'] = 'Water Acolyte Wizard Gear',
['level'] = 5,
['entities'] = {'Water Acolyte Wizard Boots', 'Water Acolyte Wizard Bottoms', 'Water Acolyte Wizard Hat', 'Water Acolyte Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Water Adept Wizard Gear',
['level'] = 39,
['entities'] = {'Water Adept Wizard Boots', 'Water Adept Wizard Bottoms', 'Water Adept Wizard Hat', 'Water Adept Wizard Robes'}
},
{
['verb'] = 'Wear',
['name'] = 'Water Expert Wizard Gear',
['level'] = 69,
['entities'] = {'Water Expert Wizard Boots', 'Water Expert Wizard Bottoms', 'Water Expert Wizard Hat', 'Water Expert Wizard Robes'}
},
{
['verb'] = 'Wield',
['name'] = 'Willow Bows',
['level'] = 20,
['entities'] = {'Willow Longbow', 'Willow Shortbow'}
},
{
['verb'] = 'Wield',
['name'] = 'Yew Bows',
['level'] = 40,
['entities'] = {'Yew Longbow', 'Yew Shortbow'}
},
{
['verb'] = 'Purchase',
['name'] = 'Skill Hats',
['level'] = 10,
['entities'] = {'Blacksmiths Hat', 'Burning Mans Hat', 'Crafters Hat', 'Fishermans Hat', 'Fletchers Hat', 'Miners Hat', 'Performance Enhancing Hat', 'Potion Makers Hat', 'Runecrafters Hat', 'Star Gazing Hat', 'Woodcutters Hat'}
},
{
['verb'] = 'Purchase',
['name'] = 'Skill Bodies',
['level'] = 20,
['entities'] = {'Blacksmiths Body', 'Burning Mans Body', 'Crafters Body', 'Fishermans Body', 'Fletchers Body', 'Miners Body', 'Performance Enhancing Body', 'Potion Makers Body', 'Runecrafters Body', 'Star Gazing Body', 'Woodcutters Body'}
},
{
['verb'] = 'Purchase',
['name'] = 'Skill Leggings',
['level'] = 40,
['entities'] = {'Blacksmiths Leggings', 'Burning Mans Leggings', 'Crafters Leggings', 'Fishermans Leggings', 'Fletchers Leggings', 'Miners Leggings', 'Performance Enhancing Leggings', 'Potion Makers Leggings', 'Runecrafters Leggings', 'Star Gazing Leggings', 'Woodcutters Leggings'}
},
{
['verb'] = 'Purchase',
['name'] = 'Skill Boots',
['level'] = 60,
['entities'] = {'Blacksmiths Boots', 'Burning Mans Boots', 'Crafters Boots', 'Fishermans Boots', 'Fletchers Boots', 'Miners Boots', 'Performance Enhancing Boots', 'Potion Makers Boots', 'Runecrafters Boots', 'Star Gazing Boots', 'Woodcutters Boots'}
}
}
function p._getEntityTrueSubtype(entityType, entityName)
-- Corrects types of a few entities with misleading/wrong data
if SUBTYPE_OVERRIDES[entityName] then
entityType = SUBTYPE_OVERRIDES[entityName]
end
return entityType
end
function p._getOtherSkillReqs(reqList, skillName)
-- Remove the current skill's requirement, leaving us with all others
local otherReqs = {}
for i, req in ipairs(reqList) do
if req.type ~= 'SkillLevel' or (req.skillID ~= Constants.getSkillID(skillName) and req.level ~= 1) then
table.insert(otherReqs, req)
end
end
return otherReqs
end
function p._getSpellReqs(spell)
-- Get spell requirements that aren't SkillLevel (ex. equipped item)
local reqs = {}
if spell.requiredItemID ~= nil then
        local item = Items.getItemByID(spell.requiredItemID)
        if item ~= nil then
            table.insert(reqs, {['type'] = 'item', ['item'] = item})
        end
end
if spell.requirements ~= nil then
for i, req in ipairs(spell.requirements) do
table.insert(reqs, req)
end
end
return reqs
end
function p._getGatherableReqs(node, skillName)
-- Get gatherable requirements that aren't SkillLevel (ex. shop item)
local reqs = {}
if node.shopItemPurchased ~= nil then
local purchase = GameData.getEntityByID('shopPurchases', node.shopItemPurchased)
if purchase ~= nil then
table.insert(reqs, {['type'] = 'shop', ['purchase'] = purchase})
end
end
if node.totalMasteryRequired ~= nil then
table.insert(reqs, {['type'] = 'totalMastery', ['mastery'] = node.totalMasteryRequired, ['skill'] = skillName})
end
return reqs
end
function p._addEntities(frame)
local args = frame.args ~= nil and frame.args or frame
local entityList = args[1]
local data = args[2]
local entityType = args.type
local typeParam = args.typeParam
local subType = args.subType
local subTypeParam = args.subTypeParam
local otherReqsFunc = args.otherReqsFunc
local imgType = args.imgType
for i, entity in ipairs(data) do
local processed = {}
processed.entityName = entity.name
processed.entityType = p._getEntityTrueSubtype(typeParam ~= nil and entity[typeParam] or entityType)
processed.subType = subTypeParam ~= nil and entity[subTypeParam] or subType
processed.skillLevel = entity.level
processed.otherReqs = otherReqsFunc ~= nil and otherReqsFunc(entity) or {}
processed.imgType = imgType
table.insert(entityList, processed)
end
return entityList
end


function p._addItemsWithSkillRequirement(entityList, skillName)
function p._addItemsWithSkillRequirement(entityList, skillName)
Line 39: Line 947:
if item.golbinRaidExclusive ~= nil and item.golbinRaidExclusive then
if item.golbinRaidExclusive ~= nil and item.golbinRaidExclusive then
return false
return false
end
if item.equipRequirements ~= nil then
for i, req in ipairs(item.equipRequirements) do
if req.type == 'AbyssalLevel' then
return false
end
end
end
end
Line 44: Line 960:
end)
end)
-- TODO: This can probably be made into a generic function for each entity
for i, item in ipairs(itemList) do
for i, item in ipairs(itemList) do
local processed = {}
local processed = {}
processed.entity = item
processed.entity = item
processed.entityName = item.name
processed.entityType = 'item'
processed.entityType = 'item'
processed.subType = item.type
processed.subType = p._getEntityTrueSubtype(item.type, item.name)
processed.entityName = item.name
processed.skillLevel = Items._getItemStat(item, skillReqLabel)
processed.skillLevel = Items._getItemStat(item, skillReqLabel)
processed.otherReqs = p._getOtherSkillReqs(item.equipRequirements, skillName)
table.insert(entityList, processed)
table.insert(entityList, processed)
end
end
Line 72: Line 988:
for i, area in ipairs(areaList) do
for i, area in ipairs(areaList) do
local processed = {}
local processed = {}
processed.entity = area
processed.entityName = area.name
processed.entityType = area.type
processed.entityType = area.type
processed.subType = area.type
processed.subType = p._getEntityTrueSubtype(area.type, area.name)
processed.entityName = area.name
for a, req in ipairs(area.entryRequirements) do
for a, req in ipairs(area.entryRequirements) do
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
Line 81: Line 996:
end
end
end
end
processed.otherReqs = p._getOtherSkillReqs(area.entryRequirements, skillName)
table.insert(entityList, processed)
table.insert(entityList, processed)
end
end
Line 87: Line 1,003:
end
end


function p._getSkillUnlockTable(skillName)
function p._addShopPurchasesWithSkillRequirements(entityList, skillName)
-- local skillReqLabel = skillName:lower() .. 'LevelRequired'
local purchaseList = Shop.getPurchases(function(purchase)
local hasSkillReq = false
for i, req in ipairs(purchase.purchaseRequirements) do
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
hasSkillReq = true
end
end
return hasSkillReq
end)
for i, purchase in ipairs(purchaseList) do
-- Skip skillcapes here, as we handle them with items
if purchase.category ~= 'melvorD:Skillcapes' and purchase.category ~= 'melvorTotH:SuperiorSkillcapes' then
local processed = {}
processed.entity = purchase
processed.entityName = Shop._getPurchaseName(purchase)
processed.entityType = 'shop'
processed.subType = Shop._getPurchaseType(purchase)
for a, req in ipairs(purchase.purchaseRequirements) do
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
processed.skillLevel = req.level
end
end
processed.otherReqs = p._getOtherSkillReqs(purchase.purchaseRequirements, skillName)
table.insert(entityList, processed)
end
end
return entityList
end
 
function p._addTraderItemsWithSkillRequirements(entityList, skillName)
-- Iterate over each tradable resource, then each item you can get with it
for i, tsResource in ipairs(Township.Township.itemConversions.fromTownship) do
for j, tsPurchase in ipairs(tsResource.items) do
-- Does this purchase require the current skill?
local hasCurrentSkill = false
local currentSkillLevel = 0
for k, req in ipairs(tsPurchase.unlockRequirements) do
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
hasCurrentSkill = true
currentSkillLevel = req.level
end
end
if hasCurrentSkill then
local item = Items.getItemByID(tsPurchase.itemID)
local processed = {}
processed.entity = item
processed.entityName = item.name
processed.entityType = 'trader'
processed.subType = 'trader'
processed.skillLevel = currentSkillLevel
processed.otherReqs = p._getOtherSkillReqs(tsPurchase.unlockRequirements, skillName)
table.insert(entityList, processed)
end
end
end
return entityList
end
 
function p._addAgilityObstaclesWithSkillRequirements(entityList, skillName)
local obstacles = GameData.getEntities(SkillData.Agility.obstacles,
function(obst)
return obst.abyssalLevel == nil
end
)
for i, obstacle in ipairs(obstacles) do
for j, req in ipairs(obstacle.skillRequirements) do
-- Does this obstacle require the current skill?
local hasCurrentSkill = false
local currentSkillLevel = 0
if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
hasCurrentSkill = true
currentSkillLevel = req.level
end
if hasCurrentSkill then
local processed = {}
processed.entity = obstacle
processed.entityName = obstacle.name
processed.entityType = 'obstacle'
-- Category is zero-indexed
processed.subType = 'Obstacle ' .. tostring(obstacle.category + 1)
processed.skillLevel = currentSkillLevel
processed.otherReqs = p._getOtherSkillReqs(obstacle.skillRequirements, skillName)
table.insert(entityList, processed)
end
end
end
return entityList
end
 
function p._addSpells(entityList, skillName)
-- Iterate through each spell type and each spell within that type
for i, spellBookDef in ipairs(Magic.spellBooks) do
local bookID = spellBookDef.id
-- Alt. Magic is in a separate function
if bookID ~= 'altMagic' then
local entList = GameData.getEntities(Magic.getSpellsBySpellBook(bookID),
function(spell)
return spell.abyssalLevel == nil
end
)
entityList = p._addEntities({entityList, entList, type='spell', subTypeParam='spellBook', imgType = spellBookDef.imgType, otherReqsFunc=p._getSpellReqs})
end
end
-- What do we need to check for this skill? (avoid checking everything for
return entityList
-- every skill to save time)
end
 
function p._addAltMagic(entityList, skillName)
local entList = GameData.getEntities(Magic.getSpellsBySpellBook('altMagic'),
function(spell)
return spell.abyssalLevel == nil
end
)
entityList = p._addEntities({entityList, entList, type='spell', subType='altMagic', imgType = 'spell', otherReqsFunc=p._getSpellReqs})
return entityList
end
 
function p._addPrayers(entityList, skillName)
local entList = GameData.getEntities('prayers',
function(prayer)
return prayer.abyssalLevel == nil
end
)
entityList = p._addEntities({entityList, entList, type='prayer', subType='prayer'})
return entityList
end
 
function p._addFiremakingActions(entityList, skillName)
local fireLogs = GameData.getEntities(SkillData.Firemaking.logs,
function(log)
return log.abyssalLevel == nil
end
)
for i, fireLog in ipairs(fireLogs) do
local processed = {}
local logItem = Items.getItemByID(fireLog.logID)
processed.entityName = logItem.name
processed.entityType = 'item'
processed.subType = 'log'
processed.skillLevel = fireLog.level
processed.otherReqs = {}
table.insert(entityList, processed)
end
return entityList
end
 
function p._addThievingTargets(entityList, skillName)
local entList = GameData.getEntities(SkillData.Thieving.npcs,
function(npc)
return npc.abyssalLevel == nil
end
)
entityList = p._addEntities({entityList, entList, type='thieving', subType='npc'})
return entityList
end
 
function p._addConstellations(entityList, skillName)
local entList = GameData.getEntities(SkillData.Astrology.recipes,
function(const)
return const.abyssalLevel == nil
end
)
entityList = p._addEntities({entityList, entList, type='constellation', subType='constellation'})
return entityList
end
 
function p._addFarmingPlots(entityList, skillName)
for i, plot in ipairs(SkillData.Farming.plots) do
local plotName = 'Unknown Plot'
if plot.categoryID == 'melvorD:Allotment' then
plotName = 'Allotment Plot'
elseif plot.categoryID == 'melvorD:Herb' then
plotName = 'Herb Plot'
elseif plot.categoryID == 'melvorD:Tree' then
plotName = 'Tree Plot'
end
local processed = {}
processed.entityName = plotName
processed.entityType = 'plot'
processed.subType = plot.categoryID
processed.skillLevel = plot.level
processed.otherReqs = {}
table.insert(entityList, processed)
end
return entityList
end
 
function p._addTownshipUnlocks(entityList, skillName)
-- This one is weird, because some of the data uses tiers and not levels
-- Add all of the biomes
for i, biome in ipairs(Township.Township.biomes) do
local tierReqs = Township._getTierRequirements(biome.tier)
local processed = {}
processed.entityName = biome.name
processed.entityType = 'biome'
processed.subType = 'biome'
processed.skillLevel = tierReqs.level
processed.otherReqs = {['population'] = tierReqs.population}
table.insert(entityList, processed)
end
-- Add all of the buildings
for i, building in ipairs(Township.Township.buildings) do
local tierReqs = Township._getTierRequirements(building.tier)
local processed = {}
processed.entityName = building.name == 'Statues' and 'Statue of Worship' or building.name
processed.entityType = 'building'
processed.subType = building.upgradesFrom ~= nil and 'upgradedBuilding' or 'basicBuilding'
processed.upgradesFrom = Township._getBuildingDowngrade(building)
processed.skillLevel = tierReqs.level
processed.otherReqs = {['population'] = tierReqs.population}
table.insert(entityList, processed)
end
-- Add the fact that health starts decreasing at level 15
local specialHealth = {
['entityType'] = 'special',
['subType'] = 'special',
['skillLevel'] = 15,
['overrideText'] = 'Township health has a 25% chance to decrease by 1% per update'
}
table.insert(entityList, specialHealth)
return entityList
end
 
function p._addAgilityObstacleSlotsAndPillars(entityList, skillName)
for i, level in ipairs(SkillData.Agility.obstacleUnlockLevels) do
local processed = {}
processed.entityName = 'Obstacle ' .. i
processed.entityType = 'obstacleslot'
processed.subType = 'obstacleslot'
processed.skillLevel = level
processed.otherReqs = {}
table.insert(entityList, processed)
end
-- Manually add pillars
table.insert(entityList, {['entityName'] = 'Passive Pillars', ['entityType'] = 'pillar', ['subType'] = 'pillar', ['skillLevel'] = 99, ['otherReqs'] = {}})
table.insert(entityList, {['entityName'] = 'Elite Passive Pillars', ['entityType'] = 'pillar', ['subType'] = 'pillar', ['skillLevel'] = 120, ['otherReqs'] = {}})
return entityList
end
 
function p._addGatherables(entityList, skillName)
-- Figure out what to look up based on the skill
local sourceData = {}
local subType = ''
if skillName == 'Woodcutting' then
sourceData = SkillData.Woodcutting.trees
subType = 'tree'
elseif skillName == 'Fishing' then
sourceData = SkillData.Fishing.fish
subType = 'fish'
elseif skillName == 'Mining' then
sourceData = SkillData.Mining.rockData
elseif skillName == 'Farming' then
sourceData = SkillData.Farming.recipes
end
sourceData = GameData.getEntities(sourceData,
function(obj)
return obj.abyssalLevel == nil
end
)
for i, node in ipairs(sourceData) do
local gatherable = Items.getItemByID(node.productId)
local processed = {
['entityName'] = node.name,
['entityType'] = 'gathering',
['subType'] = subType,
['skillLevel'] = node.level,
['item'] = gatherable
}
-- Skill-specific overrides
if skillName == 'Fishing' then
processed['entityName'] = gatherable.name
elseif skillName == 'Mining' then
processed['subType'] = node.category
elseif skillName == 'Farming' then
processed['entityName'] = gatherable.name
processed['subType'] = node.categoryID
processed['seed'] = Items.getItemByID(node.seedCost.id)
end
processed['otherReqs'] = p._getGatherableReqs(node, skillName)
table.insert(entityList, processed)
end
return entityList
end
 
function p._addRecipes(entityList, skillName)
-- Figure out what to look up based on the skill
local sourceData = GameData.getEntities(SkillData[skillName].recipes,
function(rec)
return rec.abyssalLevel == nil
end
)
local sameRecipeAndProduct = false
if skillName == 'Herblore' then
sameRecipeAndProduct = true
end
for i, recipe in ipairs(sourceData) do
local product = recipe
if not sameRecipeAndProduct then
product = Items.getItemByID(recipe.productID)
end
local processed = {
['entityName'] = product.name,
['entityType'] = 'artisan',
['subType'] = recipe.categoryID,
['skillLevel'] = recipe.level,
['item'] = product
}
table.insert(entityList, processed)
end
return entityList
end
 
function p._addSkillMastery(entityList, skillName)
-- Add the "Skill Mastery" perk when relevant
table.insert(entityList, {['entityName'] = 'Skill Mastery', ['entityType'] = 'special', ['subType'] = 'skillmastery', ['skillLevel'] = 99, ['otherReqs'] = {}})
return entityList
end
 
local SOURCE_FUNCS = {
['areas'] = p._addAreasWithSkillRequirement,
['spells'] = p._addSpells,
['altmagic'] = p._addAltMagic,
['prayers'] = p._addPrayers,
['gatheringitems'] = p._addGatherables,
['artisanitems'] = p._addRecipes,
['firemaking'] = p._addFiremakingActions,
['thieving'] = p._addThievingTargets,
['constellations'] = p._addConstellations,
['shop'] = p._addShopPurchasesWithSkillRequirements,
['farmingplots'] = p._addFarmingPlots,
['townshipunlocks'] = p._addTownshipUnlocks,
['agilityslots'] = p._addAgilityObstacleSlotsAndPillars,
['mastery'] = p._addSkillMastery
}
 
function p._prepareOtherReqs(entity)
local extraReqs = {}
if entity.otherReqs ~= nil and entity.otherReqs ~= {} then
-- Don't list a bazillion skills for the max skillcapes
if entity.entityName == 'Maximum Skillcape' then
return ' (requires level 99 in all skills)'
elseif entity.entityName == 'Superior Max Skillcape' then
return ' (requires level 120 in all skills)'
else
for i, req in ipairs(entity.otherReqs) do
-- TODO: "Completion" requirement? Might not be needed
if req.type == 'SkillLevel' then
local skillInfo = Icons.Icon({Constants.getSkillName(req.skillID), type='skill', notext='true'}) .. ' ' .. req.level
table.insert(extraReqs, skillInfo)
elseif req.type == 'DungeonCompletion' then
local dungeonName = GameData.getEntityByID('dungeons', req.dungeonID).name
-- If you only need to clear it once (Impending Darkness),
-- don't bother listing the count
if req.count > 1 then
table.insert(extraReqs, Icons.Icon({dungeonName, type='dungeon', qty=req.count, notext=true}) .. ' clears')
else
table.insert(extraReqs, Icons.Icon({dungeonName, type='dungeon', notext=true}) .. ' cleared')
end
elseif req.type == 'MonsterKilled' then
local monsterName = GameData.getEntityByID('monsters', req.monsterID).name
local monsterInfo = Icons.Icon({monsterName, type='monster', qty=req.count}) .. ' kills'
table.insert(extraReqs, monsterInfo)
elseif req.type == 'item' then
local itemInfo = Icons.Icon({req.item.name, type='item'})
table.insert(extraReqs, itemInfo)
elseif req.type == 'shop' then
table.insert(extraReqs, Shop._getPurchaseIcon({req.purchase}))
elseif req.type == 'totalMastery' then
table.insert(extraReqs, Num.formatnum(req.mastery) .. ' ' .. Icons.Icon({req.skill, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'}))
elseif req.type == 'TownshipBuilding' then
local building = Township._getBuildingByID(req.buildingID)
table.insert(extraReqs, Icons.Icon({building.name, type='building', qty=req.count}))
end
end
end
end
if not next(extraReqs) then
return ''
else
return ' (requires ' .. table.concat(extraReqs, ', ') .. ')'
end
end
 
function p._prepareSingleEntity(entity, skillName)
-- Special children that need extra attention
if entity.overrideText then
return entity.overrideText
end
if entity.subType == 'skillmastery' then
return 'Gain ' .. Icons.Icon({skillName, type='skill', notext='true'}) .. ' [[Mastery#The_Mastery_Pool|Skill Mastery]]'
end
if skillName == 'Farming' and entity.seed then
return 'Plant ' .. Icons.Icon({entity.seed.name, type='item'}) .. ' to grow ' .. Icons.Icon({entity.entityName, type='item'})
end
if skillName == 'Township' and entity.upgradesFrom then
return 'Upgrade ' .. Icons.Icon({entity.upgradesFrom.name, type='building'}) .. ' to ' .. Icons.Icon({entity.entityName, type='building'})
end
-- What are you doing with the thing you unlock? ("verbs")
local verb = ''
if VERBS_PER_SUBTYPE[entity.subType] then
verb = VERBS_PER_SUBTYPE[entity.subType] .. ' '
elseif VERBS_PER_SUBTYPE[entity.entityType] then
verb = VERBS_PER_SUBTYPE[entity.entityType] .. ' '
end
-- Icon overrides
local iconType = entity.imgType or entity.entityType
local iconLink = entity.entityName
local iconText = entity.entityName
local iconImg = entity.entityName
local noLink = ''
if Shared.contains({'slayerArea', 'depth', 'stronghold'}, entity.entityType) then
iconType = 'combatArea'
end
if entity.entityType == 'gathering' then
if entity.subType == 'fish' then
iconType = 'item'
elseif Shared.contains({
'melvorD:Ore',
'melvorD:Essence',
'melvorD:Gem',
'melvorItA:AbyssalOre',
'melvorItA:AbyssalGem',
'melvorItA:Outcrop',
'melvorItA:AbyssalEssence'
}, entity.subType) then
iconType = 'rock'
else
iconType = entity.subType
end
iconLink = entity.item.name
end
if entity.entityType == 'artisan' or entity.entityType == 'trader' then
iconType = 'item'
end
if entity.entityType == 'shop' then
iconType = string.lower(entity.subType)
end
if entity.entityType == 'plot' then
iconType = 'skill'
iconLink = 'Farming'
iconImg = 'Farming'
noLink = 'true'
end
if entity.entityType == 'obstacle' then
iconType = 'agility'
end
if entity.entityType == 'obstacleslot' then
iconType = 'agility'
iconLink = 'Obstacles'
end
if entity.entityType == 'pillar' then
iconType = 'agility'
iconLink = 'Passive_Pillars'
end
-- Any other requirements or post-scripts?
local extraReqs = p._prepareOtherReqs(entity)
local extraPost = ''
if entity.entityType == 'obstacle' then
extraPost = ' in ' .. entity.subType
end
return verb .. Icons.Icon({iconLink, iconText, img=iconImg, type=iconType, nolink=noLink}) .. extraPost .. extraReqs
end
 
function p._prepareGearSet(gearSet, entity)
local icons = ''
for i, itemName in ipairs(gearSet.entities) do
-- Skip trimmed armor, which always starts with an open parnthesis
if string.sub(itemName, 1, 1) ~= '(' then
icons = icons .. Icons.Icon({itemName, type='item', notext=true})
end
end
-- Get extra requirements for whicheve piece we processed first, as they
-- should all be the same
local extraReqs = p._prepareOtherReqs(entity)
return gearSet.verb .. ' ' .. icons .. ' ' .. gearSet.name .. extraReqs
end
 
function p._getSkillUnlockTable(skillName, args)
local itemsOnly = args.itemsOnly ~= nil and args.itemsOnly or false
-- TODO: Pass these min/max level params along to filter by them
local minLevel = args.minLevel ~= nil and args.minLevel or 0
local maxLevel = args.maxLevel ~= nil and args.maxLevel or 999
-- What do we need to check for this skill? Avoid checking everything for
-- every skill to save time, except for a few broad things
local entityList = {}
local entityList = {}
if Shared.contains(CHECK_ITEMS, skillName) then
entityList = p._addItemsWithSkillRequirement(entityList, skillName)
entityList = p._addItemsWithSkillRequirement(entityList, skillName)
end
if not itemsOnly then
if Shared.contains(CHECK_AREAS, skillName) then
entityList = p._addShopPurchasesWithSkillRequirements(entityList, skillName)
entityList = p._addAreasWithSkillRequirement(entityList, skillName)
entityList = p._addTraderItemsWithSkillRequirements(entityList, skillName)
entityList = p._addAgilityObstaclesWithSkillRequirements(entityList, skillName)
-- Now loop through the stuff relevant to this skill
for i, dataSource in ipairs(SKILL_CHECK_MAP[skillName]) do
entityList = SOURCE_FUNCS[dataSource](entityList, skillName)
end
end
end
Line 106: Line 1,542:
-- Sort the big list of everything
-- Sort the big list of everything
table.sort(entityList, function(a, b)  
table.sort(entityList, function(a, b)
local aSubTypeSort = a.subType
if SUBTYPE_SORT_OVERRIDES[a.subType] then
aSubTypeSort = SUBTYPE_SORT_OVERRIDES[a.subType]
end
local bSubTypeSort = b.subType
if SUBTYPE_SORT_OVERRIDES[b.subType] then
bSubTypeSort = SUBTYPE_SORT_OVERRIDES[b.subType]
end
-- Sort by level first
-- Sort by level first
if a.skillLevel ~= b.skillLevel then
if a.skillLevel ~= b.skillLevel then
Line 113: Line 1,557:
elseif TYPE_SORT_ORDER[a.entityType] ~= TYPE_SORT_ORDER[b.entityType] then
elseif TYPE_SORT_ORDER[a.entityType] ~= TYPE_SORT_ORDER[b.entityType] then
return TYPE_SORT_ORDER[a.entityType] < TYPE_SORT_ORDER[b.entityType]
return TYPE_SORT_ORDER[a.entityType] < TYPE_SORT_ORDER[b.entityType]
-- And finally by entity name
    -- Then by subtype
elseif aSubTypeSort ~= bSubTypeSort then
    return (aSubTypeSort or '') < (bSubTypeSort or '')
-- And finally by name
else
else
return a.entityName < b.entityName
return a.entityName < b.entityName
Line 127: Line 1,574:
-- Time to iterate!
-- Time to iterate!
local currentLevel = 0
local currentLevel = 0
local processedSets = {}
for i, entity in ipairs(entityList) do
for i, entity in ipairs(entityList) do
local foundLevel = entity.skillLevel
-- Skip this one if it's in a gear set we've already listed
local entityGearSet = nil
-- Start a new row if the current entity's level is higher than the
if entity.entityType == 'item' or entity.entityType == 'shop' then
-- current row
for i, gearSet in ipairs(GEAR_SETS) do
if foundLevel ~= currentLevel then
if Shared.contains(gearSet.entities, entity.entityName) then
currentLevel = foundLevel
entityGearSet = gearSet
table.insert(resultPart, '\r\n|-')
break
table.insert(resultPart, '\r\n|' .. foundLevel)
end
table.insert(resultPart, '\r\n|')
end
else
table.insert(resultPart, '<br/>')
end
end
-- What are you doing with the thing you unlock?
if entityGearSet == nil or Shared.contains(processedSets, entityGearSet.name) == false then
local verb = ''
local foundLevel = entity.skillLevel
if VERBS_PER_SUBTYPE[entity.subType] then
verb = VERBS_PER_SUBTYPE[entity.subType] .. ' '
-- Start a new row if the current entity's level is higher than the
end
-- current row
if foundLevel ~= currentLevel then
-- Icon overrides
currentLevel = foundLevel
local iconType = entity.entityType
table.insert(resultPart, '\r\n|-')
if entity.entityType == 'slayerArea' then
table.insert(resultPart, '\r\n|' .. foundLevel)
iconType = 'combatArea'
table.insert(resultPart, '\r\n|')
else
table.insert(resultPart, '<br/>')
end
-- Figure out what we need to list - single item or set?
local toInsert = ''
if entityGearSet ~= nil then
toInsert = p._prepareGearSet(entityGearSet, entity)
table.insert(processedSets, entityGearSet.name)
else
toInsert = p._prepareSingleEntity(entity, skillName)
end
-- Append entity to the column
table.insert(resultPart, toInsert)
end
end
-- Append entity to the column
table.insert(resultPart, verb .. Icons.Icon({entity.entityName, type=iconType}))
end
end
Line 163: Line 1,621:


function p.getSkillUnlockTable(frame)
function p.getSkillUnlockTable(frame)
local skillName = frame.args ~= nil and frame.args[1]
local args = frame.args ~= nil and frame.args or frame
return p._getSkillUnlockTable(skillName)
local skillName = args[1]
return p._getSkillUnlockTable(skillName, args)
end
end


return p
return p

Latest revision as of 21:05, 7 August 2024

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

local p = {}

-- This module polls various game data sources to produce a full list of skill
-- level unlocks for each skill. The game has a hard-coded set of "milestones"
-- for each skill that does the same thing, but it is not exhaustive and not
-- permanently visible for most combat skills.

-- TODO: Args to filter by level range and unlock type

local Shared = require('Module:Shared')
local Constants = require('Module:Constants')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local CombatAreas = require('Module:CombatAreas')
local Shop = require('Module:Shop')
local Magic = require('Module:Magic')
local Township = require('Module:Township')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Num = require('Module:Number')

-- This excludes certain checks pertinent to all skills, like equipment
local SKILL_CHECK_MAP = {
	['Attack'] = {},
	['Strength'] = {},
	['Defence'] = {},
	['Hitpoints'] = {},
	['Ranged'] = {},
	['Magic'] = {'spells', 'altmagic'},
	['Prayer'] = {'prayers'},
	['Slayer'] = {'areas'},
	['Farming'] = {'mastery', 'gatheringitems', 'farmingplots'},
	['Township'] = {'townshipunlocks'},
	['Woodcutting'] = {'mastery', 'gatheringitems'},
	['Fishing'] = {'mastery', 'gatheringitems'},
	['Firemaking'] = {'mastery', 'firemaking'},
	['Cooking'] = {'mastery', 'artisanitems'},
	['Mining'] = {'mastery', 'gatheringitems'},
	['Smithing'] = {'mastery', 'artisanitems'},
	['Thieving'] = {'mastery', 'thieving'},
	['Fletching'] = {'mastery', 'artisanitems'},
	['Crafting'] = {'mastery', 'artisanitems'},
	['Runecrafting'] = {'mastery', 'artisanitems'},
	['Herblore'] = {'mastery', 'artisanitems'},
	['Agility'] = {'mastery', 'agilityslots'},
	['Summoning'] = {'mastery', 'artisanitems'},
	['Astrology'] = {'mastery', 'constellations'},
	['Alt. Magic'] = {'altmagic'},
}
local TYPE_SORT_ORDER = {
	['special'] = 1,
	['biome'] = 2,
	['building'] = 3,
	['plot'] = 4,
	['obstacleslot'] = 5,
	['pillar'] = 6,
	['spell'] = 7,
	['prayer'] = 8,
	['thieving'] = 9,
	['constellation'] = 10,
	['gathering'] = 11,
	['artisan'] = 12,
	['item'] = 13, 
	['combatArea'] = 14,
	['slayerArea'] = 15,
	['dungeon'] = 16,
	['depth'] = 17,
	['stronghold'] = 18,
	['shop'] = 19,
	['trader'] = 20,
	['obstacle'] = 21
}
local VERBS_PER_SUBTYPE = {
	['Weapon'] = 'Wield',
	['Magic Wand'] = 'Wield',
	['Magic Staff'] = 'Wield',
	['Magic Book'] = 'Wield',
	['Ranged Weapon'] = 'Wield',
	['Ammo'] = 'Wield',
	['Equipment'] = 'Wear',
	['Armour'] = 'Wear',
	['Trimmed Armour'] = 'Wear',
	['Magic Armour'] = 'Wear',
	['Ranged Armour'] = 'Wear',
	['Slayer Armour'] = 'Wear',
	['Ring'] = 'Wear', 
	['Amulet'] = 'Wear',
	['combatArea'] = 'Access',
	['slayerArea'] = 'Access',
	['dungeon'] = 'Access',
	['standard'] = 'Cast',
	['aurora'] = 'Cast',
	['curse'] = 'Cast',
	['ancient'] = 'Cast',
	['archaic'] = 'Cast',
	['prayer'] = 'Use',
	['altMagic'] = 'Cast',
	['tree'] = 'Cut',
	['fish'] = 'Catch',
	['Essence'] = 'Mine',
	['Ore'] = 'Mine',
	['Gem'] = 'Mine',
	['artisan'] = 'Create',
	['melvorD:Fire'] = 'Cook',
	['melvorD:Furnace'] = 'Bake',
	['melvorD:Pot'] = 'Boil',
	['melvorD:Bars'] = 'Smelt',
	['melvorF:SkillPotions'] = 'Brew',
	['melvorF:CombatPotions'] = 'Brew',
	['log'] = 'Burn',
	['npc'] = 'Pickpocket',
	['constellation'] = 'Study',
	['shop'] = 'Purchase',
	['plot'] = 'New',
	['biome'] = 'Access',
	['basicBuilding'] = 'Build',
	['trader'] = 'Trade for',
	['obstacle'] = 'Build',
	['obstacleslot'] = 'Build',
	['pillar'] = 'Build'
}
local SUBTYPE_OVERRIDES = {
	['Agile Wings Rapier'] = 'Weapon',
	['Bundled Protection Body'] = 'Armour',
	['Ethereal Greataxe'] = 'Weapon',
	['Ethereal Longbow'] = 'Ranged Weapon',
	['Ethereal Staff'] = 'Magic Staff',
	['Feather Storm Crossbow'] = 'Ranged Weapon',
	['FrostSpark 1H Sword'] = 'Weapon',
	['Lightning Coil 2H Staff'] = 'Magic Staff',
	['Lightning Strike 1H Sword'] = 'Weapon',
	['Perfect Sight Legs'] = 'Armour',
	['Royal Toxins Spear'] = 'Weapon',
	['Slicing Maelstrom Wand'] = 'Magic Wand',
	['Spectral Ice Sword'] = 'Weapon',
	['Torrential Blast Crossbow'] = 'Ranged Weapon'
}
local SUBTYPE_SORT_OVERRIDES = {
	['melvorD:Bars'] = '1',
	['melvorF:StandardRunes'] = '1',
	['log'] = '1'
}
local GEAR_SETS = {
	{
		['verb'] = 'Wear',
		['name'] = 'Adamant Armour',
		['level'] = 30,
		['entities'] = {'Adamant Boots', '(S) Adamant Boots', '(G) Adamant Boots', 'Adamant Gloves', 'Adamant Helmet', '(S) Adamant Helmet', '(G) Adamant Helmet', 'Adamant Platebody', '(S) Adamant Platebody', '(G) Adamant Platebody', 'Adamant Platelegs', '(S) Adamant Platelegs', '(G) Adamant Platelegs', 'Adamant Shield', '(S) Adamant Shield', '(G) Adamant Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Adamant Ranged Weapons',
		['level'] = 30,
		['entities'] = {'Adamant Crossbow', 'Adamant Javelin', 'Adamant Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Adamant Weapons',
		['level'] = 30,
		['entities'] = {'Adamant 2H Sword', 'Adamant Battleaxe', 'Adamant Dagger', 'Adamant Scimitar', 'Adamant Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Aeris God Armour',
		['level'] = 85,
		['entities'] = {'Aeris God Boots', 'Aeris God Gloves', 'Aeris God Helmet', 'Aeris God Platebody', 'Aeris God Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Air Acolyte Wizard Gear',
		['level'] = 1,
		['entities'] = {'Air Acolyte Wizard Boots', 'Air Acolyte Wizard Bottoms', 'Air Acolyte Wizard Hat', 'Air Acolyte Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Air Adept Wizard Gear',
		['level'] = 35,
		['entities'] = {'Air Adept Wizard Boots', 'Air Adept Wizard Bottoms', 'Air Adept Wizard Hat', 'Air Adept Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Air Expert Wizard Gear',
		['level'] = 65,
		['entities'] = {'Air Expert Wizard Boots', 'Air Expert Wizard Bottoms', 'Air Expert Wizard Hat', 'Air Expert Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ancient Armour',
		['level'] = 70,
		['entities'] = {'Ancient Helmet', '(S) Ancient Helmet', '(G) Ancient Helmet', 'Ancient Platebody', '(S) Ancient Platebody', '(G) Ancient Platebody', 'Ancient Platelegs', '(S) Ancient Platelegs', '(G) Ancient Platelegs', 'Ancient Shield', '(S) Ancient Shield', '(G) Ancient Shield'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ancient Dragonhide Armour',
		['level'] = 80,
		['entities'] = {'Ancient D-hide Body', 'Ancient D-hide Chaps', 'Ancient D-hide Shield', 'Ancient D-hide Vambraces', '(U) Ancient D-hide Body', '(U) Ancient D-hide Chaps', '(U) Ancient D-hide Shield', '(U) Ancient D-hide Vambraces'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Ancient Ranged Weapons',
		['level'] = 70,
		['entities'] = {'Ancient Crossbow', 'Ancient Javelin', 'Ancient Throwing Knife', 'Ancient Longbow'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Ancient Weapons',
		['level'] = 70,
		['entities'] = {'Ancient 2H Sword', 'Ancient Claw', 'Ancient Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ancient Wizard Gear',
		['level'] = 70,
		['entities'] = {'Ancient Wizard Boots', 'Ancient Wizard Bottoms', 'Ancient Wizard Hat', 'Ancient Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Augite Armour',
		['level'] = 105,
		['entities'] = {'Augite Boots', '(I) Augite Boots', '(P) Augite Boots', 'Augite Gloves', 'Augite Helmet', '(I) Augite Helmet', '(P) Augite Helmet', 'Augite Platebody', '(I) Augite Platebody', '(P) Augite Platebody', 'Augite Platelegs', '(I) Augite Platelegs', '(P) Augite Platelegs', 'Augite Shield', '(I) Augite Shield', '(P) Augite Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Augite Ranged Weapons',
		['level'] = 105,
		['entities'] = {'Augite Crossbow', 'Augite Javelin', 'Augite Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Augite Weapons',
		['level'] = 105,
		['entities'] = {'Augite 2H Sword', 'Augite Battleaxe', 'Augite Dagger', 'Augite Scimitar', 'Augite Sword'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Battlestaves',
		['level'] = 30,
		['entities'] = {'Air Battlestaff', 'Earth Battlestaff', 'Fire Battlestaff', 'Water Battlestaff'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Black Armour',
		['level'] = 10,
		['entities'] = {'Black Boots', '(S) Black Boots', '(G) Black Boots', 'Black Helmet', '(S) Black Helmet', '(G) Black Helmet', 'Black Platebody', '(S) Black Platebody', '(G) Black Platebody', 'Black Platelegs', '(S) Black Platelegs', '(G) Black Platelegs', 'Black Shield', '(S) Black Shield', '(G) Black Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Black Weapons',
		['level'] = 10,
		['entities'] = {'Black 2H Sword', 'Black Battleaxe', 'Black Dagger', 'Black Scimitar', 'Black Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Black Dragonhide Armour',
		['level'] = 70,
		['entities'] = {'Black D-hide Body', 'Black D-hide Chaps', 'Black D-hide Shield', 'Black D-hide Vambraces', '(U) Black D-hide Body', '(U) Black D-hide Chaps', '(U) Black D-hide Shield', '(U) Black D-hide Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Basic Black Wizard Gear',
		['level'] = 50,
		['entities'] = {'Black Wizard Boots', 'Black Wizard Bottoms', 'Black Wizard Hat', 'Black Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Blue Dragonhide Armour',
		['level'] = 50,
		['entities'] = {'Blue D-hide Body', 'Blue D-hide Chaps', 'Blue D-hide Shield', 'Blue D-hide Vambraces', '(U) Blue D-hide Body', '(U) Blue D-hide Chaps', '(U) Blue D-hide Shield', '(U) Blue D-hide Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Basic Blue Wizard Gear',
		['level'] = 10,
		['entities'] = {'Blue Wizard Boots', 'Blue Wizard Bottoms', 'Blue Wizard Hat', 'Blue Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Bronze Armour',
		['level'] = 1,
		['entities'] = {'Bronze Boots', '(S) Bronze Boots', '(G) Bronze Boots', 'Bronze Gloves', 'Bronze Helmet', '(S) Bronze Helmet', '(G) Bronze Helmet', 'Bronze Platebody', '(S) Bronze Platebody', '(G) Bronze Platebody', 'Bronze Platelegs', '(S) Bronze Platelegs', '(G) Bronze Platelegs', 'Bronze Shield', '(S) Bronze Shield', '(G) Bronze Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Bronze Ranged Weapons',
		['level'] = 1,
		['entities'] = {'Bronze Crossbow', 'Bronze Javelin', 'Bronze Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Bronze Weapons',
		['level'] = 1,
		['entities'] = {'Bronze 2H Sword', 'Bronze Battleaxe', 'Bronze Dagger', 'Bronze Scimitar', 'Bronze Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Carrion Armour',
		['level'] = 110,
		['entities'] = {'Carrion Body', 'Carrion Chaps', 'Carrion Shield', 'Carrion Vambraces', '(U) Carrion Body', '(U) Carrion Chaps', '(U) Carrion Shield', '(U) Carrion Vambraces'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Carrion Bows',
		['level'] = 110,
		['entities'] = {'Carrion Longbow', 'Carrion Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Corundum Armour',
		['level'] = 100,
		['entities'] = {'Corundum Boots', '(I) Corundum Boots', '(P) Corundum Boots', 'Corundum Gloves', 'Corundum Helmet', '(I) Corundum Helmet', '(P) Corundum Helmet', 'Corundum Platebody', '(I) Corundum Platebody', '(P) Corundum Platebody', 'Corundum Platelegs', '(I) Corundum Platelegs', '(P) Corundum Platelegs', 'Corundum Shield', '(I) Corundum Shield', '(P) Corundum Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Corundum Ranged Weapons',
		['level'] = 100,
		['entities'] = {'Corundum Crossbow', 'Corundum Javelin', 'Corundum Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Corundum Weapons',
		['level'] = 100,
		['entities'] = {'Corundum 2H Sword', 'Corundum Battleaxe', 'Corundum Dagger', 'Corundum Scimitar', 'Corundum Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Divine Armour',
		['level'] = 110,
		['entities'] = {'Divine Boots', '(I) Divine Boots', '(P) Divine Boots', 'Divine Gloves', 'Divine Helmet', '(I) Divine Helmet', '(P) Divine Helmet', 'Divine Platebody', '(I) Divine Platebody', '(P) Divine Platebody', 'Divine Platelegs', '(I) Divine Platelegs', '(P) Divine Platelegs', 'Divine Shield', '(I) Divine Shield', '(P) Divine Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Divine Ranged Weapons',
		['level'] = 110,
		['entities'] = {'Divine Crossbow', 'Divine Javelin', 'Divine Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Divine Weapons',
		['level'] = 110,
		['entities'] = {'Divine 2H Sword', 'Divine Battleaxe', 'Divine Dagger', 'Divine Scimitar', 'Divine Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Dragon Armour',
		['level'] = 60,
		['entities'] = {'Dragon Boots', '(S) Dragon Boots', '(G) Dragon Boots', 'Dragon Gloves', 'Dragon Helmet', '(S) Dragon Helmet', '(G) Dragon Helmet', 'Dragon Platebody', '(S) Dragon Platebody', '(G) Dragon Platebody', 'Dragon Platelegs', '(S) Dragon Platelegs', '(G) Dragon Platelegs', 'Dragon Shield', '(S) Dragon Shield', '(G) Dragon Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Dragon Ranged Weapons',
		['level'] = 60,
		['entities'] = {'Dragon Crossbow', 'Dragon Javelin', 'Dragon Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Dragon Weapons',
		['level'] = 60,
		['entities'] = {'Dragon 2H Sword', 'Dragon Battleaxe', 'Dragon Dagger', 'Dragon Scimitar', 'Dragon Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Earth Acolyte Wizard Gear',
		['level'] = 9,
		['entities'] = {'Earth Acolyte Wizard Boots', 'Earth Acolyte Wizard Bottoms', 'Earth Acolyte Wizard Hat', 'Earth Acolyte Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Earth Adept Wizard Gear',
		['level'] = 43,
		['entities'] = {'Earth Adept Wizard Boots', 'Earth Adept Wizard Bottoms', 'Earth Adept Wizard Hat', 'Earth Adept Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Earth Expert Wizard Gear',
		['level'] = 73,
		['entities'] = {'Earth Expert Wizard Boots', 'Earth Expert Wizard Bottoms', 'Earth Expert Wizard Hat', 'Earth Expert Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Elderwood Armour',
		['level'] = 100,
		['entities'] = {'Elderwood Body', 'Elderwood Chaps', 'Elderwood Shield', 'Elderwood Vambraces', '(U) Elderwood Body', '(U) Elderwood Chaps', '(U) Elderwood Shield', '(U) Elderwood Vambraces'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Elderwood Bows',
		['level'] = 100,
		['entities'] = {'Elderwood Longbow', 'Elderwood Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Fire Acolyte Wizard Gear',
		['level'] = 14,
		['entities'] = {'Fire Acolyte Wizard Boots', 'Fire Acolyte Wizard Bottoms', 'Fire Acolyte Wizard Hat', 'Fire Acolyte Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Fire Adept Wizard Gear',
		['level'] = 48,
		['entities'] = {'Fire Adept Wizard Boots', 'Fire Adept Wizard Bottoms', 'Fire Adept Wizard Hat', 'Fire Adept Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Fire Expert Wizard Gear',
		['level'] = 78,
		['entities'] = {'Fire Expert Wizard Boots', 'Fire Expert Wizard Bottoms', 'Fire Expert Wizard Hat', 'Fire Expert Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Glacia God Gear',
		['level'] = 85,
		['entities'] = {'Glacia God Boots', 'Glacia God Gloves', 'Glacia God Helmet', 'Glacia God Platebody', 'Glacia God Platelegs'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Godswords',
		['level'] = 85,
		['entities'] = {'Aeris Godsword', 'Glacia Godsword', 'Ragnar Godsword', 'Terran Godsword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Green Dragonhide Armour',
		['level'] = 40,
		['entities'] = {'Green D-hide Body', 'Green D-hide Chaps', 'Green D-hide Shield', 'Green D-hide Vambraces', '(U) Green D-hide Body', '(U) Green D-hide Chaps', '(U) Green D-hide Shield', '(U) Green D-hide Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Basic Green Wizard Gear',
		['level'] = 1,
		['entities'] = {'Green Wizard Boots', 'Green Wizard Bottoms', 'Green Wizard Hat', 'Green Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Hard Leather Armour',
		['level'] = 10,
		['entities'] = {'Hard Leather Body', 'Hard Leather Boots', 'Hard Leather Chaps', 'Hard Leather Cowl', 'Hard Leather Gloves', 'Hard Leather Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ice Armor',
		['level'] = 40,
		['entities'] = {'Ice Boots', 'Ice Helmet', 'Ice Platebody', 'Ice Platelegs', 'Ice Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Ice Bows',
		['level'] = 40,
		['entities'] = {'Ice Longbow', 'Ice Shortbow'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Ice Weapons',
		['level'] = 40,
		['entities'] = {'Ice 2H Sword', 'Ice Battleaxe', 'Ice Dagger', 'Ice Sword'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Imbued Magic Wands',
		['level'] = 70,
		['entities'] = {'Air Imbued Wand', 'Earth Imbued Wand', 'Fire Imbued Wand', 'Water Imbued Wand'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Infernal Legendary Wizard Gear',
		['level'] = 105,
		['entities'] = {'Infernal Legendary Wizard Boots', 'Infernal Legendary Wizard Bottoms', 'Infernal Legendary Wizard Hat', 'Infernal Legendary Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Infernal Master Wizard Gear',
		['level'] = 100,
		['entities'] = {'Infernal Master Wizard Boots', 'Infernal Master Wizard Bottoms', 'Infernal Master Wizard Hat', 'Infernal Master Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Infernal Mythical Wizard Gear',
		['level'] = 110,
		['entities'] = {'Infernal Mythical Wizard Boots', 'Infernal Mythical Wizard Bottoms', 'Infernal Mythical Wizard Hat', 'Infernal Mythical Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Iron Armour',
		['level'] = 1,
		['entities'] = {'Iron Boots', '(S) Iron Boots', '(G) Iron Boots', 'Iron Gloves', 'Iron Helmet', '(S) Iron Helmet', '(G) Iron Helmet', 'Iron Platebody', '(S) Iron Platebody', '(G) Iron Platebody', 'Iron Platelegs', '(S) Iron Platelegs', '(G) Iron Platelegs', 'Iron Shield', '(S) Iron Shield', '(G) Iron Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Iron Ranged Weapons',
		['level'] = 1,
		['entities'] = {'Iron Crossbow', 'Iron Javelin', 'Iron Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Iron Weapons',
		['level'] = 1,
		['entities'] = {'Iron 2H Sword', 'Iron Battleaxe', 'Iron Dagger', 'Iron Scimitar', 'Iron Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Leather Armour',
		['level'] = 1,
		['entities'] = {'Leather Body', 'Leather Boots', 'Leather Chaps', 'Leather Cowl', 'Leather Gloves', 'Leather Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Lightning Legendary Wizard Gear',
		['level'] = 105,
		['entities'] = {'Lightning Legendary Wizard Boots', 'Lightning Legendary Wizard Bottoms', 'Lightning Legendary Wizard Hat', 'Lightning Legendary Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Lightning Master Wizard Gear',
		['level'] = 100,
		['entities'] = {'Lightning Master Wizard Boots', 'Lightning Master Wizard Bottoms', 'Lightning Master Wizard Hat', 'Lightning Master Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Lightning Mythical Wizard Gear',
		['level'] = 110,
		['entities'] = {'Lightning Mythical Wizard Boots', 'Lightning Mythical Wizard Bottoms', 'Lightning Mythical Wizard Hat', 'Lightning Mythical Wizard Robes'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Magic Bows',
		['level'] = 50,
		['entities'] = {'Magic Longbow', 'Magic Shortbow'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Maple Bows',
		['level'] = 30,
		['entities'] = {'Maple Longbow', 'Maple Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Meteorite Armour',
		['level'] = 108,
		['entities'] = {'Meteorite Helmet', 'Meteorite Platebody', 'Meteorite Platelegs'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Meteorite Ranged Weapons',
		['level'] = 1,
		['entities'] = {'Meteorite Crossbow', 'Meteorite Javelin'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Miolite Armour',
		['level'] = 40,
		['entities'] = {'Miolite Boots', 'Miolite Helmet', 'Miolite Platebody', 'Miolite Platelegs', 'Miolite Shield'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Mithril Armour',
		['level'] = 20,
		['entities'] = {'Mithril Boots', '(S) Mithril Boots', '(G) Mithril Boots', 'Mithril Gloves', 'Mithril Helmet', '(S) Mithril Helmet', '(G) Mithril Helmet', 'Mithril Platebody', '(S) Mithril Platebody', '(G) Mithril Platebody', 'Mithril Platelegs', '(S) Mithril Platelegs', '(G) Mithril Platelegs', 'Mithril Shield', '(S) Mithril Shield', '(G) Mithril Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Mithril Ranged Weapons',
		['level'] = 20,
		['entities'] = {'Mithril Crossbow', 'Mithril Javelin', 'Mithril Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Mithril Weapons',
		['level'] = 20,
		['entities'] = {'Mithril 2H Sword', 'Mithril Battleaxe', 'Mithril Dagger', 'Mithril Scimitar', 'Mithril Sword'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Mystic Magic Staves',
		['level'] = 40,
		['entities'] = {'Mystic Air Staff', 'Mystic Earth Staff', 'Mystic Fire Staff', 'Mystic Water Staff'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Normal Bows',
		['level'] = 1,
		['entities'] = {'Normal Longbow', 'Normal Shortbow'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Oak Bows',
		['level'] = 5,
		['entities'] = {'Oak Longbow', 'Oak Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Poison Legendary Wizard Gear',
		['level'] = 105,
		['entities'] = {'Poison Legendary Wizard Boots', 'Poison Legendary Wizard Bottoms', 'Poison Legendary Wizard Hat', 'Poison Legendary Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Poison Master Wizard Gear',
		['level'] = 100,
		['entities'] = {'Poison Master Wizard Boots', 'Poison Master Wizard Bottoms', 'Poison Master Wizard Hat', 'Poison Master Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Poison Mythical Wizard Gear',
		['level'] = 110,
		['entities'] = {'Poison Mythical Wizard Boots', 'Poison Mythical Wizard Bottoms', 'Poison Mythical Wizard Hat', 'Poison Mythical Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ragnar God Armour',
		['level'] = 85,
		['entities'] = {'Ragnar God Boots', 'Ragnar God Gloves', 'Ragnar God Helmet', 'Ragnar God Platebody', 'Ragnar God Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Red Dragonhide Armour',
		['level'] = 60,
		['entities'] = {'Red D-hide Body', 'Red D-hide Chaps', 'Red D-hide Shield', 'Red D-hide Vambraces', '(U) Red D-hide Body', '(U) Red D-hide Chaps', '(U) Red D-hide Shield', '(U) Red D-hide Vambraces'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Basic Red Wizard Gear',
		['level'] = 30,
		['entities'] = {'Red Wizard Boots', 'Red Wizard Bottoms', 'Red Wizard Hat', 'Red Wizard Robes'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Redwood Bows',
		['level'] = 60,
		['entities'] = {'Redwood Longbow', 'Redwood Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Revenant Armour',
		['level'] = 105,
		['entities'] = {'Revenant Body', 'Revenant Chaps', 'Revenant Shield', 'Revenant Vambraces', '(U) Revenant Body', '(U) Revenant Chaps', '(U) Revenant Shield', '(U) Revenant Vambraces'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Revenant Bows',
		['level'] = 105,
		['entities'] = {'Revenant Longbow', 'Revenant Shortbow'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Rune Armour',
		['level'] = 40,
		['entities'] = {'Rune Boots', '(S) Rune Boots', '(G) Rune Boots', 'Rune Gloves', 'Rune Helmet', '(S) Rune Helmet', '(G) Rune Helmet', 'Rune Platebody', '(S) Rune Platebody', '(G) Rune Platebody', 'Rune Platelegs', '(S) Rune Platelegs', '(G) Rune Platelegs', 'Rune Shield', '(S) Rune Shield', '(G) Rune Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Rune Ranged Weapons',
		['level'] = 40,
		['entities'] = {'Rune Crossbow', 'Rune Javelin', 'Rune Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Rune Weapons',
		['level'] = 40,
		['entities'] = {'Rune 2H Sword', 'Rune Battleaxe', 'Rune Dagger', 'Rune Scimitar', 'Rune Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Basic)',
		['level'] = 1,
		['entities'] = {'Slayer Helmet (Basic)', 'Slayer Platebody (Basic)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Strong)',
		['level'] = 30,
		['entities'] = {'Slayer Helmet (Strong)', 'Slayer Platebody (Strong)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Elite)',
		['level'] = 60,
		['entities'] = {'Slayer Helmet (Elite)', 'Slayer Platebody (Elite)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Master)',
		['level'] = 80,
		['entities'] = {'Slayer Helmet (Master)', 'Slayer Platebody (Master)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Legendary)',
		['level'] = 100,
		['entities'] = {'Slayer Helmet (Legendary)', 'Slayer Platebody (Legendary)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Melee Slayer Gear (Mythical)',
		['level'] = 110,
		['entities'] = {'Slayer Helmet (Mythical)', 'Slayer Platebody (Mythical)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ranged Slayer Gear (Strong)',
		['level'] = 30,
		['entities'] = {'Slayer Cowl (Strong)', 'Slayer Leather Body (Strong)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ranged Slayer Gear (Elite)',
		['level'] = 60,
		['entities'] = {'Slayer Cowl (Elite)', 'Slayer Leather Body (Elite)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ranged Slayer Gear (Master)',
		['level'] = 80,
		['entities'] = {'Slayer Cowl (Master)', 'Slayer Leather Body (Master)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ranged Slayer Gear (Legendary)',
		['level'] = 100,
		['entities'] = {'Slayer Cowl (Legendary)', 'Slayer Leather Body (Legendary)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Ranged Slayer Gear (Mythical)',
		['level'] = 110,
		['entities'] = {'Slayer Cowl (Mythical)', 'Slayer Leather Body (Mythical)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Magic Slayer Gear (Strong)',
		['level'] = 30,
		['entities'] = {'Slayer Wizard Hat (Strong)', 'Slayer Wizard Robes (Strong)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Magic Slayer Gear (Elite)',
		['level'] = 60,
		['entities'] = {'Slayer Wizard Hat (Elite)', 'Slayer Wizard Robes (Elite)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Magic Slayer Gear (Master)',
		['level'] = 80,
		['entities'] = {'Slayer Wizard Hat (Master)', 'Slayer Wizard Robes (Master)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Magic Slayer Gear (Legendary)',
		['level'] = 100,
		['entities'] = {'Slayer Wizard Hat (Legendary)', 'Slayer Wizard Robes (Legendary)'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Magic Slayer Gear (Mythical)',
		['level'] = 110,
		['entities'] = {'Slayer Wizard Hat (Mythical)', 'Slayer Wizard Robes (Mythical)'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Basic Magic Staves',
		['level'] = 1,
		['entities'] = {'Staff of Air', 'Staff of Earth', 'Staff of Fire', 'Staff of Water'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Steel Armour',
		['level'] = 5,
		['entities'] = {'Steel Boots', '(S) Steel Boots', '(G) Steel Boots', 'Steel Gloves', 'Steel Helmet', '(S) Steel Helmet', '(G) Steel Helmet', 'Steel Platebody', '(S) Steel Platebody', '(G) Steel Platebody', 'Steel Platelegs', '(S) Steel Platelegs', '(G) Steel Platelegs', 'Steel Shield', '(S) Steel Shield', '(G) Steel Shield'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Steel Ranged Weapons',
		['level'] = 5,
		['entities'] = {'Steel Crossbow', 'Steel Javelin', 'Steel Throwing Knife'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Steel Weapons',
		['level'] = 5,
		['entities'] = {'Steel 2H Sword', 'Steel Battleaxe', 'Steel Dagger', 'Steel Scimitar', 'Steel Sword'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Terran God Armour',
		['level'] = 85,
		['entities'] = {'Terran God Boots', 'Terran God Gloves', 'Terran God Helmet', 'Terran God Platebody', 'Terran God Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Vorloran Devastator Armour',
		['level'] = 120,
		['entities'] = {'Vorloran Devastator Boots', 'Vorloran Devastator Gauntlets', 'Vorloran Devastator Helmet', 'Vorloran Devastator Platebody', 'Vorloran Devastator Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Vorloran Protector Armour',
		['level'] = 120,
		['entities'] = {'Vorloran Protector Boots', 'Vorloran Protector Gauntlets', 'Vorloran Protector Helmet', 'Vorloran Protector Platebody', 'Vorloran Protector Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Vorloran Watcher Armour',
		['level'] = 120,
		['entities'] = {'Vorloran Watcher Boots', 'Vorloran Watcher Gauntlets', 'Vorloran Watcher Helmet', 'Vorloran Watcher Platebody', 'Vorloran Watcher Platelegs'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Water Acolyte Wizard Gear',
		['level'] = 5,
		['entities'] = {'Water Acolyte Wizard Boots', 'Water Acolyte Wizard Bottoms', 'Water Acolyte Wizard Hat', 'Water Acolyte Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Water Adept Wizard Gear',
		['level'] = 39,
		['entities'] = {'Water Adept Wizard Boots', 'Water Adept Wizard Bottoms', 'Water Adept Wizard Hat', 'Water Adept Wizard Robes'}
	},
	{
		['verb'] = 'Wear',
		['name'] = 'Water Expert Wizard Gear',
		['level'] = 69,
		['entities'] = {'Water Expert Wizard Boots', 'Water Expert Wizard Bottoms', 'Water Expert Wizard Hat', 'Water Expert Wizard Robes'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Willow Bows',
		['level'] = 20,
		['entities'] = {'Willow Longbow', 'Willow Shortbow'}
	},
	{
		['verb'] = 'Wield',
		['name'] = 'Yew Bows',
		['level'] = 40,
		['entities'] = {'Yew Longbow', 'Yew Shortbow'}
	},
	{
		['verb'] = 'Purchase',
		['name'] = 'Skill Hats',
		['level'] = 10,
		['entities'] = {'Blacksmiths Hat', 'Burning Mans Hat', 'Crafters Hat', 'Fishermans Hat', 'Fletchers Hat', 'Miners Hat', 'Performance Enhancing Hat', 'Potion Makers Hat', 'Runecrafters Hat', 'Star Gazing Hat', 'Woodcutters Hat'}
	},
	{
		['verb'] = 'Purchase',
		['name'] = 'Skill Bodies',
		['level'] = 20,
		['entities'] = {'Blacksmiths Body', 'Burning Mans Body', 'Crafters Body', 'Fishermans Body', 'Fletchers Body', 'Miners Body', 'Performance Enhancing Body', 'Potion Makers Body', 'Runecrafters Body', 'Star Gazing Body', 'Woodcutters Body'}
	},
	{
		['verb'] = 'Purchase',
		['name'] = 'Skill Leggings',
		['level'] = 40,
		['entities'] = {'Blacksmiths Leggings', 'Burning Mans Leggings', 'Crafters Leggings', 'Fishermans Leggings', 'Fletchers Leggings', 'Miners Leggings', 'Performance Enhancing Leggings', 'Potion Makers Leggings', 'Runecrafters Leggings', 'Star Gazing Leggings', 'Woodcutters Leggings'}
	},
	{
		['verb'] = 'Purchase',
		['name'] = 'Skill Boots',
		['level'] = 60,
		['entities'] = {'Blacksmiths Boots', 'Burning Mans Boots', 'Crafters Boots', 'Fishermans Boots', 'Fletchers Boots', 'Miners Boots', 'Performance Enhancing Boots', 'Potion Makers Boots', 'Runecrafters Boots', 'Star Gazing Boots', 'Woodcutters Boots'}
	}
}

function p._getEntityTrueSubtype(entityType, entityName)
	-- Corrects types of a few entities with misleading/wrong data
	if SUBTYPE_OVERRIDES[entityName] then
		entityType = SUBTYPE_OVERRIDES[entityName]
	end
	return entityType
end

function p._getOtherSkillReqs(reqList, skillName)
	-- Remove the current skill's requirement, leaving us with all others
	local otherReqs = {}
	for i, req in ipairs(reqList) do
		if req.type ~= 'SkillLevel' or (req.skillID ~= Constants.getSkillID(skillName) and req.level ~= 1) then
			table.insert(otherReqs, req)
		end
	end
	
	return otherReqs
end

function p._getSpellReqs(spell)
	-- Get spell requirements that aren't SkillLevel (ex. equipped item)
	local reqs = {}
	
	if spell.requiredItemID ~= nil then
        local item = Items.getItemByID(spell.requiredItemID)
        if item ~= nil then
            table.insert(reqs, {['type'] = 'item', ['item'] = item})
        end
	end

	if spell.requirements ~= nil then
		for i, req in ipairs(spell.requirements) do
			table.insert(reqs, req)
		end
	end
	
	return reqs
end

function p._getGatherableReqs(node, skillName)
	-- Get gatherable requirements that aren't SkillLevel (ex. shop item)
	local reqs = {}
	
	if node.shopItemPurchased ~= nil then
		local purchase = GameData.getEntityByID('shopPurchases', node.shopItemPurchased)
		if purchase ~= nil then
			table.insert(reqs, {['type'] = 'shop', ['purchase'] = purchase})
		end
	end
	
	if node.totalMasteryRequired ~= nil then
		table.insert(reqs, {['type'] = 'totalMastery', ['mastery'] = node.totalMasteryRequired, ['skill'] = skillName})
	end
	
	return reqs
end

function p._addEntities(frame)
	local args = frame.args ~= nil and frame.args or frame
	local entityList = args[1]
	local data = args[2]
	local entityType = args.type
	local typeParam = args.typeParam
	local subType = args.subType
	local subTypeParam = args.subTypeParam
	local otherReqsFunc = args.otherReqsFunc
	local imgType = args.imgType
	
	for i, entity in ipairs(data) do
		local processed = {}
		processed.entityName = entity.name
		processed.entityType = p._getEntityTrueSubtype(typeParam ~= nil and entity[typeParam] or entityType)
		processed.subType = subTypeParam ~= nil and entity[subTypeParam] or subType
		processed.skillLevel = entity.level
		processed.otherReqs = otherReqsFunc ~= nil and otherReqsFunc(entity) or {}
		processed.imgType = imgType
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addItemsWithSkillRequirement(entityList, skillName)
	local skillReqLabel = skillName:lower() .. 'LevelRequired'
	local itemList = Items.getItems(function(item)
		-- Exclude Golbin Raid exclusives
		if item.golbinRaidExclusive ~= nil and item.golbinRaidExclusive then
			return false
		end
		
		if item.equipRequirements ~= nil then
			for i, req in ipairs(item.equipRequirements) do
				if req.type == 'AbyssalLevel' then
					return false
				end
			end
		end
		
		return Items._getItemStat(item, skillReqLabel) ~= nil
	end)
	
	for i, item in ipairs(itemList) do
		local processed = {}
		processed.entity = item
		processed.entityName = item.name
		processed.entityType = 'item'
		processed.subType = p._getEntityTrueSubtype(item.type, item.name)
		processed.skillLevel = Items._getItemStat(item, skillReqLabel)
		processed.otherReqs = p._getOtherSkillReqs(item.equipRequirements, skillName)
		table.insert(entityList, processed)
	end
	
	return entityList
end

-- This covers combat areas, Slayer areas, and dungeons
function p._addAreasWithSkillRequirement(entityList, skillName)
	local areaList = CombatAreas.getAreas(function(area)
		local hasSkillReq = false
		for i, req in ipairs(area.entryRequirements) do
			if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
				hasSkillReq = true
			end
		end
		return hasSkillReq
	end)
	
	for i, area in ipairs(areaList) do
		local processed = {}
		processed.entityName = area.name
		processed.entityType = area.type
		processed.subType = p._getEntityTrueSubtype(area.type, area.name)
		for a, req in ipairs(area.entryRequirements) do
			if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
				processed.skillLevel = req.level
			end
		end
		processed.otherReqs = p._getOtherSkillReqs(area.entryRequirements, skillName)
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addShopPurchasesWithSkillRequirements(entityList, skillName)
	local purchaseList = Shop.getPurchases(function(purchase)
		local hasSkillReq = false
		for i, req in ipairs(purchase.purchaseRequirements) do
			if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
				hasSkillReq = true
			end
		end
		return hasSkillReq
	end)
	
	for i, purchase in ipairs(purchaseList) do
		-- Skip skillcapes here, as we handle them with items
		if purchase.category ~= 'melvorD:Skillcapes' and purchase.category ~= 'melvorTotH:SuperiorSkillcapes' then
			local processed = {}
			processed.entity = purchase
			processed.entityName = Shop._getPurchaseName(purchase)
			processed.entityType = 'shop'
			processed.subType = Shop._getPurchaseType(purchase)
			for a, req in ipairs(purchase.purchaseRequirements) do
				if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
					processed.skillLevel = req.level
				end
			end
			processed.otherReqs = p._getOtherSkillReqs(purchase.purchaseRequirements, skillName)
			table.insert(entityList, processed)
		end
	end
	
	return entityList
end

function p._addTraderItemsWithSkillRequirements(entityList, skillName)
	-- Iterate over each tradable resource, then each item you can get with it
	for i, tsResource in ipairs(Township.Township.itemConversions.fromTownship) do
		for j, tsPurchase in ipairs(tsResource.items) do
			-- Does this purchase require the current skill?
			local hasCurrentSkill = false
			local currentSkillLevel = 0
			for k, req in ipairs(tsPurchase.unlockRequirements) do
				if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
					hasCurrentSkill = true
					currentSkillLevel = req.level
				end
			end
			
			if hasCurrentSkill then
				local item = Items.getItemByID(tsPurchase.itemID)
				local processed = {}
				processed.entity = item
				processed.entityName = item.name
				processed.entityType = 'trader'
				processed.subType = 'trader'
				processed.skillLevel = currentSkillLevel
				processed.otherReqs = p._getOtherSkillReqs(tsPurchase.unlockRequirements, skillName)
				table.insert(entityList, processed)
			end
		end
	end
	
	return entityList
end

function p._addAgilityObstaclesWithSkillRequirements(entityList, skillName)
	local obstacles = GameData.getEntities(SkillData.Agility.obstacles,
		function(obst)
			return obst.abyssalLevel == nil
		end
	)
	for i, obstacle in ipairs(obstacles) do
		for j, req in ipairs(obstacle.skillRequirements) do
			-- Does this obstacle require the current skill?
			local hasCurrentSkill = false
			local currentSkillLevel = 0
			
			if req.type == 'SkillLevel' and req.skillID == Constants.getSkillID(skillName) then
				hasCurrentSkill = true
				currentSkillLevel = req.level
			end
			
			if hasCurrentSkill then
				local processed = {}
				processed.entity = obstacle
				processed.entityName = obstacle.name
				processed.entityType = 'obstacle'
				-- Category is zero-indexed
				processed.subType = 'Obstacle ' .. tostring(obstacle.category + 1)
				processed.skillLevel = currentSkillLevel
				processed.otherReqs = p._getOtherSkillReqs(obstacle.skillRequirements, skillName)
				table.insert(entityList, processed)
			end
		end
	end
	
	return entityList
end

function p._addSpells(entityList, skillName)
	-- Iterate through each spell type and each spell within that type
	for i, spellBookDef in ipairs(Magic.spellBooks) do
		local bookID = spellBookDef.id
		-- Alt. Magic is in a separate function
		if bookID ~= 'altMagic' then
			local entList = GameData.getEntities(Magic.getSpellsBySpellBook(bookID),
				function(spell)
					return spell.abyssalLevel == nil
				end
			)
			entityList = p._addEntities({entityList, entList, type='spell', subTypeParam='spellBook', imgType = spellBookDef.imgType, otherReqsFunc=p._getSpellReqs})
		end
	end
	
	return entityList
end

function p._addAltMagic(entityList, skillName)
	local entList = GameData.getEntities(Magic.getSpellsBySpellBook('altMagic'),
		function(spell)
			return spell.abyssalLevel == nil
		end
	)
	entityList = p._addEntities({entityList, entList, type='spell', subType='altMagic', imgType = 'spell', otherReqsFunc=p._getSpellReqs})
	return entityList
end

function p._addPrayers(entityList, skillName)
	local entList = GameData.getEntities('prayers',
		function(prayer)
			return prayer.abyssalLevel == nil
		end
	)
	entityList = p._addEntities({entityList, entList, type='prayer', subType='prayer'})
	return entityList
end

function p._addFiremakingActions(entityList, skillName)
	local fireLogs = GameData.getEntities(SkillData.Firemaking.logs,
		function(log)
			return log.abyssalLevel == nil
		end
	)
	for i, fireLog in ipairs(fireLogs) do
		local processed = {}
		local logItem = Items.getItemByID(fireLog.logID)
		processed.entityName = logItem.name
		processed.entityType = 'item'
		processed.subType = 'log'
		processed.skillLevel = fireLog.level
		processed.otherReqs = {}
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addThievingTargets(entityList, skillName)
	local entList = GameData.getEntities(SkillData.Thieving.npcs,
		function(npc)
			return npc.abyssalLevel == nil
		end
	)
	entityList = p._addEntities({entityList, entList, type='thieving', subType='npc'})
	return entityList
end

function p._addConstellations(entityList, skillName)
	local entList = GameData.getEntities(SkillData.Astrology.recipes,
		function(const)
			return const.abyssalLevel == nil
		end
	)
	entityList = p._addEntities({entityList, entList, type='constellation', subType='constellation'})
	return entityList
end

function p._addFarmingPlots(entityList, skillName)
	for i, plot in ipairs(SkillData.Farming.plots) do
		local plotName = 'Unknown Plot'
		if plot.categoryID == 'melvorD:Allotment' then
			plotName = 'Allotment Plot'
		elseif plot.categoryID == 'melvorD:Herb' then
			plotName = 'Herb Plot'
		elseif plot.categoryID == 'melvorD:Tree' then
			plotName = 'Tree Plot'
		end
		
		local processed = {}
		processed.entityName = plotName
		processed.entityType = 'plot'
		processed.subType = plot.categoryID
		processed.skillLevel = plot.level
		processed.otherReqs = {}
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addTownshipUnlocks(entityList, skillName)
	-- This one is weird, because some of the data uses tiers and not levels
	-- Add all of the biomes
	for i, biome in ipairs(Township.Township.biomes) do
		local tierReqs = Township._getTierRequirements(biome.tier)
		local processed = {}
		processed.entityName = biome.name
		processed.entityType = 'biome'
		processed.subType = 'biome'
		processed.skillLevel = tierReqs.level
		processed.otherReqs = {['population'] = tierReqs.population}
		table.insert(entityList, processed)
	end
	
	-- Add all of the buildings
	for i, building in ipairs(Township.Township.buildings) do
		local tierReqs = Township._getTierRequirements(building.tier)
		local processed = {}
		processed.entityName = building.name == 'Statues' and 'Statue of Worship' or building.name
		processed.entityType = 'building'
		processed.subType = building.upgradesFrom ~= nil and 'upgradedBuilding' or 'basicBuilding'
		processed.upgradesFrom = Township._getBuildingDowngrade(building)
		processed.skillLevel = tierReqs.level
		processed.otherReqs = {['population'] = tierReqs.population}
		table.insert(entityList, processed)
	end
	
	-- Add the fact that health starts decreasing at level 15
	local specialHealth = {
		['entityType'] = 'special',
		['subType'] = 'special',
		['skillLevel'] = 15,
		['overrideText'] = 'Township health has a 25% chance to decrease by 1% per update'
	}
	table.insert(entityList, specialHealth)
	
	return entityList
end

function p._addAgilityObstacleSlotsAndPillars(entityList, skillName)
	for i, level in ipairs(SkillData.Agility.obstacleUnlockLevels) do
		local processed = {}
		processed.entityName = 'Obstacle ' .. i
		processed.entityType = 'obstacleslot'
		processed.subType = 'obstacleslot'
		processed.skillLevel = level
		processed.otherReqs = {}
		table.insert(entityList, processed)
	end
	
	-- Manually add pillars
	table.insert(entityList, {['entityName'] = 'Passive Pillars', ['entityType'] = 'pillar', ['subType'] = 'pillar', ['skillLevel'] = 99, ['otherReqs'] = {}})
	table.insert(entityList, {['entityName'] = 'Elite Passive Pillars', ['entityType'] = 'pillar', ['subType'] = 'pillar', ['skillLevel'] = 120, ['otherReqs'] = {}})
	
	return entityList
end

function p._addGatherables(entityList, skillName)
	-- Figure out what to look up based on the skill
	local sourceData = {}
	local subType = ''
	if skillName == 'Woodcutting' then
		sourceData = SkillData.Woodcutting.trees
		subType = 'tree'
	elseif skillName == 'Fishing' then
		sourceData = SkillData.Fishing.fish
		subType = 'fish'
	elseif skillName == 'Mining' then
		sourceData = SkillData.Mining.rockData
	elseif skillName == 'Farming' then
		sourceData = SkillData.Farming.recipes
	end
	
	sourceData = GameData.getEntities(sourceData,
		function(obj)
			return obj.abyssalLevel == nil
		end
	)
	
	for i, node in ipairs(sourceData) do
		local gatherable = Items.getItemByID(node.productId)
		local processed = {
			['entityName'] = node.name,
			['entityType'] = 'gathering',
			['subType'] = subType,
			['skillLevel'] = node.level,
			['item'] = gatherable
		}
		
		-- Skill-specific overrides
		if skillName == 'Fishing' then
			processed['entityName'] = gatherable.name
		elseif skillName == 'Mining' then
			processed['subType'] = node.category
		elseif skillName == 'Farming' then
			processed['entityName'] = gatherable.name
			processed['subType'] = node.categoryID
			processed['seed'] = Items.getItemByID(node.seedCost.id)
		end
		
		processed['otherReqs'] = p._getGatherableReqs(node, skillName)
		
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addRecipes(entityList, skillName)
	-- Figure out what to look up based on the skill
	local sourceData = GameData.getEntities(SkillData[skillName].recipes,
		function(rec)
			return rec.abyssalLevel == nil
		end
	)
	local sameRecipeAndProduct = false
	if skillName == 'Herblore' then
		sameRecipeAndProduct = true
	end
	
	for i, recipe in ipairs(sourceData) do
		local product = recipe
		if not sameRecipeAndProduct then
			product = Items.getItemByID(recipe.productID)
		end
		local processed = {
			['entityName'] = product.name,
			['entityType'] = 'artisan',
			['subType'] = recipe.categoryID,
			['skillLevel'] = recipe.level,
			['item'] = product
		}
		table.insert(entityList, processed)
	end
	
	return entityList
end

function p._addSkillMastery(entityList, skillName)
	-- Add the "Skill Mastery" perk when relevant
	table.insert(entityList, {['entityName'] = 'Skill Mastery', ['entityType'] = 'special', ['subType'] = 'skillmastery', ['skillLevel'] = 99, ['otherReqs'] = {}})
	
	return entityList
end

local SOURCE_FUNCS = {
	['areas'] = p._addAreasWithSkillRequirement,
	['spells'] = p._addSpells,
	['altmagic'] = p._addAltMagic,
	['prayers'] = p._addPrayers,
	['gatheringitems'] = p._addGatherables,
	['artisanitems'] = p._addRecipes,
	['firemaking'] = p._addFiremakingActions,
	['thieving'] = p._addThievingTargets,
	['constellations'] = p._addConstellations,
	['shop'] = p._addShopPurchasesWithSkillRequirements,
	['farmingplots'] = p._addFarmingPlots,
	['townshipunlocks'] = p._addTownshipUnlocks,
	['agilityslots'] = p._addAgilityObstacleSlotsAndPillars,
	['mastery'] = p._addSkillMastery
}

function p._prepareOtherReqs(entity)
	local extraReqs = {}
	
	if entity.otherReqs ~= nil and entity.otherReqs ~= {} then
		-- Don't list a bazillion skills for the max skillcapes
		if entity.entityName == 'Maximum Skillcape' then
			return ' (requires level 99 in all skills)'
		elseif entity.entityName == 'Superior Max Skillcape' then
			return ' (requires level 120 in all skills)'
		else
			for i, req in ipairs(entity.otherReqs) do
				-- TODO: "Completion" requirement? Might not be needed
				if req.type == 'SkillLevel' then
					local skillInfo = Icons.Icon({Constants.getSkillName(req.skillID), type='skill', notext='true'}) .. ' ' .. req.level
					table.insert(extraReqs, skillInfo)
				elseif req.type == 'DungeonCompletion' then
					local dungeonName = GameData.getEntityByID('dungeons', req.dungeonID).name
					-- If you only need to clear it once (Impending Darkness),
					-- don't bother listing the count
					if req.count > 1 then
						table.insert(extraReqs, Icons.Icon({dungeonName, type='dungeon', qty=req.count, notext=true}) .. ' clears')
					else
						table.insert(extraReqs, Icons.Icon({dungeonName, type='dungeon', notext=true}) .. ' cleared')
					end
				elseif req.type == 'MonsterKilled' then
					local monsterName = GameData.getEntityByID('monsters', req.monsterID).name
					local monsterInfo = Icons.Icon({monsterName, type='monster', qty=req.count}) .. ' kills'
					table.insert(extraReqs, monsterInfo)
				elseif req.type == 'item' then
					local itemInfo = Icons.Icon({req.item.name, type='item'})
					table.insert(extraReqs, itemInfo)
				elseif req.type == 'shop' then
					table.insert(extraReqs, Shop._getPurchaseIcon({req.purchase}))
				elseif req.type == 'totalMastery' then
					table.insert(extraReqs, Num.formatnum(req.mastery) .. ' ' .. Icons.Icon({req.skill, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'}))
				elseif req.type == 'TownshipBuilding' then
					local building = Township._getBuildingByID(req.buildingID)
					table.insert(extraReqs, Icons.Icon({building.name, type='building', qty=req.count}))
				end
			end
		end
	end
	
	if not next(extraReqs) then
		return ''
	else
		return ' (requires ' .. table.concat(extraReqs, ', ') .. ')'
	end
end

function p._prepareSingleEntity(entity, skillName)
	-- Special children that need extra attention
	if entity.overrideText then
		return entity.overrideText
	end
	if entity.subType == 'skillmastery' then
		return 'Gain ' .. Icons.Icon({skillName, type='skill', notext='true'}) .. ' [[Mastery#The_Mastery_Pool|Skill Mastery]]'
	end
	if skillName == 'Farming' and entity.seed then
		return 'Plant ' .. Icons.Icon({entity.seed.name, type='item'}) .. ' to grow ' .. Icons.Icon({entity.entityName, type='item'})
	end
	if skillName == 'Township' and entity.upgradesFrom then
		return 'Upgrade ' .. Icons.Icon({entity.upgradesFrom.name, type='building'}) .. ' to ' .. Icons.Icon({entity.entityName, type='building'})
	end
	
	-- What are you doing with the thing you unlock? ("verbs")
	local verb = ''
	if VERBS_PER_SUBTYPE[entity.subType] then
		verb = VERBS_PER_SUBTYPE[entity.subType] .. ' '
	elseif VERBS_PER_SUBTYPE[entity.entityType] then
		verb = VERBS_PER_SUBTYPE[entity.entityType] .. ' '
	end
	
	-- Icon overrides
	local iconType = entity.imgType or entity.entityType
	local iconLink = entity.entityName
	local iconText = entity.entityName
	local iconImg = entity.entityName
	local noLink = ''
	if Shared.contains({'slayerArea', 'depth', 'stronghold'}, entity.entityType) then
		iconType = 'combatArea'
	end
	if entity.entityType == 'gathering' then
		if entity.subType == 'fish' then
			iconType = 'item'
		elseif Shared.contains({
			'melvorD:Ore',
			'melvorD:Essence',
			'melvorD:Gem',
			'melvorItA:AbyssalOre',
			'melvorItA:AbyssalGem',
			'melvorItA:Outcrop',
			'melvorItA:AbyssalEssence'
		}, entity.subType) then
			iconType = 'rock'
		else
			iconType = entity.subType
		end
		iconLink = entity.item.name
	end
	if entity.entityType == 'artisan' or entity.entityType == 'trader' then
		iconType = 'item'
	end
	if entity.entityType == 'shop' then
		iconType = string.lower(entity.subType)
	end
	if entity.entityType == 'plot' then
		iconType = 'skill'
		iconLink = 'Farming'
		iconImg = 'Farming'
		noLink = 'true'
	end
	if entity.entityType == 'obstacle' then
		iconType = 'agility'
	end
	if entity.entityType == 'obstacleslot' then
		iconType = 'agility'
		iconLink = 'Obstacles'
	end
	if entity.entityType == 'pillar' then
		iconType = 'agility'
		iconLink = 'Passive_Pillars'
	end
	
	-- Any other requirements or post-scripts?
	local extraReqs = p._prepareOtherReqs(entity)
	local extraPost = ''
	if entity.entityType == 'obstacle' then
		extraPost = ' in ' .. entity.subType
	end
	
	return verb .. Icons.Icon({iconLink, iconText, img=iconImg, type=iconType, nolink=noLink}) .. extraPost .. extraReqs
end

function p._prepareGearSet(gearSet, entity)
	local icons = ''
	
	for i, itemName in ipairs(gearSet.entities) do
		-- Skip trimmed armor, which always starts with an open parnthesis
		if string.sub(itemName, 1, 1) ~= '(' then
			icons = icons .. Icons.Icon({itemName, type='item', notext=true})
		end
	end
	
	-- Get extra requirements for whicheve piece we processed first, as they 
	-- should all be the same
	local extraReqs = p._prepareOtherReqs(entity)
	
	return gearSet.verb .. ' ' .. icons .. ' ' .. gearSet.name .. extraReqs
end

function p._getSkillUnlockTable(skillName, args)
	local itemsOnly = args.itemsOnly ~= nil and args.itemsOnly or false
	-- TODO: Pass these min/max level params along to filter by them
	local minLevel = args.minLevel ~= nil and args.minLevel or 0
	local maxLevel = args.maxLevel ~= nil and args.maxLevel or 999
	
	-- What do we need to check for this skill? Avoid checking everything for
	-- every skill to save time, except for a few broad things
	local entityList = {}
	entityList = p._addItemsWithSkillRequirement(entityList, skillName)
	
	if not itemsOnly then
		entityList = p._addShopPurchasesWithSkillRequirements(entityList, skillName)
		entityList = p._addTraderItemsWithSkillRequirements(entityList, skillName)
		entityList = p._addAgilityObstaclesWithSkillRequirements(entityList, skillName)
		
		-- Now loop through the stuff relevant to this skill
		for i, dataSource in ipairs(SKILL_CHECK_MAP[skillName]) do
			entityList = SOURCE_FUNCS[dataSource](entityList, skillName)
		end
	end
	
	if Shared.tableIsEmpty(entityList) then
		-- TODO: More specific empty handling
		return nil
	end
	
	-- Sort the big list of everything
	table.sort(entityList, function(a, b)
		local aSubTypeSort = a.subType
		if SUBTYPE_SORT_OVERRIDES[a.subType] then
			aSubTypeSort = SUBTYPE_SORT_OVERRIDES[a.subType]
		end
		local bSubTypeSort = b.subType
		if SUBTYPE_SORT_OVERRIDES[b.subType] then
			bSubTypeSort = SUBTYPE_SORT_OVERRIDES[b.subType]
		end
		-- Sort by level first
		if a.skillLevel ~= b.skillLevel then
			return a.skillLevel < b.skillLevel
		-- Then by type
		elseif TYPE_SORT_ORDER[a.entityType] ~= TYPE_SORT_ORDER[b.entityType] then
			return TYPE_SORT_ORDER[a.entityType] < TYPE_SORT_ORDER[b.entityType]
	    -- Then by subtype
		elseif aSubTypeSort ~= bSubTypeSort then
    		return (aSubTypeSort or '') < (bSubTypeSort or '')
		-- And finally by name
		else
			return a.entityName < b.entityName
		end
	end)
	
	-- Header and columns
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"\r\n|- class="headerRow-0"')
	table.insert(resultPart, '\r\n!' .. Icons.Icon({skillName, type='skill', notext='true'}) .. ' Level')
	table.insert(resultPart, '\r\n!Unlocks')
	
	-- Time to iterate!
	local currentLevel = 0
	local processedSets = {}
	for i, entity in ipairs(entityList) do
		-- Skip this one if it's in a gear set we've already listed
		local entityGearSet = nil
		if entity.entityType == 'item' or entity.entityType == 'shop' then
			for i, gearSet in ipairs(GEAR_SETS) do
				if Shared.contains(gearSet.entities, entity.entityName) then
					entityGearSet = gearSet
					break
				end
			end
		end
		
		if entityGearSet == nil or Shared.contains(processedSets, entityGearSet.name) == false then
			local foundLevel = entity.skillLevel
			
			-- Start a new row if the current entity's level is higher than the
			-- current row
			if foundLevel ~= currentLevel then
				currentLevel = foundLevel
				table.insert(resultPart, '\r\n|-')
				table.insert(resultPart, '\r\n|' .. foundLevel)
				table.insert(resultPart, '\r\n|')
			else
				table.insert(resultPart, '<br/>')
			end
			
			-- Figure out what we need to list - single item or set?
			local toInsert = ''
			if entityGearSet ~= nil then
				toInsert = p._prepareGearSet(entityGearSet, entity)
				table.insert(processedSets, entityGearSet.name)
			else 
				toInsert = p._prepareSingleEntity(entity, skillName)
			end
			
			-- Append entity to the column
			table.insert(resultPart, toInsert)
		end
	end
	
	table.insert(resultPart, '\r\n|}')

	return table.concat(resultPart)
end

function p.getSkillUnlockTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local skillName = args[1]
	return p._getSkillUnlockTable(skillName, args)
end

return p