|
|
(30 intermediate revisions by 2 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 |
| --Module:Prayer for Prayer | | --Module:Prayer for Prayer |
| --Module:Agility for Agility | | --Module:Skills/Agility for Agility |
| | --Module:Skills/Summoning for Summoning |
| --Module:Skills/Gathering for Mining, Fishing, Woodcutting | | --Module:Skills/Gathering for Mining, Fishing, Woodcutting |
| --Module:Skills/Artisan for Smithing, Cooking, Herblore, etc. | | --Module:Skills/Artisan for Smithing, Cooking, Herblore, etc. |
| | |
| | --Also be aware of: |
| | --Module:Navboxes for navigation boxes appearing near the bottom of pages |
|
| |
|
| local p = {} | | local p = {} |
|
| |
| local ItemData = mw.loadData('Module:Items/data')
| |
| local SkillData = mw.loadData('Module:Skills/data')
| |
| local Constants = mw.loadData('Module:Constants/data')
| |
|
| |
|
| local Shared = require('Module:Shared') | | local Shared = require('Module:Shared') |
| | local Constants = require('Module:Constants') |
| | local GameData = require('Module:GameData') |
| | local SkillData = GameData.skillData |
| local Items = require('Module:Items') | | local 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}
| | -- Thieving |
| | | function p.getThievingNPCByID(npcID) |
| function p.getSkillID(skillName) | | return GameData.getEntityByID(SkillData.Thieving.npcs, npcID) |
| for skName, ID in Shared.skpairs(Constants.skill) do
| |
| if skName == skillName then
| |
| return ID
| |
| end
| |
| end
| |
| return nil
| |
| end | | end |
|
| |
|
| function p.getSkillName(skillID) | | function p.getThievingNPC(npcName) |
| for skName, ID in Shared.skpairs(Constants.skill) do
| | return GameData.getEntityByName(SkillData.Thieving.npcs, npcName) |
| if ID == skillID then
| |
| return skName
| |
| end
| |
| end
| |
| return nil
| |
| end | | end |
|
| |
|
| function p.getThievingNPCByID(ID) | | function p.getThievingNPCArea(npc) |
| local result = Shared.clone(SkillData.Thieving[ID + 1])
| | for i, area in ipairs(SkillData.Thieving.areas) do |
| if result ~= nil then
| | for j, npcID in ipairs(area.npcIDs) do |
| result.id = ID
| | if npcID == npc.id then |
| end
| | return area |
| return result
| | end |
| | end |
| | end |
| end | | end |
|
| |
|
| function p.getThievingNPC(name) | | function p._getThievingNPCStat(npc, statName) |
| local result = nil
| | local result = nil |
| for i, npc in pairs(SkillData.Thieving) do
| |
| if name == npc.name then
| |
| result = Shared.clone(npc)
| |
| result.id = i - 1
| |
| break
| |
| end
| |
| end
| |
| return result
| |
| end
| |
|
| |
|
| function p.getThievingNPCStat(frame)
| | if statName == 'level' then |
| local args = frame.args ~= nil and frame.args or frame
| | result = Icons._SkillReq('Thieving', npc.level) |
| local npcName = args[1]
| | elseif statName == 'maxHit' then |
| local statName = args[2]
| | result = npc.maxHit * 10 |
| local npc = p.getThievingNPC(npcName)
| | elseif statName == 'area' then |
| if npc == nil then
| | local area = p.getThievingNPCArea(npc) |
| return 'ERROR: Failed to find Thieving NPC with name ' .. name .. '[[Category:Pages with script errors]]'
| | result = area.name |
| end
| | else |
|
| | result = npc[statName] |
| return p._getThievingNPCStat(npc, statName)
| | end |
| end | |
|
| |
|
| function p._getThievingNPCStat(npc, stat)
| | if result == nil then |
| local itemDropChance = 0.75
| | result = '' |
| local result = npc[stat]
| | end |
| -- Overrides below
| |
| if stat == 'maxHit' then
| |
| result = result * 10
| |
| elseif stat == 'lootList' then
| |
| return p._formatLootTable(npc['lootTable'], itemDropChance, true)
| |
| elseif stat == 'lootTable' then
| |
| return p._formatLootTable(npc['lootTable'], itemDropChance, false)
| |
| elseif stat == 'requirements' then
| |
| if npc['level'] ~= nil then
| |
| result = Icons._SkillReq('Thieving', npc['level'], true)
| |
| else
| |
| result = 'None'
| |
| end
| |
| elseif (stat == 'lootValue' or stat == 'pickpocketValue') then
| |
| if stat == 'pickpocketValue' then
| |
| local itemBP = Items.getItem("Bobby's Pocket")
| |
| result = (1 + npc['maxCoins']) / 2 + itemBP.sellsFor * (1 / 120)
| |
| else
| |
| result = 0
| |
| end
| |
| result = Shared.round(result + p._getLootTableValue(npc['lootTable']) * itemDropChance, 2, 2)
| |
| elseif stat == 'pageName' then
| |
| local linkOverrides = { ['Golbin'] = 'Golbin (thieving)' }
| |
| result = (linkOverrides[npc['name']] ~= nil and linkOverrides[npc['name']]) or npc['name']
| |
| end
| |
|
| |
|
| return result
| | return result |
| end | | end |
|
| |
|
| function p._getLootTableValue(lootTable) | | function p.getThievingNPCStat(frame) |
| -- Calculates the average GP value of a given loot table
| | local npcName = frame.args ~= nil and frame.args[1] or frame[1] |
| -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
| | local statName = frame.args ~= nil and frame.args[2] or frame[2] |
| if Shared.tableCount(lootTable) == 0 then
| | local npc = p.getThievingNPC(npcName) |
| return 0
| | if npc == nil then |
| end
| | return Shared.printError('Invalid Thieving NPC ' .. npcName) |
| | end |
|
| |
|
| local totalWeight = 0
| | return p._getThievingNPCStat(npc, statName) |
| for i, drop in pairs(lootTable) do
| | end |
| totalWeight = totalWeight + drop[2]
| |
| end
| |
| if totalWeight == 0 then
| |
| return 0
| |
| end
| |
|
| |
|
| local avgValue = 0
| | function p.getThievingSourcesForItem(itemID) |
| for i, drop in pairs(lootTable) do
| | local resultArray = {} |
| local item = Items.getItemByID(drop[1])
| | local areaNPCs = {} |
| if item ~= nil then
| |
| avgValue = avgValue + item.sellsFor * (drop[2] / totalWeight)
| |
| end
| |
| end
| |
|
| |
| return avgValue
| |
| end
| |
|
| |
|
| function p._formatLootTable(lootTableIn, chanceMultIn, asList)
| | --First check area unique drops |
| -- Expects lootTableIn to be in format {{itemID_1, itemWeight_1}, ..., {itemID_n, itemWeight_n}}
| | --If an area drops the item, add all the NPC ids to the list so we can add them later |
| if Shared.tableCount(lootTableIn) == 0 then
| | for i, area in pairs(SkillData.Thieving.areas) do |
| return ''
| | for j, drop in pairs(area.uniqueDrops) do |
| end
| | if drop.id == itemID then |
| | for k, npcID in ipairs(area.npcIDs) do |
| | areaNPCs[npcID] = drop.quantity |
| | end |
| | break |
| | end |
| | end |
| | end |
|
| |
|
| local chanceMult = (chanceMultIn or 1) * 100
| | --Now go through and get drop chances on each NPC if needed |
| local lootTable = Shared.clone(lootTableIn)
| | for i, npc in pairs(SkillData.Thieving.npcs) do |
| -- Sort table from most to least common drop
| | local totalWt = 0 |
| table.sort(lootTable, function(a, b)
| | local dropWt = 0 |
| if a[2] == b[2] then
| | local dropQty = { min = 0, max = 0 } |
| return a[1] < b[1]
| | for j, drop in ipairs(npc.lootTable) do |
| else
| | totalWt = totalWt + drop.weight |
| return a[2] > b[2]
| | if drop.itemID == itemID then |
| end
| | dropWt = drop.weight |
| end)
| | 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}) |
| | end |
|
| |
|
| local totalWeight = 0
| | --Chance of -1 on unique drops is to indicate variable chance |
| for i, drop in pairs(lootTable) do
| | if npc.uniqueDrop ~= nil and npc.uniqueDrop.id == itemID then |
| totalWeight = totalWeight + drop[2]
| | table.insert(resultArray, {npc = npc.name, minQty = npc.uniqueDrop.quantity, maxQty = npc.uniqueDrop.quantity, wt = -1, totalWt = -1, level = npc.level, npcID = npc.id}) |
| end
| | end |
| if totalWeight == 0 then
| |
| return ''
| |
| end
| |
|
| |
|
| -- Get the length (in characters) of the largest drop chance so that they can be right aligned
| | if areaNPCs[npc.id] ~= nil then |
| -- [4/16/21]: Adding info for no drop
| | table.insert(resultArray, {npc = npc.name, minQty = areaNPCs[npc.id], maxQty = areaNPCs[npc.id], wt = SkillData.Thieving.baseAreaUniqueChance, totalWt = 100, level = npc.level, npcID = npc.id}) |
| local maxDropLen = math.max(string.len(Shared.round(100 - chanceMult, 2, 2)), string.len(Shared.round(lootTable[1][2] / totalWeight * chanceMult, 2, 2)))
| | end |
| | end |
|
| |
|
| local returnPart = {}
| | for i, drop in ipairs(SkillData.Thieving.generalRareItems) do |
| -- Generate header
| | if drop.itemID == itemID then |
| if asList then
| | if drop.npcs == nil then |
| if chanceMult < 100 then
| | table.insert(resultArray, {npc = 'all', minQty = 1, maxQty = 1, wt = 1, totalWt = Shared.round2(1/(drop.chance/100), 0), level = 1, npcID = itemID}) |
| table.insert(returnPart, '* ' .. string.rep(' ', math.max(0, (maxDropLen - string.len(Shared.round(100 - chanceMult, 2, 2))) * 2)) .. Shared.round(100 - chanceMult, 2, 2) .. '% No Item')
| | else |
| end
| | for j, npcID in ipairs(drop.npcs) do |
| else
| | local npc = p.getThievingNPCByID(npcID) |
| table.insert(returnPart, '{|class="wikitable sortable"\r\n!Item!!Price!!colspan="2"|Chance')
| | if npc ~= nil then |
| end
| | 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}) |
| -- Generate row for each item
| | end |
| for i, drop in pairs(lootTable) do
| | end |
| local item, itemText, sellsFor, dropChance = Items.getItemByID(drop[1]), 'Unknown', 0, Shared.round(drop[2] / totalWeight * chanceMult, 2, 2)
| | end |
| if item ~= nil then
| | end |
| itemText, sellsFor = Icons.Icon({item.name, type='item'}), item.sellsFor
| | end |
| end
| |
| if asList then
| |
| table.insert(returnPart, '* ' .. string.rep(' ', math.max(0, (maxDropLen - string.len(dropChance)) * 2)) .. dropChance .. '% ' .. itemText)
| |
| else
| |
| table.insert(returnPart, '|-\r\n|' .. itemText)
| |
| table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. sellsFor .. '"|' .. Icons.GP(sellsFor))
| |
| table.insert(returnPart, '|style="text-align:right;" data-sort-value="' .. dropChance .. '"|' .. Shared.fraction(drop[2] * chanceMult, totalWeight * 100))
| |
| table.insert(returnPart, '|style="text-align:right;"|' .. dropChance .. '%')
| |
| end
| |
| end
| |
| if not asList then
| |
| table.insert(returnPart, '|-class="sortbottom" \r\n!colspan="2"|Total:')
| |
| local textTotChance = ''
| |
| if chanceMult < 100 then
| |
| textTotChance = '|style="text-align:right"|' .. Shared.fraction(chanceMult, 100) .. '\r\n|'
| |
| else
| |
| textTotChance = '|colspan="2" '
| |
| end
| |
| textTotChance = textTotChance .. 'style="text-align:right;"|' .. Shared.round(chanceMult, 2, 2) .. '%' .. '\r\n|}'
| |
| table.insert(returnPart, textTotChance)
| |
| end
| |
|
| |
|
| return table.concat(returnPart, '\r\n')
| | return resultArray |
| end | | end |
|
| |
|
| function p.getThievingNPCTable() | | -- Astrology |
| local returnPart = {}
| | function p.getConstellationByID(constID) |
| | return GameData.getEntityByID(SkillData.Astrology.recipes, constID) |
| | end |
|
| |
|
| -- Create table header
| | function p.getConstellation(constName) |
| table.insert(returnPart, '{| class="wikitable sortable stickyHeader"')
| | return GameData.getEntityByName(SkillData.Astrology.recipes, constName) |
| table.insert(returnPart, '|- class="headerRow-0"\r\n!Target!!Name!!' .. Icons.Icon({'Thieving', type='skill', notext=true}).. ' Level!!Experience!!Max Hit!!Max Coins!!<abbr title="Assumes all loot is sold, and no GP boosts apply (such as those from Mastery & Gloves of Silence)">GP/Theft</abbr>')
| | end |
|
| |
| -- Create row for each NPC
| |
| for i, npc in Shared.skpairs(SkillData.Thieving) do
| |
| local linkText = (npc.name ~= p._getThievingNPCStat(npc, 'pageName') and p._getThievingNPCStat(npc, 'pageName') .. '|' .. npc.name) or npc.name
| |
| table.insert(returnPart, '|-\r\n|style="text-align: left;" |' .. Icons.Icon({npc.name, type='thieving', size=50, notext=true}))
| |
| table.insert(returnPart, '|style="text-align: left;" |[[' .. linkText .. ']]')
| |
| table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'level'))
| |
| table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'xp'))
| |
| table.insert(returnPart, '|style="text-align: right;" |' .. p._getThievingNPCStat(npc, 'maxHit'))
| |
| table.insert(returnPart, '|style="text-align: right;" data-sort-value="' .. p._getThievingNPCStat(npc, 'maxCoins') .. '" |' .. Icons.GP(p._getThievingNPCStat(npc, 'maxCoins')))
| |
| table.insert(returnPart, '|style="text-align: right;" data-sort-value="' .. p._getThievingNPCStat(npc, 'pickpocketValue') .. '" |' .. Icons.GP(p._getThievingNPCStat(npc, 'pickpocketValue')))
| |
| end
| |
| table.insert(returnPart, '|}')
| |
|
| |
|
| return table.concat(returnPart, '\r\n')
| | function p.getConstellations(checkFunc) |
| | return GameData.getEntities(SkillData.Astrology.recipes, checkFunc) |
| end | | end |
|
| |
|
| function p.getThievingNavbox() | | -- For a given constellation cons and modifier value modValue, generates and returns |
| local returnPart = {}
| | -- 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 |
|
| |
|
| -- Create table header
| | local addToArray = function(modArray, modNew) |
| table.insert(returnPart, '{| class="wikitable" style="text-align:center; clear:both; margin:auto; margin-bottom:1em;"')
| | if not isDistinct or (isDistinct and not containsMod(modArray, modNew)) then |
| table.insert(returnPart, '|-\r\n!' .. Icons.Icon({'Thieving', type='skill', notext=true}) .. '[[Thieving|Thieving Targets]]')
| | table.insert(modArray, modNew) |
| table.insert(returnPart, '|-\r\n|')
| | end |
|
| | end |
| local npcList = {}
| |
| -- Create row for each NPC
| |
| for i, npc in Shared.skpairs(SkillData.Thieving) do
| |
| local linkText = (npc.name ~= p._getThievingNPCStat(npc, 'pageName') and p._getThievingNPCStat(npc, 'pageName') .. '|' .. npc.name) or npc.name
| |
| table.insert(npcList, Icons.Icon({npc.name, type='thieving', notext=true}) .. ' [[' .. linkText .. ']]')
| |
| end
| |
| table.insert(returnPart, table.concat(npcList, ' • '))
| |
| table.insert(returnPart, '|}')
| |
|
| |
|
| return table.concat(returnPart, '\r\n')
| | local modTypes = {} |
| end | | if includeStandard then |
| | table.insert(modTypes, 'standardModifiers') |
| | end |
| | if includeUnique then |
| | table.insert(modTypes, 'uniqueModifiers') |
| | end |
|
| |
|
| function p.getMasteryUnlockTable(frame)
| | local modArray = {} |
| local skillName = frame.args ~= nil and frame.args[1] or frame
| | local isSkillMod = {} |
| local skillID = p.getSkillID(skillName)
| | --Adding a Group Number to hold together different bonuses from the same modifier [Falterfire 22/10/27] |
| if skillID == nil then
| | local groupNum = 0 |
| return "ERROR: Failed to find a skill ID for "..skillName
| |
| end
| |
|
| |
|
| local unlockTable = SkillData.MasteryUnlocks[skillID]
| | for _, modType in ipairs(modTypes) do |
| if unlockTable == nil then
| | for i, modTypeData in ipairs(cons[modType]) do |
| return 'ERROR: Failed to find Mastery Unlock data for '..skillName
| | groupNum = groupNum + 1 |
| end
| | 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 |
|
| |
|
| local result = '{|class="wikitable"\r\n!Level!!Unlock'
| | if asKeyValue then |
| for i, unlock in Shared.skpairs(unlockTable) do
| | local modArrayKV = {} |
| result = result..'\r\n|-'
| | for i, modDefn in ipairs(modArray) do |
| result = result..'\r\n|'..unlock.level..'||'..unlock.unlock
| | local modName, modVal = modDefn[1], modDefn[2] |
| end
| | local isSkill = type(modVal) == 'table' and modVal.skillID ~= nil |
| result = result..'\r\n|}'
| | if modArrayKV[modName] == nil then |
| return result
| | 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.getMasteryCheckpointTable(frame) | | -- Mastery |
| local skillName = frame.args ~= nil and frame.args[1] or frame
| | function p.getMasteryUnlockTable(frame) |
| local skillID = p.getSkillID(skillName)
| | local skillName = frame.args ~= nil and frame.args[1] or frame |
| if skillID == nil then
| | local skillID = Constants.getSkillID(skillName) |
| return "ERROR: Failed to find a skill ID for "..skillName
| | if skillID == nil then |
| end
| | return Shared.printError('Failed to find a skill ID for ' .. skillName) |
| | | end |
| if SkillData.MasteryCheckpoints[skillID] == nil then
| |
| return 'ERROR: Failed to find Mastery Unlock data for '..skillName
| |
| end
| |
|
| |
|
| local bonuses = SkillData.MasteryCheckpoints[skillID].bonuses
| | local _, localSkillID = GameData.getLocalID(skillID) |
| local totalPoolXP = SkillData.MasteryPoolXP[skillID + 1]
| | -- 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!Pool %!!style="width:100px"|Pool XP!!Bonus'
| | local result = '{|class="wikitable"\r\n!Level!!Unlock' |
| for i, bonus in Shared.skpairs(bonuses) do
| | for i, unlock in ipairs(unlockTable) do |
| result = result..'\r\n|-'
| | result = result..'\r\n|-' |
| result = result..'\r\n|'..(MasteryCheckpoints[i] * 100)..'%||'
| | result = result..'\r\n|'..unlock.level..'||'..unlock.description |
| result = result..Shared.formatnum(totalPoolXP * MasteryCheckpoints[i])..' xp||'..bonus
| | end |
| end
| | result = result..'\r\n|}' |
| result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP'
| | return result |
| result = result..'\r\n|'..Shared.formatnum(totalPoolXP)
| |
| result = result..'\r\n|}'
| |
| return result
| |
| end | | end |
|
| |
|
| function p._getFarmingTable(category) | | function p.getMasteryCheckpointTable(frame) |
| local seedList = {}
| | local skillName = frame.args ~= nil and frame.args[1] or frame |
| if category == 'Allotment' or category == 'Herb' or category == 'Tree' then
| | local skillID = Constants.getSkillID(skillName) |
| seedList = Items.getItems(function(item) return item.tier == category end)
| | if skillID == nil then |
| else
| | return Shared.printError('Failed to find a skill ID for ' .. skillName) |
| return 'ERROR: Invalid farming category. Please choose Allotment, Herb, or Tree'
| | end |
| end
| |
|
| |
|
| local result = '{|class="wikitable sortable stickyHeader"'
| | local _, localSkillID = GameData.getLocalID(skillID) |
| result = result..'\r\n|- class="headerRow-0"'
| | local checkpoints = SkillData[localSkillID].masteryCheckpoints |
| result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
| | if checkpoints == nil then |
| result = result..'!!XP!!Growth Time!!Seed Value'
| | return Shared.printError('Failed to find Mastery Unlock data for ' .. skillName) |
| if category == 'Allotment' then
| | end |
| result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
| |
| elseif category == 'Herb' then
| |
| result = result..'!!colspan="2"|Herb!!Herb Value'
| |
| elseif category == 'Tree' then
| |
| result = result..'!!colspan="2"|Logs!!Log Value'
| |
| end
| |
| result = result..'!!Seed Sources'
| |
|
| |
| table.sort(seedList, function(a, b) return a.farmingLevel < b.farmingLevel end)
| |
|
| |
|
| for i, seed in pairs(seedList) do
| | local totalPoolXP = SkillData[localSkillID].baseMasteryPoolCap |
| result = result..'\r\n|-'
| | local checkpointPct = GameData.rawData.masteryCheckpoints |
| result = result..'\r\n|'..Icons.Icon({seed.name, type='item', size='50', notext=true})..'||[['..seed.name..']]'
| | local result = '{|class="wikitable"\r\n!Pool %!!style="width:100px"|Pool XP!!Bonus' |
| result = result..'||'..seed.farmingLevel..'||'..Shared.formatnum(seed.farmingXP)
| | for i, checkpointDesc in ipairs(checkpoints) do |
| result = result..'||data-sort-value="'..seed.timeToGrow..'"|'..Shared.timeString(seed.timeToGrow, true)
| | result = result..'\r\n|-' |
| result = result..'||data-sort-value="'..seed.sellsFor..'"|'..Icons.GP(seed.sellsFor)
| | result = result..'\r\n|'..checkpointPct[i]..'%||' |
| | | result = result..Shared.formatnum(math.floor(totalPoolXP * checkpointPct[i] / 100))..' xp||'..checkpointDesc |
| local crop = Items.getItemByID(seed.grownItemID)
| | end |
| result = result..'||'..Icons.Icon({crop.name, type='item', size='50', notext=true})..'||[['..crop.name..']]'
| | result = result..'\r\n|-\r\n!colspan="2"|Total Mastery Pool XP' |
| if category == 'Allotment' then
| | result = result..'\r\n|'..Shared.formatnum(totalPoolXP) |
| result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..(crop.healsFor * 10)
| | result = result..'\r\n|}' |
| end
| | return result |
| result = result..'||data-sort-value="'..crop.sellsFor..'"|'..Icons.GP(crop.sellsFor)
| |
| result = result..'||'..ItemSourceTables._getItemSources(seed)
| |
| end
| |
| | |
| result = result..'\r\n|}'
| |
| return result
| |
| end | | end |
|
| |
|
| function p.getFarmingTable(frame) | | function p.getMasteryTokenTable() |
| local category = frame.args ~= nil and frame.args[1] or frame
| | local baseTokenChance = 18500 |
| | local masterySkills = {} |
| | local CCI = Items.getItemByID('melvorD:Clue_Chasers_Insignia') |
| | if CCI == nil then return '' end |
|
| |
|
| return p._getFarmingTable(category)
| | -- Build table of mastery skills |
| end | | for skillLocalID, skill in pairs(SkillData) do |
| | if skill.masteryTokenID ~= nil then |
| | table.insert(masterySkills, skill) |
| | end |
| | end |
| | table.sort(masterySkills, |
| | function(a, b) |
| | if a.milestoneCount == b.milestoneCount then |
| | return a.name < b.name |
| | else |
| | return a.milestoneCount > b.milestoneCount |
| | end |
| | end) |
|
| |
|
| function p.getFarmingFoodTable(frame)
| | -- Generate output table |
| local result = '{| class="wikitable sortable stickyHeader"'
| | local resultPart = {} |
| result = result..'\r\n|- class="headerRow-0"'
| | local CCIIcon = Icons.Icon({CCI.name, type='item', notext=true}) |
| 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)
| | 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, item in Shared.skpairs(itemArray) do
| | for i, skill in ipairs(masterySkills) do |
| local crop = Items.getItemByID(item.grownItemID)
| | local token = Items.getItemByID(skill.masteryTokenID) |
| if crop.healsFor ~= nil and crop.healsFor > 0 then
| | local denom = math.floor(baseTokenChance / skill.milestoneCount) |
| result = result..'\r\n|-'
| | local denomCCI = Shared.round(baseTokenChance / (skill.milestoneCount * (1 + CCI.modifiers.increasedOffItemChance / 100)), 0, 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
| |
| 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
| |
|
| |
|
| result = result..'\r\n|}'
| | table.insert(resultPart, '\r\n|-') |
| | table.insert(resultPart, '\r\n|style="text-align:center"|' .. Icons.Icon({token.name, type='item', size=50, notext=true})) |
| | table.insert(resultPart, '\r\n|' .. Icons.Icon({skill.name, type='skill'})) |
| | table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denom .. '"|1/' .. Shared.formatnum(denom)) |
| | table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. denomCCI .. '"|1/' .. Shared.formatnum(denomCCI)) |
| | end |
| | table.insert(resultPart, '\r\n|}') |
|
| |
|
| return result
| | return table.concat(resultPart) |
| end | | end |
|
| |
|
| function p.getFarmingPlotTable(frame) | | -- Skill unlock costs for Adventure game mode |
| local areaName = frame.args ~= nil and frame.args[1] or frame
| | function p.getSkillUnlockCostTable() |
| local patches = nil
| | local advMode = GameData.getEntityByID('gamemodes', 'melvorF:Adventure') |
| for i, area in Shared.skpairs(SkillData.Farming.Patches) do
| | if advMode ~= nil then |
| if area.areaName == areaName then
| | local unlockCount = Shared.tableCount(GameData.skillData) - Shared.tableCount(advMode.startingSkills) |
| patches = area.patches
| | local costLength = Shared.tableCount(advMode.skillUnlockCost) |
| break
| | local returnPart = {} |
| end
| | table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost') |
| end
| |
| if patches == nil then
| |
| return "ERROR: Invalid area name.[[Category:Pages with script errors"
| |
| end
| |
| | |
| local result = '{|class="wikitable"'
| |
| result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
| |
|
| |
|
| for i, patch in Shared.skpairs(patches) do
| | local accCost = 0 |
| result = result..'\r\n|-\r\n|'..i
| | for i = 1, unlockCount, 1 do |
| result = result..'||style="text-align:right;" data-sort-value="0"|'..patch.level
| | local cost = advMode.skillUnlockCost[math.min(i, costLength)] |
| if patch.cost == 0 then
| | accCost = accCost + cost |
| result = result..'||Free'
| | table.insert(returnPart, '|-') |
| else
| | table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost)) |
| result = result..'||style="text-align:right;" data-sort-value="'..patch.cost..'"|'..Icons.GP(patch.cost)
| | end |
| end
| | table.insert(returnPart, '|}') |
| end
| |
|
| |
|
| result = result..'\r\n|}'
| | return table.concat(returnPart, '\r\n') |
| return result
| | end |
| end | | end |
|
| |
|
| | -- Accepts 1 parameter, being either: |
| | -- 'Bars', for which a table of all bars is generated, or |
| | -- A bar or tier name, which if valid generates a table of all smithing recipes using that bar/tier |
| function p.getSmithingTable(frame) | | function p.getSmithingTable(frame) |
| local tableType = frame.args ~= nil and frame.args[1] or frame
| | local tableType = frame.args ~= nil and frame.args[1] or frame |
| local bar = nil
| |
| if tableType ~= 'Smelting' then
| |
| bar = Items.getItem(tableType)
| |
| if bar == nil then
| |
| return 'ERROR: Could not find an item named '..tableType..' to build a smithing table with'
| |
| elseif bar.type ~= 'Bar' then
| |
| return 'ERROR: '..tableType.." is not a bar and thus can't be used for smithing"
| |
| end
| |
| end
| |
|
| |
|
| local smithList = {}
| | -- Has a valid category been passed (by name)? |
| for i, item in pairs(ItemData.Items) do
| | local category = GameData.getEntityByName(SkillData.Smithing.categories, tableType) |
| if item.smithingLevel ~= nil then
| | if category == nil then |
| if tableType == 'Smelting' then
| | return Shared.printError('Invalid Smithing category: "' .. tableType .. '"') |
| if item.type == 'Bar' then
| | end |
| table.insert(smithList, item)
| |
| end
| |
| else
| |
| for j, req in pairs(item.smithReq) do
| |
| if req.id == bar.id then
| |
| table.insert(smithList, item)
| |
| end
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
|
| local result = '{|class="wikitable sortable stickyHeader"'
| | -- Build a list of recipes to be included, and a list of bars while we're at it |
| result = result..'\r\n|-class="headerRow-0"'
| | -- The bar list will be used later for value/bar calculations |
| result = result..'\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients'
| | local recipeList, barIDList = {}, {} |
| --Adding value/bar for things other than smelting
| | for i, recipe in ipairs(SkillData.Smithing.recipes) do |
| if bar ~= nil then result = result..'!!Value/Bar' end
| | if recipe.categoryID == category.id then |
| | local recipeItem = Items.getItemByID(recipe.productID) |
| | if recipeItem ~= nil then |
| | table.insert(recipeList, { id = i, level = recipe.level, itemName = recipeItem.name, itemValue = recipeItem.sellsFor, expIcon = Icons.getExpansionIcon(recipeItem.id) }) |
| | end |
| | elseif recipe.categoryID == 'melvorD:Bars' then |
| | barIDList[recipe.productID] = true |
| | end |
| | end |
|
| |
|
| table.sort(smithList, function(a, b)
| | -- Generate output table |
| if a.smithingLevel ~= b.smithingLevel then
| | local resultPart = {} |
| return a.smithingLevel < b.smithingLevel
| | table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') |
| else
| | table.insert(resultPart, '\r\n|-class="headerRow-0"') |
| return a.name < b.name
| | table.insert(resultPart, '\r\n!Item!!Name!!'..Icons.Icon({'Smithing', type='skill', notext=true})..' Level!!XP!!Value!!Ingredients') |
| end end)
| | --Adding value/bar for things other than smelting |
| for i, item in Shared.skpairs(smithList) do
| | if category.id ~= 'melvorD:Bars' then |
| result = result..'\r\n|-'
| | table.insert(resultPart, '!!Value/Bar') |
| result = result..'\r\n|'..Icons.Icon({item.name, type='item', size='50', notext=true})..'||'
| | end |
| 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
| |
| --Add the data for the value per bar
| |
| if bar ~= nil then
| |
| if barQty == 0 then
| |
| result = result..'||data-sort-value="0"|N/A'
| |
| else
| |
| local barVal = totalValue / barQty
| |
| result = result..'||data-sort-value="'..barVal..'"|'..Icons.GP(Shared.round(barVal, 1, 1))
| |
| end
| |
| end
| |
| end
| |
|
| |
|
| result = result..'\r\n|}'
| | table.sort(recipeList, function(a, b) |
| return result
| | if a.level ~= b.level then |
| end | | return a.level < b.level |
| | else |
| | return a.itemName < b.itemName |
| | end |
| | end) |
|
| |
|
| function p.getFiremakingTable(frame)
| | for i, recipeDef in ipairs(recipeList) do |
| local resultPart = {}
| | local recipe = SkillData.Smithing.recipes[recipeDef.id] |
| table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
| | local totalValue = recipe.baseQuantity * recipeDef.itemValue |
| table.insert(resultPart, '\r\n|-class="headerRow-0"')
| | -- Determine the bar quantity & build the recipe cost string |
| table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level')
| | local barQty, costString = 0, {} |
| table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time')
| | for j, itemCost in ipairs(recipe.itemCosts) do |
| table.insert(resultPart, '\r\n|-class="headerRow-1"')
| | local costItem = Items.getItemByID(itemCost.id) |
| table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s')
| | if costItem ~= nil then |
| | table.insert(costString, Icons.Icon({costItem.name, type='item', qty=itemCost.quantity, notext=true})) |
| | end |
| | if barIDList[itemCost.id] then |
| | barQty = barQty + itemCost.quantity |
| | end |
| | end |
|
| |
|
| for i, logData in Shared.skpairs(SkillData.Firemaking) do
| | table.insert(resultPart, '\r\n|-') |
| local name = Shared.titleCase(logData.type..' Logs')
| | table.insert(resultPart, '\r\n| ' .. Icons.Icon({recipeDef.itemName, type='item', size=50, notext=true})) |
| local burnTime = logData.interval / 1000
| | table.insert(resultPart, '\r\n| ') |
| local XPS = logData.xp / burnTime
| | table.insert(resultPart, recipeDef.expIcon) |
| local XP_BF = logData.xp * (1 + logData.bonfireBonus / 100)
| | if recipe.baseQuantity > 1 then |
| local XPS_BF = XP_BF / burnTime
| | table.insert(resultPart, recipe.baseQuantity .. 'x ') |
| | end |
| | table.insert(resultPart, Icons.Icon({recipeDef.itemName, type='item', noicon=true})) |
| | table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.level .. '"| ' .. Icons._SkillReq('Smithing', recipe.level)) |
| | table.insert(resultPart, '\r\n|data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience)) |
| | table.insert(resultPart, '\r\n|data-sort-value="' .. totalValue .. '"| ' .. Icons.GP(recipeDef.itemValue)) |
| | if recipe.baseQuantity > 1 then |
| | table.insert(resultPart, ' (x' .. recipe.baseQuantity .. ')') |
| | end |
| | table.insert(resultPart, '\r\n| ' .. table.concat(costString, ', ')) |
| | if category.id ~= 'melvorD:Bars' then |
| | local barVal, barValTxt = 0, 'N/A' |
| | if barQty > 0 then |
| | barVal = totalValue / barQty |
| | barValTxt = Icons.GP(Shared.round(barVal, 1, 1)) |
| | end |
| | table.insert(resultPart, '\r\n|data-sort-value="' .. barVal .. '"| ' .. barValTxt) |
| | end |
| | end |
| | table.insert(resultPart, '\r\n|}') |
|
| |
|
| table.insert(resultPart, '\r\n|-')
| | return table.concat(resultPart) |
| table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true}))
| |
| table.insert(resultPart, '||[['..name..']]')
| |
| table.insert(resultPart, '||style ="text-align: right;"|'..logData.level)
| |
| table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true))
| |
| table.insert(resultPart, '||style ="text-align: right;"|'..logData.xp)
| |
| table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2))
| |
| table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0))
| |
| table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2))
| |
| table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireBonus..'"|'..logData.bonfireBonus..'%')
| |
| table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireInterval..'"|'..Shared.timeString(logData.bonfireInterval / 1000, true))
| |
| end
| |
| | |
| table.insert(resultPart, '\r\n|}')
| |
| return table.concat(resultPart)
| |
| end | | end |
|
| |
|
| function p.getMasteryTokenTable() | | function p.getFiremakingTable(frame) |
| local baseTokenChance = 18500
| | local resultPart = {} |
| local masterySkills = {}
| | table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') |
|
| | table.insert(resultPart, '\r\n|-class="headerRow-0"') |
| -- Find all mastery tokens
| | table.insert(resultPart, '\r\n!colspan="2" rowspan="2"|Logs!!rowspan="2"|'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level') |
| local masteryTokens = Items.getItems(function(item) return item.isToken ~= nil and item.skill ~= nil and item.isToken end)
| | table.insert(resultPart, '!!rowspan="2"|Burn Time!!colspan="2"|Without Bonfire!!colspan="2"|With Bonfire!!rowspan="2"|Bonfire Bonus!!rowspan="2"|Bonfire Time') |
| for i, item in pairs(masteryTokens) do
| | table.insert(resultPart, '\r\n|-class="headerRow-1"') |
| local milestones = SkillData.Milestones[item.skill + 1]
| | table.insert(resultPart, '\r\n!XP!!XP/s!!XP!!XP/s') |
| 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 = math.floor(baseTokenChance / m['milestoneCount'] * (1 - CCI.increasedItemChance / 100))
| |
| | |
| table.insert(resultPart, '\r\n|-')
| |
| table.insert(resultPart, '\r\n|' .. Icons.Icon({token.name, type='item', size=50, notext=true}))
| |
| table.insert(resultPart, '\r\n|' .. Icons.Icon({p.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()
| | for i, logData in ipairs(SkillData.Firemaking.logs) do |
| local returnPart = {}
| | local logs = Items.getItemByID(logData.logID) |
| table.insert(returnPart, '{| class="wikitable stickyHeader"\r\n|- class="headerRow-0"\r\n!Unlock!!Cost!!Cumulative Cost')
| | local name = logs.name |
| | local burnTime = logData.baseInterval / 1000 |
| | local bonfireTime = logData.baseBonfireInterval / 1000 |
| | local XPS = logData.baseExperience / burnTime |
| | local XP_BF = logData.baseExperience * (1 + logData.bonfireXPBonus / 100) |
| | local XPS_BF = XP_BF / burnTime |
|
| |
|
| local accCost = 0
| | table.insert(resultPart, '\r\n|-') |
| for i, cost in ipairs(SkillData.SkillUnlockCosts) do
| | table.insert(resultPart, '\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='item', size='50', notext=true})) |
| accCost = accCost + cost
| | table.insert(resultPart, '||'..Icons.getExpansionIcon(logs.id)..Icons.Icon({name, type='item', noicon=true})) |
| table.insert(returnPart, '|-')
| | table.insert(resultPart, '||style ="text-align: right;"|'..logData.level) |
| table.insert(returnPart, '|' .. i .. '||' .. Icons.GP(cost) .. '||' .. Icons.GP(accCost))
| | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..burnTime..'"|'..Shared.timeString(burnTime, true)) |
| end
| | table.insert(resultPart, '||style ="text-align: right;"|'..logData.baseExperience) |
| table.insert(returnPart, '|}')
| | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS..'"|'..Shared.round(XPS, 2, 2)) |
| | table.insert(resultPart, '||style ="text-align: right;"|'..Shared.round(XP_BF, 2, 0)) |
| | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..XPS_BF..'"|'..Shared.round(XPS_BF, 2, 2)) |
| | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..logData.bonfireXPBonus..'"|'..logData.bonfireXPBonus..'%') |
| | table.insert(resultPart, '||style ="text-align: right;" data-sort-value="'..bonfireTime..'"|'..Shared.timeString(bonfireTime, true)) |
| | end |
|
| |
|
| return table.concat(returnPart, '\r\n')
| | table.insert(resultPart, '\r\n|}') |
| | return table.concat(resultPart) |
| end | | end |
|
| |
|
| return p | | return p |