Anonymous

Module:Skills: Difference between revisions

From Melvor Idle
Adjusted the appearance of the Lesser Relics table
(Update for v1.0.3)
(Adjusted the appearance of the Lesser Relics table)
 
(31 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
-- Thieving
function p.getThievingNPCByID(npcID)
return GameData.getEntityByID(SkillData.Thieving.npcs, npcID)
end


local unlockTable = SkillData.MasteryUnlocks[skillID]
function p.getThievingNPC(npcName)
if unlockTable == nil then
return GameData.getEntityByName(SkillData.Thieving.npcs, npcName)
return 'ERROR: Failed to find Mastery Unlock data for '..skillName
end
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
result = result..'!!Seed Sources'
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


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


for i, seed in pairs(seedList) do
local areaNPC = areaNPCs[npc.id]
result = result..'\r\n|-'
if areaNPC ~= nil then
result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
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'})
result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
end
result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
end
result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)


local crop = Items.getItemByID(seed.grownItemID)
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
if drop.itemID == itemID then
if category == 'Allotment' then
if drop.npcs == nil then
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
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'})
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, type = 'generalRare'})
end
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
 
-- Astrology
function p.getConstellationByID(constID)
return GameData.getEntityByID(SkillData.Astrology.recipes, constID)
end
end


function p.getFarmingTable(frame)
function p.getConstellation(constName)
local category = frame.args ~= nil and frame.args[1] or frame
return GameData.getEntityByName(SkillData.Astrology.recipes, constName)
end


return p._getFarmingTable(category)
function p.getConstellations(checkFunc)
return GameData.getEntities(SkillData.Astrology.recipes, checkFunc)
end
end


function p.getFarmingFoodTable(frame)
-- For a given constellation cons and modifier value modValue, generates and returns
local result = '{| class="wikitable sortable stickyHeader"'
-- a table of modifiers, much like any other item/object elsewhere in the game.
result = result..'\r\n|- class="headerRow-0"'
-- includeStandard: true|false, determines whether standard modifiers are included
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
-- includeUnique: true|false, determines whether unique modifiers are included
result = result..'!!Healing!!Value'
-- 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 itemArray = Items.getItems(function(item) return item.grownItemID ~= nil 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 }
}


table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
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 i, item in Shared.skpairs(itemArray) do
for _, modType in ipairs(modTypes) do
local crop = Items.getItemByID(item.grownItemID)
for i, modTypeData in ipairs(cons[modType]) do
if crop.healsFor ~= nil and crop.healsFor > 0 then
groupNum = masteryReq[modType][i]
result = result..'\r\n|-'
local modVal = nil
result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
if modValue ~= nil then
result = result..'||style="text-align:right;"|'..item.farmingLevel
modVal = modValue
result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
else
result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
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
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|}'
result = result..'\r\n|}'
return result
return result
end
end


function p.getFarmingPlotTable(frame)
function p.getMasteryCheckpointTable(frame)
local areaName = frame.args ~= nil and frame.args[1] or frame
local skillName = frame.args ~= nil and frame.args[1] or frame
local patches = nil
local skillID = Constants.getSkillID(skillName)
for i, area in Shared.skpairs(SkillData.Farming.Patches) do
if skillID == nil then
if area.areaName == areaName then
return Shared.printError('Failed to find a skill ID for ' .. skillName)
patches = area.patches
break
end
end
end
if patches == nil then
 
return "ERROR: Invalid area name.[[Category:Pages with script errors]]"
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
end


local result = '{|class="wikitable"'
local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
local checkpointPct = GameData.rawData.masteryCheckpoints
 
local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus'
for i, patch in Shared.skpairs(patches) do
for i, checkpointDesc in ipairs(checkpoints) do
result = result..'\r\n|-\r\n|'..i
result = result..'\r\n|-'
result = result..'||style="text-align:right;" data-sort-value="' .. patch.level .. '"|'..patch.level
result = result..'\r\n|'..checkpointPct[i]..'%||'
if patch.cost == 0 then
result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc
result = result..'||Free'
else
result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
end
end
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


-- Accepts 1 parameter, being either:
function p.getMasteryTokenTable()
--  'Smelting', for which a table of all bars is generated, or
-- Defines which skill levels should be included within the output
--  A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier
local skillLevels = {
function p.getSmithingTable(frame)
{
local tableType = frame.args ~= nil and frame.args[1] or frame
["id"] = 'Base',
tableType = Shared.splitString(tableType, ' ')[1]
["level"] = 99,
-- Translates Smithing category names to Smithing recipe data categories
["description"] = '[[Full Version|Base Game]] (Level 99)'
local categoryMap = {
}, {
['Smelting'] = 0,
["id"] = 'TotH',
['Bronze'] = 1,
["level"] = 120,
['Iron'] = 2,
["description"] = Icons.TotH() .. ' [[Throne of the Herald Expansion|Throne of the Herald]] (Level 120)'
['Steel'] = 3,
}
['Mithril'] = 4,
['Adamant'] = 5,
['Adamantite'] = 5,
['Rune'] = 6,
['Runite'] = 6,
['Dragon'] = 7,
['Dragonite'] = 7
}
}
local categoryID = categoryMap[tableType]
local baseTokenChance = 18500
if categoryID == nil then
local masteryActionCount = {}
return 'ERROR: Invalid Smithing category: "' .. tableType .. '"[[Category:Pages with script errors]]'
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


-- Build a list of recipes to be included, and a list of bars while we're at it
-- Iterate over each skill with mastery, determining the number of
-- The bar list will be used later for value/bar calculations
-- mastery actions for each
local recipeList, barIDList = {}, {}
for skillLocalID, skill in pairs(SkillData) do
for i, recipe in ipairs(SkillData.Smithing.Recipes) do
if skill.masteryTokenID ~= nil then
if recipe.category == categoryID then
local actCount = { ["skill"] = skill }
local recipeItem = Items.getItemByID(recipe.itemID)
for i, levelDef in ipairs(skillLevels) do
if recipeItem ~= nil then
actCount[levelDef.id] = 0
table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor })
end
end
elseif recipe.category == 0 then
 
barIDList[recipe.itemID] = true
local recipeKey = p.getSkillRecipeKey(skillLocalID)
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
table.insert(masteryActionCount, actCount)
end
end
end
end


local firstID = skillLevels[1].id
table.sort(masteryActionCount,
function(a, b)
if a[firstID] == b[firstID] then
return a.skill.name < b.skill.name
else
return a[firstID] > b[firstID]
end
end)
-- Generate output table
-- Generate output table
local resultPart = {}
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true})
table.insert(resultPart, '\r\n|-class="headerRow-0"')
local columnPairs = Shared.tableCount(skillLevels)
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
-- Generate header
if categoryID > 0 then
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '!!Value/Bar')
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
end
table.insert(resultPart, '\n|-' .. string.rep('\n!Without ' .. CCIIcon .. '\n!With ' .. CCIIcon, columnPairs))


table.sort(recipeList, function(a, b)
for i, rowData in ipairs(masteryActionCount) do
if a.level ~= b.level then
local token = Items.getItemByID(rowData.skill.masteryTokenID)
return a.level < b.level
table.insert(resultPart, '\n|-')
else
table.insert(resultPart, '\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
return a.itemName < b.itemName
table.insert(resultPart, '\n|' .. Icons.Icon({rowData.skill.name, type='skill'}))
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|-')
for j, levelDef in ipairs(skillLevels) do
table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true}))
local actCount = rowData[levelDef.id]
table.insert(resultPart, '\r\n| ')
local denom, denomCCI = 0, 0
if recipe.baseQuantity > 1 then
if actCount > 0 then
table.insert(resultPart, recipe.baseQuantity .. 'x ')
denom = math.floor(baseTokenChance / actCount)
end
denomCCI = Shared.round(baseTokenChance / (actCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 0)
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
end
table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barTxt)
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
end
end
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\n|}')


return table.concat(resultPart)
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
end


Line 284: 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.level)
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 310: 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 = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 100)), 0, 0)
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