Module:Skills/Gathering: Difference between revisions
From Melvor Idle
(getMiningOresTable: Further fixes for v1.0.2 data) |
(Use printError function) |
||
(41 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
--Splitting some functions into here to avoid bloating a single file | --Splitting some functions into here to avoid bloating a single file | ||
local p = {} | local p = {} | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
local Shared = require('Module:Shared') | local Shared = require('Module:Shared') | ||
local GameData = require('Module:GameData') | |||
local SkillData = GameData.skillData | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local Shop = require('Module:Shop') | |||
local Skills = require('Module:Skills') | |||
local ItemSourceTables = require('Module:Items/SourceTables') | |||
local | function p.getRecipeRequirements(skillName, recipe) | ||
local reqText = {} | |||
if recipe.level ~= nil then | |||
table.insert(reqText, Icons._SkillReq(skillName, recipe.level, false)) | |||
end | |||
end | if recipe.totalMasteryRequired ~= nil then | ||
table.insert(reqText, Shared.formatnum(recipe.totalMasteryRequired) .. ' ' .. Icons.Icon({skillName, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'})) | |||
end | end | ||
if recipe.shopItemPurchased ~= nil then | |||
local purchReq = Shop.getPurchaseByID(recipe.shopItemPurchased) | |||
if purchReq ~= nil then | |||
table.insert(reqText, Shop._getPurchaseIcon({purchReq})) | |||
if | |||
table.insert( | |||
end | end | ||
end | end | ||
return | return table.concat(reqText, '<br/>') | ||
end | end | ||
function p. | function p.getTreesTable(frame) | ||
local | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable"') | |||
table.insert(resultPart, '\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\n!colspan="2"|Tree!!colspan="2"|Logs!!Requirements') | |||
table.insert(resultPart, '!!XP!!Cut Time!!XP/s!!GP/s') | |||
local | for i, tree in ipairs(SkillData.Woodcutting.trees) do | ||
local log = Items.getItemByID(tree.productId) | |||
table.insert(resultPart, '\n|-') | |||
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..tree.name..'"| '..Icons.Icon({log.name, img=tree.name, type='tree', notext=true, size=50})) | |||
table.insert(resultPart, '\n|data-sort-value="'..tree.name..'"|'..Icons.getExpansionIcon(tree.id)..tree.name) | |||
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..log.name..'"| '..Icons.Icon({log.name, type='item', notext=true, size=50})) | |||
table.insert(resultPart, '\n| '..Icons.Icon({log.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\n|data-sort-value="' .. tree.level .. '"| ' .. p.getRecipeRequirements(SkillData.Woodcutting.name, tree)) | |||
table.insert(resultPart, '\n|style="text-align:right"| '..tree.baseExperience) | |||
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..tree.baseInterval..'"| '..Shared.timeString(tree.baseInterval/1000, true)) | |||
local XPs = tree.baseExperience / (tree.baseInterval / 1000) | |||
local GPs = log.sellsFor / (tree.baseInterval / 1000) | |||
table.insert(resultPart, '\n|style="text-align:right"| '..Shared.round(XPs, 2, 2)) | |||
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..GPs..'"| '..Icons.GP(Shared.round(GPs, 2, 2))) | |||
local | |||
end | end | ||
table.insert(resultPart, '\n|}') | |||
return | return table.concat(resultPart) | ||
end | end | ||
function p. | function p.getSpecialFishingTable(frame) | ||
local | local totalWt, lootValue = 0, 0 | ||
local itemArray = GameData.getEntities(SkillData.Fishing.specialItems, function(item) return true end) | |||
for i, itemDef in ipairs(itemArray) do | |||
totalWt = totalWt + itemDef.weight | |||
end | end | ||
-- Sort the loot table by weight in descending order | |||
table.sort(itemArray, function(a, b) return a.weight > b.weight end) | |||
local | local resultPart = {} | ||
table.insert(resultPart, '\r\n{|class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|- class="headerRow-0"\r\n!colspan="2"| Item\r\n!Value\r\n!colspan="2"|Chance') | |||
for i, itemDef in ipairs(itemArray) do | |||
local item = Items.getItemByID(itemDef.itemID) | |||
if item ~= nil then | |||
local dropChance = itemDef.weight / totalWt * 100 | |||
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places | |||
local fmt = (dropChance < 0.10 and '%.2g') or '%.2f' | |||
table.insert(resultPart, '\r\n|-\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true})) | |||
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor))) | |||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. itemDef.weight .. '"| ' .. Shared.fraction(itemDef.weight, totalWt)) | |||
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. string.format(fmt, dropChance) .. '%') | |||
lootValue = lootValue + (dropChance / 100 * item.sellsFor) | |||
end | end | ||
end | end | ||
table.insert(resultPart, '\r\n|}\r\nThe average value of a roll on the special fishing loot table is ' .. Icons.GP(Shared.round(lootValue, 2, 0))) | |||
return table.concat(resultPart) | |||
return | |||
end | end | ||
function p. | function p.getFishingJunkTable(frame) | ||
local | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!colspan="2"|Item!!Value') | |||
local | local itemArray = {} | ||
for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do | |||
for i, | local item = Items.getItemByID(itemID) | ||
if item ~= nil then | |||
table.insert(itemArray, item) | |||
if | |||
end | end | ||
end | end | ||
table.sort(itemArray, function(a, b) return a.name < b.name end) | |||
for i, item in ipairs(itemArray) do | |||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true, size=50})) | |||
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor))) | |||
for i, | |||
end | end | ||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
return | |||
end | end | ||
function p. | function p.getMiningOresTable(frame) | ||
local | local resultPart = {} | ||
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\n!colspan="2"|Rock!!colspan=2|Ore!!Type!!Requirements') | |||
table.insert(resultPart, '!!XP!!Respawn Time!!Ore Value') | |||
local mineData = GameData.getEntities(SkillData.Mining.rockData, function(rock) return true end) | |||
table.sort(mineData, function(a, b) return a.level < b.level end) | |||
local | for i, oreData in ipairs(mineData) do | ||
local ore = Items.getItemByID(oreData.productId) | |||
local respawnStyle, respawnSort, respawnText = 'class="table-na"', 0, 'N/A' | |||
if oreData.hasPassiveRegen then | |||
respawnStyle = 'style="text-align:right;' | |||
respawnSort = oreData.baseRespawnInterval / 1000 | |||
respawnText = Shared.timeString(respawnSort, true) | |||
end | |||
local rockName = Icons.Icon({oreData.name, type='rock', noicon=true, nolink=true}) | |||
local qtyText = (oreData.baseQuantity > 1 and '<b>' .. oreData.baseQuantity .. 'x</b> ') or '' | |||
table.insert(resultPart, '\n|-\n|class="table-img" data-sort-value="' .. rockName .. '"| '..Icons.Icon({oreData.name, type='rock', size='50', notext=true, nolink=true})) | |||
table.insert(resultPart, '\n| data-sort-value="' ..rockName.. '"|'..Icons.getExpansionIcon(oreData.id) .. rockName) | |||
table.insert(resultPart, '\n|class="table-img" data-sort-value="' .. ore.name .. '"| '..Icons.Icon({ore.name, type='item', size='50', notext=true})) | |||
table.insert(resultPart, '\n| ' .. qtyText .. Icons.Icon({ore.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\n| ' .. oreData.type) | |||
table.insert(resultPart, '\n|data-sort-value="' .. oreData.level ..'"| '..p.getRecipeRequirements(SkillData.Mining.name, oreData)) | |||
table.insert(resultPart, '\n|style="text-align:right"| '..oreData.baseExperience) | |||
table.insert(resultPart, '\n|' .. respawnStyle .. ' data-sort-value="'..respawnSort..'"| ' .. respawnText) | |||
table.insert(resultPart, '\n|data-sort-value="'..ore.sellsFor..'"| '..Icons.GP(ore.sellsFor)) | |||
end | end | ||
return | table.insert(resultPart, '\n|}') | ||
return table.concat(resultPart) | |||
end | end | ||
function p. | function p._getMiningGemsTable(gemType) | ||
if type(gemType) ~= 'string' then | |||
gemType = 'Normal' | |||
end | |||
local validTypes = { | |||
local | ["Normal"] = 'randomGems', | ||
["Superior"] = 'randomSuperiorGems' | |||
} | |||
local gemDataKey = validTypes[gemType] | |||
if gemDataKey == nil then | |||
return Shared.printError('No such gem type "' .. gemType .. '"') | |||
end | |||
local gemData = GameData.rawData[gemDataKey] | |||
local totalWeight = 0 | |||
for i, gem in ipairs(gemData) do | |||
totalWeight = totalWeight + gem.weight | |||
end | end | ||
local resultPart = {} | |||
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\n!colspan=2|Gem!!Gem Chance!!Gem Price') | |||
for i, gem in ipairs(gemData) do | |||
local gemItem = Items.getItemByID(gem.itemID) | |||
local gemPct = gem.weight / totalWeight * 100 | |||
table.insert(resultPart, '\n|-\n|class="table-img"| ') | |||
table.insert(resultPart, Icons.Icon({gemItem.name, type='item', size='50', notext=true})) | |||
table.insert(resultPart, '\n| data-sort-value="'..gemItem.name..'"|'..Icons.getExpansionIcon(gemItem.id) .. Icons.Icon({gemItem.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. gemPct .. '" | ' .. string.format("%.1f%%", gemPct)) | |||
table.insert(resultPart, '\n|data-sort-value="' .. gemItem.sellsFor .. '"| ' .. Icons.GP(gemItem.sellsFor)) | |||
for i, | |||
local | |||
end | end | ||
table.insert(resultPart, '\n|}') | |||
return | return table.concat(resultPart) | ||
end | end | ||
function p.getMiningGemsTable(frame) | function p.getMiningGemsTable(frame) | ||
local | local gemType = frame.args ~= nil and frame.args[1] or frame | ||
return p._getMiningGemsTable(gemType) | |||
end | end | ||
function p.getFishTable(frame) | function p.getFishTable(frame) | ||
local | local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(fish) return true end) | ||
table.sort(recipeList, function(a, b) return a.level < b.level end) | |||
-- Determine cooking levels for all fish | |||
local cookReq = {} | |||
for i, recipe in ipairs(SkillData.Cooking.recipes) do | |||
-- This assumes that each raw fish only appears in a single recipe, which is a bit rubbish | |||
-- but currently holds | |||
for j, mat in ipairs(recipe.itemCosts) do | |||
if cookReq[mat.id] == nil then | |||
cookReq[mat.id] = recipe.level | |||
end | |||
end | |||
end | |||
local | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!Fish\r\n!Name\r\n!' .. Icons.Icon({'Fishing', type='skill', notext=true}) .. ' Level\r\n!Catch Time') | |||
table.insert(resultPart, '\r\n!XP\r\n!Value\r\n!XP/s\r\n!GP/s') | |||
table.insert(resultPart, '\r\n!' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' Level') | |||
for i, fish | for i, recipe in ipairs(recipeList) do | ||
local fish = Items.getItemByID(recipe.productId) | |||
if fish ~= nil then | |||
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000 | |||
local timeStr = string.format("%.1fs - %.1fs", recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000) | |||
local XPs = recipe.baseExperience / timeSortVal | |||
local GPs = fish.sellsFor / timeSortVal | |||
local cookSortVal = cookReq[recipe.productId] or 0 | |||
local cookStyle = (cookReq[recipe.productId] ~= nil and 'style="text-align:right" ' or 'class="table-na" ') | |||
local cookStr = cookReq[recipe.productId] or 'N/A' | |||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({fish.name, type='item', size='50', notext=true})) | |||
table.insert(resultPart, '\r\n|data-sort-value="'..fish.name..'"|'..Icons.getExpansionIcon(fish.id) .. Icons.Icon({fish.name, type='item', noicon=true})) | |||
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. recipe.level) | |||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. timeSortVal .. '"| ' .. timeStr) | |||
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience)) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. fish.sellsFor .. '"| ' .. Icons.GP(fish.sellsFor)) | |||
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. Shared.round(XPs, 2, 2)) | |||
table.insert(resultPart, '\r\n|data-sort-value="' .. GPs .. '"|' .. Icons.GP(Shared.round(GPs, 2, 2))) | |||
table.insert(resultPart, '\r\n|' .. cookStyle .. 'data-sort-value="' .. cookSortVal .. '"| ' .. cookStr) | |||
end | end | ||
end | end | ||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
return | |||
end | end | ||
function p.getFishingAreasTable(frame) | function p.getFishingAreasTable(frame) | ||
local result = '{| class="wikitable sortable stickyHeader"' | local result = '{| class="wikitable sortable stickyHeader"' | ||
result = result..'\r\n|- class="headerRow-0"' | result = result..'\r\n|- class="headerRow-0"' | ||
Line 325: | Line 244: | ||
result = result..'\r\n!Junk Chance\r\n!Special Chance' | result = result..'\r\n!Junk Chance\r\n!Special Chance' | ||
for i, area in | for i, area in ipairs(SkillData.Fishing.areas) do | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n| style ="text-align: left;" |'..area.name | result = result..'\r\n| style ="text-align: left;" |'..area.name | ||
local fishArray = {} | local fishArray = {} | ||
for j, | for j, fishID in ipairs(area.fishIDs) do | ||
local | local fishItem = Items.getItemByID(fishID) | ||
if fishItem ~= nil then | |||
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'})) | |||
end | |||
end | end | ||
result = result..'\r\n|'..table.concat(fishArray, '<br />') | result = result..'\r\n|'..table.concat(fishArray, '<br/>') | ||
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%' | result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%' | ||
Line 346: | Line 266: | ||
end | end | ||
function p. | function p.getThievingGeneralRareTable(frame) | ||
return p._getThievingGeneralRareTable() | |||
return | |||
end | end | ||
function p. | function p._getThievingGeneralRareTable(npcID) | ||
local rareTxt = '{|class="wikitable sortable"' | local rareTxt = '{|class="wikitable sortable"' | ||
rareTxt = rareTxt..'\r\n!Item!!Qty' | rareTxt = rareTxt..'\r\n!Item!!Qty' | ||
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance' | rareTxt = rareTxt..'!!Price!!colspan="2"|Chance' | ||
for i, drop in | for i, drop in ipairs(SkillData.Thieving.generalRareItems) do | ||
local thisItem = Items.getItemByID(drop.itemID) | -- If an npcID has been passed and the item is NPC specific, only display | ||
-- the item if it may be obtained while pickpocketing that NPC | |||
if npcID == nil or drop.npcs == nil or Shared.contains(drop.npcs, npcID) then | |||
local thisItem = Items.getItemByID(drop.itemID) | |||
local odds = drop.chance | |||
rareTxt = rareTxt..'\r\n|-\r\n|data-sort-value="'..thisItem.name..'"|'..Icons.getExpansionIcon(thisItem.id)..Icons.Icon({thisItem.name, type='item'}) | |||
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor) | |||
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0)) | |||
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%' | |||
end | |||
end | end | ||
rareTxt = rareTxt..'\r\n|}' | rareTxt = rareTxt..'\r\n|}' | ||
Line 435: | Line 303: | ||
--(Skip if no loot) | --(Skip if no loot) | ||
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then | if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then | ||
local normalTxt = '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:' | local normalTxt = {} | ||
table.insert(normalTxt, '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:') | |||
local totalWt = 0 | local totalWt = 0 | ||
local lootChance = | local lootChance = SkillData.Thieving.itemChance | ||
local lootValue = 0 | local lootValue = 0 | ||
--First loop through to get the total weight so we have it for later | --First loop through to get the total weight so we have it for later | ||
for i, loot in pairs(npc.lootTable) do | for i, loot in pairs(npc.lootTable) do | ||
totalWt = totalWt + loot | totalWt = totalWt + loot.weight | ||
end | end | ||
normalTxt | table.insert(normalTxt, '\r\n{|class="wikitable sortable"') | ||
normalTxt | table.insert(normalTxt, '\r\n!Item!!Qty') | ||
normalTxt | table.insert(normalTxt, '!!Price!!colspan="2"|Chance') | ||
local lootTable = GameData.getEntities(npc.lootTable, function(loot) return true end) | |||
--Then sort the loot table by weight | --Then sort the loot table by weight | ||
table.sort( | table.sort(lootTable, function(a, b) return a.weight > b.weight end) | ||
for i, | for i, loot in ipairs(lootTable) do | ||
local thisItem = Items.getItemByID( | local thisItem = Items.getItemByID(loot.itemID) | ||
if thisItem ~= nil then | if thisItem ~= nil then | ||
normalTxt | table.insert(normalTxt, '\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})) | ||
else | else | ||
normalTxt | table.insert(normalTxt, '\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]') | ||
end | end | ||
normalTxt | table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..(loot.minQuantity + loot.maxQuantity)..'"|') | ||
if | if loot.minQuantity ~= loot.maxQuantity then | ||
normalTxt | table.insert(normalTxt, Shared.formatnum(loot.minQuantity) .. ' - ' .. Shared.formatnum(loot.maxQuantity)) | ||
else | |||
table.insert(normalTxt, Shared.formatnum(loot.maxQuantity)) | |||
end | end | ||
--Adding price columns | --Adding price columns | ||
local itemPrice = 0 | local itemPrice = 0 | ||
if thisItem == nil then | if thisItem == nil then | ||
normalTxt | table.insert(normalTxt, '||data-sort-value="0"|???') | ||
else | else | ||
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0 | itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0 | ||
if itemPrice == 0 or | if itemPrice == 0 or loot.minQuantity == loot.maxQuantity then | ||
normalTxt | table.insert(normalTxt, '||'..Icons.GP(itemPrice)) | ||
else | else | ||
normalTxt | table.insert(normalTxt, '||'..Icons.GP(itemPrice * loot.minQuantity, itemPrice * loot.maxQuantity)) | ||
end | end | ||
end | end | ||
--Getting the drop chance | --Getting the drop chance | ||
local dropChance = ( | local dropChance = (loot.weight / totalWt * lootChance) | ||
if dropChance | if dropChance < 100 then | ||
--Show fraction as long as it isn't going to be 1/1 | --Show fraction as long as it isn't going to be 1/1 | ||
normalTxt | table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..loot.weight..'"') | ||
normalTxt | table.insert(normalTxt, '|'..Shared.fraction(loot.weight * lootChance, totalWt * 100)) | ||
normalTxt | table.insert(normalTxt, '||') | ||
else | else | ||
normalTxt | table.insert(normalTxt, '||colspan="2" data-sort-value="'..loot.weight..'"') | ||
end | end | ||
normalTxt | table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%') | ||
--Adding to the average loot value based on price & dropchance | --Adding to the average loot value based on price & dropchance | ||
lootValue = lootValue + (dropChance * 0.01 * itemPrice * ( | lootValue = lootValue + (dropChance * 0.01 * itemPrice * (loot.minQuantity + loot.maxQuantity) / 2) | ||
end | end | ||
if Shared.tableCount(npc.lootTable) > 1 then | if Shared.tableCount(npc.lootTable) > 1 then | ||
normalTxt | table.insert(normalTxt, '\r\n|-class="sortbottom" \r\n!colspan="3"|Total:') | ||
if lootChance < 100 then | if lootChance < 100 then | ||
normalTxt | table.insert(normalTxt, '\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||') | ||
else | else | ||
normalTxt | table.insert(normalTxt, '\r\n|colspan="2" ') | ||
end | end | ||
normalTxt | table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%') | ||
end | end | ||
normalTxt | table.insert(normalTxt, '\r\n|}') | ||
table.insert(sectionTxt, normalTxt) | table.insert(normalTxt, '\r\nThe loot obtained from the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue, 2, 2)) .. ' if sold.') | ||
table.insert(normalTxt, '\r\n\r\nIncluding GP, the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue + (1 + npc.maxGP) / 2, 2, 2)) .. '.') | |||
table.insert(sectionTxt, table.concat(normalTxt)) | |||
end | end | ||
--After normal drops, add in rare drops | --After normal drops, add in rare drops | ||
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket' | local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket:' | ||
rareTxt = rareTxt..'\r\n'..p. | rareTxt = rareTxt..'\r\n'..p._getThievingGeneralRareTable(npc.id) | ||
table.insert(sectionTxt, rareTxt) | table.insert(sectionTxt, rareTxt) | ||
Line 517: | Line 389: | ||
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.' | areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.' | ||
local area = | local area = Skills.getThievingNPCArea(npc) | ||
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"' | areaTxt = areaTxt..'\r\n{|class="wikitable sortable"' | ||
areaTxt = areaTxt..'\r\n!Item!!Qty' | areaTxt = areaTxt..'\r\n!Item!!Qty' | ||
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance' | areaTxt = areaTxt..'!!Price!!colspan="2"|Chance' | ||
local dropLines = {} | local dropLines = {} | ||
for i, drop in | for i, drop in ipairs(area.uniqueDrops) do | ||
local thisItem = Items.getItemByID(drop. | local thisItem = Items.getItemByID(drop.id) | ||
local lineTxt = '' | local lineTxt = '' | ||
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}) | ||
lineTxt = lineTxt..'||'..drop. | lineTxt = lineTxt..'||data-sort-value="'..drop.quantity..'"| '..Shared.formatnum(drop.quantity)..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor) | ||
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/( | lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100)) | ||
lineTxt = lineTxt..'||'..Shared.round( | lineTxt = lineTxt..'||'..Shared.round(SkillData.Thieving.baseAreaUniqueChance, 2, 2)..'%' | ||
dropLines[thisItem.name] = lineTxt | dropLines[thisItem.name] = lineTxt | ||
end | end | ||
for i, txt in | for i, txt in pairs(dropLines) do | ||
areaTxt = areaTxt..txt | areaTxt = areaTxt..txt | ||
end | end | ||
areaTxt = areaTxt..'\r\n|}' | areaTxt = areaTxt..'\r\n|}' | ||
table.insert(sectionTxt, areaTxt) | table.insert(sectionTxt, areaTxt) | ||
if npc.uniqueDrop ~= nil and npc.uniqueDrop. | if npc.uniqueDrop ~= nil and npc.uniqueDrop.id ~= nil then | ||
local uniqueTxt = '===Possible NPC Unique Drop===' | local thisItem = Items.getItemByID(npc.uniqueDrop.id) | ||
if thisItem ~= nil then | |||
local uniqueTxt = '===Possible NPC Unique Drop===' | |||
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.' | |||
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.' | |||
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is ' | |||
if npc.uniqueDrop.quantity > 1 then | |||
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.quantity}) .. '.' | |||
else | |||
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'}) .. '.' | |||
end | |||
table.insert(sectionTxt, uniqueTxt) | |||
end | end | ||
end | end | ||
Line 561: | Line 430: | ||
function p.getThievingNPCLootTables(frame) | function p.getThievingNPCLootTables(frame) | ||
local npcName = frame.args ~= nil and frame.args[1] or frame | local npcName = frame.args ~= nil and frame.args[1] or frame | ||
local npc = | local npc = Skills.getThievingNPC(npcName) | ||
if npc == nil then | if npc == nil then | ||
return | return Shared.printError('Invalid Thieving NPC "' .. npcName .. '"') | ||
end | end | ||
Line 573: | Line 442: | ||
result = result..'\r\n|- class="headerRow-0"' | result = result..'\r\n|- class="headerRow-0"' | ||
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!GP!!Unique Drop' | result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!GP!!Unique Drop' | ||
local npcArray = | local npcArray = GameData.getEntities(SkillData.Thieving.npcs, function(npc) return true end) | ||
table.sort(npcArray, function(a, b) return a.level < b.level end) | table.sort(npcArray, function(a, b) return a.level < b.level end) | ||
for i, npc in | for i, npc in ipairs(npcArray) do | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true}) | result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true}) | ||
result = result..'||'..Icons.Icon({npc.name, type='thieving', noicon=true}) | result = result..'||data-sort-value="'..npc.name..'"|'..Icons.getExpansionIcon(npc.id)..Icons.Icon({npc.name, type='thieving', noicon=true}) | ||
local area = | local area = Skills.getThievingNPCArea(npc) | ||
result = result..'||'..area.name | result = result..'||'..area.name | ||
result = result..'||'..Icons._SkillReq('Thieving', npc.level) | result = result..'||data-sort-value="' .. npc.level .. '"| '..Icons._SkillReq('Thieving', npc.level) | ||
result = result..'||style="text-align:right"|'..npc. | result = result..'||style="text-align:right"|'..npc.baseExperience | ||
result = result..'||style="text-align:right"|'..(npc.maxHit * 10) | result = result..'||style="text-align:right"|'..(npc.maxHit * 10) | ||
result = result..'||style="text-align:right"|'..npc.perception | result = result..'||style="text-align:right" data-sort-value="' .. npc.perception .. '| '..Shared.formatnum(npc.perception) | ||
result = result..'||data-sort-value="' .. npc.maxGP .. '"|'..Icons.GP(1, npc.maxGP) | result = result..'||data-sort-value="' .. npc.maxGP .. '"|'..Icons.GP(1, npc.maxGP) | ||
if npc.uniqueDrop ~= nil | if npc.uniqueDrop ~= nil then | ||
local uniqueDrop = Items.getItemByID(npc.uniqueDrop. | local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id) | ||
if npc.uniqueDrop. | if npc.uniqueDrop.quantity > 1 then | ||
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop. | result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.quantity}) | ||
else | else | ||
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'}) | result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'}) | ||
Line 609: | Line 478: | ||
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops') | table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops') | ||
for i, area in ipairs(SkillData.Thieving.areas) do | |||
for i, area in ipairs( | |||
local minLevel, npcList, areaItemList = nil, {}, {} | local minLevel, npcList, areaItemList = nil, {}, {} | ||
-- Build NPC list & determine level for area, this is the minimum | -- Build NPC list & determine level for area, this is the minimum | ||
-- Thieving level required for all NPCs within that area | -- Thieving level required for all NPCs within that area | ||
if area. | if area.npcIDs ~= nil and not Shared.tableIsEmpty(area.npcIDs) then | ||
for j, npcID in ipairs(area. | for j, npcID in ipairs(area.npcIDs) do | ||
local npc = Skills.getThievingNPCByID(npcID) | |||
local npc = | |||
if minLevel == nil or npc.level < minLevel then | if minLevel == nil or npc.level < minLevel then | ||
minLevel = npc.level | minLevel = npc.level | ||
Line 631: | Line 497: | ||
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then | if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then | ||
for k, drop in ipairs(area.uniqueDrops) do | for k, drop in ipairs(area.uniqueDrops) do | ||
local areaItem = Items.getItemByID(drop. | local areaItem = Items.getItemByID(drop.id) | ||
if areaItem == nil then | if areaItem == nil then | ||
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]') | table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]') | ||
else | else | ||
local iconDef = {areaItem.name, type='item'} | local iconDef = {areaItem.name, type='item'} | ||
if drop. | if drop.quantity > 1 then | ||
iconDef.qty = drop. | iconDef.qty = drop.quantity | ||
end | end | ||
table.insert(areaItemList, Icons.Icon(iconDef)) | table.insert(areaItemList, Icons.Icon(iconDef)) | ||
Line 658: | Line 524: | ||
end | end | ||
function p. | function p._getFarmingTable(categoryName) | ||
local | local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName) | ||
if category == nil then | |||
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, or Trees') | |||
end | |||
local seedList = GameData.getEntities(SkillData.Farming.recipes, | |||
function(recipe) | |||
return recipe.categoryID == category.id | |||
end) | |||
if Shared.tableIsEmpty(seedList) then | |||
return '' | |||
end | |||
local result = '{|class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level' | |||
result = result..'!!XP!!Growth Time!!Seed Value' | |||
if category.id == 'melvorD:Allotment' then | |||
result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value' | |||
elseif category.id == 'melvorD:Herb' then | |||
result = result..'!!colspan="2"|Herb!!Herb Value' | |||
elseif category.id == 'melvorD:Tree' then | |||
result = result..'!!colspan="2"|Logs!!Log Value' | |||
end | |||
result = result..'!!Seed Sources' | |||
table.sort(seedList, function(a, b) return a.level < b.level end) | |||
- | for i, seed in ipairs(seedList) do | ||
local seedItem = Items.getItemByID(seed.seedCost.id) | |||
local productItem = Items.getItemByID(seed.productId) | |||
if seedItem ~= nil and productItem ~= nil then | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..Icons.Icon({seedItem.name, type='item', size='50', notext=true}) | |||
result = result..'|| ' .. Icons.getExpansionIcon(seedItem.id) .. Icons.Icon({seedItem.name, type='item', noicon=true}) | |||
result = result..'||'..seed.level..'||'..Shared.formatnum(seed.baseExperience) | |||
result = result..'||data-sort-value="'..(seed.baseInterval / 1000)..'"|'..Shared.timeString(seed.baseInterval / 1000, true) | |||
result = result..'||data-sort-value="'..seedItem.sellsFor..'"|'..Icons.GP(seedItem.sellsFor) | |||
result = result..'||'..Icons.Icon({productItem.name, type='item', size='50', notext=true}) | |||
result = result..'|| ' .. Icons.getExpansionIcon(productItem.id) .. Icons.Icon({productItem.name, type='item', noicon=true}) | |||
if category.id == 'melvorD:Allotment' then | |||
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..((productItem.healsFor or 0) * 10) | |||
end | end | ||
result = result..'||data-sort-value="'..productItem.sellsFor..'"|'..Icons.GP(productItem.sellsFor) | |||
result = result..'||'..ItemSourceTables._getItemSources(seedItem) | |||
end | end | ||
end | end | ||
result = result..'\r\n|}' | |||
return result | |||
end | |||
function p.getFarmingTable(frame) | |||
local category = frame.args ~= nil and frame.args[1] or frame | |||
return p._getFarmingTable(category) | |||
end | |||
function p.getFarmingFoodTable(frame) | |||
local result = '{| class="wikitable sortable stickyHeader"' | |||
result = result..'\r\n|- class="headerRow-0"' | |||
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level' | |||
result = result..'!!Healing!!Value' | |||
local recipes = GameData.getEntities(SkillData.Farming.recipes, | |||
function(recipe) | |||
local product = Items.getItemByID(recipe.productId) | |||
return product ~= nil and product.healsFor ~= nil and product.healsFor > 0 | |||
end) | |||
table.sort(recipes, function(a, b) return a.level < b.level end) | |||
if | for i, recipe in ipairs(recipes) do | ||
local product = Items.getItemByID(recipe.productId) | |||
if product ~= nil and product.healsFor ~= nil and product.healsFor > 0 then | |||
result = result..'\r\n|-' | |||
result = result..'\r\n|'..Icons.Icon({product.name, type='item', notext='true', size='50'}) | |||
result = result..'|| ' .. Icons.getExpansionIcon(product.id) .. Icons.Icon({product.name, type='item', noicon=true}) | |||
result = result..'||style="text-align:right;"|'..recipe.level | |||
result = result..'||style="text-align:right" data-sort-value="'..product.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(product.healsFor * 10) | |||
result = result..'||style="text-align:right" data-sort-value="'..product.sellsFor..'"|'..Icons.GP(product.sellsFor) | |||
end | end | ||
end | end | ||
result = result..'\r\n|}' | |||
return | return result | ||
end | end | ||
function p.getFarmingPlotTable(frame) | |||
local areaName = frame.args ~= nil and frame.args[1] or frame | |||
local category = GameData.getEntityByName(SkillData.Farming.categories, areaName) | |||
if category == nil then | |||
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, or Trees') | |||
end | |||
function p. | local patches = GameData.getEntities(SkillData.Farming.plots, | ||
function(plot) | |||
local | return plot.categoryID == category.id | ||
end) | |||
if Shared.tableIsEmpty(patches) then | |||
return '' | |||
end | |||
local | local result = '{|class="wikitable sortable stickyHeader"' | ||
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost' | |||
for i, patch in Shared.skpairs(patches) do | |||
result = result..'\r\n|-\r\n|'..i | |||
result = result..'||style="text-align:right;" data-sort-value="' .. patch.level .. '"|'..patch.level | |||
local costText = (patch.gpCost > 0 and Icons.GP(patch.gpCost)) or 'Free' | |||
result = result..'||style="text-align:right;" data-sort-value="'..patch.gpCost..'"|'..costText | |||
end | end | ||
result = result..'\r\n|}' | |||
return result | |||
end | end | ||
function p._buildAstrologyConstellationTable() | function p._buildAstrologyConstellationTable() | ||
local result = '{|class="wikitable sortable stickyHeader"' | local result = '{|class="wikitable sortable stickyHeader"' | ||
result = result..'\r\n|- class="headerRow-0"' | result = result..'\r\n|- class="headerRow-0"' | ||
Line 815: | Line 646: | ||
result = result..'!!XP!!Skills!!Standard Modifiers!!Unique Modifiers' | result = result..'!!XP!!Skills!!Standard Modifiers!!Unique Modifiers' | ||
for i, cons in | for i, cons in ipairs(SkillData.Astrology.recipes) do | ||
local name = cons.name | local name = cons.name | ||
result = result..'\r\n|-' | result = result..'\r\n|-' | ||
result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='constellation', size='50', notext=true})..'||'..name | result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='constellation', size='50', notext=true}) | ||
result = result..'||'..cons.level..'||'..cons. | result = result..'|| ' .. Icons.getExpansionIcon(cons.id) .. name | ||
result = result..'||'..cons.level..'||'..cons.baseExperience | |||
local skillIconArray = {} | local skillIconArray = {} | ||
for j, skillID in ipairs(cons. | for j, skillID in ipairs(cons.skillIDs) do | ||
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'})) | table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'})) | ||
end | end | ||
result = result..'||'..table.concat(skillIconArray, '<br/>') | result = result..'||'..table.concat(skillIconArray, '<br/>') | ||
local standModsRaw = | --Adding a function that converts an array of connected bonuses into text [Falterfire 22/10/27] | ||
local groupedModsToText = function(allMods) | |||
local outArray = {} | |||
for i, group in ipairs(allMods) do | |||
local groupTxt = table.concat(group, ' & ') | |||
table.insert(outArray, groupTxt) | |||
end | |||
return table.concat(outArray, '<br/>') | |||
end | |||
local standModsRaw = Skills._buildAstrologyModifierArray(cons, nil, true, false, false, false) | |||
local standMods = {} | local standMods = {} | ||
--Building the list of Standard modifiers: | --Building the list of Standard modifiers: | ||
for j, modifier in ipairs(standModsRaw) do | for j, modifier in ipairs(standModsRaw) do | ||
table.insert(standMods, Constants._getModifierText(modifier[1], | local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2] | ||
local groupNum = modifier.group | |||
if standMods[groupNum] == nil then standMods[groupNum] = {} end | |||
table.insert(standMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false)) | |||
end | end | ||
result = result..'|| '.. | result = result..'|| '..groupedModsToText(standMods) | ||
--Building the list of all Unique Modifiers | --Building the list of all Unique Modifiers | ||
local uModsRaw = | local uModsRaw = Skills._buildAstrologyModifierArray(cons, nil, false, true, false, false) | ||
local uMods = {} | local uMods = {} | ||
for j, modifier in ipairs(uModsRaw) do | for j, modifier in ipairs(uModsRaw) do | ||
table.insert(uMods, Constants._getModifierText(modifier[1], | local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2] | ||
local groupNum = modifier.group | |||
if uMods[groupNum] == nil then uMods[groupNum] = {} end | |||
table.insert(uMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false)) | |||
end | end | ||
result = result..'||'.. | result = result..'|| '..groupedModsToText(uMods) | ||
end | end | ||
result = result..'\r\n|}' | result = result..'\r\n|}' | ||
Line 850: | Line 699: | ||
function p.buildAstrologyConstellationTable(frame) | function p.buildAstrologyConstellationTable(frame) | ||
return p._buildAstrologyConstellationTable() | return p._buildAstrologyConstellationTable() | ||
end | end | ||
return p | return p |
Revision as of 21:25, 27 December 2022
Documentation for this module may be created at Module:Skills/Gathering/doc
--Splitting some functions into here to avoid bloating a single file
local p = {}
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Shop = require('Module:Shop')
local Skills = require('Module:Skills')
local ItemSourceTables = require('Module:Items/SourceTables')
function p.getRecipeRequirements(skillName, recipe)
local reqText = {}
if recipe.level ~= nil then
table.insert(reqText, Icons._SkillReq(skillName, recipe.level, false))
end
if recipe.totalMasteryRequired ~= nil then
table.insert(reqText, Shared.formatnum(recipe.totalMasteryRequired) .. ' ' .. Icons.Icon({skillName, type='skill', notext=true}) .. ' ' .. Icons.Icon({'Mastery'}))
end
if recipe.shopItemPurchased ~= nil then
local purchReq = Shop.getPurchaseByID(recipe.shopItemPurchased)
if purchReq ~= nil then
table.insert(reqText, Shop._getPurchaseIcon({purchReq}))
end
end
return table.concat(reqText, '<br/>')
end
function p.getTreesTable(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2"|Tree!!colspan="2"|Logs!!Requirements')
table.insert(resultPart, '!!XP!!Cut Time!!XP/s!!GP/s')
for i, tree in ipairs(SkillData.Woodcutting.trees) do
local log = Items.getItemByID(tree.productId)
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..tree.name..'"| '..Icons.Icon({log.name, img=tree.name, type='tree', notext=true, size=50}))
table.insert(resultPart, '\n|data-sort-value="'..tree.name..'"|'..Icons.getExpansionIcon(tree.id)..tree.name)
table.insert(resultPart, '\n|class="table-img" data-sort-value="'..log.name..'"| '..Icons.Icon({log.name, type='item', notext=true, size=50}))
table.insert(resultPart, '\n| '..Icons.Icon({log.name, type='item', noicon=true}))
table.insert(resultPart, '\n|data-sort-value="' .. tree.level .. '"| ' .. p.getRecipeRequirements(SkillData.Woodcutting.name, tree))
table.insert(resultPart, '\n|style="text-align:right"| '..tree.baseExperience)
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..tree.baseInterval..'"| '..Shared.timeString(tree.baseInterval/1000, true))
local XPs = tree.baseExperience / (tree.baseInterval / 1000)
local GPs = log.sellsFor / (tree.baseInterval / 1000)
table.insert(resultPart, '\n|style="text-align:right"| '..Shared.round(XPs, 2, 2))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="'..GPs..'"| '..Icons.GP(Shared.round(GPs, 2, 2)))
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
function p.getSpecialFishingTable(frame)
local totalWt, lootValue = 0, 0
local itemArray = GameData.getEntities(SkillData.Fishing.specialItems, function(item) return true end)
for i, itemDef in ipairs(itemArray) do
totalWt = totalWt + itemDef.weight
end
-- Sort the loot table by weight in descending order
table.sort(itemArray, function(a, b) return a.weight > b.weight end)
local resultPart = {}
table.insert(resultPart, '\r\n{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"\r\n!colspan="2"| Item\r\n!Value\r\n!colspan="2"|Chance')
for i, itemDef in ipairs(itemArray) do
local item = Items.getItemByID(itemDef.itemID)
if item ~= nil then
local dropChance = itemDef.weight / totalWt * 100
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
local fmt = (dropChance < 0.10 and '%.2g') or '%.2f'
table.insert(resultPart, '\r\n|-\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true}))
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. itemDef.weight .. '"| ' .. Shared.fraction(itemDef.weight, totalWt))
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. string.format(fmt, dropChance) .. '%')
lootValue = lootValue + (dropChance / 100 * item.sellsFor)
end
end
table.insert(resultPart, '\r\n|}\r\nThe average value of a roll on the special fishing loot table is ' .. Icons.GP(Shared.round(lootValue, 2, 0)))
return table.concat(resultPart)
end
function p.getFishingJunkTable(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!colspan="2"|Item!!Value')
local itemArray = {}
for i, itemID in ipairs(SkillData.Fishing.junkItemIDs) do
local item = Items.getItemByID(itemID)
if item ~= nil then
table.insert(itemArray, item)
end
end
table.sort(itemArray, function(a, b) return a.name < b.name end)
for i, item in ipairs(itemArray) do
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true, size=50}))
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Icons.GP(math.floor(item.sellsFor)))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.getMiningOresTable(frame)
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2"|Rock!!colspan=2|Ore!!Type!!Requirements')
table.insert(resultPart, '!!XP!!Respawn Time!!Ore Value')
local mineData = GameData.getEntities(SkillData.Mining.rockData, function(rock) return true end)
table.sort(mineData, function(a, b) return a.level < b.level end)
for i, oreData in ipairs(mineData) do
local ore = Items.getItemByID(oreData.productId)
local respawnStyle, respawnSort, respawnText = 'class="table-na"', 0, 'N/A'
if oreData.hasPassiveRegen then
respawnStyle = 'style="text-align:right;'
respawnSort = oreData.baseRespawnInterval / 1000
respawnText = Shared.timeString(respawnSort, true)
end
local rockName = Icons.Icon({oreData.name, type='rock', noicon=true, nolink=true})
local qtyText = (oreData.baseQuantity > 1 and '<b>' .. oreData.baseQuantity .. 'x</b> ') or ''
table.insert(resultPart, '\n|-\n|class="table-img" data-sort-value="' .. rockName .. '"| '..Icons.Icon({oreData.name, type='rock', size='50', notext=true, nolink=true}))
table.insert(resultPart, '\n| data-sort-value="' ..rockName.. '"|'..Icons.getExpansionIcon(oreData.id) .. rockName)
table.insert(resultPart, '\n|class="table-img" data-sort-value="' .. ore.name .. '"| '..Icons.Icon({ore.name, type='item', size='50', notext=true}))
table.insert(resultPart, '\n| ' .. qtyText .. Icons.Icon({ore.name, type='item', noicon=true}))
table.insert(resultPart, '\n| ' .. oreData.type)
table.insert(resultPart, '\n|data-sort-value="' .. oreData.level ..'"| '..p.getRecipeRequirements(SkillData.Mining.name, oreData))
table.insert(resultPart, '\n|style="text-align:right"| '..oreData.baseExperience)
table.insert(resultPart, '\n|' .. respawnStyle .. ' data-sort-value="'..respawnSort..'"| ' .. respawnText)
table.insert(resultPart, '\n|data-sort-value="'..ore.sellsFor..'"| '..Icons.GP(ore.sellsFor))
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
function p._getMiningGemsTable(gemType)
if type(gemType) ~= 'string' then
gemType = 'Normal'
end
local validTypes = {
["Normal"] = 'randomGems',
["Superior"] = 'randomSuperiorGems'
}
local gemDataKey = validTypes[gemType]
if gemDataKey == nil then
return Shared.printError('No such gem type "' .. gemType .. '"')
end
local gemData = GameData.rawData[gemDataKey]
local totalWeight = 0
for i, gem in ipairs(gemData) do
totalWeight = totalWeight + gem.weight
end
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan=2|Gem!!Gem Chance!!Gem Price')
for i, gem in ipairs(gemData) do
local gemItem = Items.getItemByID(gem.itemID)
local gemPct = gem.weight / totalWeight * 100
table.insert(resultPart, '\n|-\n|class="table-img"| ')
table.insert(resultPart, Icons.Icon({gemItem.name, type='item', size='50', notext=true}))
table.insert(resultPart, '\n| data-sort-value="'..gemItem.name..'"|'..Icons.getExpansionIcon(gemItem.id) .. Icons.Icon({gemItem.name, type='item', noicon=true}))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. gemPct .. '" | ' .. string.format("%.1f%%", gemPct))
table.insert(resultPart, '\n|data-sort-value="' .. gemItem.sellsFor .. '"| ' .. Icons.GP(gemItem.sellsFor))
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
function p.getMiningGemsTable(frame)
local gemType = frame.args ~= nil and frame.args[1] or frame
return p._getMiningGemsTable(gemType)
end
function p.getFishTable(frame)
local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(fish) return true end)
table.sort(recipeList, function(a, b) return a.level < b.level end)
-- Determine cooking levels for all fish
local cookReq = {}
for i, recipe in ipairs(SkillData.Cooking.recipes) do
-- This assumes that each raw fish only appears in a single recipe, which is a bit rubbish
-- but currently holds
for j, mat in ipairs(recipe.itemCosts) do
if cookReq[mat.id] == nil then
cookReq[mat.id] = recipe.level
end
end
end
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Fish\r\n!Name\r\n!' .. Icons.Icon({'Fishing', type='skill', notext=true}) .. ' Level\r\n!Catch Time')
table.insert(resultPart, '\r\n!XP\r\n!Value\r\n!XP/s\r\n!GP/s')
table.insert(resultPart, '\r\n!' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' Level')
for i, recipe in ipairs(recipeList) do
local fish = Items.getItemByID(recipe.productId)
if fish ~= nil then
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000
local timeStr = string.format("%.1fs - %.1fs", recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000)
local XPs = recipe.baseExperience / timeSortVal
local GPs = fish.sellsFor / timeSortVal
local cookSortVal = cookReq[recipe.productId] or 0
local cookStyle = (cookReq[recipe.productId] ~= nil and 'style="text-align:right" ' or 'class="table-na" ')
local cookStr = cookReq[recipe.productId] or 'N/A'
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({fish.name, type='item', size='50', notext=true}))
table.insert(resultPart, '\r\n|data-sort-value="'..fish.name..'"|'..Icons.getExpansionIcon(fish.id) .. Icons.Icon({fish.name, type='item', noicon=true}))
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. recipe.level)
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. timeSortVal .. '"| ' .. timeStr)
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. recipe.baseExperience .. '"| ' .. Shared.formatnum(recipe.baseExperience))
table.insert(resultPart, '\r\n|data-sort-value="' .. fish.sellsFor .. '"| ' .. Icons.GP(fish.sellsFor))
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. Shared.round(XPs, 2, 2))
table.insert(resultPart, '\r\n|data-sort-value="' .. GPs .. '"|' .. Icons.GP(Shared.round(GPs, 2, 2)))
table.insert(resultPart, '\r\n|' .. cookStyle .. 'data-sort-value="' .. cookSortVal .. '"| ' .. cookStr)
end
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.getFishingAreasTable(frame)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance'
result = result..'\r\n!Junk Chance\r\n!Special Chance'
for i, area in ipairs(SkillData.Fishing.areas) do
result = result..'\r\n|-'
result = result..'\r\n| style ="text-align: left;" |'..area.name
local fishArray = {}
for j, fishID in ipairs(area.fishIDs) do
local fishItem = Items.getItemByID(fishID)
if fishItem ~= nil then
table.insert(fishArray, Icons.Icon({fishItem.name, type='item'}))
end
end
result = result..'\r\n|'..table.concat(fishArray, '<br/>')
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%'
result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%'
result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%'
end
result = result..'\r\n|}'
return result
end
function p.getThievingGeneralRareTable(frame)
return p._getThievingGeneralRareTable()
end
function p._getThievingGeneralRareTable(npcID)
local rareTxt = '{|class="wikitable sortable"'
rareTxt = rareTxt..'\r\n!Item!!Qty'
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
-- If an npcID has been passed and the item is NPC specific, only display
-- the item if it may be obtained while pickpocketing that NPC
if npcID == nil or drop.npcs == nil or Shared.contains(drop.npcs, npcID) then
local thisItem = Items.getItemByID(drop.itemID)
local odds = drop.chance
rareTxt = rareTxt..'\r\n|-\r\n|data-sort-value="'..thisItem.name..'"|'..Icons.getExpansionIcon(thisItem.id)..Icons.Icon({thisItem.name, type='item'})
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0))
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
end
end
rareTxt = rareTxt..'\r\n|}'
return rareTxt
end
function p._getThievingNPCLootTables(npc)
local result = ''
local sectionTxt = {}
--Five sections here: GP, normal loot, area loot, rare loot, and unique item
--First up, GP:
local gpTxt = 'Successfully pickpocketing the '..npc.name..' will always give '..Icons.GP(1, npc.maxGP)
table.insert(sectionTxt, gpTxt)
--Next up, normal loot:
--(Skip if no loot)
if npc.lootTable ~= nil and Shared.tableCount(npc.lootTable) > 0 then
local normalTxt = {}
table.insert(normalTxt, '===Possible Common Drops:===\r\nUp to one of these will be received on a successful pickpocket:')
local totalWt = 0
local lootChance = SkillData.Thieving.itemChance
local lootValue = 0
--First loop through to get the total weight so we have it for later
for i, loot in pairs(npc.lootTable) do
totalWt = totalWt + loot.weight
end
table.insert(normalTxt, '\r\n{|class="wikitable sortable"')
table.insert(normalTxt, '\r\n!Item!!Qty')
table.insert(normalTxt, '!!Price!!colspan="2"|Chance')
local lootTable = GameData.getEntities(npc.lootTable, function(loot) return true end)
--Then sort the loot table by weight
table.sort(lootTable, function(a, b) return a.weight > b.weight end)
for i, loot in ipairs(lootTable) do
local thisItem = Items.getItemByID(loot.itemID)
if thisItem ~= nil then
table.insert(normalTxt, '\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'}))
else
table.insert(normalTxt, '\r\n|-\r\n|Unknown Item[[Category:Pages with script errors]]')
end
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..(loot.minQuantity + loot.maxQuantity)..'"|')
if loot.minQuantity ~= loot.maxQuantity then
table.insert(normalTxt, Shared.formatnum(loot.minQuantity) .. ' - ' .. Shared.formatnum(loot.maxQuantity))
else
table.insert(normalTxt, Shared.formatnum(loot.maxQuantity))
end
--Adding price columns
local itemPrice = 0
if thisItem == nil then
table.insert(normalTxt, '||data-sort-value="0"|???')
else
itemPrice = thisItem.sellsFor ~= nil and thisItem.sellsFor or 0
if itemPrice == 0 or loot.minQuantity == loot.maxQuantity then
table.insert(normalTxt, '||'..Icons.GP(itemPrice))
else
table.insert(normalTxt, '||'..Icons.GP(itemPrice * loot.minQuantity, itemPrice * loot.maxQuantity))
end
end
--Getting the drop chance
local dropChance = (loot.weight / totalWt * lootChance)
if dropChance < 100 then
--Show fraction as long as it isn't going to be 1/1
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..loot.weight..'"')
table.insert(normalTxt, '|'..Shared.fraction(loot.weight * lootChance, totalWt * 100))
table.insert(normalTxt, '||')
else
table.insert(normalTxt, '||colspan="2" data-sort-value="'..loot.weight..'"')
end
table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%')
--Adding to the average loot value based on price & dropchance
lootValue = lootValue + (dropChance * 0.01 * itemPrice * (loot.minQuantity + loot.maxQuantity) / 2)
end
if Shared.tableCount(npc.lootTable) > 1 then
table.insert(normalTxt, '\r\n|-class="sortbottom" \r\n!colspan="3"|Total:')
if lootChance < 100 then
table.insert(normalTxt, '\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||')
else
table.insert(normalTxt, '\r\n|colspan="2" ')
end
table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%')
end
table.insert(normalTxt, '\r\n|}')
table.insert(normalTxt, '\r\nThe loot obtained from the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue, 2, 2)) .. ' if sold.')
table.insert(normalTxt, '\r\n\r\nIncluding GP, the average successful pickpocket is worth ' .. Icons.GP(Shared.round(lootValue + (1 + npc.maxGP) / 2, 2, 2)) .. '.')
table.insert(sectionTxt, table.concat(normalTxt))
end
--After normal drops, add in rare drops
local rareTxt = '===Possible Rare Drops:===\r\nAny of these can be received after a successful pickpocket:'
rareTxt = rareTxt..'\r\n'..p._getThievingGeneralRareTable(npc.id)
table.insert(sectionTxt, rareTxt)
local areaTxt = '===Possible Area Unique Drops==='
areaTxt = areaTxt..'\r\nAny Area Unique Drop is equally likely to be obtained after a successful pickpocket. '
areaTxt = areaTxt..'\r\nEach Area Unique Drop is rolled for separately, so it is possible to receive multiple Area Unique Drops from a single action. '
areaTxt = areaTxt..'The chance of receiving an Area Unique drop is tripled if the 95% Thieving Mastery Pool checkpoint is active.'
local area = Skills.getThievingNPCArea(npc)
areaTxt = areaTxt..'\r\n{|class="wikitable sortable"'
areaTxt = areaTxt..'\r\n!Item!!Qty'
areaTxt = areaTxt..'!!Price!!colspan="2"|Chance'
local dropLines = {}
for i, drop in ipairs(area.uniqueDrops) do
local thisItem = Items.getItemByID(drop.id)
local lineTxt = ''
lineTxt = lineTxt..'\r\n|-\r\n|'..Icons.Icon({thisItem.name, type='item'})
lineTxt = lineTxt..'||data-sort-value="'..drop.quantity..'"| '..Shared.formatnum(drop.quantity)..'||data-sort-value="'..thisItem.sellsFor..'"|'..Icons.GP(thisItem.sellsFor)
lineTxt = lineTxt..'||style="text-align:right"|'..Shared.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100))
lineTxt = lineTxt..'||'..Shared.round(SkillData.Thieving.baseAreaUniqueChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
end
for i, txt in pairs(dropLines) do
areaTxt = areaTxt..txt
end
areaTxt = areaTxt..'\r\n|}'
table.insert(sectionTxt, areaTxt)
if npc.uniqueDrop ~= nil and npc.uniqueDrop.id ~= nil then
local thisItem = Items.getItemByID(npc.uniqueDrop.id)
if thisItem ~= nil then
local uniqueTxt = '===Possible NPC Unique Drop==='
uniqueTxt = uniqueTxt..'\r\nThe chance of receiving the unique drop for an NPC is based on a combination of several factors.'
uniqueTxt = uniqueTxt..' The unique drop chance for an NPC is included in the tooltip for your Stealth against that NPC.'
uniqueTxt = uniqueTxt..'\r\nThe unique drop for the '..npc.name..' is '
if npc.uniqueDrop.quantity > 1 then
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item', qty=npc.uniqueDrop.quantity}) .. '.'
else
uniqueTxt = uniqueTxt..Icons.Icon({thisItem.name, type='item'}) .. '.'
end
table.insert(sectionTxt, uniqueTxt)
end
end
return table.concat(sectionTxt, '\r\n')
end
function p.getThievingNPCLootTables(frame)
local npcName = frame.args ~= nil and frame.args[1] or frame
local npc = Skills.getThievingNPC(npcName)
if npc == nil then
return Shared.printError('Invalid Thieving NPC "' .. npcName .. '"')
end
return p._getThievingNPCLootTables(npc)
end
function p.getThievingNPCTable()
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!GP!!Unique Drop'
local npcArray = GameData.getEntities(SkillData.Thieving.npcs, function(npc) return true end)
table.sort(npcArray, function(a, b) return a.level < b.level end)
for i, npc in ipairs(npcArray) do
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
result = result..'||data-sort-value="'..npc.name..'"|'..Icons.getExpansionIcon(npc.id)..Icons.Icon({npc.name, type='thieving', noicon=true})
local area = Skills.getThievingNPCArea(npc)
result = result..'||'..area.name
result = result..'||data-sort-value="' .. npc.level .. '"| '..Icons._SkillReq('Thieving', npc.level)
result = result..'||style="text-align:right"|'..npc.baseExperience
result = result..'||style="text-align:right"|'..(npc.maxHit * 10)
result = result..'||style="text-align:right" data-sort-value="' .. npc.perception .. '| '..Shared.formatnum(npc.perception)
result = result..'||data-sort-value="' .. npc.maxGP .. '"|'..Icons.GP(1, npc.maxGP)
if npc.uniqueDrop ~= nil then
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id)
if npc.uniqueDrop.quantity > 1 then
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.quantity})
else
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
end
else
result = result..'|| '
end
end
result = result..'\r\n|}'
return result
end
function p.getThievingAreaTable(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
for i, area in ipairs(SkillData.Thieving.areas) do
local minLevel, npcList, areaItemList = nil, {}, {}
-- Build NPC list & determine level for area, this is the minimum
-- Thieving level required for all NPCs within that area
if area.npcIDs ~= nil and not Shared.tableIsEmpty(area.npcIDs) then
for j, npcID in ipairs(area.npcIDs) do
local npc = Skills.getThievingNPCByID(npcID)
if minLevel == nil or npc.level < minLevel then
minLevel = npc.level
end
table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
end
else
table.insert(npcList, '')
end
-- Build area unique item list
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
for k, drop in ipairs(area.uniqueDrops) do
local areaItem = Items.getItemByID(drop.id)
if areaItem == nil then
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
else
local iconDef = {areaItem.name, type='item'}
if drop.quantity > 1 then
iconDef.qty = drop.quantity
end
table.insert(areaItemList, Icons.Icon(iconDef))
end
end
else
table.insert(areaItemList, '')
end
-- Generate table row
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|' .. area.name)
table.insert(resultPart, '\r\n|' .. Icons._SkillReq('Thieving', minLevel))
table.insert(resultPart, '\r\n|' .. table.concat(npcList, '<br/>'))
table.insert(resultPart, '\r\n|' .. table.concat(areaItemList, '<br/>'))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p._getFarmingTable(categoryName)
local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName)
if category == nil then
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, or Trees')
end
local seedList = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
return recipe.categoryID == category.id
end)
if Shared.tableIsEmpty(seedList) then
return ''
end
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan=2|Seeds!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level'
result = result..'!!XP!!Growth Time!!Seed Value'
if category.id == 'melvorD:Allotment' then
result = result..'!!colspan="2"|Crop!!Crop Healing!!Crop Value'
elseif category.id == 'melvorD:Herb' then
result = result..'!!colspan="2"|Herb!!Herb Value'
elseif category.id == 'melvorD:Tree' then
result = result..'!!colspan="2"|Logs!!Log Value'
end
result = result..'!!Seed Sources'
table.sort(seedList, function(a, b) return a.level < b.level end)
for i, seed in ipairs(seedList) do
local seedItem = Items.getItemByID(seed.seedCost.id)
local productItem = Items.getItemByID(seed.productId)
if seedItem ~= nil and productItem ~= nil then
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({seedItem.name, type='item', size='50', notext=true})
result = result..'|| ' .. Icons.getExpansionIcon(seedItem.id) .. Icons.Icon({seedItem.name, type='item', noicon=true})
result = result..'||'..seed.level..'||'..Shared.formatnum(seed.baseExperience)
result = result..'||data-sort-value="'..(seed.baseInterval / 1000)..'"|'..Shared.timeString(seed.baseInterval / 1000, true)
result = result..'||data-sort-value="'..seedItem.sellsFor..'"|'..Icons.GP(seedItem.sellsFor)
result = result..'||'..Icons.Icon({productItem.name, type='item', size='50', notext=true})
result = result..'|| ' .. Icons.getExpansionIcon(productItem.id) .. Icons.Icon({productItem.name, type='item', noicon=true})
if category.id == 'melvorD:Allotment' then
result = result..'||'..Icons.Icon({'Hitpoints', type='skill', notext=true})..' '..((productItem.healsFor or 0) * 10)
end
result = result..'||data-sort-value="'..productItem.sellsFor..'"|'..Icons.GP(productItem.sellsFor)
result = result..'||'..ItemSourceTables._getItemSources(seedItem)
end
end
result = result..'\r\n|}'
return result
end
function p.getFarmingTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
return p._getFarmingTable(category)
end
function p.getFarmingFoodTable(frame)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
result = result..'!!Healing!!Value'
local recipes = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
local product = Items.getItemByID(recipe.productId)
return product ~= nil and product.healsFor ~= nil and product.healsFor > 0
end)
table.sort(recipes, function(a, b) return a.level < b.level end)
for i, recipe in ipairs(recipes) do
local product = Items.getItemByID(recipe.productId)
if product ~= nil and product.healsFor ~= nil and product.healsFor > 0 then
result = result..'\r\n|-'
result = result..'\r\n|'..Icons.Icon({product.name, type='item', notext='true', size='50'})
result = result..'|| ' .. Icons.getExpansionIcon(product.id) .. Icons.Icon({product.name, type='item', noicon=true})
result = result..'||style="text-align:right;"|'..recipe.level
result = result..'||style="text-align:right" data-sort-value="'..product.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(product.healsFor * 10)
result = result..'||style="text-align:right" data-sort-value="'..product.sellsFor..'"|'..Icons.GP(product.sellsFor)
end
end
result = result..'\r\n|}'
return result
end
function p.getFarmingPlotTable(frame)
local areaName = frame.args ~= nil and frame.args[1] or frame
local category = GameData.getEntityByName(SkillData.Farming.categories, areaName)
if category == nil then
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, or Trees')
end
local patches = GameData.getEntities(SkillData.Farming.plots,
function(plot)
return plot.categoryID == category.id
end)
if Shared.tableIsEmpty(patches) then
return ''
end
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n!Plot!!'..Icons.Icon({'Farming', type='skill', notext=true})..' Level!!Cost'
for i, patch in Shared.skpairs(patches) do
result = result..'\r\n|-\r\n|'..i
result = result..'||style="text-align:right;" data-sort-value="' .. patch.level .. '"|'..patch.level
local costText = (patch.gpCost > 0 and Icons.GP(patch.gpCost)) or 'Free'
result = result..'||style="text-align:right;" data-sort-value="'..patch.gpCost..'"|'..costText
end
result = result..'\r\n|}'
return result
end
function p._buildAstrologyConstellationTable()
local result = '{|class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Constellation!!'..Icons.Icon({"Astrology", type='skill', notext='true'})..' Level'
result = result..'!!XP!!Skills!!Standard Modifiers!!Unique Modifiers'
for i, cons in ipairs(SkillData.Astrology.recipes) do
local name = cons.name
result = result..'\r\n|-'
result = result..'\r\n|data-sort-value="'..name..'"|'..Icons.Icon({name, type='constellation', size='50', notext=true})
result = result..'|| ' .. Icons.getExpansionIcon(cons.id) .. name
result = result..'||'..cons.level..'||'..cons.baseExperience
local skillIconArray = {}
for j, skillID in ipairs(cons.skillIDs) do
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
end
result = result..'||'..table.concat(skillIconArray, '<br/>')
--Adding a function that converts an array of connected bonuses into text [Falterfire 22/10/27]
local groupedModsToText = function(allMods)
local outArray = {}
for i, group in ipairs(allMods) do
local groupTxt = table.concat(group, ' & ')
table.insert(outArray, groupTxt)
end
return table.concat(outArray, '<br/>')
end
local standModsRaw = Skills._buildAstrologyModifierArray(cons, nil, true, false, false, false)
local standMods = {}
--Building the list of Standard modifiers:
for j, modifier in ipairs(standModsRaw) do
local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2]
local groupNum = modifier.group
if standMods[groupNum] == nil then standMods[groupNum] = {} end
table.insert(standMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false))
end
result = result..'|| '..groupedModsToText(standMods)
--Building the list of all Unique Modifiers
local uModsRaw = Skills._buildAstrologyModifierArray(cons, nil, false, true, false, false)
local uMods = {}
for j, modifier in ipairs(uModsRaw) do
local modMagnitude = type(modifier[2]) == 'table' and {modifier[2]} or modifier[2]
local groupNum = modifier.group
if uMods[groupNum] == nil then uMods[groupNum] = {} end
table.insert(uMods[groupNum], Constants._getModifierText(modifier[1], modMagnitude, false))
end
result = result..'|| '..groupedModsToText(uMods)
end
result = result..'\r\n|}'
return result
end
function p.buildAstrologyConstellationTable(frame)
return p._buildAstrologyConstellationTable()
end
return p