Module:Skills
From Melvor Idle
Data pulled from Module:GameData
Some skills have their own modules:
- Module:Magic for Magic
- Module:Prayer for Prayer
- Module:Skills/Agility for Agility
- Module:Skills/Summoning for Summoning
- Module:Skills/Gathering for Mining, Fishing, Woodcutting
- Module:Skills/Artisan for Smithing, Cooking, Herblore, etc.
Also be aware of:
- Module:Navboxes for navigation boxes appearing near the bottom of pages
--This module should avoid including skill specific functions which generate
--output for wiki pages, especially those which require() other modules. For
--these functions, consider using the appropriate module from the below list.
--Some skills have their own modules:
--Module:Magic for Magic
--Module:Prayer for Prayer
--Module:Skills/Agility for Agility
--Module:Skills/Summoning for Summoning
--Module:Skills/Gathering for Mining, Fishing, Woodcutting
--Module:Skills/Artisan for Smithing, Cooking, Herblore, etc.
--Also be aware of:
--Module:Navboxes for navigation boxes appearing near the bottom of pages
local p = {}
local Shared = require('Module:Shared')
local Constants = require('Module:Constants')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Items = require('Module:Items')
local Icons = require('Module:Icons')
-- Thieving
function p.getThievingNPCByID(npcID)
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
end
function p.getThievingNPC(npcName)
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName)
end
function p.getThievingNPCArea(npc)
for i, area in ipairs(SkillData.Thieving.areas) do
for j, npcID in ipairs(area.npcIDs) do
if npcID == npc.id then
return area
end
end
end
end
function p._getThievingNPCStat(npc, statName)
local result = nil
if statName == 'level' then
result = Icons._SkillReq('Thieving', npc.level)
elseif statName == 'maxHit' then
result = npc.maxHit * 10
elseif statName == 'area' then
local area = p.getThievingNPCArea(npc)
result = area.name
else
result = npc[statName]
end
if result == nil then
result = ''
end
return result
end
function p.getThievingNPCStat(frame)
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
local statName = frame.args ~= nil and frame.args[2] or frame[2]
local npc = p.getThievingNPC(npcName)
if npc == nil then
return Shared.printError('Invalid Thieving NPC ' .. npcName)
end
return p._getThievingNPCStat(npc, statName)
end
function p.getThievingSourcesForItem(itemID)
local resultArray = {}
local areaNPCs = {}
--First check area unique drops
--If an area drops the item, add all the NPC ids to the list so we can add them later
for i, area in pairs(SkillData.Thieving.areas) do
for j, drop in pairs(area.uniqueDrops) do
if drop.id == itemID then
for k, npcID in ipairs(area.npcIDs) do
areaNPCs[npcID] = drop.quantity
end
break
end
end
end
--Now go through and get drop chances on each NPC if needed
for i, npc in pairs(SkillData.Thieving.npcs) do
local totalWt = 0
local dropWt = 0
local dropQty = { min = 0, max = 0 }
for j, drop in ipairs(npc.lootTable) do
totalWt = totalWt + drop.weight
if drop.itemID == itemID then
dropWt = drop.weight
dropQty = { min = drop.minQuantity, max = drop.maxQuantity }
end
end
if dropWt > 0 then
table.insert(resultArray, {npc = npc.name, minQty = dropQty.min, maxQty = dropQty.max, wt = dropWt * SkillData.Thieving.itemChance, totalWt = totalWt * 100, level = npc.level, npcID = npc.id})
end
--Chance of -1 on unique drops is to indicate variable chance
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.quantity, maxQty = npc.uniqueDrop.quantity, wt = -1, totalWt = -1, level = npc.level, npcID = npc.id})
end
if areaNPCs[npc.id] ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level, npcID = npc.id})
end
end
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
if drop.itemID == itemID then
if drop.npcs == nil then
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1, npcID = itemID})
else
for j, npcID in ipairs(drop.npcs) do
local npc = p.getThievingNPCByID(npcID)
if npc ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = npc.level, npcID = npc.id})
end
end
end
end
end
return resultArray
end
-- Astrology
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end
function p.getConstellation(constName)
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end
function p.getConstellations(checkFunc)
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
end
-- For a given constellation cons and modifier value modValue, generates and returns
-- a table of modifiers, much like any other item/object elsewhere in the game.
-- includeStandard: true|false, determines whether standard modifiers are included
-- includeUnique: true|false, determines whether unique modifiers are included
-- isDistinct: true|false, if true, the returned list of modifiers is de-duplicated
-- asKeyValue: true|false, if true, returns key/value pairs like usual modifier objects
function p._buildAstrologyModifierArray(cons, modValue, includeStandard, includeUnique, isDistinct, asKeyValue)
-- Temporary function to determine if the table already contains a given modifier
local containsMod = function(modList, modNew)
for i, modItem in ipairs(modList) do
-- Check mod names & value data types both equal
if modItem[1] == modNew[1] and type(modItem[2]) == type(modNew[2]) then
if type(modItem[2]) == 'table' then
if Shared.tablesEqual(modItem[2], modNew[2]) then
return true
end
elseif modItem[2] == modNew[2] then
return true
end
end
end
return false
end
local addToArray = function(modArray, modNew)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
table.insert(modArray, modNew)
end
end
local modTypes = {}
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end
local masteryReq = {
['standardModifiers'] = { 1, 40, 80 },
['uniqueModifiers'] = { 20, 60, 99 }
}
local modArray = {}
local isSkillMod = {}
--Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27]
local groupNum = 0
for _, modType in ipairs(modTypes) do
for i, modTypeData in ipairs(cons[modType]) do
groupNum = masteryReq[modType][i]
local modVal = nil
if modValue ~= nil then
modVal = modValue
else
modVal = modTypeData.incrementValue * modTypeData.maxCount
end
for j, modifier in ipairs(modTypeData.modifiers) do
local modEntry = (modifier.skill ~= nil and { skillID = modifier.skill, value = modVal }) or modVal
addToArray(modArray, {modifier.key, modEntry, group = groupNum})
end
end
end
if asKeyValue then
local modArrayKV = {}
for i, modDefn in ipairs(modArray) do
local modName, modVal = modDefn[1], modDefn[2]
local isSkill = type(modVal) == 'table' and modVal.skillID ~= nil
if modArrayKV[modName] == nil then
modArrayKV[modName] = (isSkill and { modVal } or modVal)
elseif isSkill then
table.insert(modArrayKV[modName], modVal)
else
modArrayKV[modName] = modArrayKV[modName] + modVal
end
end
return modArrayKV
else
return modArray
end
end
-- Mastery
function p.getMasteryUnlockTable(frame)
local skillName = frame.args ~= nil and frame.args[1] or frame
local skillID = Constants.getSkillID(skillName)
if skillID == nil then
return Shared.printError('Failed to find a skill ID for ' .. skillName)
end
local _, localSkillID = GameData.getLocalID(skillID)
-- Clone so that we can sort by level
local unlockTable = Shared.clone(SkillData[localSkillID].masteryLevelUnlocks)
if unlockTable == nil then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
end
table.sort(unlockTable, function(a, b) return (a.level == b.level and a.descriptionID < b.descriptionID) or a.level < b.level end)
local result = '{|class="wikitable"\r\n!Level!!Unlock'
for i, unlock in ipairs(unlockTable) do
result = result..'\r\n|-'
result = result..'\r\n|'..unlock.level..'||'..unlock.description
end
result = result..'\r\n|}'
return result
end
function p.getMasteryCheckpointTable(frame)
local skillName = frame.args ~= nil and frame.args[1] or frame
local skillID = Constants.getSkillID(skillName)
if skillID == nil then
return Shared.printError('Failed to find a skill ID for ' .. skillName)
end
local _, localSkillID = GameData.getLocalID(skillID)
local checkpoints = SkillData[localSkillID].masteryCheckpoints
if checkpoints == nil then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
end
local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap
local checkpointPct = GameData.rawData.masteryCheckpoints
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
for i, checkpointDesc in ipairs(checkpoints) do
result = result..'\r\n|-'
result = result..'\r\n|'..checkpointPct[i]..'%||'
result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc
end
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
result = result..'\r\n|}'
return result
end
function p.getMasteryTokenTable()
local baseTokenChance = 18500
local masterySkills = {}
local CCI = Items.getItemByID('melvorD:Clue_Chasers_Insignia')
if CCI == nil then return '' end
-- Build table of mastery skills
for skillLocalID, skill in pairs(SkillData) do
if skill.masteryTokenID ~= nil then
table.insert(masterySkills, skill)
end
end
table.sort(masterySkills,
function(a, b)
if a.milestoneCount == b.milestoneCount then
return a.name < b.name
else
return a.milestoneCount > b.milestoneCount
end
end)
-- Generate output table
local resultPart = {}
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
for i, skill in ipairs(masterySkills) do
local token = Items.getItemByID(skill.masteryTokenID)
local denom = math.floor(baseTokenChance / skill.milestoneCount)
local denomCCI = Shared.round(baseTokenChance / (skill.milestoneCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0)
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
table.insert(resultPart, '\r\n|' .. Icons.Icon({skill.name, type='skill'}))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
-- Skill unlock costs for Adventure game mode
function p.getSkillUnlockCostTable()
local advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure')
if advMode ~= nil then
local unlockCount = Shared.tableCount(GameData.skillData) - Shared.tableCount(advMode.startingSkills)
local costLength = Shared.tableCount(advMode.skillUnlockCost)
local returnPart = {}
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
local accCost = 0
for i = 1, unlockCount, 1 do
local cost = advMode.skillUnlockCost[math.min(i, costLength)]
accCost = accCost + cost
table.insert(returnPart, '|-')
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
end
table.insert(returnPart, '|}')
return table.concat(returnPart, '\r\n')
end
end
function p.getFiremakingTable(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|-class="headerRow-0"')
table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level')
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!XP!!XP/s!!XP!!XP/s')
for i, logData in ipairs(SkillData.Firemaking.logs) do
local logs = Items.getItemByID(logData.logID)
local name = logs.name
local burnTime = logData.baseInterval / 1000
local bonfireTime = logData.baseBonfireInterval / 1000
local XPS = logData.baseExperience / burnTime
local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100)
local XPS_BF = Shared.round(XP_BF / burnTime, 2, 2)
XP_BF = Shared.round(XP_BF, 2, 0)
table.insert(resultPart, '\r\n|-')
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, '||style ="text-align: right;"|'..logData.level)
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="' .. logData.baseExperience .. '"| ' .. Shared.formatnum(logData.baseExperience))
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))
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="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
return p