Anonymous

Module:Sandbox/Skills: Difference between revisions

From Melvor Idle
no edit summary
mNo edit summary
No edit summary
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:Sandbox/Items/data')
local SkillData = mw.loadData('Module:Sandbox/Skills/data')


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Constants = require('Module:Sandbox/Constants')
local Constants = require('Module:Constants')
local Items = require('Module:Sandbox/Items')
local GameData = require('Module:GameData')
local ItemSourceTables = require('Module:Sandbox/Items/SourceTables')
local SkillData = GameData.skillData
local Items = require('Module:Items')
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
if recipe.level ~= nil then
-- Obstacle
return recipe.level
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
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)
-- 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


table.sort(itemArray, function(a, b) return a.farmingLevel < b.farmingLevel end)
local addToArray = function(modArray, modNew)
if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then
table.insert(modArray, modNew)
end
end
 
local modTypes = {}
if includeStandard then
table.insert(modTypes, 'standardModifiers')
end
if includeUnique then
table.insert(modTypes, 'uniqueModifiers')
end
local masteryReq = {
['standardModifiers'] = { 1, 40, 80 },
['uniqueModifiers'] = { 20, 60, 99 }
}


for i, item in Shared.skpairs(itemArray) do
local modArray = {}
local crop = Items.getItemByID(item.grownItemID)
local isSkillMod = {}
if crop.healsFor ~= nil and crop.healsFor > 0 then
--Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27]
result = result..'\r\n|-'
local groupNum = 0
result = result..'\r\n|'..Icons.Icon({crop.name, type='item', notext='true', size='50'})..'||[['..crop.name..']]'
 
result = result..'||style="text-align:right;"|'..item.farmingLevel
for _, modType in ipairs(modTypes) do
result = result..'||style="text-align:right" data-sort-value="'..crop.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(crop.healsFor * 10)
for i, modTypeData in ipairs(cons[modType]) do
result = result..'||style="text-align:right" data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
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
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 463:
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 308: Line 488:
table.insert(resultPart, '\r\n|}')
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
return table.concat(resultPart)
end
function p.getMasteryTokenTable()
local baseTokenChance = 18500
local masterySkills = {}
-- Find all mastery tokens
local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
for i, item in pairs(masteryTokens) do
local milestones = SkillData.Milestones[item.skill + 1]
if milestones ~= nil then
table.insert(masterySkills, {tokenRef = i, skillID = item.skill, milestoneCount = milestones})
end
end
table.sort(masterySkills, function(a, b)
if a['milestoneCount'] == b['milestoneCount'] then
return a['skillID'] < b['skillID']
else
return a['milestoneCount'] > b['milestoneCount']
end
end)
-- Generate output table
local resultPart = {}
local CCI = Items.getItem('Clue Chasers Insignia')
local CCIIcon = Icons.Icon({'Clue Chasers Insignia', type='item', notext=true})
if CCI == nil then return '' end
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\r\n!rowspan="2"|Token!!rowspan="2"|Skill!!colspan="2"|Approximate Mastery Token Chance')
table.insert(resultPart, '\r\n|-\r\n!Without ' .. CCIIcon .. '!!With ' .. CCIIcon)
for i, m in ipairs(masterySkills) do
local token = masteryTokens[m.tokenRef]
local denom = math.floor(baseTokenChance / m['milestoneCount'])
local denomCCI = Shared.round(baseTokenChance / (m['milestoneCount'] * (1 + CCI.increasedItemChance / 100)), 0, 0)
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
table.insert(resultPart, '\r\n|' .. Icons.Icon({Constants.getSkillName(m['skillID']), type='skill'}))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.getSkillUnlockCostTable()
local returnPart = {}
table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
local accCost = 0
for i, cost in ipairs(SkillData.SkillUnlockCosts) do
accCost = accCost + cost
table.insert(returnPart, '|-')
table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
end
table.insert(returnPart, '|}')
return table.concat(returnPart, '\r\n')
end
end


return p
return p