Module:Skills
From Melvor Idle
Data pulled from Module:Skills/data
--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 ItemData = mw.loadData('Module:Items/data') local SkillData = mw.loadData('Module:Skills/data') local Shared = require('Module:Shared') local Constants = require('Module:Constants') local Items = require('Module:Items') local Icons = require('Module:Icons') local MasteryCheckpoints = {.1, .25, .5, .95} -- Thieving function p.getThievingNPC(npcName) local result = nil for i, npc in Shared.skpairs(SkillData.Thieving.NPCs) do if npc.name == npcName then result = Shared.clone(npc) break end end return result end function p.getThievingNPCArea(npc) if type(npc) == 'string' then npc = p.getThievingNPC(npc) end local result = nil for i, area in Shared.skpairs(SkillData.Thieving.Areas) do for j, npcID in pairs(area.npcs) do if npcID == npc.id then result = area break end end end return result 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 "ERROR: Invalid Thieving NPC "..npcName.."[[Category:Pages with script errors]]" 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 if not result then for i, area in pairs(SkillData.Thieving.Areas) do for j, drop in pairs(area.uniqueDrops) do if drop.itemID == itemID then for k, npcID in pairs(area.npcs) do areaNPCs[npcID] = drop.qty end break end 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 = 0 for j, drop in pairs(npc.lootTable) do totalWt = totalWt + drop[2] if drop[1] == itemID then dropWt = drop[2] dropQty = drop[3] end end if dropWt > 0 then table.insert(resultArray, {npc = npc.name, minQty = 1, maxQty = dropQty, wt = dropWt * SkillData.Thieving.ItemChance, totalWt = totalWt * 100, level = npc.level}) end --Chance of -1 on unique drops is to indicate variable chance if npc.uniqueDrop ~= nil and npc.uniqueDrop.itemID == itemID then table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.qty, maxQty = npc.uniqueDrop.qty, wt = -1, totalWt = -1, level = npc.level}) end if areaNPCs[npc.id] ~= nil then table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.AreaUniqueChance, totalWt = 100, level = npc.level}) end end for i, drop in pairs(SkillData.Thieving.RareItems) do if drop.itemID == itemID then table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1}) end end return resultArray end -- Astrology function p.getConstellationByID(constID) return SkillData.Astrology.Constellations[constID] end function p.getConstellation(constName) for i, const in ipairs(SkillData.Astrology.Constellations) do if const.name == constName then return const end end return nil end function p.getConstellations(checkFunc) local result = {} for i, const in ipairs(SkillData.Astrology.Constellations) do if checkFunc(const) then table.insert(result, const) end end return result 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 modArray = {} local isSkillMod = {} for _, modType in ipairs(modTypes) do for i, skillMods in ipairs(cons[modType]) do local skillID = cons.skills[i] if skillID ~= nil then for j, modName in ipairs(skillMods) do local modBaseName, modText, sign, isNegative, unsign, modBase = Constants.getModifierDetails(modName) -- Check if modifier varies by skill, and amend the modifier value accordingly local modVal = modValue if Shared.contains(modText, '{SV0}') then isSkillMod[modName] = true modVal = {skillID, modValue} end addToArray(modArray, {modName, modVal}) end end end end if asKeyValue then local modArrayKV = {} for i, modDefn in ipairs(modArray) do local modName, modVal = modDefn[1], modDefn[2] local isSkill = isSkillMod[modName] 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 "ERROR: Failed to find a skill ID for "..skillName end local unlockTable = SkillData.MasteryUnlocks[skillID] if unlockTable == nil then return 'ERROR: Failed to find Mastery Unlock data for '..skillName end local result = '{|class="wikitable"\r\n!Level!!Unlock' for i, unlock in Shared.skpairs(unlockTable) do result = result..'\r\n|-' result = result..'\r\n|'..unlock.level..'||'..unlock.unlock 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 "ERROR: Failed to find a skill ID for "..skillName end if SkillData.MasteryCheckpoints[skillID] == nil then return 'ERROR: Failed to find Mastery Unlock data for '..skillName end local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1] local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus' for i, bonus in Shared.skpairs(bonuses) do result = result..'\r\n|-' result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||' result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus 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 = {} -- Find all mastery tokens local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end) for i, item in pairs(masteryTokens) do local milestones = SkillData.Milestones[item.skill + 1] if milestones ~= nil then table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones}) end end table.sort(masterySkills, function(a, b) if a['milestoneCount'] == b['milestoneCount'] then return a['skillID'] < b['skillID'] else return a['milestoneCount'] > b['milestoneCount'] end end) -- Generate output table local resultPart = {} local CCI = Items.getItem('Clue Chasers Insignia') local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true}) if CCI == nil then return '' end 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, m in ipairs(masterySkills) do local token = masteryTokens[m.tokenRef] local denom = math.floor(baseTokenChance / m['milestoneCount']) local denomCCI = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 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({Constants.getSkillName(m['skillID']), 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 returnPart = {} table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost') local accCost = 0 for i, cost in ipairs(SkillData.SkillUnlockCosts) do 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 -- Accepts 1 parameter, being either: -- 'Smelting', for which a table of all bars is generated, or -- A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier function p.getSmithingTable(frame) local tableType = frame.args ~= nil and frame.args[1] or frame tableType = Shared.splitString(tableType, ' ')[1] -- Translates Smithing category names to Smithing recipe data categories local categoryMap = { ['Smelting'] = 0, ['Bronze'] = 1, ['Iron'] = 2, ['Steel'] = 3, ['Mithril'] = 4, ['Adamant'] = 5, ['Adamantite'] = 5, ['Rune'] = 6, ['Runite'] = 6, ['Dragon'] = 7, ['Dragonite'] = 7 } local categoryID = categoryMap[tableType] if categoryID == nil then return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]' end -- Build a list of recipes to be included, and a list of bars while we're at it -- The bar list will be used later for value/bar calculations local recipeList, barIDList = {}, {} for i, recipe in ipairs(SkillData.Smithing.Recipes) do if recipe.category == categoryID then local recipeItem = Items.getItemByID(recipe.itemID) if recipeItem ~= nil then table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor }) end elseif recipe.category == 0 then barIDList[recipe.itemID] = true end end -- Generate output table local resultPart = {} table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') table.insert(resultPart, '\r\n|-class="headerRow-0"') table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients') --Adding value/bar for things other than smelting if categoryID > 0 then table.insert(resultPart, '!!Value/Bar') end table.sort(recipeList, function(a, b) if a.level ~= b.level then return a.level < b.level else return a.itemName < b.itemName end end) for i, recipeDef in ipairs(recipeList) do local recipe = SkillData.Smithing.Recipes[recipeDef.id] local totalValue = recipe.baseQuantity * recipeDef.itemValue -- Determine the bar quantity & build the recipe cost string local barQty, costString = 0, {} for j, itemCost in ipairs(recipe.itemCosts) do local costItem = Items.getItemByID(itemCost.id) if costItem ~= nil then table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.qty, notext=true})) end if barIDList[itemCost.id] then barQty = barQty + itemCost.qty end end table.insert(resultPart, '\r\n|-') table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true})) table.insert(resultPart, '\r\n| ') if recipe.baseQuantity > 1 then table.insert(resultPart, recipe.baseQuantity .. 'x ') end table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true})) table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level)) table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseXP .. '"| ' .. Shared.formatnum(recipe.baseXP)) table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue)) if recipe.baseQuantity > 1 then table.insert(resultPart, ' (x' .. recipe.baseQuantity .. ')') end table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', ')) if categoryID > 0 then local barVal, barValTxt = 0, 'N/A' if barQty > 0 then barVal = totalValue / barQty barTxt = Icons.GP(Shared.round(barVal, 1, 1)) end table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barTxt) end end table.insert(resultPart, '\r\n|}') return table.concat(resultPart) 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 Shared.skpairs(SkillData.Firemaking) do local logs = Items.getItemByID(logData.logID) local name = logs.name local burnTime = logData.baseInterval / 1000 local bonfireTime = logData.baseBonfireInterval / 1000 local XPS = logData.baseXP / burnTime local XP_BF = logData.baseXP * (1 + logData.bonfireXPBonus / 100) local XPS_BF = XP_BF / burnTime 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, '||[['..name..']]') 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;"|'..logData.baseXP) table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2)) table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0)) table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(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