2,869
edits
No edit summary |
No edit summary |
||
(20 intermediate revisions by 2 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 Township = require('Module:Township') | |||
local GameData = require('Module:GameData') | local GameData = require('Module:GameData') | ||
local SkillData = GameData.skillData | local SkillData = GameData.skillData | ||
local Num = require('Module:Number') | |||
-- This excludes certain checks pertinent to all skills, like equipment | |||
local SKILL_CHECK_MAP = { | local SKILL_CHECK_MAP = { | ||
['Attack'] = {}, | ['Attack'] = {}, | ||
Line 20: | Line 26: | ||
['Hitpoints'] = {}, | ['Hitpoints'] = {}, | ||
['Ranged'] = {}, | ['Ranged'] = {}, | ||
['Magic'] = {'spells'}, | ['Magic'] = {'spells', 'altmagic'}, | ||
['Prayer'] = {'prayers'}, | ['Prayer'] = {'prayers'}, | ||
['Slayer'] = {'areas'}, | ['Slayer'] = {'areas'}, | ||
['Farming'] = {}, | ['Farming'] = {'mastery', 'gatheringitems', 'farmingplots'}, | ||
['Township'] = {}, | ['Township'] = {'townshipunlocks'}, | ||
['Woodcutting'] = {' | ['Woodcutting'] = {'mastery', 'gatheringitems'}, | ||
['Fishing'] = {' | ['Fishing'] = {'mastery', 'gatheringitems'}, | ||
['Firemaking'] = {}, | ['Firemaking'] = {'mastery', 'firemaking'}, | ||
['Cooking'] = {}, | ['Cooking'] = {'mastery', 'artisanitems'}, | ||
['Mining'] = {' | ['Mining'] = {'mastery', 'gatheringitems'}, | ||
['Smithing'] = {}, | ['Smithing'] = {'mastery', 'artisanitems'}, | ||
['Thieving'] = {}, | ['Thieving'] = {'mastery', 'thieving'}, | ||
['Fletching'] = {}, | ['Fletching'] = {'mastery', 'artisanitems'}, | ||
['Crafting'] = {}, | ['Crafting'] = {'mastery', 'artisanitems'}, | ||
['Runecrafting'] = {}, | ['Runecrafting'] = {'mastery', 'artisanitems'}, | ||
['Herblore'] = {}, | ['Herblore'] = {'mastery', 'artisanitems'}, | ||
['Agility'] = {}, | ['Agility'] = {'mastery', 'agilityslots'}, | ||
['Summoning'] = {}, | ['Summoning'] = {'mastery', 'artisanitems'}, | ||
['Astrology'] = {}, | ['Astrology'] = {'mastery', 'constellations'}, | ||
['Alt. Magic'] = {}, | ['Alt. Magic'] = {'altmagic'}, | ||
} | } | ||
local TYPE_SORT_ORDER = { | local TYPE_SORT_ORDER = { | ||
['spell'] = | ['special'] = 1, | ||
['prayer'] = | ['biome'] = 2, | ||
['gathering'] = | ['building'] = 3, | ||
['item'] = | ['plot'] = 4, | ||
['combatArea'] = | ['obstacleslot'] = 5, | ||
['slayerArea'] = | ['pillar'] = 6, | ||
['dungeon'] = | ['spell'] = 7, | ||
['prayer'] = 8, | |||
['thieving'] = 9, | |||
['constellation'] = 10, | |||
['gathering'] = 11, | |||
['artisan'] = 12, | |||
['item'] = 13, | |||
['combatArea'] = 14, | |||
['slayerArea'] = 15, | |||
['dungeon'] = 16, | |||
['shop'] = 17, | |||
['trader'] = 18, | |||
['obstacle'] = 19 | |||
} | } | ||
local VERBS_PER_SUBTYPE = { | local VERBS_PER_SUBTYPE = { | ||
Line 56: | Line 74: | ||
['Magic Book'] = 'Wield', | ['Magic Book'] = 'Wield', | ||
['Ranged Weapon'] = 'Wield', | ['Ranged Weapon'] = 'Wield', | ||
['Ammo'] = ' | ['Ammo'] = 'Wield', | ||
['Equipment'] = 'Wear', | ['Equipment'] = 'Wear', | ||
['Armour'] = 'Wear', | ['Armour'] = 'Wear', | ||
Line 73: | Line 91: | ||
['ancient'] = 'Cast', | ['ancient'] = 'Cast', | ||
['archaic'] = 'Cast', | ['archaic'] = 'Cast', | ||
['prayer'] = 'Cast', | ['prayer'] = 'Use', | ||
['altMagic'] = 'Cast', | |||
['tree'] = 'Cut', | ['tree'] = 'Cut', | ||
['fish'] = 'Catch', | ['fish'] = 'Catch', | ||
['Essence'] = 'Mine', | ['Essence'] = 'Mine', | ||
['Ore'] = 'Mine', | ['Ore'] = 'Mine', | ||
['Gem'] = '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 = { | local SUBTYPE_OVERRIDES = { | ||
Line 95: | Line 132: | ||
['Spectral Ice Sword'] = 'Weapon', | ['Spectral Ice Sword'] = 'Weapon', | ||
['Torrential Blast Crossbow'] = 'Ranged Weapon' | ['Torrential Blast Crossbow'] = 'Ranged Weapon' | ||
} | |||
local SUBTYPE_SORT_OVERRIDES = { | |||
['melvorD:Bars'] = '1', | |||
['melvorF:StandardRunes'] = '1', | |||
['log'] = '1' | |||
} | } | ||
local GEAR_SETS = { | local GEAR_SETS = { | ||
Line 612: | Line 654: | ||
['level'] = 40, | ['level'] = 40, | ||
['entities'] = {'Rune 2H Sword', 'Rune Battleaxe', 'Rune Dagger', 'Rune Scimitar', 'Rune Sword'} | ['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)'} | |||
}, | }, | ||
{ | { | ||
Line 780: | Line 828: | ||
['level'] = 40, | ['level'] = 40, | ||
['entities'] = {'Yew Longbow', 'Yew Shortbow'} | ['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'} | |||
} | } | ||
} | } | ||
Line 789: | Line 861: | ||
end | end | ||
return entityType | 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 | |||
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 {} | |||
table.insert(entityList, processed) | |||
end | |||
return entityList | |||
end | end | ||
Line 802: | Line 947: | ||
end) | end) | ||
for i, item in ipairs(itemList) do | for i, item in ipairs(itemList) do | ||
local processed = {} | local processed = {} | ||
Line 810: | Line 954: | ||
processed.subType = p._getEntityTrueSubtype(item.type, item.name) | processed.subType = p._getEntityTrueSubtype(item.type, 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 830: | Line 975: | ||
for i, area in ipairs(areaList) do | for i, area in ipairs(areaList) do | ||
local processed = {} | local processed = {} | ||
processed.entityName = area.name | processed.entityName = area.name | ||
processed.entityType = area.type | processed.entityType = area.type | ||
Line 839: | Line 983: | ||
end | end | ||
end | end | ||
processed.otherReqs = p._getOtherSkillReqs(area.entryRequirements, skillName) | |||
table.insert(entityList, processed) | table.insert(entityList, processed) | ||
end | end | ||
Line 845: | Line 990: | ||
end | end | ||
function p. | 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) | |||
for i, obstacle in ipairs(SkillData.Agility.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) | |||
-- Alt. Magic is in a separate function | |||
local SPELL_TYPES = {'standardSpells', 'auroraSpells', 'curseSpells', 'ancientSpells', 'archaicSpells'} | local SPELL_TYPES = {'standardSpells', 'auroraSpells', 'curseSpells', 'ancientSpells', 'archaicSpells'} | ||
-- Iterate through each spell type and each spell within that type | -- Iterate through each spell type and each spell within that type | ||
for i, spellType in ipairs(SPELL_TYPES) do | for i, spellType in ipairs(SPELL_TYPES) do | ||
entityList = p._addEntities({entityList, GameData.rawData[spellType], type='spell', subTypeParam='spellBook', otherReqsFunc=p._getSpellReqs}) | |||
end | |||
return entityList | |||
end | |||
function p._addAltMagic(entityList, skillName) | |||
entityList = p._addEntities({entityList, GameData.getSkillData('melvorD:Magic')['altSpells'], type='spell', subType='altMagic', otherReqsFunc=p._getSpellReqs}) | |||
return entityList | |||
end | |||
function p._addPrayers(entityList, skillName) | |||
entityList = p._addEntities({entityList, GameData.rawData.prayers, type='prayer', subType='prayer'}) | |||
return entityList | |||
end | |||
function p._addFiremakingActions(entityList, skillName) | |||
for i, fireLog in ipairs(SkillData.Firemaking.logs) 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) | |||
entityList = p._addEntities({entityList, SkillData.Thieving.npcs, type='thieving', subType='npc'}) | |||
return entityList | |||
end | |||
function p._addConstellations(entityList, skillName) | |||
entityList = p._addEntities({entityList, SkillData.Astrology.recipes, 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 | end | ||
local processed = {} | |||
processed.entityName = plotName | |||
processed.entityType = 'plot' | |||
processed.subType = plot.categoryID | |||
processed.skillLevel = plot.level | |||
processed.otherReqs = {} | |||
table.insert(entityList, processed) | |||
end | end | ||
Line 864: | Line 1,152: | ||
end | end | ||
function p. | function p._addTownshipUnlocks(entityList, skillName) | ||
for i, | -- 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 = {} | local processed = {} | ||
processed.entityName = biome.name | |||
processed.entityName = | processed.entityType = 'biome' | ||
processed.entityType = ' | processed.subType = 'biome' | ||
processed.subType = ' | processed.skillLevel = tierReqs.level | ||
processed.skillLevel = | processed.otherReqs = {['population'] = tierReqs.population} | ||
table.insert(entityList, processed) | table.insert(entityList, processed) | ||
end | 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 | return entityList | ||
end | end | ||
function p. | 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 | -- Figure out what to look up based on the skill | ||
local sourceData = {} | local sourceData = {} | ||
local subType = '' | local subType = '' | ||
if skillName == 'Woodcutting' then | if skillName == 'Woodcutting' then | ||
Line 891: | Line 1,221: | ||
elseif skillName == 'Mining' then | elseif skillName == 'Mining' then | ||
sourceData = SkillData.Mining.rockData | sourceData = SkillData.Mining.rockData | ||
elseif skillName == 'Farming' then | |||
sourceData = SkillData.Farming.recipes | |||
end | end | ||
for i, node in ipairs(sourceData) do | for i, node in ipairs(sourceData) do | ||
local gatherable = Items.getItemByID(node.productId) | local gatherable = Items.getItemByID(node.productId) | ||
local | local processed = { | ||
[' | ['entityName'] = node.name, | ||
['entityType'] = 'gathering', | |||
['subType'] = subType, | |||
['skillLevel'] = node.level, | |||
['item'] = gatherable | ['item'] = gatherable | ||
} | } | ||
-- Skill-specific overrides | |||
if skillName == 'Fishing' then | 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 | end | ||
table.insert( | |||
processed['otherReqs'] = p._getGatherableReqs(node, skillName) | |||
table.insert(entityList, processed) | |||
end | end | ||
return entityList | |||
end | |||
function p._addRecipes(entityList, skillName) | |||
-- Figure out what to look up based on the skill | |||
local sourceData = SkillData[skillName].recipes | |||
local sameRecipeAndProduct = false | |||
if skillName == 'Herblore' then | |||
sameRecipeAndProduct = true | |||
if | end | ||
for i, recipe in ipairs(sourceData) do | |||
local product = recipe | |||
if not sameRecipeAndProduct then | |||
product = Items.getItemByID(recipe.productID) | |||
end | end | ||
processed.skillLevel = | local processed = { | ||
['entityName'] = product.name, | |||
['entityType'] = 'artisan', | |||
['subType'] = recipe.categoryID, | |||
['skillLevel'] = recipe.level, | |||
['item'] = product | |||
} | |||
table.insert(entityList, processed) | table.insert(entityList, processed) | ||
end | 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 | return entityList | ||
Line 927: | Line 1,289: | ||
local SOURCE_FUNCS = { | local SOURCE_FUNCS = { | ||
['areas'] = p._addAreasWithSkillRequirement, | ['areas'] = p._addAreasWithSkillRequirement, | ||
['spells'] = p. | ['spells'] = p._addSpells, | ||
['prayers'] = p. | ['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._prepareSingleEntity(entity) | 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") | -- What are you doing with the thing you unlock? ("verbs") | ||
local verb = '' | local verb = '' | ||
if VERBS_PER_SUBTYPE[entity.subType] then | if VERBS_PER_SUBTYPE[entity.subType] then | ||
verb = VERBS_PER_SUBTYPE[entity.subType] .. ' ' | verb = VERBS_PER_SUBTYPE[entity.subType] .. ' ' | ||
elseif VERBS_PER_SUBTYPE[entity.entityType] then | |||
verb = VERBS_PER_SUBTYPE[entity.entityType] .. ' ' | |||
end | end | ||
-- Icon overrides | -- Icon overrides | ||
local iconType = entity.entityType | local iconType = entity.entityType | ||
local | local iconLink = entity.entityName | ||
local iconText = entity.entityName | |||
local iconImg = entity.entityName | |||
local noLink = '' | |||
if entity.entityType == 'slayerArea' then | if entity.entityType == 'slayerArea' then | ||
iconType = 'combatArea' | iconType = 'combatArea' | ||
Line 951: | Line 1,392: | ||
if entity.subType == 'fish' then | if entity.subType == 'fish' then | ||
iconType = 'item' | 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 | else | ||
iconType = entity.subType | iconType = entity.subType | ||
end | end | ||
name = entity.item. | 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 | end | ||
return verb .. Icons.Icon({ | return verb .. Icons.Icon({iconLink, iconText, img=iconImg, type=iconType, nolink=noLink}) .. extraPost .. extraReqs | ||
end | end | ||
function p._prepareGearSet(gearSet) | function p._prepareGearSet(gearSet, entity) | ||
local icons = '' | local icons = '' | ||
for i, | |||
for i, itemName in ipairs(gearSet.entities) do | |||
-- Skip trimmed armor, which always starts with an open parnthesis | -- Skip trimmed armor, which always starts with an open parnthesis | ||
if string.sub( | if string.sub(itemName, 1, 1) ~= '(' then | ||
icons = icons .. Icons.Icon({ | icons = icons .. Icons.Icon({itemName, type='item', notext=true}) | ||
end | end | ||
end | end | ||
return gearSet.verb .. ' ' .. icons .. ' ' .. gearSet.name | -- 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 | end | ||
function p._getSkillUnlockTable(skillName) | 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 | -- What do we need to check for this skill? Avoid checking everything for | ||
-- every skill to save time | -- every skill to save time, except for a few broad things | ||
local entityList = {} | local entityList = {} | ||
entityList = p._addItemsWithSkillRequirement(entityList, skillName) | entityList = p._addItemsWithSkillRequirement(entityList, skillName) | ||
for i, dataSource in ipairs(SKILL_CHECK_MAP[skillName]) do | |||
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 | end | ||
Line 988: | Line 1,486: | ||
-- 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 996: | Line 1,502: | ||
return TYPE_SORT_ORDER[a.entityType] < TYPE_SORT_ORDER[b.entityType] | return TYPE_SORT_ORDER[a.entityType] < TYPE_SORT_ORDER[b.entityType] | ||
-- Then by subtype | -- Then by subtype | ||
elseif | elseif aSubTypeSort ~= bSubTypeSort then | ||
return | return aSubTypeSort < bSubTypeSort | ||
-- And finally by name | -- And finally by name | ||
else | else | ||
Line 1,016: | Line 1,522: | ||
-- Skip this one if it's in a gear set we've already listed | -- Skip this one if it's in a gear set we've already listed | ||
local entityGearSet = nil | local entityGearSet = nil | ||
if entity.entityType == 'item' then | if entity.entityType == 'item' or entity.entityType == 'shop' then | ||
for i, gearSet in ipairs(GEAR_SETS) do | for i, gearSet in ipairs(GEAR_SETS) do | ||
if Shared.contains(gearSet.entities, entity.entityName) then | if Shared.contains(gearSet.entities, entity.entityName) then | ||
Line 1,042: | Line 1,548: | ||
local toInsert = '' | local toInsert = '' | ||
if entityGearSet ~= nil then | if entityGearSet ~= nil then | ||
toInsert = p._prepareGearSet(entityGearSet) | toInsert = p._prepareGearSet(entityGearSet, entity) | ||
table.insert(processedSets, entityGearSet.name) | table.insert(processedSets, entityGearSet.name) | ||
else | else | ||
toInsert = p._prepareSingleEntity(entity) | toInsert = p._prepareSingleEntity(entity, skillName) | ||
end | end | ||
Line 1,059: | Line 1,565: | ||
function p.getSkillUnlockTable(frame) | function p.getSkillUnlockTable(frame) | ||
local | 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 |
edits