17,101
edits
(Adjusted the appearance of the Lesser Relics table) |
(Update for v1.3) |
||
Line 18: | Line 18: | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local Common = require('Module:Common') | |||
local GameData = require('Module:GameData') | local GameData = require('Module:GameData') | ||
local SkillData = GameData.skillData | local SkillData = GameData.skillData | ||
local Modifiers = require('Module:Modifiers') | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
Line 44: | Line 46: | ||
["Runecrafting"] = 'recipes', | ["Runecrafting"] = 'recipes', | ||
["Herblore"] = 'recipes', | ["Herblore"] = 'recipes', | ||
["Astrology"] = 'recipes' | ["Astrology"] = 'recipes', | ||
["Harvesting"] = 'veinData' | |||
} | } | ||
return recipeIDs[localSkillID] | return recipeIDs[localSkillID] | ||
Line 50: | Line 53: | ||
-- Given a skill ID & recipe, returns the skill level requirement for | -- Given a skill ID & recipe, returns the skill level requirement for | ||
-- that recipe. If the level could not be determined, then the return | -- that recipe and a boolean value indicating whether the level if abyssal or not. | ||
-- If the level could not be determined, then the return value is nil, nil | |||
function p. | function p.getRecipeLevelRealm(skillID, recipe) | ||
local level, isAbyssal = nil, nil | |||
-- Convert skillID to local ID if not already | -- Convert skillID to local ID if not already | ||
local ns, localSkillID = GameData.getLocalID(skillID) | local ns, localSkillID = GameData.getLocalID(skillID) | ||
local realmID = p.getRecipeRealm(recipe) | |||
if localSkillID == 'Agility' then | if localSkillID == 'Agility' then | ||
local course = GameData.getEntityByProperty(SkillData.Agility.courses, 'realm', realmID) | |||
isAbyssal = (realmID == 'melvorItA:Abyssal') | |||
-- For Agility, level is derived from obstacle category | -- For Agility, level is derived from obstacle category | ||
if recipe.category ~= nil then | if recipe.category ~= nil then | ||
-- Obstacle | -- Obstacle | ||
level = course.obstacleSlots[recipe.category + 1].level | |||
elseif recipe.slot ~= nil then | |||
-- Pillar | -- Pillar | ||
level = course.pillarSlots[recipe.slot + 1].level | |||
end | end | ||
elseif recipe.abyssalLevel ~= nil then | |||
level, isAbyssal = recipe.abyssalLevel, true | |||
else | else | ||
-- For all other skills, the recipe should have a level property | -- For all other skills, the recipe should have a level property | ||
return recipe.level | level, isAbyssal = recipe.level, false | ||
end | |||
return level, isAbyssal | |||
end | |||
function p.getRecipeLevel(skillID, recipe) | |||
local level, isAbyssal = p.getRecipeLevelRealm(skillID, recipe) | |||
return level | |||
end | |||
function p.getRecipeRequirementText(skillName, recipe) | |||
local reqText = {} | |||
if recipe.abyssalLevel ~= nil and recipe.abyssalLevel > 0 then | |||
table.insert(reqText, Icons._AbyssalSkillReq(skillName, recipe.abyssalLevel, false)) | |||
elseif recipe.level ~= nil and recipe.level > 0 then | |||
table.insert(reqText, Icons._SkillReq(skillName, recipe.level, false)) | |||
end | |||
if recipe.totalMasteryRequired ~= nil then | |||
table.insert(reqText, Shared.formatnum(recipe.totalMasteryRequired) .. ' ' .. Icons.Icon({skillName, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'})) | |||
end | |||
local reqsData = {} | |||
if type(recipe.requirements) == 'table' then | |||
reqsData = Shared.shallowClone(recipe.requirements) | |||
end | |||
if recipe.shopItemPurchased ~= nil then | |||
-- Mining requirements are stored differently than other skills like | |||
-- Woodcutting, standardize here | |||
table.insert(reqsData, { | |||
["type"] = 'ShopPurchase', | |||
["purchaseID"] = recipe.shopItemPurchased, | |||
["count"] = 1 | |||
}) | |||
end | |||
if not Shared.tableIsEmpty(reqsData) then | |||
local reqs = Common.getRequirementString(reqsData) | |||
if reqs ~= nil then | |||
table.insert(reqText, reqs) | |||
end | |||
end | |||
return table.concat(reqText, '<br/>') | |||
end | |||
function p.standardRecipeSort(skillID, recipeA, recipeB) | |||
local levelA, isAbyssalA = p.getRecipeLevelRealm(skillID, recipeA) | |||
local levelB, isAbyssalB = p.getRecipeLevelRealm(skillID, recipeB) | |||
local isAbyssalNumA, isAbyssalNumB = (isAbyssalA and 1) or 0, (isAbyssalB and 1) or 0 | |||
return (isAbyssalA == isAbyssalB and levelA < levelB) or isAbyssalNumA < isAbyssalNumB | |||
end | |||
function p.getRecipeRealm(recipe) | |||
return recipe.realm or 'melvorD:Melvor' | |||
end | |||
function p.getRealmFromName(realmName) | |||
local realm = nil | |||
if realmName == nil or realmName == '' then | |||
-- Default realm | |||
realm = GameData.getEntityByID('realms', 'melvorD:Melvor') | |||
else | |||
realm = GameData.getEntityByName('realms', realmName) | |||
end | end | ||
return realm | |||
end | |||
function p.getMasteryActionCount(skillID, realmID, levelLimit) | |||
local actCount = 0 | |||
local skillNS, skillLocalID = Shared.getLocalID(skillID) | |||
local skillData = SkillData[skillLocalID] | |||
local recipeKey = p.getSkillRecipeKey(skillLocalID) | |||
if recipeKey ~= nil then | |||
local recipeData = skillData[recipeKey] | |||
for i, recipe in ipairs(recipeData) do | |||
if ( | |||
p.getRecipeRealm(recipe) == realmID | |||
and (recipe.noMastery == nil or not recipe.noMastery) | |||
and (levelLimit == nil or p.getRecipeLevel(skillLocalID, recipe) <= levelLimit) | |||
) then | |||
actCount = actCount + 1 | |||
end | |||
end | |||
end | |||
return actCount | |||
end | end | ||
Line 203: | Line 287: | ||
end | end | ||
-- | -- Combines Astrology constellation modifiers into an object similar to other entities, | ||
-- and multiplies the values up to their maximum possible amount | |||
function p._getConstellationModifiers(cons) | |||
-- | local result = {} | ||
local modKeys = { 'standardModifiers', 'uniqueModifiers', 'abyssalModifiers' } | |||
function p. | for _, keyID in ipairs(modKeys) do | ||
result[keyID] = {} | |||
local | local mods = cons[keyID] | ||
for | if mods ~= nil then | ||
for _, mod in ipairs(mods) do | |||
local multValue = mod.maxCount | |||
local subKeys = { 'modifiers', 'enemyModifiers' } | |||
for _, subKey in ipairs(subKeys) do | |||
local modAdj = Shared.clone(mod[subKey]) | |||
if type(modAdj) == 'table' then | |||
for modName, modValueDef in pairs(modAdj) do | |||
if type(modValueDef) == 'table' then | |||
if modValueDef[1] ~= nil then | |||
-- Table of multiple values | |||
for i, subValue in ipairs(modValueDef) do | |||
if type(subValue) == 'table' and subValue.value ~= nil then | |||
subValue.value = subValue.value * multValue | |||
elseif type(subValue) == 'number' then | |||
modValueDef[i] = subValue * multValue | |||
end | |||
end | |||
elseif modValueDef.value ~= nil then | |||
-- Table but with a single value | |||
modValueDef.value = modValueDef.value * multValue | |||
end | |||
elseif type(modValueDef) == 'number' then | |||
-- Single value | |||
modAdj[modName] = modValueDef * multValue | |||
end | |||
end | end | ||
end | end | ||
table.insert(result[keyID], modAdj) | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
return result | |||
end | end | ||
Line 295: | Line 342: | ||
local _, localSkillID = GameData.getLocalID(skillID) | local _, localSkillID = GameData.getLocalID(skillID) | ||
-- Clone so that we can sort by level | -- Clone so that we can sort by level | ||
local unlockTable = Shared. | local unlockTable = Shared.shallowClone(SkillData[localSkillID].masteryLevelUnlocks) | ||
if unlockTable == nil then | if unlockTable == nil then | ||
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName) | return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName) | ||
Line 311: | Line 358: | ||
function p.getMasteryCheckpointTable(frame) | function p.getMasteryCheckpointTable(frame) | ||
local skillName = frame.args ~= nil and frame.args[1] | local skillName = frame.args ~= nil and frame.args[1] | ||
local realmName = frame.args ~= nil and frame.args[2] | |||
local skillID = Constants.getSkillID(skillName) | local skillID = Constants.getSkillID(skillName) | ||
local realm = nil | |||
if realmName ~= nil then | |||
realm = GameData.getEntityByName('realms', realmName) | |||
else | |||
realm = GameData.getEntityByID('realms', 'melvorD:Melvor') | |||
end | |||
if skillID == nil then | if skillID == nil then | ||
return Shared.printError('Failed to find a skill ID for ' .. skillName) | return Shared.printError('Failed to find a skill ID for ' .. skillName) | ||
elseif realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | end | ||
local _, localSkillID = GameData.getLocalID(skillID) | local _, localSkillID = GameData.getLocalID(skillID) | ||
local checkpoints = SkillData[localSkillID]. | local checkpoints = SkillData[localSkillID].masteryPoolBonuses | ||
if checkpoints == nil then | if checkpoints == nil then | ||
return Shared.printError('Failed to find Mastery | return Shared.printError('Failed to find Mastery checkpoint data for ' .. skillName) | ||
end | end | ||
local totalPoolXP = | local totalPoolXP = p.getMasteryActionCount(localSkillID, realm.id) * 500000 | ||
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus' | local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus' | ||
for i, | for i, checkpointData in ipairs(checkpoints) do | ||
result = result..'\r\n|-' | if checkpointData.realm == realm.id then | ||
local chkDesc = Modifiers.getModifiersText(checkpointData.modifiers, false, false) | |||
local chkPercent = checkpointData.percent | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..chkPercent..'%||' | |||
result = result..Shared.formatnum(math.floor(totalPoolXP * chkPercent / 100))..' xp||'..chkDesc | |||
end | |||
end | end | ||
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP' | result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP' | ||
Line 356: | Line 416: | ||
if CCI == nil then | if CCI == nil then | ||
return Shared.printError('Failed to find item with ID ' .. CCI_ID) | return Shared.printError('Failed to find item with ID ' .. CCI_ID) | ||
end | |||
local tokens = Items.getItems(function(item) return item.itemType == 'MasteryToken' end) | |||
local tokenItems = {} | |||
for _, item in ipairs(tokens) do | |||
if item.realm == 'melvorD:Melvor' and item.skill ~= nil then | |||
local skillNS, skillLocalID = Shared.getLocalID(item.skill) | |||
tokenItems[skillLocalID] = item | |||
end | |||
end | end | ||
Line 361: | Line 430: | ||
-- mastery actions for each | -- mastery actions for each | ||
for skillLocalID, skill in pairs(SkillData) do | for skillLocalID, skill in pairs(SkillData) do | ||
if skill. | if skill.masteryPoolBonuses ~= nil then | ||
local actCount = { ["skill"] = skill } | local actCount = { ["skill"] = skill, ["token"] = tokenItems[skillLocalID] } | ||
for i, levelDef in ipairs(skillLevels) do | for i, levelDef in ipairs(skillLevels) do | ||
actCount[levelDef.id] | actCount[levelDef.id] = p.getMasteryActionCount(skillLocalID, 'melvorD:Melvor', levelDef.level) | ||
end | end | ||
table.insert(masteryActionCount, actCount) | table.insert(masteryActionCount, actCount) | ||
Line 412: | Line 464: | ||
for i, rowData in ipairs(masteryActionCount) do | for i, rowData in ipairs(masteryActionCount) do | ||
local token = | local token = rowData.token | ||
table.insert(resultPart, '\n|-') | table.insert(resultPart, '\n|-') | ||
local tokenImg = (token == nil and '?') or Icons.Icon({token.name, type='item', size=50, notext=true}) | |||
table.insert(resultPart, '\n|style="text-align:center"|' .. tokenImg) | |||
table.insert(resultPart, '\n|' .. Icons.Icon({rowData.skill.name, type='skill'})) | table.insert(resultPart, '\n|' .. Icons.Icon({rowData.skill.name, type='skill'})) | ||
Line 422: | Line 475: | ||
if actCount > 0 then | if actCount > 0 then | ||
denom = math.floor(baseTokenChance / actCount) | denom = math.floor(baseTokenChance / actCount) | ||
denomCCI = Shared.round(baseTokenChance / (actCount * (1 + CCI.modifiers. | denomCCI = Shared.round(baseTokenChance / (actCount * (1 + CCI.modifiers.offItemChance / 100)), 0, 0) | ||
end | end | ||
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom)) | table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom)) | ||
Line 456: | Line 509: | ||
function p.getFiremakingTable(frame) | function p.getFiremakingTable(frame) | ||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realm = p.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local skillID = 'Firemaking' | |||
local resultPart = {} | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | ||
table.insert(resultPart, '\r\n|-class="headerRow-0"') | table.insert(resultPart, '\r\n|-class="headerRow-0"') | ||
table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"| | table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|Requirements') | ||
table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time') | table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time') | ||
table.insert(resultPart, '\r\n|-class="headerRow-1"') | table.insert(resultPart, '\r\n|-class="headerRow-1"') | ||
table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s') | table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s') | ||
for i, logData in ipairs( | local logsData = GameData.getEntities(SkillData.Firemaking.logs, | ||
function(obj) | |||
return p.getRecipeRealm(obj) == realm.id | |||
end | |||
) | |||
table.sort(logsData, function(a, b) return p.standardRecipeSort(skillID, a, b) end) | |||
for i, logData in ipairs(logsData) do | |||
local logs = Items.getItemByID(logData.logID) | local logs = Items.getItemByID(logData.logID) | ||
local name = logs.name | local name = logs.name | ||
local level = p.getRecipeLevel(skillID, logData) | |||
local baseXP = logData.baseAbyssalExperience or logData.baseExperience | |||
local reqText = p.getRecipeRequirementText(SkillData.Firemaking.name, logData) | |||
local bonfireBonus = logData.bonfireAXPBonus or logData.bonfireXPBonus | |||
local burnTime = logData.baseInterval / 1000 | local burnTime = logData.baseInterval / 1000 | ||
local bonfireTime = logData.baseBonfireInterval / 1000 | local bonfireTime = logData.baseBonfireInterval / 1000 | ||
local XPS = | local XPS = baseXP / burnTime | ||
local XP_BF = | local XP_BF = baseXP * (1 + bonfireBonus / 100) | ||
local XPS_BF = Shared.round(XP_BF / burnTime, 2, 2) | local XPS_BF = Shared.round(XP_BF / burnTime, 2, 2) | ||
XP_BF = Shared.round(XP_BF, 2, 0) | XP_BF = Shared.round(XP_BF, 2, 0) | ||
Line 477: | Line 548: | ||
table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})) | table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})) | ||
table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true})) | table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true})) | ||
table.insert(resultPart, '||style ="text-align: right;"|'.. | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. level .. '"|'..reqText) | ||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)) | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)) | ||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. baseXP .. '"| ' .. Shared.formatnum(baseXP)) | ||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.formatnum(Shared.round(XPS, 2, 2))) | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.formatnum(Shared.round(XPS, 2, 2))) | ||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. XP_BF .. '"| ' .. Shared.formatnum(XP_BF)) | if bonfireBonus == 0 then | ||
table.insert(resultPart, '||colspan="4" class="table-na"| N/A ') | |||
else | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="' .. XP_BF .. '"| ' .. Shared.formatnum(XP_BF)) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.formatnum(XPS_BF, 2, 2)) | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireBonus..'"|'..bonfireBonus..'%') | |||
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true)) | |||
end | |||
end | end | ||