Module:Skills/Agility: Difference between revisions
From Melvor Idle
(Remove unused modules/data; use ipairs instead of skpairs where possible to respect data order; getObstacleCourseTable: Display obstacles in same order as in-game) |
(Account for AP and ASC; Fix GP icon and GP text in abyssal agility table) |
||
(27 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local Num = require('Module:Number') | |||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Common = require('Module:Common') | |||
local GameData = require('Module:GameData') | |||
local SkillData = GameData.skillData | |||
local Modifiers = require('Module:Modifiers') | |||
local Skills = require('Module:Skills') | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local Num = require('Module:Number') | |||
function p.getObstacleByID(obstID) | function p.getObstacleByID(obstID) | ||
return GameData.getEntityByID(SkillData.Agility.obstacles, obstID) | |||
end | end | ||
function p.getObstacle(name) | function p.getObstacle(name) | ||
return GameData.getEntityByName(SkillData.Agility.obstacles, name) | |||
end | |||
function p.getPillar(name) | |||
local result = p.getPillars( | |||
function(x) | |||
return x.name == name | |||
end | |||
) | |||
return result[1] | |||
end | end | ||
function p.getObstacles(checkFunc) | function p.getObstacles(checkFunc) | ||
return GameData.getEntities(SkillData.Agility.obstacles, checkFunc) | |||
end | end | ||
function p.getPillars(checkFunc) | function p.getPillars(checkFunc) | ||
local | return GameData.getEntities(SkillData.Agility.pillars, checkFunc) | ||
end | |||
-- Applies the cost reduction to the provided ItemCosts table. | |||
-- CostReduction is a table { ['GP'] = num, ['SC'] = num, ['Item'] = {} } | |||
-- Reduction values range from 0 to 100. | |||
function p.applyCostReduction(itemCosts, costReduction) | |||
if costReduction == nil then return itemCosts end | |||
local gp = Num.clamp((costReduction['GP'] or 0), 0, 100) / 100 | |||
local sc = Num.clamp((costReduction['SC'] or 0), 0, 100) / 100 | |||
local ap = Num.clamp((costReduction['AP'] or 0), 0, 100) / 100 | |||
local asc = Num.clamp((costReduction['ASC'] or 0), 0, 100) / 100 | |||
local item = Num.clamp((costReduction['Item'] or 0), 0, 100) / 100 | |||
if gp > 0 and itemCosts['GP'] then | |||
itemCosts['GP'] = math.ceil(itemCosts['GP'] * (1 - gp)) | |||
end | |||
if sc > 0 and itemCosts['SC'] then | |||
itemCosts['SC'] = math.ceil(itemCosts['SC'] * (1 - sc)) | |||
end | |||
if ap > 0 and itemCosts['AP'] then | |||
itemCosts['AP'] = math.ceil(itemCosts['AP'] * (1 - ap)) | |||
end | |||
if asc > 0 and itemCosts['ASC'] then | |||
itemCosts['ASC'] = math.ceil(itemCosts['ASC'] * (1 - asc)) | |||
end | |||
local items = itemCosts['Items'] | |||
if item > 0 then | |||
for k, v in pairs(items) do | |||
items[k] = math.ceil(items[k] * (1 - item)) | |||
end | |||
end | |||
itemCosts['Items'] = items | |||
return itemCosts | |||
end | |||
--- Gets all required levels to build the given obstacle. | |||
function p.getObstacleRequirements(obstacle) | |||
local realmRequirements = p.getObstacleRequirementsRealm(obstacle) | |||
local levelRequirements = {} | |||
for skillName, skillReq in pairs(realmRequirements) do | |||
levelRequirements[skillName] = skillReq.level | |||
end | |||
return levelRequirements | |||
end | |||
--- Gets all required levels to build the given obstacle. | |||
function p.getObstacleRequirementsRealm(obstacle) | |||
local levelRequirements = {} | |||
-- Add agility level requirement. | |||
local agilityLevelRequirement, isAbyssal = Skills.getRecipeLevelRealm('Agility', obstacle) | |||
levelRequirements['Agility'] = { | |||
level = agilityLevelRequirement, | |||
isAbyssal = isAbyssal | |||
} | |||
-- Add other level requirements. | |||
if type(obstacle.skillRequirements) == 'table' then | |||
for i, skillReq in ipairs(obstacle.skillRequirements) do | |||
local skillName = Constants.getSkillName(skillReq.skillID) | |||
if skillName ~= nil then | |||
levelRequirements[skillName] = { | |||
level = skillReq.level, | |||
isAbyssal = skillReq.type == 'AbyssalLevel' | |||
} | |||
end | |||
end | |||
end | |||
return levelRequirements | |||
end | |||
-- Gets all items and gp/sc costs required to build the given obstacle. | |||
function p.getObstacleCosts(obstacle) | |||
local costs = {} | |||
local items = {} | |||
for j, currCost in ipairs(obstacle.currencyCosts) do | |||
local shortID = nil | |||
if currCost.id == 'melvorD:GP' then | |||
shortID = 'GP' | |||
elseif currCost.id == 'melvorD:SlayerCoins' then | |||
shortID = 'SC' | |||
elseif currCost.id == 'melvorItA:AbyssalPieces' then | |||
shortID = 'AP' | |||
elseif currCost.id == 'melvorItA:AbyssalSlayerCoins' then | |||
shortID = 'ASC' | |||
end | |||
if shortID ~= nil then | |||
costs[shortID] = currCost.quantity | |||
end | |||
end | |||
for j, itemCost in ipairs(obstacle.itemCosts) do | |||
local item = Items.getItemByID(itemCost.id) | |||
items[item.name] = itemCost.quantity | |||
end | |||
costs['Items'] = items | |||
return costs | |||
end | |||
function p._getObstacleRequirements(obstacle) | |||
local resultPart = {} | |||
local requirements = p.getObstacleRequirementsRealm(obstacle) | |||
for skill, req in pairs(requirements) do | |||
local icon | |||
if req.isAbyssal then | |||
icon = Icons._SkillReq(skill, req.level, nil, 'melvorItA:Abyssal') | |||
else | |||
icon = Icons._SkillReq(skill, req.level) | |||
end | |||
table.insert(resultPart, icon) | |||
end | |||
return table.concat(resultPart, '<br/>') | |||
end | end | ||
function p.getObstacleCourseTable(frame) | function p.getObstacleCourseTable(frame) | ||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realmID = nil | |||
if realmName ~= nil and realmName ~= '' then | |||
local realm = GameData.getEntityByName('realms', realmName) | |||
if realm == nil then | |||
return Shared.printError('No such realm: ' .. realmName) | |||
end | |||
realmID = realm.id | |||
end | |||
local currName = 'GP' | |||
if realmID == 'melvorItA:Abyssal' then | |||
currName = 'AP' | |||
end | |||
local result = '' | |||
result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!Slot!!Name!!XP!!'..currName..'!!Time!!XP/s!!'..currName..'/s!!Bonuses!!Requirements!!Cost' | |||
local catLog = {} | |||
local obstacles = p.getObstacles( | |||
function(obst) | |||
return realmID == nil or Skills.getRecipeRealm(obst) == realmID | |||
end | |||
) | |||
table.sort(obstacles, function(a, b) return a.category < b.category end) | |||
local catCounts = {} | |||
for i, obst in ipairs(obstacles) do | |||
if catCounts[obst.category] == nil then | |||
catCounts[obst.category] = 1 | |||
else | |||
catCounts[obst.category] = catCounts[obst.category] + 1 | |||
end | |||
end | |||
for i, obst in ipairs(obstacles) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|' | |||
if catLog[obst.category] == nil then | |||
local rowspan = catCounts[obst.category] | |||
result = result..'rowspan="'..rowspan..'" style="border:1px solid black"|'..(obst.category + 1)..'||' | |||
catLog[obst.category] = true | |||
end | |||
result = result .. 'id="'..string.gsub(obst.name,' ', '')..'"|'.. Icons.getExpansionIcon(obst.id) .. obst.name | |||
--After the name & category, doing XP, GP, Time, and rates | |||
local XP = obst.baseExperience | |||
if XP == 0 then | |||
XP = obst.baseAbyssalExperience | |||
end | |||
local GP = obst.gpReward | |||
local Time = obst.baseInterval / 1000 | |||
local costString = Common.getCostString({ | |||
items = obst.itemCosts, | |||
currencies = obst.currencyCosts | |||
}) | |||
local rewardString = Common.getCostString({ | |||
items = obst.itemRewards, | |||
currencies = obst.currencyRewards | |||
}) | |||
local costVal = (obst.currencyCosts ~= nil and obst.currencyCosts[1].quantity or 0) | |||
local rewardVal = (obst.currencyRewards ~= nil and obst.currencyRewards[1].quantity or 0) | |||
result = result..'||'..XP..'||data-sort-value="'..rewardVal..'"|'..rewardString | |||
result = result..'||data-sort-value="'..Time..'"|'..Shared.timeString(Time, true) | |||
-- Readded XP/Time and GP/Time (previously commented out) | |||
result = result..'||'..Num.round(XP / Time, 2, 2) | |||
result = result..'||data-sort-value="'..rewardVal/Time..'"|'.. Icons._Currency(currName, Num.round(rewardVal/Time, 2, 2)) | |||
local bonuses = {} | |||
--After that, adding the bonuses | |||
local modText = Modifiers.getModifiersText(obst.modifiers, true, false, 10) | |||
if modText == '' then | |||
modText = '<span style="text-negative">None :(</span>' | |||
end | |||
result = result..'||'..modText | |||
--Grabbing requirements to create | |||
result = result..'|| ' .. p._getObstacleRequirements(obst) | |||
--Finally, the cost | |||
result = result..'|| data-sort-value="'..costVal..'"|'..costString | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getPassivePillarTable(frame) | function p.getPassivePillarTable(frame) | ||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realmID = nil | |||
if realmName ~= nil and realmName ~= '' then | |||
local realm = GameData.getEntityByName('realms', realmName) | |||
if realm == nil then | |||
return Shared.printError('No such realm: ' .. realmName) | |||
end | |||
realmID = realm.id | |||
end | |||
local result = '' | |||
result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!Name!!Bonuses!!Cost' | |||
local pillars = p.getPillars( | |||
function(pill) | |||
return realmID == nil or Skills.getRecipeRealm(pill) == realmID | |||
end | |||
) | |||
for i, pill in ipairs(pillars) do | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|' .. 'id="'..string.gsub(pill.name,' ', '')..'"|'..Icons.getExpansionIcon(pill.id) .. pill.name | |||
--After that, adding the bonuses | |||
local modText = Modifiers.getModifiersText(pill.modifiers, true, false, 10) | |||
if modText == '' then | |||
modText = '<span style="text-negative">None :(</span>' | |||
end | |||
result = result..'||'..modText | |||
--Finally, the cost | |||
local costString = Common.getCostString({ | |||
items = pill.itemCosts, | |||
currencies = pill.currencyCosts | |||
}) | |||
local costVal = (pill.currencyCosts ~= nil and pill.currencyCosts[1].quantity or 0) | |||
result = result..'|| data-sort-value="'..costVal..'"|'.. costString | |||
end | |||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p.getObstaclesForItem(itemID) | function p.getObstaclesForItem(itemID) | ||
local costFunc = | |||
function(obst) | |||
for i, itemCost in ipairs(obst.itemCosts) do | |||
if itemCost.id == itemID then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
local pillars = p.getPillars(costFunc) | |||
local result = p.getObstacles(costFunc) | |||
if result == nil or Shared.tableIsEmpty(result) then | |||
result = pillars | |||
else | |||
for i, pillar in ipairs(pillars) do | |||
table.insert(result, pillar) | |||
end | |||
end | |||
return result | |||
end | end | ||
return p | return p |
Latest revision as of 17:58, 22 July 2024
Documentation for this module may be created at Module:Skills/Agility/doc
local p = {}
local Constants = require('Module:Constants')
local Num = require('Module:Number')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Skills = require('Module:Skills')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Num = require('Module:Number')
function p.getObstacleByID(obstID)
return GameData.getEntityByID(SkillData.Agility.obstacles, obstID)
end
function p.getObstacle(name)
return GameData.getEntityByName(SkillData.Agility.obstacles, name)
end
function p.getPillar(name)
local result = p.getPillars(
function(x)
return x.name == name
end
)
return result[1]
end
function p.getObstacles(checkFunc)
return GameData.getEntities(SkillData.Agility.obstacles, checkFunc)
end
function p.getPillars(checkFunc)
return GameData.getEntities(SkillData.Agility.pillars, checkFunc)
end
-- Applies the cost reduction to the provided ItemCosts table.
-- CostReduction is a table { ['GP'] = num, ['SC'] = num, ['Item'] = {} }
-- Reduction values range from 0 to 100.
function p.applyCostReduction(itemCosts, costReduction)
if costReduction == nil then return itemCosts end
local gp = Num.clamp((costReduction['GP'] or 0), 0, 100) / 100
local sc = Num.clamp((costReduction['SC'] or 0), 0, 100) / 100
local ap = Num.clamp((costReduction['AP'] or 0), 0, 100) / 100
local asc = Num.clamp((costReduction['ASC'] or 0), 0, 100) / 100
local item = Num.clamp((costReduction['Item'] or 0), 0, 100) / 100
if gp > 0 and itemCosts['GP'] then
itemCosts['GP'] = math.ceil(itemCosts['GP'] * (1 - gp))
end
if sc > 0 and itemCosts['SC'] then
itemCosts['SC'] = math.ceil(itemCosts['SC'] * (1 - sc))
end
if ap > 0 and itemCosts['AP'] then
itemCosts['AP'] = math.ceil(itemCosts['AP'] * (1 - ap))
end
if asc > 0 and itemCosts['ASC'] then
itemCosts['ASC'] = math.ceil(itemCosts['ASC'] * (1 - asc))
end
local items = itemCosts['Items']
if item > 0 then
for k, v in pairs(items) do
items[k] = math.ceil(items[k] * (1 - item))
end
end
itemCosts['Items'] = items
return itemCosts
end
--- Gets all required levels to build the given obstacle.
function p.getObstacleRequirements(obstacle)
local realmRequirements = p.getObstacleRequirementsRealm(obstacle)
local levelRequirements = {}
for skillName, skillReq in pairs(realmRequirements) do
levelRequirements[skillName] = skillReq.level
end
return levelRequirements
end
--- Gets all required levels to build the given obstacle.
function p.getObstacleRequirementsRealm(obstacle)
local levelRequirements = {}
-- Add agility level requirement.
local agilityLevelRequirement, isAbyssal = Skills.getRecipeLevelRealm('Agility', obstacle)
levelRequirements['Agility'] = {
level = agilityLevelRequirement,
isAbyssal = isAbyssal
}
-- Add other level requirements.
if type(obstacle.skillRequirements) == 'table' then
for i, skillReq in ipairs(obstacle.skillRequirements) do
local skillName = Constants.getSkillName(skillReq.skillID)
if skillName ~= nil then
levelRequirements[skillName] = {
level = skillReq.level,
isAbyssal = skillReq.type == 'AbyssalLevel'
}
end
end
end
return levelRequirements
end
-- Gets all items and gp/sc costs required to build the given obstacle.
function p.getObstacleCosts(obstacle)
local costs = {}
local items = {}
for j, currCost in ipairs(obstacle.currencyCosts) do
local shortID = nil
if currCost.id == 'melvorD:GP' then
shortID = 'GP'
elseif currCost.id == 'melvorD:SlayerCoins' then
shortID = 'SC'
elseif currCost.id == 'melvorItA:AbyssalPieces' then
shortID = 'AP'
elseif currCost.id == 'melvorItA:AbyssalSlayerCoins' then
shortID = 'ASC'
end
if shortID ~= nil then
costs[shortID] = currCost.quantity
end
end
for j, itemCost in ipairs(obstacle.itemCosts) do
local item = Items.getItemByID(itemCost.id)
items[item.name] = itemCost.quantity
end
costs['Items'] = items
return costs
end
function p._getObstacleRequirements(obstacle)
local resultPart = {}
local requirements = p.getObstacleRequirementsRealm(obstacle)
for skill, req in pairs(requirements) do
local icon
if req.isAbyssal then
icon = Icons._SkillReq(skill, req.level, nil, 'melvorItA:Abyssal')
else
icon = Icons._SkillReq(skill, req.level)
end
table.insert(resultPart, icon)
end
return table.concat(resultPart, '<br/>')
end
function p.getObstacleCourseTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realmID = nil
if realmName ~= nil and realmName ~= '' then
local realm = GameData.getEntityByName('realms', realmName)
if realm == nil then
return Shared.printError('No such realm: ' .. realmName)
end
realmID = realm.id
end
local currName = 'GP'
if realmID == 'melvorItA:Abyssal' then
currName = 'AP'
end
local result = ''
result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Slot!!Name!!XP!!'..currName..'!!Time!!XP/s!!'..currName..'/s!!Bonuses!!Requirements!!Cost'
local catLog = {}
local obstacles = p.getObstacles(
function(obst)
return realmID == nil or Skills.getRecipeRealm(obst) == realmID
end
)
table.sort(obstacles, function(a, b) return a.category < b.category end)
local catCounts = {}
for i, obst in ipairs(obstacles) do
if catCounts[obst.category] == nil then
catCounts[obst.category] = 1
else
catCounts[obst.category] = catCounts[obst.category] + 1
end
end
for i, obst in ipairs(obstacles) do
result = result..'\r\n|-'
result = result..'\r\n|'
if catLog[obst.category] == nil then
local rowspan = catCounts[obst.category]
result = result..'rowspan="'..rowspan..'" style="border:1px solid black"|'..(obst.category + 1)..'||'
catLog[obst.category] = true
end
result = result .. 'id="'..string.gsub(obst.name,' ', '')..'"|'.. Icons.getExpansionIcon(obst.id) .. obst.name
--After the name & category, doing XP, GP, Time, and rates
local XP = obst.baseExperience
if XP == 0 then
XP = obst.baseAbyssalExperience
end
local GP = obst.gpReward
local Time = obst.baseInterval / 1000
local costString = Common.getCostString({
items = obst.itemCosts,
currencies = obst.currencyCosts
})
local rewardString = Common.getCostString({
items = obst.itemRewards,
currencies = obst.currencyRewards
})
local costVal = (obst.currencyCosts ~= nil and obst.currencyCosts[1].quantity or 0)
local rewardVal = (obst.currencyRewards ~= nil and obst.currencyRewards[1].quantity or 0)
result = result..'||'..XP..'||data-sort-value="'..rewardVal..'"|'..rewardString
result = result..'||data-sort-value="'..Time..'"|'..Shared.timeString(Time, true)
-- Readded XP/Time and GP/Time (previously commented out)
result = result..'||'..Num.round(XP / Time, 2, 2)
result = result..'||data-sort-value="'..rewardVal/Time..'"|'.. Icons._Currency(currName, Num.round(rewardVal/Time, 2, 2))
local bonuses = {}
--After that, adding the bonuses
local modText = Modifiers.getModifiersText(obst.modifiers, true, false, 10)
if modText == '' then
modText = '<span style="text-negative">None :(</span>'
end
result = result..'||'..modText
--Grabbing requirements to create
result = result..'|| ' .. p._getObstacleRequirements(obst)
--Finally, the cost
result = result..'|| data-sort-value="'..costVal..'"|'..costString
end
result = result..'\r\n|}'
return result
end
function p.getPassivePillarTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realmID = nil
if realmName ~= nil and realmName ~= '' then
local realm = GameData.getEntityByName('realms', realmName)
if realm == nil then
return Shared.printError('No such realm: ' .. realmName)
end
realmID = realm.id
end
local result = ''
result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Name!!Bonuses!!Cost'
local pillars = p.getPillars(
function(pill)
return realmID == nil or Skills.getRecipeRealm(pill) == realmID
end
)
for i, pill in ipairs(pillars) do
result = result..'\r\n|-'
result = result..'\r\n|' .. 'id="'..string.gsub(pill.name,' ', '')..'"|'..Icons.getExpansionIcon(pill.id) .. pill.name
--After that, adding the bonuses
local modText = Modifiers.getModifiersText(pill.modifiers, true, false, 10)
if modText == '' then
modText = '<span style="text-negative">None :(</span>'
end
result = result..'||'..modText
--Finally, the cost
local costString = Common.getCostString({
items = pill.itemCosts,
currencies = pill.currencyCosts
})
local costVal = (pill.currencyCosts ~= nil and pill.currencyCosts[1].quantity or 0)
result = result..'|| data-sort-value="'..costVal..'"|'.. costString
end
result = result..'\r\n|}'
return result
end
function p.getObstaclesForItem(itemID)
local costFunc =
function(obst)
for i, itemCost in ipairs(obst.itemCosts) do
if itemCost.id == itemID then
return true
end
end
return false
end
local pillars = p.getPillars(costFunc)
local result = p.getObstacles(costFunc)
if result == nil or Shared.tableIsEmpty(result) then
result = pillars
else
for i, pillar in ipairs(pillars) do
table.insert(result, pillar)
end
end
return result
end
return p