Module:Skills: Difference between revisions
From Melvor Idle
(Move Farming functions to Module:Skills/Gathering) |
(Update for v1.1) |
||
Line 17: | Line 17: | ||
local ItemData = mw.loadData('Module:Items/data') | local ItemData = mw.loadData('Module:Items/data') | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local GameData = require('Module:GameData') | |||
local SkillData = GameData.skillData | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
-- Thieving | |||
function p.getThievingNPCByID(npcID) | |||
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID) | |||
end | |||
function p.getThievingNPC(npcName) | function p.getThievingNPC(npcName) | ||
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName) | |||
end | end | ||
function p.getThievingNPCArea(npc) | function p.getThievingNPCArea(npc) | ||
for i, area in ipairs(SkillData.Thieving.areas) do | |||
for j, npcID in ipairs(area.npcIDs) do | |||
for i, area in | |||
for j, npcID in | |||
if npcID == npc.id then | if npcID == npc.id then | ||
return area | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
Line 89: | Line 78: | ||
function p.getThievingSourcesForItem(itemID) | function p.getThievingSourcesForItem(itemID) | ||
local resultArray = {} | local resultArray = {} | ||
local areaNPCs = {} | local areaNPCs = {} | ||
--First check area unique drops | --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 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 pairs(area.npcs) do | |||
areaNPCs[npcID] = drop.quantity | |||
end | end | ||
break | |||
end | end | ||
end | end | ||
Line 108: | Line 94: | ||
--Now go through and get drop chances on each NPC if needed | --Now go through and get drop chances on each NPC if needed | ||
for i, npc in pairs(SkillData.Thieving. | for i, npc in pairs(SkillData.Thieving.npcs) do | ||
local totalWt = 0 | local totalWt = 0 | ||
local dropWt = 0 | local dropWt = 0 | ||
local dropQty = 0 | local dropQty = { min = 0, max = 0 } | ||
for j, drop in | for j, drop in ipairs(npc.lootTable) do | ||
totalWt = totalWt + drop | totalWt = totalWt + drop.weight | ||
if drop | if drop.itemID == itemID then | ||
dropWt = drop | dropWt = drop.weight | ||
dropQty = drop | dropQty = { min = drop.minQuantity, max = drop.maxQuantity } | ||
end | end | ||
end | end | ||
if dropWt > 0 then | if dropWt > 0 then | ||
table.insert(resultArray, {npc = npc.name, minQty = | table.insert(resultArray, {npc = npc.name, minQty = dropQty.min, maxQty = dropQty.max, wt = dropWt * SkillData.Thieving.itemChance, totalWt = totalWt * 100, level = npc.level}) | ||
end | end | ||
--Chance of -1 on unique drops is to indicate variable chance | --Chance of -1 on unique drops is to indicate variable chance | ||
if npc.uniqueDrop ~= nil and npc.uniqueDrop. | if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then | ||
table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop. | table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.quantity, maxQty = npc.uniqueDrop.quantity, wt = -1, totalWt = -1, level = npc.level}) | ||
end | end | ||
if areaNPCs[npc.id] ~= nil then | if areaNPCs[npc.id] ~= nil then | ||
table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving. | table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level}) | ||
end | end | ||
end | end | ||
for i, drop in | for i, drop in ipairs(SkillData.Thieving.generalRareItems) do | ||
if drop.itemID == itemID then | 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}) | 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}) | |||
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}) | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
Line 144: | Line 139: | ||
-- Astrology | -- Astrology | ||
function p.getConstellationByID(constID) | function p.getConstellationByID(constID) | ||
return SkillData.Astrology. | return GameData.getEntityByID(SkillData.Astrology.recipes, constID) | ||
end | end | ||
function p.getConstellation(constName) | function p.getConstellation(constName) | ||
return GameData.getEntityByName(SkillData.Astrology.recipes, constName) | |||
end | end | ||
function p.getConstellations(checkFunc) | function p.getConstellations(checkFunc) | ||
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc) | |||
end | end | ||
Line 206: | Line 190: | ||
local modArray = {} | local modArray = {} | ||
local isSkillMod = {} | local isSkillMod = {} | ||
for _, modType in ipairs(modTypes) do | for _, modType in ipairs(modTypes) do | ||
for i, | for i, modTypeData in ipairs(cons[modType]) do | ||
local | local modVal = nil | ||
if | if modValue ~= nil then | ||
for j, | modVal = modValue | ||
else | |||
modVal = modTypeData.incrementValue * modTypeData.maxCount | |||
end | |||
for j, modifier in ipairs(modTypeData.modifiers) do | |||
local modEntry = (modifier.skill ~= nil and {modifier.skill, modVal}) or modVal | |||
addToArray(modArray, {modifier.key, modEntry}) | |||
end | end | ||
end | end | ||
Line 228: | Line 210: | ||
for i, modDefn in ipairs(modArray) do | for i, modDefn in ipairs(modArray) do | ||
local modName, modVal = modDefn[1], modDefn[2] | local modName, modVal = modDefn[1], modDefn[2] | ||
local isSkill = | local isSkill = type(modVal) == 'table' and type(modVal[1]) == 'string' | ||
if modArrayKV[modName] == nil then | if modArrayKV[modName] == nil then | ||
modArrayKV[modName] = (isSkill and { modVal } or modVal) | modArrayKV[modName] = (isSkill and { modVal } or modVal) | ||
Line 251: | Line 233: | ||
end | end | ||
local unlockTable = SkillData | local unlockTable = SkillData[skillID].masteryLevelUnlocks | ||
if unlockTable == nil then | if unlockTable == nil then | ||
return 'ERROR: Failed to find Mastery Unlock data for '..skillName | return 'ERROR: Failed to find Mastery Unlock data for '..skillName | ||
Line 257: | Line 239: | ||
local result = '{|class="wikitable"\r\n!Level!!Unlock' | local result = '{|class="wikitable"\r\n!Level!!Unlock' | ||
for i, unlock in | for i, unlock in ipairs(unlockTable) do | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n|'..unlock.level..'||'..unlock. | result = result..'\r\n|'..unlock.level..'||'..unlock.description | ||
end | end | ||
result = result..'\r\n|}' | result = result..'\r\n|}' | ||
Line 272: | Line 254: | ||
end | end | ||
local checkpoints = SkillData[skillID].masteryCheckpoints | |||
if checkpoints == nil then | |||
return 'ERROR: Failed to find Mastery Unlock data for '..skillName | return 'ERROR: Failed to find Mastery Unlock data for '..skillName | ||
end | end | ||
local | local totalPoolXP = SkillData[skillID].baseMasteryPoolCap | ||
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, checkpointDesc in ipairs(checkpoints) do | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n|'.. | result = result..'\r\n|'..GameData.masteryCheckpoints[i]..'%||' | ||
result = result..Shared.formatnum(totalPoolXP * | result = result..Shared.formatnum(math.floor(totalPoolXP * GameData.masteryCheckpoints[i] / 100))..' xp||'..checkpointDesc | ||
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 294: | Line 275: | ||
local baseTokenChance = 18500 | local baseTokenChance = 18500 | ||
local masterySkills = {} | 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 | |||
for | if skill.masteryTokenID ~= nil then | ||
table.insert(masterySkills, skill) | |||
table.insert(masterySkills, | |||
end | end | ||
end | end | ||
table.sort(masterySkills, function(a, b) | 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 | -- Generate output table | ||
local resultPart = {} | local resultPart = {} | ||
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true}) | |||
local CCIIcon = Icons.Icon({ | |||
table.insert(resultPart, '{| class="wikitable sortable"') | table.insert(resultPart, '{| class="wikitable sortable"') | ||
Line 321: | Line 301: | ||
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon) | table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon) | ||
for i, | for i, skill in ipairs(masterySkills) do | ||
local token = | local token = Items.getItemByID(skill.masteryTokenID) | ||
local denom = math.floor(baseTokenChance / | local denom = math.floor(baseTokenChance / skill.milestoneCount) | ||
local denomCCI = Shared.round(baseTokenChance / ( | local denomCCI = Shared.round(baseTokenChance / (skill.milestoneCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0) | ||
table.insert(resultPart, '\r\n|-') | 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|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true})) | ||
table.insert(resultPart, '\r\n|' .. Icons.Icon({ | 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="' .. denom .. '"|1/' .. Shared.formatnum(denom)) | ||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI)) | table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI)) | ||
Line 339: | Line 319: | ||
-- Skill unlock costs for Adventure game mode | -- Skill unlock costs for Adventure game mode | ||
function p.getSkillUnlockCostTable() | function p.getSkillUnlockCostTable() | ||
local returnPart = {} | local advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure') | ||
if advMode ~= nil then | |||
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(advMode.skillUnlockCost) 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 | end | ||
end | end | ||
Line 359: | Line 342: | ||
local tableType = frame.args ~= nil and frame.args[1] or frame | local tableType = frame.args ~= nil and frame.args[1] or frame | ||
tableType = Shared.splitString(tableType, ' ')[1] | tableType = Shared.splitString(tableType, ' ')[1] | ||
-- | |||
local | -- Has a valid category been passed (by name)? | ||
local category = GameData.getEntityByName(SkillData.Smithing.categories, tableType) | |||
if category == nil then | |||
if | |||
return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]' | return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]' | ||
end | end | ||
Line 381: | Line 352: | ||
-- The bar list will be used later for value/bar calculations | -- The bar list will be used later for value/bar calculations | ||
local recipeList, barIDList = {}, {} | local recipeList, barIDList = {}, {} | ||
for i, recipe in ipairs(SkillData.Smithing. | for i, recipe in ipairs(SkillData.Smithing.recipes) do | ||
if recipe. | if recipe.categoryID == category.id then | ||
local recipeItem = Items.getItemByID(recipe. | local recipeItem = Items.getItemByID(recipe.productID) | ||
if recipeItem ~= nil then | if recipeItem ~= nil then | ||
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor }) | table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor }) | ||
end | end | ||
elseif recipe. | elseif recipe.categoryID == 'melvorD:Bars' then | ||
barIDList[recipe. | barIDList[recipe.productID] = true | ||
end | end | ||
end | end | ||
Line 398: | Line 369: | ||
table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients') | 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 | --Adding value/bar for things other than smelting | ||
if | if category.id ~= 'melvorD:Bars' then | ||
table.insert(resultPart, '!!Value/Bar') | table.insert(resultPart, '!!Value/Bar') | ||
end | end | ||
Line 411: | Line 382: | ||
for i, recipeDef in ipairs(recipeList) do | for i, recipeDef in ipairs(recipeList) do | ||
local recipe = SkillData.Smithing. | local recipe = SkillData.Smithing.recipes[recipeDef.id] | ||
local totalValue = recipe.baseQuantity * recipeDef.itemValue | local totalValue = recipe.baseQuantity * recipeDef.itemValue | ||
-- Determine the bar quantity & build the recipe cost string | -- Determine the bar quantity & build the recipe cost string | ||
Line 418: | Line 389: | ||
local costItem = Items.getItemByID(itemCost.id) | local costItem = Items.getItemByID(itemCost.id) | ||
if costItem ~= nil then | if costItem ~= nil then | ||
table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost. | table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity, notext=true})) | ||
end | end | ||
if barIDList[itemCost.id] then | if barIDList[itemCost.id] then | ||
barQty = barQty + itemCost. | barQty = barQty + itemCost.quantity | ||
end | end | ||
end | end | ||
Line 433: | Line 404: | ||
table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true})) | 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.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level)) | ||
table.insert(resultPart, '\r\n|data-sort-value="' .. recipe. | table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience)) | ||
table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue)) | table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue)) | ||
if recipe.baseQuantity > 1 then | if recipe.baseQuantity > 1 then | ||
Line 439: | Line 410: | ||
end | end | ||
table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', ')) | table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', ')) | ||
if | if category.id ~= 'melvorD:Bars' then | ||
local barVal, barValTxt = 0, 'N/A' | local barVal, barValTxt = 0, 'N/A' | ||
if barQty > 0 then | if barQty > 0 then | ||
barVal = totalValue / barQty | barVal = totalValue / barQty | ||
barValTxt = Icons.GP(Shared.round(barVal, 1, 1)) | |||
end | end | ||
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. | table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barValTxt) | ||
end | end | ||
end | end | ||
Line 462: | Line 433: | ||
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 | for i, logData in ipairs(SkillData.Firemaking.logs) do | ||
local logs = Items.getItemByID(logData.logID) | local logs = Items.getItemByID(logData.logID) | ||
local name = logs.name | local name = logs.name | ||
local burnTime = logData.baseInterval / 1000 | local burnTime = logData.baseInterval / 1000 | ||
local bonfireTime = logData.baseBonfireInterval / 1000 | local bonfireTime = logData.baseBonfireInterval / 1000 | ||
local XPS = logData. | local XPS = logData.baseExperience / burnTime | ||
local XP_BF = logData. | local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100) | ||
local XPS_BF = XP_BF / burnTime | local XPS_BF = XP_BF / burnTime | ||
Revision as of 15:52, 22 October 2022
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 ItemData = mw.loadData('Module:Items/data')
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 "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
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 pairs(area.npcs) 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})
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})
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})
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})
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})
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 modArray = {}
local isSkillMod = {}
for _, modType in ipairs(modTypes) do
for i, modTypeData in ipairs(cons[modType]) do
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 {modifier.skill, modVal}) or modVal
addToArray(modArray, {modifier.key, modEntry})
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 type(modVal[1]) == 'string'
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[skillID].masteryLevelUnlocks
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 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 "ERROR: Failed to find a skill ID for "..skillName
end
local checkpoints = SkillData[skillID].masteryCheckpoints
if checkpoints == nil then
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
local totalPoolXP = SkillData[skillID].baseMasteryPoolCap
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|'..GameData.masteryCheckpoints[i]..'%||'
result = result..Shared.formatnum(math.floor(totalPoolXP * GameData.masteryCheckpoints[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 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(advMode.skillUnlockCost) 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
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]
-- Has a valid category been passed (by name)?
local category = GameData.getEntityByName(SkillData.Smithing.categories, tableType)
if category == 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.categoryID == category.id then
local recipeItem = Items.getItemByID(recipe.productID)
if recipeItem ~= nil then
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor })
end
elseif recipe.categoryID == 'melvorD:Bars' then
barIDList[recipe.productID] = 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 category.id ~= 'melvorD:Bars' 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.quantity, notext=true}))
end
if barIDList[itemCost.id] then
barQty = barQty + itemCost.quantity
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.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience))
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 category.id ~= 'melvorD:Bars' then
local barVal, barValTxt = 0, 'N/A'
if barQty > 0 then
barVal = totalValue / barQty
barValTxt = Icons.GP(Shared.round(barVal, 1, 1))
end
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barValTxt)
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 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 = 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