Module:Skills/Gathering: Difference between revisions
From Melvor Idle
(getSpecialFishingTable: Show smaller chance values to 2sf) |
m (Replaced Thieving npc.name link with an icon link) |
||
(87 intermediate revisions by 5 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 Common = require('Module:Common') | |||
local Modifiers = require('Module:Modifiers') | |||
local Items = require('Module:Items') | local Items = require('Module:Items') | ||
local Icons = require('Module:Icons') | local Icons = require('Module:Icons') | ||
local Skills = require('Module:Skills') | |||
local ItemSourceTables = require('Module:Items/SourceTables') | |||
local Num = require("Module:Number") | |||
local | local function lootValueText(lootValue) | ||
local returnPart = {} | |||
for _, currencyDefn in ipairs(GameData.rawData.currencies) do | |||
function | -- Guarantee order by iterating through currency game data definition | ||
local currID = currencyDefn.id | |||
local val = Num.round(lootValue[currID], 2, 2) | |||
if val ~= nil then | |||
table.insert(returnPart, Icons._Currency(currID, val)) | |||
for | |||
end | end | ||
end | end | ||
return | return table.concat(returnPart, ', ') | ||
end | end | ||
function p. | function p.getTreesTable(frame) | ||
local | local args = frame.args ~= nil and frame.args or frame | ||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | end | ||
local skillID = 'Woodcutting' | |||
local html = mw.html.create('table') | |||
:addClass('wikitable sortable stickyHeader') | |||
html:tag('tr') | |||
:addClass("headerRow-0") | |||
:tag('th'):attr('colspan', 2) | |||
:wikitext('Tree') | |||
:tag('th'):attr('colspan', 2) | |||
:wikitext('Logs') | |||
:tag('th'):wikitext('Requirements') | |||
:tag('th'):wikitext('[[DLC]]') | |||
:tag('th'):wikitext('XP') | |||
:tag('th'):wikitext('Cut Time') | |||
:tag('th'):wikitext('XP/s') | |||
:tag('th'):wikitext('Price/s') | |||
local | local trees = GameData.getEntities(SkillData.Woodcutting.trees, | ||
function(tree) | |||
return Skills.getRecipeRealm(tree) == realm.id | |||
end | end | ||
) | |||
table.sort(trees, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | |||
for i, tree in ipairs(trees) do | |||
local level = Skills.getRecipeLevel(skillID, tree) | |||
local baseXP = tree.baseAbyssalExperience or tree.baseExperience | |||
local baseInt = tree.baseInterval | |||
local reqText = Skills.getRecipeRequirementText(SkillData.Woodcutting.name, tree) | |||
local log = Items.getItemByID(tree.productId) | |||
local sellCurrency = log.sellsForCurrency or 'melvorD:GP' | |||
local XPSec = baseXP / (baseInt / 1000) | |||
local currSec = Num.round(log.sellsFor / (baseInt / 1000), 2, 2) | |||
html:tag('tr') | |||
:tag('td'):wikitext(Icons.Icon({log.name, img=tree.name, type='tree', notext=true})) | |||
:addClass('table-img') | |||
:attr('data-sort-value', tree.name) | |||
:tag('td'):wikitext(tree.name) | |||
:tag('td'):wikitext(Icons.Icon({log.name, type='item', notext=true})) | |||
:addClass('table-img') | |||
:attr('data-sort-value', log.name) | |||
:tag('td'):wikitext('[[' .. log.name .. ']]') | |||
:tag('td'):wikitext(reqText) | |||
:attr('data-sort-value', level) | |||
:tag('td'):wikitext(Icons.getDLCColumnIcon(tree.id)) | |||
:attr('data-sort-value', Icons.getExpansionID(tree.id)) | |||
:css('text-align', 'center') | |||
:tag('td'):wikitext(Num.formatnum(baseXP)) | |||
:css('text-align', 'right') | |||
:tag('td'):wikitext(Shared.timeString(baseInt / 1000, true)) | |||
:attr('data-sort-value', baseInt) | |||
:css('text-align', 'right') | |||
:tag('td'):wikitext(Num.round(XPSec, 2, 2)) | |||
:css('text-align', 'right') | |||
:tag('td'):wikitext(Icons._Currency(sellCurrency, currSec)) | |||
:attr('data-sort-value', currSec) | |||
:css('text-align', 'right') | |||
end | end | ||
return tostring(html) | |||
return | |||
end | end | ||
function p. | function p.getSpecialFishingTable(frame) | ||
local | local args = frame.args ~= nil and frame.args or frame | ||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | end | ||
local | local totalWt, lootValue = 0, {} | ||
local realmSpecials = GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'realmID', realm.id) | |||
if realmSpecials == nil then | |||
return '' | |||
end | |||
local itemArray = realmSpecials.drops | |||
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 | local html = mw.html.create('table') | ||
html:addClass('wikitable sortable stickyHeader') | |||
-- Add header row | |||
local headerRow = html:tag('tr'):addClass('headerRow-0') | |||
headerRow:tag('th'):attr('colspan', '2'):wikitext('Item') | |||
headerRow:tag('th'):wikitext('Value') | |||
headerRow:tag('th'):attr('colspan', '2'):wikitext('Chance') | |||
local | -- Add item rows | ||
for i, itemDef in ipairs(itemArray) do | |||
local item = Items.getItemByID(itemDef.itemID) | |||
if item ~= nil then | |||
local dropChance = itemDef.weight / totalWt * 100 | |||
local currID = item.sellsForCurrency or 'melvorD:GP' | |||
-- 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' | |||
local row = html:tag('tr') | |||
row:tag('td'):addClass('table-img'):wikitext(Icons.Icon({item.name, type='item', notext=true})) | |||
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', noicon=true})) | |||
row:tag('td') | |||
:attr('data-sort-value', item.sellsFor) | |||
:wikitext(Items.getValueText(item)) | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', itemDef.weight) | |||
:wikitext(Num.fraction(itemDef.weight, totalWt)) | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:wikitext(string.format(fmt, dropChance) .. '%') | |||
if lootValue[currID] == nil then | |||
lootValue[currID] = 0 | |||
end | |||
lootValue[currID] = lootValue[currID] + (dropChance / 100 * item.sellsFor) | |||
end | |||
end | end | ||
result = result..' | local result = tostring(html) | ||
local averageValueText = 'The average value of a roll on the special fishing loot table is ' .. lootValueText(lootValue) | |||
return result .. '\n' .. averageValueText | |||
end | end | ||
function p. | function p.getFishingJunkTable(frame) | ||
local | local html = mw.html.create('table') | ||
html:addClass('wikitable sortable stickyHeader') | |||
-- Add header row | |||
local headerRow = html:tag('tr'):addClass('headerRow-0') | |||
headerRow:tag('th'):attr('colspan', '2'):wikitext('Item') | |||
headerRow:tag('th'):wikitext('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 | |||
table.sort(itemArray, function(a, b) return a.name < b.name end) | |||
local | -- Add item rows | ||
for i, item in ipairs(itemArray) do | |||
local row = html:tag('tr') | |||
row:tag('td'):addClass('table-img') | |||
:wikitext(Icons.Icon({item.name, type='item', notext=true})) | |||
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', noicon=true})) | |||
row:tag('td') | |||
:attr('data-sort-value', item.sellsFor) | |||
:wikitext(Items.getValueText(item)) | |||
end | end | ||
return tostring(html) | |||
return | |||
end | end | ||
function p. | function p.getMiningOresTable(frame) | ||
local | local args = frame.args ~= nil and frame.args or frame | ||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local skillID = 'Mining' | |||
local html = mw.html.create('table') | |||
:addClass("wikitable sortable stickyHeader") | |||
html:tag('tr'):addClass("headerRow-0") | |||
:tag('th'):wikitext('Rock') | |||
:attr('colspan', 2) | |||
:tag('th'):wikitext('Ore') | |||
:attr('colspan', 2) | |||
:tag('th'):wikitext('Type') | |||
:tag('th'):wikitext('Requirements') | |||
:tag('th'):wikitext('[[DLC]]') | |||
:tag('th'):wikitext('XP') | |||
:tag('th'):wikitext('Respawn<br>Time') | |||
:tag('th'):wikitext('Ore Value') | |||
for i, | local mineData = GameData.getEntities(SkillData.Mining.rockData, | ||
function(obj) | |||
local | return Skills.getRecipeRealm(obj) == realm.id | ||
local | end | ||
) | |||
table.sort(mineData, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | |||
for i, oreData in ipairs(mineData) do | |||
local level = Skills.getRecipeLevel(skillID, oreData) | |||
local baseXP = oreData.baseAbyssalExperience or oreData.baseExperience | |||
local reqText = Skills.getRecipeRequirementText(SkillData.Mining.name, oreData) | |||
local ore = Items.getItemByID(oreData.productId) | |||
local respawnSort, respawnText = 0, 'N/A' | |||
if oreData.hasPassiveRegen then | |||
local | respawnSort = oreData.baseRespawnInterval / 1000 | ||
respawnText = Shared.timeString(respawnSort, true) | |||
end | |||
local categoryName = '' | |||
local category = GameData.getEntityByID(SkillData.Mining.categories, oreData.category) | |||
if category ~= nil and category.name ~= nil then | |||
categoryName = category.name | |||
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 '' | |||
local row = html:tag('tr') | |||
row :tag('td'):wikitext(Icons.Icon({oreData.name, type='rock', notext=true, nolink=true})) | |||
:addClass('table-img') | |||
:attr('data-sort-value', rockName) | |||
:tag('td'):wikitext(rockName) | |||
:tag('td'):wikitext(Icons.Icon({ore.name, type='item', notext=true})) | |||
:addClass('table-img') | |||
:attr('data-sort-value', ore.name) | |||
:tag('td'):wikitext(qtyText .. '[[' .. ore.name .. ']]') | |||
:tag('td'):wikitext(categoryName) | |||
:tag('td'):wikitext(reqText) | |||
:attr('data-sort-value', level) | |||
:tag('td'):wikitext(Icons.getDLCColumnIcon(oreData.id)) | |||
:attr('data-sort-value', Icons.getExpansionID(oreData.id)) | |||
:css('text-align', 'center') | |||
:tag('td'):wikitext(Num.formatnum(baseXP)) | |||
:css('text-align', 'right') | |||
local respawn = | |||
row:tag('td'):wikitext(respawnText) | |||
:attr('data-sort-value', respawnSort) | |||
row:tag('td'):wikitext(Items.getValueText(ore)) | |||
:attr('data-sort-value', ore.sellsFor) | |||
if respawnText == 'N/A' then | |||
respawn:addClass('table-na') | |||
else | |||
respawn:css('text-align', 'right') | |||
end | |||
end | end | ||
return tostring(html) | |||
return | |||
end | end | ||
function p. | function p._getMiningGemsTable(gemType) | ||
if type(gemType) ~= 'string' then | |||
gemType = 'Normal' | |||
end | |||
local validTypes = { | |||
["Normal"] = 'randomGems', | |||
["Superior"] = 'randomSuperiorGems', | |||
["Abyssal"] = 'randomAbyssalGems' | |||
} | |||
local gemDataKey = validTypes[gemType] | |||
if gemDataKey == nil then | |||
return Shared.printError('No such gem type "' .. gemType .. '"') | |||
end | end | ||
local gemData = GameData.rawData[gemDataKey] | |||
local totalWeight = 0 | |||
for i, gem in ipairs(gemData) do | |||
totalWeight = totalWeight + gem.weight | |||
local | |||
for i, | |||
end | end | ||
local html = mw.html.create('table') | |||
:addClass('wikitable sortable') | |||
-- Add header row | |||
local headerRow = html:tag('tr'):addClass('headerRow-0') | |||
:tag('th'):wikitext('Gem') | |||
:attr('colspan', '2') | |||
:tag('th'):wikitext('[[DLC]]') | |||
:tag('th'):wikitext('Gem Chance') | |||
local | :tag('th'):wikitext('Gem Price') | ||
-- Add gem rows | |||
for i, gem in ipairs(gemData) do | |||
local gemItem = Items.getItemByID(gem.itemID) | |||
local gemPct = gem.weight / totalWeight * 100 | |||
local row = html:tag('tr') | |||
row:tag('td'):addClass('table-img') | |||
:wikitext(Icons.Icon({gemItem.name, type='item', notext=true})) | |||
row:tag('td'):attr('data-sort-value', gemItem.name) | |||
:wikitext('[[' .. gemItem.name ..']]') | |||
row:tag('td'):wikitext(Icons.getDLCColumnIcon(gemItem.id)) | |||
:attr('data-sort-value', Icons.getExpansionID(gemItem.id)) | |||
:css('text-align', 'center') | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', gemPct) | |||
:wikitext(string.format("%.1f%%", gemPct)) | |||
row:tag('td'):attr('data-sort-value', gemItem.sellsFor) | |||
:wikitext(Items.getValueText(gemItem)) | |||
end | end | ||
return tostring(html) | |||
return | |||
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 | |||
function p.getFishTable(frame) | |||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | end | ||
local skillID = 'Fishing' | |||
local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(obj) | |||
return | return Skills.getRecipeRealm(obj) == realm.id | ||
end | end) | ||
table.sort(recipeList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | |||
-- Determine cooking levels for all fish | |||
local | local cookRecipes = {} | ||
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 cookRecipes[mat.id] == nil then | |||
cookRecipes[mat.id] = recipe | |||
end | |||
end | |||
end | |||
local html = mw.html.create('table') | |||
html:addClass('wikitable sortable stickyHeader') | |||
local | -- Add header row | ||
local headerRow0 = html:tag('tr'):addClass('headerRow-0') | |||
headerRow0:tag('th'):attr('colspan', '2'):attr('rowspan', '2'):wikitext('Fish') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext(Icons._SkillRealmIcon('Fishing', realm.id) .. '<br>Level') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('[[DLC]]') | |||
headerRow0:tag('th'):attr('colspan', '3'):wikitext('Catch Time') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('XP') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('XP/s') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('Value') | |||
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('GP/s') | |||
local headerRow1 = html:tag('tr'):addClass('headerRow-1') | |||
headerRow1:tag('th'):wikitext('Min') | |||
headerRow1:tag('th'):wikitext('Max') | |||
headerRow1:tag('th'):wikitext('Avg') | |||
local | -- Add fish rows | ||
local | for i, recipe in ipairs(recipeList) do | ||
local fish = Items.getItemByID(recipe.productId) | |||
if fish ~= nil then | |||
local timeMin, timeMax = recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000 | |||
local timeAvg = (timeMin + timeMax) / 2 | |||
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000 | |||
local level = Skills.getRecipeLevel(skillID, recipe) | |||
local reqText = Skills.getRecipeRequirementText(skillID, recipe) | |||
local baseXP = recipe.baseAbyssalExperience or recipe.baseExperience | |||
local XPSec = baseXP / timeSortVal | |||
local sellCurrency = fish.sellsForCurrency or 'melvorD:GP' | |||
local GPSec = fish.sellsFor / timeSortVal | |||
local row = html:tag('tr') | |||
row:tag('td'):wikitext(Icons.Icon({fish.name, type='item', notext=true})) | |||
:addClass('table-img') | |||
row:tag('td'):wikitext('[[' .. fish.name .. ']]') | |||
:attr('data-sort-value', fish.name) | |||
row:tag('td'):wikitext(level) | |||
:css('text-align', 'center') | |||
:attr('data-sort-value', level) | |||
row:tag('td'):wikitext(Icons.getDLCColumnIcon(fish.id)) | |||
:css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(fish.id)) | |||
row:tag('td'):wikitext(string.format("%.1fs", timeMin)) | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', timeMin) | |||
row:tag('td'):wikitext(string.format("%.1fs", timeMax)) | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', timeMax) | |||
row:tag('td'):wikitext(string.format("%.1fs", timeAvg)) | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', timeAvg) | |||
row:tag('td'):wikitext(Num.formatnum(baseXP)) | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', baseXP) | |||
row:tag('td'):wikitext(Num.round(XPSec, 2, 2)) | |||
:css('text-align', 'right') | |||
row:tag('td'):wikitext(Items.getValueText(fish)) | |||
:attr('data-sort-value', fish.sellsFor) | |||
:css('text-align', 'right') | |||
row:tag('td'):wikitext(Icons._Currency(sellCurrency, Num.round(GPSec, 2, 2))) | |||
:attr('data-sort-value', GPSec) | |||
:css('text-align', 'right') | |||
end | end | ||
end | end | ||
return tostring(html) | |||
return | |||
end | end | ||
function p.getFishingAreasTable(frame) | function p.getFishingAreasTable(frame) | ||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local skillID = 'Fishing' | |||
local | local html = mw.html.create('table') | ||
html:addClass('wikitable sortable stickyHeader') | |||
for i, area in | -- Add header row | ||
local headerRow = html:tag('tr'):addClass('headerRow-0') | |||
headerRow:tag('th'):wikitext('Name') | |||
headerRow:tag('th'):wikitext('Fish') | |||
headerRow:tag('th'):wikitext('[[DLC]]') | |||
headerRow:tag('th'):wikitext('Fish Chance') | |||
headerRow:tag('th'):wikitext('Junk Chance') | |||
headerRow:tag('th'):wikitext('Special Chance') | |||
-- Get fishing areas | |||
local fishAreas = GameData.getEntities(SkillData.Fishing.areas, function(obj) | |||
return Skills.getRecipeRealm(obj) == realm.id | |||
end) | |||
-- Add rows for each fishing area | |||
for i, area in ipairs(fishAreas) do | |||
local row = html:tag('tr') | |||
row:tag('td') | |||
:css('text-align', 'left') | |||
:attr('id', area.name) | |||
:wikitext(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 | ||
row:tag('td') | |||
:wikitext(table.concat(fishArray, '<br/>')) | |||
row:tag('td') | |||
:css('text-align', 'center') | |||
:wikitext(Icons.getDLCColumnIcon(area.id)) | |||
:attr('data-sort-value', Icons.getExpansionID(area.id)) | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:wikitext(area.fishChance .. '%') | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:wikitext(area.junkChance .. '%') | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:wikitext(area.specialChance .. '%') | |||
end | end | ||
return tostring(html) | |||
return | |||
end | end | ||
function p. | |||
function p.getThievingGeneralRareTable(frame) | |||
return p._getThievingGeneralRareTable() | |||
end | end | ||
function p. | function p._getThievingGeneralRareTable(npc) | ||
if | local npcRealm = nil | ||
if npc ~= nil then | |||
npcRealm = Skills.getRecipeRealm(npc) | |||
end | end | ||
local html = mw.html.create('table') | |||
html:addClass('wikitable sortable') | |||
-- Add header row | |||
local headerRow = html:tag('tr') | |||
headerRow:tag('th'):wikitext('Item') | |||
headerRow:tag('th'):wikitext('[[DLC]]') | |||
headerRow:tag('th'):wikitext('Qty') | |||
headerRow:tag('th'):wikitext('Price') | |||
headerRow:tag('th'):attr('colspan', '2'):wikitext('Chance') | |||
if | -- Add rows for each rare item | ||
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 | |||
local npcMatch = (npc == nil or drop.npcs == nil or Shared.contains(drop.npcs, npc.id)) | |||
local realmMatch = (npcRealm == nil or drop.realms == nil or Shared.contains(drop.realms, npcRealm)) | |||
if npcMatch and realmMatch then | |||
local thisItem = Items.getItemByID(drop.itemID) | |||
local odds = drop.chance | |||
local row = html:tag('tr') | |||
row:tag('td') | |||
:attr('data-sort-value', thisItem.name) | |||
:wikitext(Icons.Icon({thisItem.name, type='item'})) | |||
row:tag('td') | |||
:wikitext(Icons.getDLCColumnIcon(thisItem.id)) | |||
:attr('data-sort-value', Icons.getExpansionID(thisItem.id)) | |||
:css('text-align', 'center') | |||
row:tag('td') | |||
:wikitext('1') | |||
row:tag('td') | |||
:attr('data-sort-value', thisItem.sellsFor) | |||
:wikitext(Items.getValueText(thisItem)) | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', odds) | |||
:wikitext(Num.fraction(1, Num.round2(1/(odds/100), 0))) | |||
row:tag('td') | |||
:css('text-align', 'right') | |||
:attr('data-sort-value', odds) | |||
:wikitext(Num.round(odds, 4, 4) .. '%') | |||
end | |||
end | end | ||
return | return tostring(html) | ||
end | end | ||
function p._getThievingNPCCurrencyText(npc) | |||
local currTextPart = {} | |||
for _, currencyDrop in ipairs(npc.currencyDrops) do | |||
table.insert(currTextPart, Icons._Currency(currencyDrop.id, 1, currencyDrop.quantity)) | |||
end | end | ||
return table.concat(currTextPart, ', ') | |||
end | end | ||
Line 429: | Line 566: | ||
local sectionTxt = {} | local sectionTxt = {} | ||
--Five sections here: | --Five sections here: Currency, normal loot, area loot, rare loot, and unique item | ||
--First up, | --First up, currency: | ||
table.insert(sectionTxt, 'Successfully pickpocketing the ' .. npc.name .. ' will always give '.. p._getThievingNPCCurrencyText(npc)) | |||
--Next up, normal loot: | --Next up, normal loot: | ||
--(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 = | local lootValue = {} | ||
--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 = Shared.shallowClone(npc.lootTable) | |||
--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, Num.formatnum(loot.minQuantity) .. ' - ' .. Num.formatnum(loot.maxQuantity)) | ||
else | |||
table.insert(normalTxt, Num.formatnum(loot.maxQuantity)) | |||
end | end | ||
--Adding price columns | --Adding price columns | ||
local | local sellAmount, sellCurrency = nil, nil | ||
if thisItem == nil then | if thisItem == nil then | ||
normalTxt | table.insert(normalTxt, '||data-sort-value="0"|???') | ||
else | else | ||
sellAmount = thisItem.sellsFor or 0 | |||
sellCurrency = thisItem.sellsForCurrency or 'melvorD:GP' | |||
table.insert(normalTxt, '||' .. Items.getValueText(thisItem, loot.minQuantity, loot.maxQuantity)) | |||
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, '|'..Num.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"|'..Num.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 * | if sellAmount ~= nil and sellCurrency ~= nil then | ||
if lootValue[sellCurrency] == nil then | |||
lootValue[sellCurrency] = 0 | |||
end | |||
lootValue[sellCurrency] = lootValue[sellCurrency] + (dropChance * 0.01 * sellAmount * (loot.minQuantity + loot.maxQuantity) / 2) | |||
end | |||
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"|'..Num.fraction(lootChance, 100)..'||') | ||
else | else | ||
normalTxt | table.insert(normalTxt, '\r\n|colspan="2" ') | ||
end | end | ||
normalTxt | table.insert(normalTxt, 'style="text-align:right"|'..Num.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 ' .. lootValueText(lootValue) .. ' if sold.') | |||
-- Amend lootValue | |||
for _, currencyDrop in ipairs(npc.currencyDrops) do | |||
lootValue[currencyDrop.id] = lootValue[currencyDrop.id] + (1 + currencyDrop.quantity) / 2 | |||
end | |||
table.insert(normalTxt, '\r\n\r\nIncluding currency, the average successful pickpocket is worth ' .. lootValueText(lootValue) .. '.') | |||
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) | ||
table.insert(sectionTxt, rareTxt) | table.insert(sectionTxt, rareTxt) | ||
Line 519: | Line 668: | ||
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..'"| '..Num.formatnum(drop.quantity)..'||data-sort-value="'..thisItem.sellsFor..'"|'..Items.getValueText(thisItem) | ||
lineTxt = lineTxt..'||style="text-align:right"|'.. | lineTxt = lineTxt..'||style="text-align:right"|'..Num.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100)) | ||
lineTxt = lineTxt..'||'.. | lineTxt = lineTxt..'||'..Num.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 563: | Line 709: | ||
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 571: | Line 717: | ||
end | end | ||
function p.getThievingNPCTable() | function p.getThievingNPCTable(frame) | ||
local args = frame.args or frame:getParent().args | |||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local skillID = 'Thieving' | |||
local root = mw.html.create('table') | |||
:addClass('wikitable sortable stickyHeader') | |||
-- Header row | |||
local headerRow = root:tag('tr') | |||
:addClass('headerRow-0') | |||
:tag('th'):attr('colspan', '2'):wikitext('Name') | |||
:tag('th'):wikitext('Area') | |||
:tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level') | |||
:tag('th'):wikitext('[[DLC]]') | |||
:tag('th'):wikitext('Experience') | |||
:tag('th'):wikitext('Max Hit') | |||
:tag('th'):wikitext('Perception') | |||
:tag('th'):wikitext('Currency') | |||
:tag('th'):wikitext('Unique Drop') | |||
local npcArray = GameData.getEntities(SkillData.Thieving.npcs, | |||
function(obj) | |||
return Skills.getRecipeRealm(obj) == realm.id | |||
end | |||
) | |||
table.sort(npcArray, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | |||
for i, npc in ipairs(npcArray) do | |||
local level = Skills.getRecipeLevel(skillID, npc) | |||
local baseXP = npc.baseAbyssalExperience or npc.baseExperience | |||
local area = Skills.getThievingNPCArea(npc) | |||
local currSortAmt = npc.currencyDrops[1].quantity | |||
local row = root:tag('tr') | |||
row:tag('td'):wikitext(Icons.Icon({npc.name, type='thieving', notext=true})) | |||
row:tag('td'):attr('data-sort-value', npc.name) | |||
:wikitext(Icons.Icon({npc.name, type='thieving', noicon=true})) | |||
row:tag('td'):wikitext(area.name) | |||
row:tag('td'):wikitext(level) | |||
:css('text-align', 'center') | |||
row:tag('td'):wikitext(Icons.getDLCColumnIcon(npc.id)) | |||
:css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(npc.id)) | |||
row:tag('td'):css('text-align', 'right') | |||
:wikitext(Num.formatnum(baseXP)) | |||
row:tag('td'):css('text-align', 'right') | |||
:wikitext(Num.formatnum(npc.maxHit * 10)) | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', npc.perception) | |||
:wikitext(Num.formatnum(npc.perception)) | |||
row:tag('td'):attr('data-sort-value', currSortAmt) | |||
:wikitext(p._getThievingNPCCurrencyText(npc)) | |||
if npc.uniqueDrop ~= nil then | |||
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id) | |||
if npc.uniqueDrop.quantity > 1 then | |||
row:tag('td'):attr('data-sort-value', uniqueDrop.name) | |||
:wikitext(Icons.Icon({uniqueDrop.name, type='item', qty=npc.uniqueDrop.quantity})) | |||
else | |||
row:tag('td'):attr('data-sort-value', uniqueDrop.name) | |||
:wikitext(Icons.Icon({uniqueDrop.name, type='item'})) | |||
end | |||
else | |||
row:tag('td'):wikitext(' ') | |||
end | |||
end | |||
return tostring(root) | |||
end | |||
function p.getThievingAreaTable(frame) | |||
local args = frame.args or frame:getParent().args | |||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local skillID = 'Thieving' | |||
local root = mw.html.create('table') | |||
:addClass('wikitable sortable stickyHeader') | |||
-- Header row | |||
local headerRow = root:tag('tr') | |||
:addClass('headerRow-0') | |||
headerRow:tag('th'):wikitext('Area') | |||
headerRow:tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level') | |||
headerRow:tag('th'):wikitext('NPCs') | |||
headerRow:tag('th'):wikitext('Unique Drops') | |||
local areas = GameData.getEntities(SkillData.Thieving.areas, | |||
function(obj) | |||
return Skills.getRecipeRealm(obj) == realm.id | |||
end | |||
) | |||
for i, area in ipairs(areas) do | |||
local minLevel, npcList, areaItemList = nil, {}, {} | |||
-- Build NPC list & determine minimum Thieving level | |||
if area.npcIDs and not Shared.tableIsEmpty(area.npcIDs) then | |||
for j, npcID in ipairs(area.npcIDs) do | |||
local npc = Skills.getThievingNPCByID(npcID) | |||
local level = Skills.getRecipeLevel(skillID, npc) | |||
if not minLevel or level < minLevel then | |||
minLevel = 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 and Shared.tableCount(area.uniqueDrops) > 0 then | |||
for k, drop in ipairs(area.uniqueDrops) do | |||
local areaItem = Items.getItemByID(drop.id) | |||
if areaItem then | |||
local iconDef = {areaItem.name, type='item'} | |||
if drop.quantity > 1 then | |||
iconDef.qty = drop.quantity | |||
end | |||
table.insert(areaItemList, Icons.Icon(iconDef)) | |||
else | |||
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]') | |||
end | |||
end | |||
else | |||
table.insert(areaItemList, '') | |||
end | |||
-- Generate table row | |||
local row = root:tag('tr') | |||
row:tag('td'):wikitext(area.name) | |||
:attr('id', area.name) | |||
row:tag('td'):wikitext(minLevel) | |||
:css('text-align', 'center') | |||
row:tag('td'):wikitext(table.concat(npcList, '<br/>')) | |||
row:tag('td'):wikitext(table.concat(areaItemList, '<br/>')) | |||
end | |||
return tostring(root) | |||
end | |||
function p. | function p._getFarmingTable(realmID, category) | ||
local | local seedList = GameData.getEntities(SkillData.Farming.recipes, | ||
function(recipe) | |||
return recipe.categoryID == category.id and Skills.getRecipeRealm(recipe) == realmID | |||
end) | |||
if Shared.tableIsEmpty(seedList) then | |||
return '' | |||
end | |||
local | local skillID = 'Farming' | ||
local tbl = mw.html.create() | |||
local html = tbl:tag("table") | |||
:addClass("wikitable sortable stickyHeader") | |||
:tag('tr'):addClass("headerRow-0") | |||
:tag('th'):attr("colspan", 2):wikitext("Seeds") | |||
:tag('th'):wikitext(Icons.Icon({'Farming', type='skill', notext=true}) .. '<br>Level') | |||
:tag('th'):wikitext('[[DLC]]') | |||
:tag('th'):wikitext('XP') | |||
:tag('th'):wikitext('Growth Time') | |||
:tag('th'):wikitext('Seed Value') | |||
if category.id == 'melvorD:Allotment' then | |||
html:tag('th'):attr("colspan", 2):wikitext("Produce") | |||
:tag('th'):wikitext('Healing') | |||
:tag('th'):wikitext('Produce<br>Value') | |||
else | |||
html:tag('th'):attr("colspan", 2):wikitext("Produce") | |||
:tag('th'):wikitext('Produce<br>Value') | |||
end | end | ||
--html = html:tag('th'):wikitext('Seed Sources') | |||
return | table.sort(seedList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | ||
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 | |||
local level = Skills.getRecipeLevel(skillID, seed) | |||
local baseXP = seed.baseAbyssalExperience or seed.baseExperience | |||
local baseInt = seed.baseInterval | |||
local reqText = Skills.getRecipeRequirementText(SkillData.Farming.name, seed) | |||
html = | |||
html:tag('tr') | |||
:tag('td'):wikitext(Icons.Icon({seedItem.name, type='item', notext=true})) | |||
:tag('td'):wikitext('[[' .. seedItem.name .. ']]') | |||
:tag('td'):wikitext(level) | |||
:css('text-align', 'center') | |||
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id)) | |||
:css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(seedItem.id)) | |||
:tag('td'):wikitext(Num.formatnum(baseXP)) | |||
:tag('td'):attr('data-sort-value', (baseInt / 1000)) | |||
:wikitext(Shared.timeString(baseInt / 1000, true)) | |||
:tag('td'):attr('data-sort-value', seedItem.sellsFor) | |||
:wikitext(Items.getValueText(seedItem)) | |||
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true})) | |||
:tag('td'):wikitext('[[' .. productItem.name .. ']]') | |||
if category.id == 'melvorD:Allotment' then | |||
html:tag('td'):wikitext(Icons.Icon({'Hitpoints', type='skill', notext=true})) | |||
:wikitext(' ') | |||
:wikitext(((productItem.healsFor or 0) * 10)) | |||
end | end | ||
html = | |||
html:tag('td'):attr('data-sort-value', productItem.sellsFor) | |||
:wikitext(Items.getValueText(productItem)) | |||
--:tag('td'):wikitext(ItemSourceTables._getItemSources(seedItem)) | |||
-- :css('text-align', 'left') | |||
:done() | |||
end | end | ||
end | end | ||
return tostring(tbl:done()) | |||
end | |||
function p._getSlimFarmingTable(realmID, category) | |||
local seedList = GameData.getEntities(SkillData.Farming.recipes, | |||
function(recipe) | |||
return recipe.categoryID == category.id and Skills.getRecipeRealm(recipe) == realmID | |||
end) | |||
if Shared.tableIsEmpty(seedList) then | |||
return '' | |||
end | end | ||
local skillID = 'Farming' | |||
local tbl = mw.html.create() | |||
local html = tbl:tag("table") | |||
:addClass("wikitable sortable stickyHeader") | |||
:tag('tr'):addClass("headerRow-0") | |||
:tag('th'):wikitext(Icons.Icon({'Farming', type='skill', notext=true}) .. '<br>Level') | |||
:tag('th'):attr("colspan", 2):wikitext("Seeds") | |||
:tag('th'):attr("colspan", 2):wikitext("Produce") | |||
:tag('th'):wikitext('[[DLC]]') | |||
table.sort(seedList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end) | |||
function | |||
local | 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 | |||
local level = Skills.getRecipeLevel(skillID, seed) | |||
html = | |||
html:tag('tr') | |||
:tag('td'):wikitext(level) | |||
:css('text-align', 'center') | |||
:tag('td'):wikitext(Icons.Icon({seedItem.name, type='item', notext=true})) | |||
:tag('td'):wikitext('[[' .. seedItem.name .. ']]') | |||
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true})) | |||
:tag('td'):wikitext('[[' .. productItem.name .. ']]') | |||
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id)) | |||
:css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(seedItem.id)) | |||
:done() | |||
end | end | ||
end | end | ||
return tostring(tbl:done()) | |||
end | |||
function p.getFarmingTable(frame) | |||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local slim = args.slim == 'true' or args.slim == 'True' | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
local categoryName = args[1] | |||
local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName) | |||
if category == nil then | |||
return Shared.printError('Invalid farming category: ' .. categoryName .. '. Please choose Allotments, Herbs, Trees or Special') | |||
end | end | ||
if | if slim == true then | ||
return p._getSlimFarmingTable(realm.id, category) | |||
else | else | ||
return | return p._getFarmingTable(realm.id, category) | ||
end | |||
end | |||
function p.getFarmingFoodTable(frame) | |||
local args = frame.args ~= nil and frame.args or frame | |||
local realmName = args.realm | |||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | end | ||
local skillID = 'Farming' | |||
local root = mw.html.create('table') | |||
:addClass('wikitable sortable stickyHeader') | |||
-- Header row | |||
local headerRow = root:tag('tr') | |||
:addClass('headerRow-0') | |||
headerRow:tag('th'):attr('colspan', '2'):wikitext('Crop') | |||
headerRow:tag('th'):wikitext(Icons.Icon({"Farming", type="skill", notext=true}) .. '<br>Level') | |||
headerRow:tag('th'):wikitext('[[DLC]]') | |||
headerRow:tag('th'):wikitext('Healing') | |||
headerRow:tag('th'):wikitext('Value') | |||
local recipes = GameData.getEntities(SkillData[skillID].recipes, | |||
function(recipe) | |||
if Skills.getRecipeRealm(recipe) ~= realm.id then | |||
return false | |||
end | |||
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 Skills.standardRecipeSort(skillID, a, b) end) | |||
for i, recipe in ipairs(recipes) do | |||
local product = Items.getItemByID(recipe.productId) | |||
if product and product.healsFor and product.healsFor > 0 then | |||
local row = root:tag('tr') | |||
row:tag('td'):wikitext(Icons.Icon({product.name, type='item', notext='true'})) | |||
row:tag('td'):wikitext('[[' .. product.name .. ']]') | |||
row:tag('td'):css('text-align', 'center') | |||
:wikitext(Skills.getRecipeLevel(skillID, recipe)) | |||
row:tag('td'):css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(product.id)) | |||
:wikitext(Icons.getDLCColumnIcon(product.id)) | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', product.healsFor) | |||
:wikitext(Icons.Icon({"Hitpoints", type="skill", notext=true}) .. ' ' .. (product.healsFor * 10)) | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', product.sellsFor) | |||
:wikitext(Items.getValueText(product)) | |||
end | |||
end | |||
return tostring(root) | |||
end | end | ||
function p.getFarmingPlotTable(frame) | |||
local skillID = 'Farming' | |||
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, Trees or Special') | |||
end | |||
local patches = GameData.getEntities(SkillData.Farming.plots, | |||
function(plot) | |||
return plot.categoryID == category.id | |||
end | |||
) | |||
table.sort(patches, | |||
function(a, b) | |||
local abyssA, abyssB = a.abyssalLevel or 0, b.abyssalLevel or 0 | |||
if abyssA == abyssB then | |||
if a.level == b.level then | |||
return a.id < b.id | |||
else | |||
return a.level < b.level | |||
end | |||
else | |||
return abyssA < abyssB | |||
end | |||
end | |||
) | |||
if Shared.tableIsEmpty(patches) then | |||
return '' | |||
end | |||
local root = mw.html.create('table') | |||
:addClass('wikitable sortable stickyHeader') | |||
-- Header row | |||
local headerRow = root:tag('tr') | |||
headerRow:tag('th'):wikitext('Plot') | |||
headerRow:tag('th'):wikitext('Requirements') | |||
headerRow:tag('th'):wikitext('Cost') | |||
for i, patch in ipairs(patches) do | |||
local level = Skills.getRecipeLevel(skillID, patch) | |||
local reqText = Skills.getRecipeRequirementText(skillID, patch) | |||
local costText = Common.getCostString({ items = patch.itemCosts, currencies = patch.currencyCosts }, 'Free') | |||
local costVal = (patch.currencyCosts and patch.currencyCosts[1] and patch.currencyCosts[1].quantity) or 0 | |||
local row = root:tag('tr') | |||
row:tag('td'):wikitext(i) | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', level) | |||
:wikitext(reqText) | |||
row:tag('td'):css('text-align', 'right') | |||
:attr('data-sort-value', costVal) | |||
:wikitext(costText) | |||
end | |||
return tostring(root) | |||
end | |||
function p._buildAstrologyConstellationTable(realmID) | |||
local modTypes = { | |||
{ | |||
name = 'Standard', | |||
modKey = 'standardModifiers', | |||
levels = SkillData.Astrology.standardModifierLevels, | |||
inUse = false | |||
}, | |||
{ | |||
name = 'Unique', | |||
modKey = 'uniqueModifiers', | |||
levels = SkillData.Astrology.uniqueModifierLevels, | |||
inUse = false | |||
}, | |||
{ | |||
name = 'Abyssal', | |||
modKey = 'abyssalModifiers', | |||
levels = SkillData.Astrology.abyssalModifierLevels, | |||
inUse = false | |||
}, | |||
} | |||
local recipes = GameData.getEntities(SkillData.Astrology.recipes, | |||
function(cons) | |||
return Skills.getRecipeRealm(cons) == realmID | |||
end | |||
) | |||
table.sort(recipes, | |||
function(a, b) | |||
return Skills.getRecipeLevel('Astrology', a) < Skills.getRecipeLevel('Astrology', b) | |||
end | |||
) | |||
-- Determine which mod types are in use | |||
for _, recipe in ipairs(recipes) do | |||
for _, modType in ipairs(modTypes) do | |||
if not modType.inUse then | |||
local recipeMods = recipe[modType.modKey] | |||
if recipeMods ~= nil and not Shared.tableIsEmpty(recipeMods) then | |||
modType.inUse = true | |||
end | |||
end | |||
end | |||
end | |||
local root = mw.html.create() | |||
local tableRoot = root:tag('table') | |||
tableRoot:addClass('wikitable stickyHeader') | |||
-- Header rows | |||
local headerRow1 = tableRoot:tag('tr'):addClass('headerRow-0') | |||
headerRow1:tag('th'):attr('rowspan', 2) | |||
:attr('colspan', 2) | |||
:wikitext('Constellation') | |||
headerRow1:tag('th'):attr('rowspan', 2) | |||
:wikitext(Icons.Icon({ "Astrology", type='skill', notext='true' }) .. '<br>Level') | |||
headerRow1:tag('th'):attr('rowspan', 2) | |||
:wikitext('[[DLC]]') | |||
headerRow1:tag('th'):attr('rowspan', 2) | |||
:wikitext('XP') | |||
headerRow1:tag('th'):attr('rowspan', 2) | |||
:wikitext('Skills') | |||
local headerRow2 = tableRoot:tag('tr'):addClass('headerRow-1') | |||
for _, modType in ipairs(modTypes) do | |||
if modType.inUse then | |||
local spanCount = modType.name == 'Abyssal' and 3 or 2 | |||
headerRow1:tag('th'):attr('colspan', spanCount):wikitext(modType.name .. ' Stars') | |||
headerRow2:tag('th'):wikitext(Icons.Icon({'Mastery', notext=true}) .. '<br>Level') | |||
if modType.name == "Abyssal" then | |||
headerRow2:tag('th'):wikitext('Level') | |||
end | |||
headerRow2:tag('th'):wikitext('Modifiers') | |||
end | |||
end | |||
-- Data rows | |||
for _, cons in ipairs(recipes) do | |||
local modData = Skills._getConstellationModifiers(cons) | |||
local name = cons.name | |||
local skillIconArray = {} | |||
for _, skillID in ipairs(cons.skillIDs) do | |||
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'})) | |||
end | |||
-- Calculate maximum rows needed | |||
local maxRows = 1 | |||
for _, modTypeData in pairs(modData) do | |||
maxRows = math.max(maxRows, Shared.tableCount(modTypeData)) | |||
end | |||
-- Iterate through rows | |||
for rowIdx = 1, maxRows do | |||
local row = tableRoot:tag('tr') | |||
if rowIdx == 1 then | |||
row:tag('td'):attr('rowspan', maxRows) | |||
:wikitext(Icons.Icon({name, type='constellation', size=50, notext=true})) | |||
:css('text-align', 'center') | |||
row:tag('td'):attr('rowspan', maxRows) | |||
:wikitext(name) | |||
:attr('id', name) | |||
row:tag('td'):attr('rowspan', maxRows) | |||
:wikitext((cons.abyssalLevel or cons.level)) | |||
:css('text-align', 'center') | |||
row:tag('td'):css('text-align', 'center') | |||
:attr('data-sort-value', Icons.getExpansionID(cons.id)) | |||
:attr('rowspan', maxRows) | |||
:wikitext(Icons.getDLCColumnIcon(cons.id)) | |||
row:tag('td'):attr('rowspan', maxRows) | |||
:wikitext(Num.formatnum(cons.baseAbyssalExperience or cons.baseExperience)) | |||
:css('text-align', 'right') | |||
row:tag('td'):attr('rowspan', maxRows) | |||
:wikitext(table.concat(skillIconArray, '<br/>')) | |||
:css('text-wrap', 'nowrap') | |||
end | |||
-- Modifiers data | |||
for _, modType in ipairs(modTypes) do | |||
if modType.inUse then | |||
local masteryLevel = modType.levels[rowIdx] | |||
local rowModData = modData[modType.modKey][rowIdx] | |||
local rowConsData = cons[modType.modKey][rowIdx] | |||
local cell1 = row:tag('td') | |||
if modType.name == "Abyssal" then | |||
local starUnlockReq = nil | |||
local cell2 = row:tag('td') | |||
if rowConsData ~= nil and rowConsData.unlockRequirements ~= nil and Shared.tableCount(rowConsData.unlockRequirements) == 2 then | |||
starUnlockReq = Common.getRequirementString({cons[modType.modKey][rowIdx].unlockRequirements[2]}, nil) | |||
end | |||
if starUnlockReq ~= nil then | |||
cell2:wikitext(starUnlockReq) | |||
:css('text-align', 'right') | |||
else | |||
cell2:wikitext('N/A') | |||
:addClass('table-na') | |||
end | |||
end | |||
local cell3 = row:tag('td') | |||
if masteryLevel ~= nil and rowModData ~= nil then | |||
local modText = {} | |||
for _, modKey in ipairs({'modifiers', 'enemyModifiers'}) do | |||
local mods = rowModData[modKey] | |||
if mods ~= nil then | |||
if modKey == 'enemyModifiers' then | |||
table.insert(modText, 'Gives the enemy:') | |||
end | |||
table.insert(modText, Modifiers.getModifiersText(mods, false, false, 10)) | |||
end | |||
end | |||
cell1:wikitext(masteryLevel) | |||
:css('text-align', 'right') | |||
cell3:wikitext(table.concat(modText, '<br>')) | |||
else | |||
cell1:attr('colspan', 2) | |||
:wikitext('N/A') | |||
:addClass('table-na') | |||
end | |||
end | |||
end | |||
end | |||
end | |||
return | return tostring(root) | ||
end | end | ||
function p.buildAstrologyConstellationTable(frame) | function p.buildAstrologyConstellationTable(frame) | ||
return | local args = frame.args ~= nil and frame.args or frame | ||
end | local realmName = args.realm | ||
local realm = Skills.getRealmFromName(realmName) | |||
if realm == nil then | |||
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil')) | |||
end | |||
return p._buildAstrologyConstellationTable(realm.id) | |||
end | end | ||
return p | return p |
Latest revision as of 04:39, 4 November 2024
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 Common = require('Module:Common')
local Modifiers = require('Module:Modifiers')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Skills = require('Module:Skills')
local ItemSourceTables = require('Module:Items/SourceTables')
local Num = require("Module:Number")
local function lootValueText(lootValue)
local returnPart = {}
for _, currencyDefn in ipairs(GameData.rawData.currencies) do
-- Guarantee order by iterating through currency game data definition
local currID = currencyDefn.id
local val = Num.round(lootValue[currID], 2, 2)
if val ~= nil then
table.insert(returnPart, Icons._Currency(currID, val))
end
end
return table.concat(returnPart, ', ')
end
function p.getTreesTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Woodcutting'
local html = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
html:tag('tr')
:addClass("headerRow-0")
:tag('th'):attr('colspan', 2)
:wikitext('Tree')
:tag('th'):attr('colspan', 2)
:wikitext('Logs')
:tag('th'):wikitext('Requirements')
:tag('th'):wikitext('[[DLC]]')
:tag('th'):wikitext('XP')
:tag('th'):wikitext('Cut Time')
:tag('th'):wikitext('XP/s')
:tag('th'):wikitext('Price/s')
local trees = GameData.getEntities(SkillData.Woodcutting.trees,
function(tree)
return Skills.getRecipeRealm(tree) == realm.id
end
)
table.sort(trees, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
for i, tree in ipairs(trees) do
local level = Skills.getRecipeLevel(skillID, tree)
local baseXP = tree.baseAbyssalExperience or tree.baseExperience
local baseInt = tree.baseInterval
local reqText = Skills.getRecipeRequirementText(SkillData.Woodcutting.name, tree)
local log = Items.getItemByID(tree.productId)
local sellCurrency = log.sellsForCurrency or 'melvorD:GP'
local XPSec = baseXP / (baseInt / 1000)
local currSec = Num.round(log.sellsFor / (baseInt / 1000), 2, 2)
html:tag('tr')
:tag('td'):wikitext(Icons.Icon({log.name, img=tree.name, type='tree', notext=true}))
:addClass('table-img')
:attr('data-sort-value', tree.name)
:tag('td'):wikitext(tree.name)
:tag('td'):wikitext(Icons.Icon({log.name, type='item', notext=true}))
:addClass('table-img')
:attr('data-sort-value', log.name)
:tag('td'):wikitext('[[' .. log.name .. ']]')
:tag('td'):wikitext(reqText)
:attr('data-sort-value', level)
:tag('td'):wikitext(Icons.getDLCColumnIcon(tree.id))
:attr('data-sort-value', Icons.getExpansionID(tree.id))
:css('text-align', 'center')
:tag('td'):wikitext(Num.formatnum(baseXP))
:css('text-align', 'right')
:tag('td'):wikitext(Shared.timeString(baseInt / 1000, true))
:attr('data-sort-value', baseInt)
:css('text-align', 'right')
:tag('td'):wikitext(Num.round(XPSec, 2, 2))
:css('text-align', 'right')
:tag('td'):wikitext(Icons._Currency(sellCurrency, currSec))
:attr('data-sort-value', currSec)
:css('text-align', 'right')
end
return tostring(html)
end
function p.getSpecialFishingTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local totalWt, lootValue = 0, {}
local realmSpecials = GameData.getEntityByProperty(SkillData.Fishing.specialItems, 'realmID', realm.id)
if realmSpecials == nil then
return ''
end
local itemArray = realmSpecials.drops
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 html = mw.html.create('table')
html:addClass('wikitable sortable stickyHeader')
-- Add header row
local headerRow = html:tag('tr'):addClass('headerRow-0')
headerRow:tag('th'):attr('colspan', '2'):wikitext('Item')
headerRow:tag('th'):wikitext('Value')
headerRow:tag('th'):attr('colspan', '2'):wikitext('Chance')
-- Add item rows
for i, itemDef in ipairs(itemArray) do
local item = Items.getItemByID(itemDef.itemID)
if item ~= nil then
local dropChance = itemDef.weight / totalWt * 100
local currID = item.sellsForCurrency or 'melvorD:GP'
-- 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'
local row = html:tag('tr')
row:tag('td'):addClass('table-img'):wikitext(Icons.Icon({item.name, type='item', notext=true}))
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', noicon=true}))
row:tag('td')
:attr('data-sort-value', item.sellsFor)
:wikitext(Items.getValueText(item))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', itemDef.weight)
:wikitext(Num.fraction(itemDef.weight, totalWt))
row:tag('td')
:css('text-align', 'right')
:wikitext(string.format(fmt, dropChance) .. '%')
if lootValue[currID] == nil then
lootValue[currID] = 0
end
lootValue[currID] = lootValue[currID] + (dropChance / 100 * item.sellsFor)
end
end
local result = tostring(html)
local averageValueText = 'The average value of a roll on the special fishing loot table is ' .. lootValueText(lootValue)
return result .. '\n' .. averageValueText
end
function p.getFishingJunkTable(frame)
local html = mw.html.create('table')
html:addClass('wikitable sortable stickyHeader')
-- Add header row
local headerRow = html:tag('tr'):addClass('headerRow-0')
headerRow:tag('th'):attr('colspan', '2'):wikitext('Item')
headerRow:tag('th'):wikitext('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)
-- Add item rows
for i, item in ipairs(itemArray) do
local row = html:tag('tr')
row:tag('td'):addClass('table-img')
:wikitext(Icons.Icon({item.name, type='item', notext=true}))
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', noicon=true}))
row:tag('td')
:attr('data-sort-value', item.sellsFor)
:wikitext(Items.getValueText(item))
end
return tostring(html)
end
function p.getMiningOresTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Mining'
local html = mw.html.create('table')
:addClass("wikitable sortable stickyHeader")
html:tag('tr'):addClass("headerRow-0")
:tag('th'):wikitext('Rock')
:attr('colspan', 2)
:tag('th'):wikitext('Ore')
:attr('colspan', 2)
:tag('th'):wikitext('Type')
:tag('th'):wikitext('Requirements')
:tag('th'):wikitext('[[DLC]]')
:tag('th'):wikitext('XP')
:tag('th'):wikitext('Respawn<br>Time')
:tag('th'):wikitext('Ore Value')
local mineData = GameData.getEntities(SkillData.Mining.rockData,
function(obj)
return Skills.getRecipeRealm(obj) == realm.id
end
)
table.sort(mineData, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
for i, oreData in ipairs(mineData) do
local level = Skills.getRecipeLevel(skillID, oreData)
local baseXP = oreData.baseAbyssalExperience or oreData.baseExperience
local reqText = Skills.getRecipeRequirementText(SkillData.Mining.name, oreData)
local ore = Items.getItemByID(oreData.productId)
local respawnSort, respawnText = 0, 'N/A'
if oreData.hasPassiveRegen then
respawnSort = oreData.baseRespawnInterval / 1000
respawnText = Shared.timeString(respawnSort, true)
end
local categoryName = ''
local category = GameData.getEntityByID(SkillData.Mining.categories, oreData.category)
if category ~= nil and category.name ~= nil then
categoryName = category.name
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 ''
local row = html:tag('tr')
row :tag('td'):wikitext(Icons.Icon({oreData.name, type='rock', notext=true, nolink=true}))
:addClass('table-img')
:attr('data-sort-value', rockName)
:tag('td'):wikitext(rockName)
:tag('td'):wikitext(Icons.Icon({ore.name, type='item', notext=true}))
:addClass('table-img')
:attr('data-sort-value', ore.name)
:tag('td'):wikitext(qtyText .. '[[' .. ore.name .. ']]')
:tag('td'):wikitext(categoryName)
:tag('td'):wikitext(reqText)
:attr('data-sort-value', level)
:tag('td'):wikitext(Icons.getDLCColumnIcon(oreData.id))
:attr('data-sort-value', Icons.getExpansionID(oreData.id))
:css('text-align', 'center')
:tag('td'):wikitext(Num.formatnum(baseXP))
:css('text-align', 'right')
local respawn =
row:tag('td'):wikitext(respawnText)
:attr('data-sort-value', respawnSort)
row:tag('td'):wikitext(Items.getValueText(ore))
:attr('data-sort-value', ore.sellsFor)
if respawnText == 'N/A' then
respawn:addClass('table-na')
else
respawn:css('text-align', 'right')
end
end
return tostring(html)
end
function p._getMiningGemsTable(gemType)
if type(gemType) ~= 'string' then
gemType = 'Normal'
end
local validTypes = {
["Normal"] = 'randomGems',
["Superior"] = 'randomSuperiorGems',
["Abyssal"] = 'randomAbyssalGems'
}
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 html = mw.html.create('table')
:addClass('wikitable sortable')
-- Add header row
local headerRow = html:tag('tr'):addClass('headerRow-0')
:tag('th'):wikitext('Gem')
:attr('colspan', '2')
:tag('th'):wikitext('[[DLC]]')
:tag('th'):wikitext('Gem Chance')
:tag('th'):wikitext('Gem Price')
-- Add gem rows
for i, gem in ipairs(gemData) do
local gemItem = Items.getItemByID(gem.itemID)
local gemPct = gem.weight / totalWeight * 100
local row = html:tag('tr')
row:tag('td'):addClass('table-img')
:wikitext(Icons.Icon({gemItem.name, type='item', notext=true}))
row:tag('td'):attr('data-sort-value', gemItem.name)
:wikitext('[[' .. gemItem.name ..']]')
row:tag('td'):wikitext(Icons.getDLCColumnIcon(gemItem.id))
:attr('data-sort-value', Icons.getExpansionID(gemItem.id))
:css('text-align', 'center')
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', gemPct)
:wikitext(string.format("%.1f%%", gemPct))
row:tag('td'):attr('data-sort-value', gemItem.sellsFor)
:wikitext(Items.getValueText(gemItem))
end
return tostring(html)
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 args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Fishing'
local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(obj)
return Skills.getRecipeRealm(obj) == realm.id
end)
table.sort(recipeList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
-- Determine cooking levels for all fish
local cookRecipes = {}
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 cookRecipes[mat.id] == nil then
cookRecipes[mat.id] = recipe
end
end
end
local html = mw.html.create('table')
html:addClass('wikitable sortable stickyHeader')
-- Add header row
local headerRow0 = html:tag('tr'):addClass('headerRow-0')
headerRow0:tag('th'):attr('colspan', '2'):attr('rowspan', '2'):wikitext('Fish')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext(Icons._SkillRealmIcon('Fishing', realm.id) .. '<br>Level')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('[[DLC]]')
headerRow0:tag('th'):attr('colspan', '3'):wikitext('Catch Time')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('XP')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('XP/s')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('Value')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('GP/s')
local headerRow1 = html:tag('tr'):addClass('headerRow-1')
headerRow1:tag('th'):wikitext('Min')
headerRow1:tag('th'):wikitext('Max')
headerRow1:tag('th'):wikitext('Avg')
-- Add fish rows
for i, recipe in ipairs(recipeList) do
local fish = Items.getItemByID(recipe.productId)
if fish ~= nil then
local timeMin, timeMax = recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000
local timeAvg = (timeMin + timeMax) / 2
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000
local level = Skills.getRecipeLevel(skillID, recipe)
local reqText = Skills.getRecipeRequirementText(skillID, recipe)
local baseXP = recipe.baseAbyssalExperience or recipe.baseExperience
local XPSec = baseXP / timeSortVal
local sellCurrency = fish.sellsForCurrency or 'melvorD:GP'
local GPSec = fish.sellsFor / timeSortVal
local row = html:tag('tr')
row:tag('td'):wikitext(Icons.Icon({fish.name, type='item', notext=true}))
:addClass('table-img')
row:tag('td'):wikitext('[[' .. fish.name .. ']]')
:attr('data-sort-value', fish.name)
row:tag('td'):wikitext(level)
:css('text-align', 'center')
:attr('data-sort-value', level)
row:tag('td'):wikitext(Icons.getDLCColumnIcon(fish.id))
:css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(fish.id))
row:tag('td'):wikitext(string.format("%.1fs", timeMin))
:css('text-align', 'right')
:attr('data-sort-value', timeMin)
row:tag('td'):wikitext(string.format("%.1fs", timeMax))
:css('text-align', 'right')
:attr('data-sort-value', timeMax)
row:tag('td'):wikitext(string.format("%.1fs", timeAvg))
:css('text-align', 'right')
:attr('data-sort-value', timeAvg)
row:tag('td'):wikitext(Num.formatnum(baseXP))
:css('text-align', 'right')
:attr('data-sort-value', baseXP)
row:tag('td'):wikitext(Num.round(XPSec, 2, 2))
:css('text-align', 'right')
row:tag('td'):wikitext(Items.getValueText(fish))
:attr('data-sort-value', fish.sellsFor)
:css('text-align', 'right')
row:tag('td'):wikitext(Icons._Currency(sellCurrency, Num.round(GPSec, 2, 2)))
:attr('data-sort-value', GPSec)
:css('text-align', 'right')
end
end
return tostring(html)
end
function p.getFishingAreasTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Fishing'
local html = mw.html.create('table')
html:addClass('wikitable sortable stickyHeader')
-- Add header row
local headerRow = html:tag('tr'):addClass('headerRow-0')
headerRow:tag('th'):wikitext('Name')
headerRow:tag('th'):wikitext('Fish')
headerRow:tag('th'):wikitext('[[DLC]]')
headerRow:tag('th'):wikitext('Fish Chance')
headerRow:tag('th'):wikitext('Junk Chance')
headerRow:tag('th'):wikitext('Special Chance')
-- Get fishing areas
local fishAreas = GameData.getEntities(SkillData.Fishing.areas, function(obj)
return Skills.getRecipeRealm(obj) == realm.id
end)
-- Add rows for each fishing area
for i, area in ipairs(fishAreas) do
local row = html:tag('tr')
row:tag('td')
:css('text-align', 'left')
:attr('id', area.name)
:wikitext(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
row:tag('td')
:wikitext(table.concat(fishArray, '<br/>'))
row:tag('td')
:css('text-align', 'center')
:wikitext(Icons.getDLCColumnIcon(area.id))
:attr('data-sort-value', Icons.getExpansionID(area.id))
row:tag('td')
:css('text-align', 'right')
:wikitext(area.fishChance .. '%')
row:tag('td')
:css('text-align', 'right')
:wikitext(area.junkChance .. '%')
row:tag('td')
:css('text-align', 'right')
:wikitext(area.specialChance .. '%')
end
return tostring(html)
end
function p.getThievingGeneralRareTable(frame)
return p._getThievingGeneralRareTable()
end
function p._getThievingGeneralRareTable(npc)
local npcRealm = nil
if npc ~= nil then
npcRealm = Skills.getRecipeRealm(npc)
end
local html = mw.html.create('table')
html:addClass('wikitable sortable')
-- Add header row
local headerRow = html:tag('tr')
headerRow:tag('th'):wikitext('Item')
headerRow:tag('th'):wikitext('[[DLC]]')
headerRow:tag('th'):wikitext('Qty')
headerRow:tag('th'):wikitext('Price')
headerRow:tag('th'):attr('colspan', '2'):wikitext('Chance')
-- Add rows for each rare item
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
local npcMatch = (npc == nil or drop.npcs == nil or Shared.contains(drop.npcs, npc.id))
local realmMatch = (npcRealm == nil or drop.realms == nil or Shared.contains(drop.realms, npcRealm))
if npcMatch and realmMatch then
local thisItem = Items.getItemByID(drop.itemID)
local odds = drop.chance
local row = html:tag('tr')
row:tag('td')
:attr('data-sort-value', thisItem.name)
:wikitext(Icons.Icon({thisItem.name, type='item'}))
row:tag('td')
:wikitext(Icons.getDLCColumnIcon(thisItem.id))
:attr('data-sort-value', Icons.getExpansionID(thisItem.id))
:css('text-align', 'center')
row:tag('td')
:wikitext('1')
row:tag('td')
:attr('data-sort-value', thisItem.sellsFor)
:wikitext(Items.getValueText(thisItem))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', odds)
:wikitext(Num.fraction(1, Num.round2(1/(odds/100), 0)))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', odds)
:wikitext(Num.round(odds, 4, 4) .. '%')
end
end
return tostring(html)
end
function p._getThievingNPCCurrencyText(npc)
local currTextPart = {}
for _, currencyDrop in ipairs(npc.currencyDrops) do
table.insert(currTextPart, Icons._Currency(currencyDrop.id, 1, currencyDrop.quantity))
end
return table.concat(currTextPart, ', ')
end
function p._getThievingNPCLootTables(npc)
local result = ''
local sectionTxt = {}
--Five sections here: Currency, normal loot, area loot, rare loot, and unique item
--First up, currency:
table.insert(sectionTxt, 'Successfully pickpocketing the ' .. npc.name .. ' will always give '.. p._getThievingNPCCurrencyText(npc))
--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 = {}
--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 = Shared.shallowClone(npc.lootTable)
--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, Num.formatnum(loot.minQuantity) .. ' - ' .. Num.formatnum(loot.maxQuantity))
else
table.insert(normalTxt, Num.formatnum(loot.maxQuantity))
end
--Adding price columns
local sellAmount, sellCurrency = nil, nil
if thisItem == nil then
table.insert(normalTxt, '||data-sort-value="0"|???')
else
sellAmount = thisItem.sellsFor or 0
sellCurrency = thisItem.sellsForCurrency or 'melvorD:GP'
table.insert(normalTxt, '||' .. Items.getValueText(thisItem, loot.minQuantity, loot.maxQuantity))
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, '|'..Num.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"|'..Num.round(dropChance, 2, 2)..'%')
--Adding to the average loot value based on price & dropchance
if sellAmount ~= nil and sellCurrency ~= nil then
if lootValue[sellCurrency] == nil then
lootValue[sellCurrency] = 0
end
lootValue[sellCurrency] = lootValue[sellCurrency] + (dropChance * 0.01 * sellAmount * (loot.minQuantity + loot.maxQuantity) / 2)
end
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"|'..Num.fraction(lootChance, 100)..'||')
else
table.insert(normalTxt, '\r\n|colspan="2" ')
end
table.insert(normalTxt, 'style="text-align:right"|'..Num.round(lootChance, 2, 2)..'%')
end
table.insert(normalTxt, '\r\n|}')
table.insert(normalTxt, '\r\nThe loot obtained from the average successful pickpocket is worth ' .. lootValueText(lootValue) .. ' if sold.')
-- Amend lootValue
for _, currencyDrop in ipairs(npc.currencyDrops) do
lootValue[currencyDrop.id] = lootValue[currencyDrop.id] + (1 + currencyDrop.quantity) / 2
end
table.insert(normalTxt, '\r\n\r\nIncluding currency, the average successful pickpocket is worth ' .. lootValueText(lootValue) .. '.')
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)
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..'"| '..Num.formatnum(drop.quantity)..'||data-sort-value="'..thisItem.sellsFor..'"|'..Items.getValueText(thisItem)
lineTxt = lineTxt..'||style="text-align:right"|'..Num.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100))
lineTxt = lineTxt..'||'..Num.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(frame)
local args = frame.args or frame:getParent().args
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Thieving'
local root = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
-- Header row
local headerRow = root:tag('tr')
:addClass('headerRow-0')
:tag('th'):attr('colspan', '2'):wikitext('Name')
:tag('th'):wikitext('Area')
:tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level')
:tag('th'):wikitext('[[DLC]]')
:tag('th'):wikitext('Experience')
:tag('th'):wikitext('Max Hit')
:tag('th'):wikitext('Perception')
:tag('th'):wikitext('Currency')
:tag('th'):wikitext('Unique Drop')
local npcArray = GameData.getEntities(SkillData.Thieving.npcs,
function(obj)
return Skills.getRecipeRealm(obj) == realm.id
end
)
table.sort(npcArray, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
for i, npc in ipairs(npcArray) do
local level = Skills.getRecipeLevel(skillID, npc)
local baseXP = npc.baseAbyssalExperience or npc.baseExperience
local area = Skills.getThievingNPCArea(npc)
local currSortAmt = npc.currencyDrops[1].quantity
local row = root:tag('tr')
row:tag('td'):wikitext(Icons.Icon({npc.name, type='thieving', notext=true}))
row:tag('td'):attr('data-sort-value', npc.name)
:wikitext(Icons.Icon({npc.name, type='thieving', noicon=true}))
row:tag('td'):wikitext(area.name)
row:tag('td'):wikitext(level)
:css('text-align', 'center')
row:tag('td'):wikitext(Icons.getDLCColumnIcon(npc.id))
:css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(npc.id))
row:tag('td'):css('text-align', 'right')
:wikitext(Num.formatnum(baseXP))
row:tag('td'):css('text-align', 'right')
:wikitext(Num.formatnum(npc.maxHit * 10))
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', npc.perception)
:wikitext(Num.formatnum(npc.perception))
row:tag('td'):attr('data-sort-value', currSortAmt)
:wikitext(p._getThievingNPCCurrencyText(npc))
if npc.uniqueDrop ~= nil then
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id)
if npc.uniqueDrop.quantity > 1 then
row:tag('td'):attr('data-sort-value', uniqueDrop.name)
:wikitext(Icons.Icon({uniqueDrop.name, type='item', qty=npc.uniqueDrop.quantity}))
else
row:tag('td'):attr('data-sort-value', uniqueDrop.name)
:wikitext(Icons.Icon({uniqueDrop.name, type='item'}))
end
else
row:tag('td'):wikitext(' ')
end
end
return tostring(root)
end
function p.getThievingAreaTable(frame)
local args = frame.args or frame:getParent().args
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Thieving'
local root = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
-- Header row
local headerRow = root:tag('tr')
:addClass('headerRow-0')
headerRow:tag('th'):wikitext('Area')
headerRow:tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level')
headerRow:tag('th'):wikitext('NPCs')
headerRow:tag('th'):wikitext('Unique Drops')
local areas = GameData.getEntities(SkillData.Thieving.areas,
function(obj)
return Skills.getRecipeRealm(obj) == realm.id
end
)
for i, area in ipairs(areas) do
local minLevel, npcList, areaItemList = nil, {}, {}
-- Build NPC list & determine minimum Thieving level
if area.npcIDs and not Shared.tableIsEmpty(area.npcIDs) then
for j, npcID in ipairs(area.npcIDs) do
local npc = Skills.getThievingNPCByID(npcID)
local level = Skills.getRecipeLevel(skillID, npc)
if not minLevel or level < minLevel then
minLevel = 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 and Shared.tableCount(area.uniqueDrops) > 0 then
for k, drop in ipairs(area.uniqueDrops) do
local areaItem = Items.getItemByID(drop.id)
if areaItem then
local iconDef = {areaItem.name, type='item'}
if drop.quantity > 1 then
iconDef.qty = drop.quantity
end
table.insert(areaItemList, Icons.Icon(iconDef))
else
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
end
end
else
table.insert(areaItemList, '')
end
-- Generate table row
local row = root:tag('tr')
row:tag('td'):wikitext(area.name)
:attr('id', area.name)
row:tag('td'):wikitext(minLevel)
:css('text-align', 'center')
row:tag('td'):wikitext(table.concat(npcList, '<br/>'))
row:tag('td'):wikitext(table.concat(areaItemList, '<br/>'))
end
return tostring(root)
end
function p._getFarmingTable(realmID, category)
local seedList = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
return recipe.categoryID == category.id and Skills.getRecipeRealm(recipe) == realmID
end)
if Shared.tableIsEmpty(seedList) then
return ''
end
local skillID = 'Farming'
local tbl = mw.html.create()
local html = tbl:tag("table")
:addClass("wikitable sortable stickyHeader")
:tag('tr'):addClass("headerRow-0")
:tag('th'):attr("colspan", 2):wikitext("Seeds")
:tag('th'):wikitext(Icons.Icon({'Farming', type='skill', notext=true}) .. '<br>Level')
:tag('th'):wikitext('[[DLC]]')
:tag('th'):wikitext('XP')
:tag('th'):wikitext('Growth Time')
:tag('th'):wikitext('Seed Value')
if category.id == 'melvorD:Allotment' then
html:tag('th'):attr("colspan", 2):wikitext("Produce")
:tag('th'):wikitext('Healing')
:tag('th'):wikitext('Produce<br>Value')
else
html:tag('th'):attr("colspan", 2):wikitext("Produce")
:tag('th'):wikitext('Produce<br>Value')
end
--html = html:tag('th'):wikitext('Seed Sources')
table.sort(seedList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) 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
local level = Skills.getRecipeLevel(skillID, seed)
local baseXP = seed.baseAbyssalExperience or seed.baseExperience
local baseInt = seed.baseInterval
local reqText = Skills.getRecipeRequirementText(SkillData.Farming.name, seed)
html =
html:tag('tr')
:tag('td'):wikitext(Icons.Icon({seedItem.name, type='item', notext=true}))
:tag('td'):wikitext('[[' .. seedItem.name .. ']]')
:tag('td'):wikitext(level)
:css('text-align', 'center')
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id))
:css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(seedItem.id))
:tag('td'):wikitext(Num.formatnum(baseXP))
:tag('td'):attr('data-sort-value', (baseInt / 1000))
:wikitext(Shared.timeString(baseInt / 1000, true))
:tag('td'):attr('data-sort-value', seedItem.sellsFor)
:wikitext(Items.getValueText(seedItem))
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true}))
:tag('td'):wikitext('[[' .. productItem.name .. ']]')
if category.id == 'melvorD:Allotment' then
html:tag('td'):wikitext(Icons.Icon({'Hitpoints', type='skill', notext=true}))
:wikitext(' ')
:wikitext(((productItem.healsFor or 0) * 10))
end
html =
html:tag('td'):attr('data-sort-value', productItem.sellsFor)
:wikitext(Items.getValueText(productItem))
--:tag('td'):wikitext(ItemSourceTables._getItemSources(seedItem))
-- :css('text-align', 'left')
:done()
end
end
return tostring(tbl:done())
end
function p._getSlimFarmingTable(realmID, category)
local seedList = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
return recipe.categoryID == category.id and Skills.getRecipeRealm(recipe) == realmID
end)
if Shared.tableIsEmpty(seedList) then
return ''
end
local skillID = 'Farming'
local tbl = mw.html.create()
local html = tbl:tag("table")
:addClass("wikitable sortable stickyHeader")
:tag('tr'):addClass("headerRow-0")
:tag('th'):wikitext(Icons.Icon({'Farming', type='skill', notext=true}) .. '<br>Level')
:tag('th'):attr("colspan", 2):wikitext("Seeds")
:tag('th'):attr("colspan", 2):wikitext("Produce")
:tag('th'):wikitext('[[DLC]]')
table.sort(seedList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) 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
local level = Skills.getRecipeLevel(skillID, seed)
html =
html:tag('tr')
:tag('td'):wikitext(level)
:css('text-align', 'center')
:tag('td'):wikitext(Icons.Icon({seedItem.name, type='item', notext=true}))
:tag('td'):wikitext('[[' .. seedItem.name .. ']]')
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true}))
:tag('td'):wikitext('[[' .. productItem.name .. ']]')
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id))
:css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(seedItem.id))
:done()
end
end
return tostring(tbl:done())
end
function p.getFarmingTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local slim = args.slim == 'true' or args.slim == 'True'
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local categoryName = args[1]
local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName)
if category == nil then
return Shared.printError('Invalid farming category: ' .. categoryName .. '. Please choose Allotments, Herbs, Trees or Special')
end
if slim == true then
return p._getSlimFarmingTable(realm.id, category)
else
return p._getFarmingTable(realm.id, category)
end
end
function p.getFarmingFoodTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
local skillID = 'Farming'
local root = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
-- Header row
local headerRow = root:tag('tr')
:addClass('headerRow-0')
headerRow:tag('th'):attr('colspan', '2'):wikitext('Crop')
headerRow:tag('th'):wikitext(Icons.Icon({"Farming", type="skill", notext=true}) .. '<br>Level')
headerRow:tag('th'):wikitext('[[DLC]]')
headerRow:tag('th'):wikitext('Healing')
headerRow:tag('th'):wikitext('Value')
local recipes = GameData.getEntities(SkillData[skillID].recipes,
function(recipe)
if Skills.getRecipeRealm(recipe) ~= realm.id then
return false
end
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 Skills.standardRecipeSort(skillID, a, b) end)
for i, recipe in ipairs(recipes) do
local product = Items.getItemByID(recipe.productId)
if product and product.healsFor and product.healsFor > 0 then
local row = root:tag('tr')
row:tag('td'):wikitext(Icons.Icon({product.name, type='item', notext='true'}))
row:tag('td'):wikitext('[[' .. product.name .. ']]')
row:tag('td'):css('text-align', 'center')
:wikitext(Skills.getRecipeLevel(skillID, recipe))
row:tag('td'):css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(product.id))
:wikitext(Icons.getDLCColumnIcon(product.id))
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', product.healsFor)
:wikitext(Icons.Icon({"Hitpoints", type="skill", notext=true}) .. ' ' .. (product.healsFor * 10))
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', product.sellsFor)
:wikitext(Items.getValueText(product))
end
end
return tostring(root)
end
function p.getFarmingPlotTable(frame)
local skillID = 'Farming'
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, Trees or Special')
end
local patches = GameData.getEntities(SkillData.Farming.plots,
function(plot)
return plot.categoryID == category.id
end
)
table.sort(patches,
function(a, b)
local abyssA, abyssB = a.abyssalLevel or 0, b.abyssalLevel or 0
if abyssA == abyssB then
if a.level == b.level then
return a.id < b.id
else
return a.level < b.level
end
else
return abyssA < abyssB
end
end
)
if Shared.tableIsEmpty(patches) then
return ''
end
local root = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
-- Header row
local headerRow = root:tag('tr')
headerRow:tag('th'):wikitext('Plot')
headerRow:tag('th'):wikitext('Requirements')
headerRow:tag('th'):wikitext('Cost')
for i, patch in ipairs(patches) do
local level = Skills.getRecipeLevel(skillID, patch)
local reqText = Skills.getRecipeRequirementText(skillID, patch)
local costText = Common.getCostString({ items = patch.itemCosts, currencies = patch.currencyCosts }, 'Free')
local costVal = (patch.currencyCosts and patch.currencyCosts[1] and patch.currencyCosts[1].quantity) or 0
local row = root:tag('tr')
row:tag('td'):wikitext(i)
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', level)
:wikitext(reqText)
row:tag('td'):css('text-align', 'right')
:attr('data-sort-value', costVal)
:wikitext(costText)
end
return tostring(root)
end
function p._buildAstrologyConstellationTable(realmID)
local modTypes = {
{
name = 'Standard',
modKey = 'standardModifiers',
levels = SkillData.Astrology.standardModifierLevels,
inUse = false
},
{
name = 'Unique',
modKey = 'uniqueModifiers',
levels = SkillData.Astrology.uniqueModifierLevels,
inUse = false
},
{
name = 'Abyssal',
modKey = 'abyssalModifiers',
levels = SkillData.Astrology.abyssalModifierLevels,
inUse = false
},
}
local recipes = GameData.getEntities(SkillData.Astrology.recipes,
function(cons)
return Skills.getRecipeRealm(cons) == realmID
end
)
table.sort(recipes,
function(a, b)
return Skills.getRecipeLevel('Astrology', a) < Skills.getRecipeLevel('Astrology', b)
end
)
-- Determine which mod types are in use
for _, recipe in ipairs(recipes) do
for _, modType in ipairs(modTypes) do
if not modType.inUse then
local recipeMods = recipe[modType.modKey]
if recipeMods ~= nil and not Shared.tableIsEmpty(recipeMods) then
modType.inUse = true
end
end
end
end
local root = mw.html.create()
local tableRoot = root:tag('table')
tableRoot:addClass('wikitable stickyHeader')
-- Header rows
local headerRow1 = tableRoot:tag('tr'):addClass('headerRow-0')
headerRow1:tag('th'):attr('rowspan', 2)
:attr('colspan', 2)
:wikitext('Constellation')
headerRow1:tag('th'):attr('rowspan', 2)
:wikitext(Icons.Icon({ "Astrology", type='skill', notext='true' }) .. '<br>Level')
headerRow1:tag('th'):attr('rowspan', 2)
:wikitext('[[DLC]]')
headerRow1:tag('th'):attr('rowspan', 2)
:wikitext('XP')
headerRow1:tag('th'):attr('rowspan', 2)
:wikitext('Skills')
local headerRow2 = tableRoot:tag('tr'):addClass('headerRow-1')
for _, modType in ipairs(modTypes) do
if modType.inUse then
local spanCount = modType.name == 'Abyssal' and 3 or 2
headerRow1:tag('th'):attr('colspan', spanCount):wikitext(modType.name .. ' Stars')
headerRow2:tag('th'):wikitext(Icons.Icon({'Mastery', notext=true}) .. '<br>Level')
if modType.name == "Abyssal" then
headerRow2:tag('th'):wikitext('Level')
end
headerRow2:tag('th'):wikitext('Modifiers')
end
end
-- Data rows
for _, cons in ipairs(recipes) do
local modData = Skills._getConstellationModifiers(cons)
local name = cons.name
local skillIconArray = {}
for _, skillID in ipairs(cons.skillIDs) do
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
end
-- Calculate maximum rows needed
local maxRows = 1
for _, modTypeData in pairs(modData) do
maxRows = math.max(maxRows, Shared.tableCount(modTypeData))
end
-- Iterate through rows
for rowIdx = 1, maxRows do
local row = tableRoot:tag('tr')
if rowIdx == 1 then
row:tag('td'):attr('rowspan', maxRows)
:wikitext(Icons.Icon({name, type='constellation', size=50, notext=true}))
:css('text-align', 'center')
row:tag('td'):attr('rowspan', maxRows)
:wikitext(name)
:attr('id', name)
row:tag('td'):attr('rowspan', maxRows)
:wikitext((cons.abyssalLevel or cons.level))
:css('text-align', 'center')
row:tag('td'):css('text-align', 'center')
:attr('data-sort-value', Icons.getExpansionID(cons.id))
:attr('rowspan', maxRows)
:wikitext(Icons.getDLCColumnIcon(cons.id))
row:tag('td'):attr('rowspan', maxRows)
:wikitext(Num.formatnum(cons.baseAbyssalExperience or cons.baseExperience))
:css('text-align', 'right')
row:tag('td'):attr('rowspan', maxRows)
:wikitext(table.concat(skillIconArray, '<br/>'))
:css('text-wrap', 'nowrap')
end
-- Modifiers data
for _, modType in ipairs(modTypes) do
if modType.inUse then
local masteryLevel = modType.levels[rowIdx]
local rowModData = modData[modType.modKey][rowIdx]
local rowConsData = cons[modType.modKey][rowIdx]
local cell1 = row:tag('td')
if modType.name == "Abyssal" then
local starUnlockReq = nil
local cell2 = row:tag('td')
if rowConsData ~= nil and rowConsData.unlockRequirements ~= nil and Shared.tableCount(rowConsData.unlockRequirements) == 2 then
starUnlockReq = Common.getRequirementString({cons[modType.modKey][rowIdx].unlockRequirements[2]}, nil)
end
if starUnlockReq ~= nil then
cell2:wikitext(starUnlockReq)
:css('text-align', 'right')
else
cell2:wikitext('N/A')
:addClass('table-na')
end
end
local cell3 = row:tag('td')
if masteryLevel ~= nil and rowModData ~= nil then
local modText = {}
for _, modKey in ipairs({'modifiers', 'enemyModifiers'}) do
local mods = rowModData[modKey]
if mods ~= nil then
if modKey == 'enemyModifiers' then
table.insert(modText, 'Gives the enemy:')
end
table.insert(modText, Modifiers.getModifiersText(mods, false, false, 10))
end
end
cell1:wikitext(masteryLevel)
:css('text-align', 'right')
cell3:wikitext(table.concat(modText, '<br>'))
else
cell1:attr('colspan', 2)
:wikitext('N/A')
:addClass('table-na')
end
end
end
end
end
return tostring(root)
end
function p.buildAstrologyConstellationTable(frame)
local args = frame.args ~= nil and frame.args or frame
local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
if realm == nil then
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
end
return p._buildAstrologyConstellationTable(realm.id)
end
return p