Module:Skills: Difference between revisions

Adjusted the appearance of the Lesser Relics table
(Amend incomplete category & indentation)
(Adjusted the appearance of the Lesser Relics table)
 
(35 intermediate revisions by 3 users not shown)
Line 1: Line 1:
--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:
--Some skills have their own modules:
--Module:Magic for Magic
--Module:Magic for Magic
Line 11: Line 15:


local p = {}
local p = {}
local ItemData = mw.loadData('Module:Items/data')
local SkillData = mw.loadData('Module:Skills/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 ItemSourceTables = require('Module:Items/SourceTables')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')


local MasteryCheckpoints = {.1, .25, .5, .95}
-- Given a skill ID, returns the key for that skill's recipe data.
-- If the skill has no recipes (e.g. is a combat skill) then the
-- return value is nil
function p.getSkillRecipeKey(skillID)
-- Convert skillID to local ID if not already
local ns, localSkillID = GameData.getLocalID(skillID)
local recipeIDs = {
["Woodcutting"] = 'trees',
["Fishing"] = 'fish',
["Firemaking"] = 'logs',
["Mining"] = 'rockData',
["Thieving"] = 'npcs',
["Agility"] = 'obstacles',
["Cooking"] = 'recipes',
["Smithing"] = 'recipes',
["Farming"] = 'recipes',
["Summoning"] = 'recipes',
["Fletching"] = 'recipes',
["Crafting"] = 'recipes',
["Runecrafting"] = 'recipes',
["Herblore"] = 'recipes',
["Astrology"] = 'recipes'
}
return recipeIDs[localSkillID]
end


function p.getMasteryUnlockTable(frame)
-- Given a skill ID & recipe, returns the skill level requirement for
local skillName = frame.args ~= nil and frame.args[1] or frame
-- that recipe. If the level could not be determined, then the return
local skillID = Constants.getSkillID(skillName)
-- value is nil
if skillID == nil then
function p.getRecipeLevel(skillID, recipe)
return "ERROR: Failed to find a skill ID for "..skillName
-- Convert skillID to local ID if not already
local ns, localSkillID = GameData.getLocalID(skillID)
if localSkillID == 'Agility' then
-- For Agility, level is derived from obstacle category
if recipe.category ~= nil then
-- Obstacle
return SkillData.Agility.obstacleUnlockLevels[recipe.category+1]
else
-- Pillar
local nsR, localRecipeID = GameData.getLocalID(recipe.id)
if localRecipeID ~= nil then
if string.find(localRecipeID, '^Pillar') ~= nil then
return 99
elseif string.find(localRecipeID, '^ElitePillar') ~= nil then
return 120
end
end
end
else
-- For all other skills, the recipe should have a level property
return recipe.level
end
end
end


local unlockTable = SkillData.MasteryUnlocks[skillID]
-- Thieving
if unlockTable == nil then
function p.getThievingNPCByID(npcID)
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
end
end
 
function p.getThievingNPC(npcName)
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName)
end


local result = '{|class="wikitable"\r\n!Level!!Unlock'
function p.getThievingNPCArea(npc)
for i, unlock in Shared.skpairs(unlockTable) do
for i, area in ipairs(SkillData.Thieving.areas) do
result = result..'\r\n|-'
for j, npcID in ipairs(area.npcIDs) do
result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
if npcID == npc.id then
return area
end
end
end
end
result = result..'\r\n|}'
return result
end
end


function p.getMasteryCheckpointTable(frame)
function p._getThievingNPCStat(npc, statName)
local skillName = frame.args ~= nil and frame.args[1] or frame
local result = nil
local skillID = Constants.getSkillID(skillName)
 
if skillID == nil then
if statName == 'level' then
return "ERROR: Failed to find a skill ID for "..skillName
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
end


if SkillData.MasteryCheckpoints[skillID] == nil then
if result == nil then
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
result = ''
end
end


local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
return result
local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
end


local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
function p.getThievingNPCStat(frame)
for i, bonus in Shared.skpairs(bonuses) do
local npcName = frame.args ~= nil and frame.args[1] or frame[1]
result = result..'\r\n|-'
local statName = frame.args ~= nil and frame.args[2] or frame[2]
result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||'
local npc = p.getThievingNPC(npcName)
result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus
if npc == nil then
return Shared.printError('Invalid Thieving NPC ' .. npcName)
end
end
result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
 
result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
return p._getThievingNPCStat(npc, statName)
result = result..'\r\n|}'
return result
end
end


function p._getFarmingTable(category)
function p.getThievingSourcesForItem(itemID)
local seedList = {}
local resultArray = {}
if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
local areaNPCs = {}
seedList = Items.getItems(function(item) return item.tier == category end)
 
else
--First check area unique drops
return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
--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] = { qty = drop.quantity, area = area }
end
break
end
end
end
end


local result = '{|class="wikitable sortable stickyHeader"'
--Now go through and get drop chances on each NPC if needed
result = result..'\r\n|- class="headerRow-0"'
for i, npc in pairs(SkillData.Thieving.npcs) do
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
local totalWt = 0
result = result..'!!XP!!Growth Time!!Seed Value'
local dropWt = 0
if category == 'Allotment' then
local dropQty = { min = 0, max = 0 }
result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
for j, drop in ipairs(npc.lootTable) do
elseif category == 'Herb' then
totalWt = totalWt + drop.weight
result = result..'!!colspan="2"|Herb!!Herb Value'
if drop.itemID == itemID then
elseif category == 'Tree' then
dropWt = drop.weight
result = result..'!!colspan="2"|Logs!!Log Value'
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, type = 'npc'})
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, type = 'npcUnique'})
end
 
local areaNPC = areaNPCs[npc.id]
if areaNPC ~= nil then
table.insert(resultArray, {npc = npc.name, minQty = areaNPC.qty, maxQty = areaNPC.qty, wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level, npcID = npc.id, area = areaNPC.area, type = 'areaUnique'})
end
end
end
result = result..'!!Seed Sources'
table.sort(seedList, function(a, b) return a.farmingLevel < b.farmingLevel end)


for i, seed in pairs(seedList) do
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
result = result..'\r\n|-'
if drop.itemID == itemID then
result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
if drop.npcs == nil then
result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1, npcID = itemID, type = 'generalRare'})
result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
else
result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
for j, npcID in ipairs(drop.npcs) do
 
local npc = p.getThievingNPCByID(npcID)
local crop = Items.getItemByID(seed.grownItemID)
if npc ~= nil then
result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
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, type = 'generalRare'})
if category == 'Allotment' then
end
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
end
end
end
end
result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
result = result..'||'..ItemSourceTables._getItemSources(seed)
end
end


result = result..'\r\n|}'
return resultArray
return result
end
end


function p.getFarmingTable(frame)
-- Astrology
local category = frame.args ~= nil and frame.args[1] or frame
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end


return p._getFarmingTable(category)
function p.getConstellation(constName)
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end
end


function p.getFarmingFoodTable(frame)
function p.getConstellations(checkFunc)
local result = '{| class="wikitable sortable stickyHeader"'
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
result = result..'\r\n|- class="headerRow-0"'
end
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
result = result..'!!Healing!!Value'
local itemArray = Items.getItems(function(item) return item.grownItemID ~= nil end)


table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel 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


for i, item in Shared.skpairs(itemArray) do
local addToArray = function(modArray, modNew)
local crop = Items.getItemByID(item.grownItemID)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
if crop.healsFor ~= nil and crop.healsFor > 0 then
table.insert(modArray, modNew)
result = result..'\r\n|-'
end
result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
result = result..'||style="text-align:right;"|'..item.farmingLevel
result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
end
end
local modTypes = {}
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end
local masteryReq = {
['standardModifiers'] = { 1, 40, 80 },
['uniqueModifiers'] = { 20, 60, 99 }
}


result = result..'\r\n|}'
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


return result
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
end


function p.getFarmingPlotTable(frame)
-- Mastery
local areaName = frame.args ~= nil and frame.args[1] or frame
function p.getMasteryUnlockTable(frame)
local patches = nil
local skillName = frame.args ~= nil and frame.args[1] or frame
for i, area in Shared.skpairs(SkillData.Farming.Patches) do
local skillID = Constants.getSkillID(skillName)
if area.areaName == areaName then
if skillID == nil then
patches = area.patches
return Shared.printError('Failed to find a skill ID for ' .. skillName)
break
end
end
end
if patches == nil then
 
return "ERROR: Invalid area name.[[Category:Pages with script errors]]"
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
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"'
local result = '{|class="wikitable"\r\n!Level!!Unlock'
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
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


for i, patch in Shared.skpairs(patches) do
local _, localSkillID = GameData.getLocalID(skillID)
result = result..'\r\n|-\r\n|'..i
local checkpoints = SkillData[localSkillID].masteryCheckpoints
result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
if checkpoints == nil then
if patch.cost == 0 then
return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName)
result = result..'||Free'
else
result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
end
end
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|}'
result = result..'\r\n|}'
return result
return result
end
end


function p.getSmithingTable(frame)
function p.getMasteryTokenTable()
local tableType = frame.args ~= nil and frame.args[1] or frame
-- Defines which skill levels should be included within the output
local bar = nil
local skillLevels = {
if tableType ~= 'Smelting' then
{
bar = Items.getItem(tableType)
["id"] = 'Base',
if bar == nil then
["level"] = 99,
return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
["description"] = '[[Full Version|Base Game]] (Level 99)'
elseif bar.type ~= 'Bar' then
}, {
return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
["id"] = 'TotH',
end
["level"] = 120,
["description"] = Icons.TotH() .. ' [[Throne of the Herald Expansion|Throne of the Herald]] (Level 120)'
}
}
local baseTokenChance = 18500
local masteryActionCount = {}
local CCI_ID = 'melvorD:Clue_Chasers_Insignia'
local CCI = Items.getItemByID(CCI_ID)
if CCI == nil then
return Shared.printError('Failed to find item with ID ' .. CCI_ID)
end
end


local smithList = {}
-- Iterate over each skill with mastery, determining the number of
for i, item in pairs(ItemData.Items) do
-- mastery actions for each
if item.smithingLevel ~= nil then
for skillLocalID, skill in pairs(SkillData) do
if tableType == 'Smelting' then
if skill.masteryTokenID ~= nil then
if item.type == 'Bar' then
local actCount = { ["skill"] = skill }
table.insert(smithList, item)
for i, levelDef in ipairs(skillLevels) do
end
actCount[levelDef.id] = 0
else
end
for j, req in pairs(item.smithReq) do
 
if req.id == bar.id then
local recipeKey = p.getSkillRecipeKey(skillLocalID)
table.insert(smithList, item)
if recipeKey ~= nil then
local recipeData = skill[recipeKey]
for i, recipe in ipairs(recipeData) do
if recipe.noMastery == nil or not recipe.noMastery then
local skillLevel = p.getRecipeLevel(skillLocalID, recipe)
if skillLevel ~= nil then
for j, levelDef in ipairs(skillLevels) do
if skillLevel <= levelDef.level then
actCount[levelDef.id] = actCount[levelDef.id] + 1
end
end
end
end
end
end
end
end
end
table.insert(masteryActionCount, actCount)
end
end
end
end


local result = '{|class="wikitable sortable stickyHeader"'
local firstID = skillLevels[1].id
result = result..'\r\n|-class="headerRow-0"'
table.sort(masteryActionCount,
result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
function(a, b)
--Adding value/bar for things other than smelting
if a[firstID] == b[firstID] then
if bar ~= nil then result = result..'!!Value/Bar' end
return a.skill.name < b.skill.name
else
return a[firstID] > b[firstID]
end
end)
-- Generate output table
local resultPart = {}
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})
local columnPairs = Shared.tableCount(skillLevels)
 
-- Generate header
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\n!rowspan="3"|Token!!rowspan="3"|Skill!!colspan="' .. columnPairs * 2 .. '"|Approximate Mastery Token Chance')
table.insert(resultPart, '\n|-')
for i, levelDef in ipairs(skillLevels) do
table.insert(resultPart, '\n!colspan="2"| ' .. levelDef.description)
end
table.insert(resultPart, '\n|-' .. string.rep('\n!Without ' .. CCIIcon .. '\n!With ' .. CCIIcon, columnPairs))
 
for i, rowData in ipairs(masteryActionCount) do
local token = Items.getItemByID(rowData.skill.masteryTokenID)
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
table.insert(resultPart, '\n|' .. Icons.Icon({rowData.skill.name, type='skill'}))


table.sort(smithList, function(a, b)
for j, levelDef in ipairs(skillLevels) do
if a.smithingLevel ~= b.smithingLevel then
local actCount = rowData[levelDef.id]
return a.smithingLevel < b.smithingLevel
local denom, denomCCI = 0, 0
else
if actCount > 0 then
return a.name < b.name
denom = math.floor(baseTokenChance / actCount)
end end)
denomCCI = Shared.round(baseTokenChance / (actCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0)
for i, item in Shared.skpairs(smithList) do
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
local qty = item.smithingQty ~= nil and item.smithingQty or 1
if qty > 1 then
result = result..item.smithingQty..'x '
end
result = result..'[['..item.name..']]'
result = result..'||data-sort-value="'..item.smithingLevel..'"|'..Icons._SkillReq('Smithing', item.smithingLevel)
result = result..'||'..item.smithingXP
local totalValue = item.sellsFor * qty
result = result..'||data-sort-value="'..totalValue..'"|'..Icons.GP(item.sellsFor)
if qty > 1 then
result = result..' (x'..qty..')'
end
result = result..'||'
local barQty = 0
for i, mat in Shared.skpairs(item.smithReq) do
matItem = Items.getItemByID(mat.id)
if i > 1 then result = result..', ' end
result = result..Icons.Icon({matItem.name, type='item', qty=mat.qty, notext=true})
if bar ~= nil and mat.id == bar.id then
barQty = mat.qty
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="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
end
end
--Add the data for the value per bar
end
if bar ~= nil then
table.insert(resultPart, '\n|}')
if barQty == 0 then
 
result = result..'||data-sort-value="0"|N/A'
return table.concat(resultPart)
else
end
local barVal = totalValue / barQty
 
result = result..'||data-sort-value="'..barVal..'"|'..Icons.GP(Shared.round(barVal, 1, 1))
-- Skill unlock costs for Adventure game mode
end
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
end
table.insert(returnPart, '|}')
return table.concat(returnPart, '\r\n')
end
end
result = result..'\r\n|}'
return result
end
end


Line 265: Line 464:
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 Shared.skpairs(SkillData.Firemaking) do
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.baseXP / burnTime
local XPS = logData.baseExperience / burnTime
local XP_BF = logData.baseXP * (1 + logData.bonfireXPBonus / 100)
local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100)
local XPS_BF = XP_BF / burnTime
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|-')
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, '||[['..name..']]')
table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true}))
table.insert(resultPart, '||style ="text-align: right;"|'..logData.levelRequired)
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="'..burnTime..'"|'..Shared.timeString(burnTime, true))
table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseXP)
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.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;"|'..Shared.round(XP_BF, 2, 0))
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.round(XPS_BF, 2, 2))
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="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%')
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true))
Line 291: Line 491:
end
end


function p.getMasteryTokenTable()
function p.getAncientRelicsTable(frame)
local baseTokenChance = 18500
local skillName = frame.args ~= nil and frame.args[1] or frame
local masterySkills = {}
local skillID = nil
if skillName ~= nil and skillName ~= '' then
-- Find all mastery tokens
skillID = Constants.getSkillID(skillName)
local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
if skillID == nil then
for i, item in pairs(masteryTokens) do
return Shared.printError('Failed to find a skill ID for ' .. skillName)
local milestones = SkillData.Milestones[item.skill + 1]
if milestones ~= nil then
table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones})
end
end
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 resultPart = {}
local CCI = Items.getItem('Clue Chasers Insignia')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader lighttable"')
local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true})
table.insert(resultPart, '\n|-class="headerRow-0"')
if CCI == nil then return '' end
table.insert(resultPart, '\n|-\n!colspan="2"|Skill\n!Relic\n!Modifiers')
 
local relics = GameData.getEntities('ancientRelics',
function(relic)
return skillID == nil or relic.skillID == skillID
end)
table.sort(relics,
function (a, b)
local skillNameA, skillNameB = Constants.getSkillName(a.skillID), Constants.getSkillName(b.skillID)
if skillNameA == skillNameB then
-- Order by numbers at the end of relic IDs
-- Relics have a 'number' property, but this appears to contain duplicates
return string.sub(a.id, string.len(a.id)) < string.sub(b.id, string.len(b.id))
else
return skillNameA < skillNameB
end
end)


table.insert(resultPart, '{| class="wikitable sortable"')
local function appendSkillRows(resultTable, rowTable, relicCount, skillID)
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
local skillName = Constants.getSkillName(skillID)
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
table.insert(resultTable, '\n|-\n|rowspan="' .. relicCount .. '"| ' .. Icons.Icon({skillName, type='skill', notext=true, size=50}))
table.insert(resultTable, '\n|rowspan="' .. relicCount .. '"| ' .. Icons.Icon({skillName, type='skill', noicon=true}))
table.insert(resultTable, table.concat(rowTable))
end


for i, m in ipairs(masterySkills) do
local skillRelicCount, currentSkillID, tablePart = 0, nil, {}
local token = masteryTokens[m.tokenRef]
for i, relic in ipairs(relics) do
local denom = math.floor(baseTokenChance / m['milestoneCount'])
if currentSkillID == nil then
local denomCCI = math.floor(baseTokenChance / m['milestoneCount'] * (1 - CCI.increasedItemChance / 100))
currentSkillID = relic.skillID
elseif relic.skillID ~= currentSkillID then
appendSkillRows(resultPart, tablePart, skillRelicCount, currentSkillID)
tablePart = {}
currentSkillID = relic.skillID
skillRelicCount = 0
end


table.insert(resultPart, '\r\n|-')
skillRelicCount = skillRelicCount + 1
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
if skillRelicCount > 1 then
table.insert(resultPart, '\r\n|' .. Icons.Icon({Constants.getSkillName(m['skillID']), type='skill'}))
table.insert(tablePart, '\n|-')
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
end
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
table.insert(tablePart, '\n| ' .. skillRelicCount .. '\n| ' .. Constants.getModifiersText(relic.modifiers))
end
end
table.insert(resultPart, '\r\n|}')
appendSkillRows(resultPart, tablePart, skillRelicCount, currentSkillID)
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
return table.concat(resultPart)
end
end


function p.getSkillUnlockCostTable()
function p.getLesserRelicsTable(frame)
local returnPart = {}
local lesserRelics = {}
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
-- Iterate over each skill with a global rare drop then check
-- if the skill has a Lesser Relic drop
for skillLocalID, skill in pairs(SkillData) do
if skill.rareDrops ~= nil then
for i, drops in pairs(skill.rareDrops) do
if string.match(drops.itemID, '_Lesser_Relic') then
table.insert(lesserRelics, Items.getItemByID(drops.itemID))
end
end
end
end
table.sort(lesserRelics, function(a, b) return a.name < b.name end)
 
-- Create the Table
local resultTable = mw.html.create('table')
resultTable:addClass('wikitable sortable')
resultTable:tag('tr'):addClass('headerRow-0')
:tag('th'):wikitext('Icon')
:tag('th'):wikitext('Lesser Relic')
:tag('th'):wikitext('Modifiers')


local accCost = 0
for _, relic in ipairs(lesserRelics) do
for i, cost in ipairs(SkillData.SkillUnlockCosts) do
local tr = mw.html.create('tr')
accCost = accCost + cost
tr:tag('td'):wikitext(Icons.Icon({relic.name, type='item', size='50', notext=true}))
table.insert(returnPart, '|-')
tr:tag('td'):wikitext(Icons.Icon({relic.name, type='item', noicon=true}))
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
tr:tag('td'):wikitext(Constants.getModifiersText(relic.modifiers))
resultTable:node(tr)
end
end
table.insert(returnPart, '|}')
return resultTable
 
return table.concat(returnPart, '\r\n')
end
end


return p
return p
463

edits