Module:Skills/Gathering: Difference between revisions

Fixed Level/XP issues with Abyssal Constellations; Added additional requirements to Abyssal Stars;
(Disable seed sources)
(Fixed Level/XP issues with Abyssal Constellations; Added additional requirements to Abyssal Stars;)
(15 intermediate revisions by 2 users not shown)
Line 12: Line 12:
local Skills = require('Module:Skills')
local Skills = require('Module:Skills')
local ItemSourceTables = require('Module:Items/SourceTables')
local ItemSourceTables = require('Module:Items/SourceTables')
local Num = require("Module:Number")


local function lootValueText(lootValue)
local function lootValueText(lootValue)
Line 18: Line 19:
-- Guarantee order by iterating through currency game data definition
-- Guarantee order by iterating through currency game data definition
local currID = currencyDefn.id
local currID = currencyDefn.id
local val = Shared.round(lootValue[currID], 2, 2)
local val = Num.round(lootValue[currID], 2, 2)
if val ~= nil then
if val ~= nil then
table.insert(returnPart, Icons._Currency(currID, val))
table.insert(returnPart, Icons._Currency(currID, val))
Line 66: Line 67:
local sellCurrency = log.sellsForCurrency or 'melvorD:GP'
local sellCurrency = log.sellsForCurrency or 'melvorD:GP'
local XPSec = baseXP / (baseInt / 1000)
local XPSec = baseXP / (baseInt / 1000)
local currSec = Shared.round(log.sellsFor / (baseInt / 1000), 2, 2)
local currSec = Num.round(log.sellsFor / (baseInt / 1000), 2, 2)


Line 80: Line 81:
:tag('td'):wikitext(reqText)
:tag('td'):wikitext(reqText)
          :attr('data-sort-value', level)
          :attr('data-sort-value', level)
:tag('td'):wikitext(Icons.getExpansionIcon(tree.id))
:tag('td'):wikitext(Icons.getDLCColumnIcon(tree.id))
  :attr('data-sort-value', Icons.getExpansionID(tree.id))
  :attr('data-sort-value', Icons.getExpansionID(tree.id))
  :css('text-align', 'center')
  :css('text-align', 'center')
:tag('td'):wikitext(Shared.formatnum(baseXP))
:tag('td'):wikitext(Num.formatnum(baseXP))
  :css('text-align', 'right')
  :css('text-align', 'right')
:tag('td'):wikitext(Shared.timeString(baseInt / 1000, true))
:tag('td'):wikitext(Shared.timeString(baseInt / 1000, true))
  :attr('data-sort-value', baseInt)
  :attr('data-sort-value', baseInt)
  :css('text-align', 'right')
  :css('text-align', 'right')
:tag('td'):wikitext(Shared.round(XPSec, 2, 2))
:tag('td'):wikitext(Num.round(XPSec, 2, 2))
  :css('text-align', 'right')
  :css('text-align', 'right')
:tag('td'):wikitext(Icons._Currency(sellCurrency, currSec))
:tag('td'):wikitext(Icons._Currency(sellCurrency, currSec))
Line 118: Line 119:
table.sort(itemArray, function(a, b) return a.weight > b.weight end)
table.sort(itemArray, function(a, b) return a.weight > b.weight end)


local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '\r\n{|class="wikitable sortable stickyHeader"')
html:addClass('wikitable sortable stickyHeader')
table.insert(resultPart, '\r\n|- class="headerRow-0"\r\n!colspan="2"| Item\r\n!Value\r\n!colspan="2"|Chance')
 
-- 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
for i, itemDef in ipairs(itemArray) do
local item = Items.getItemByID(itemDef.itemID)
local item = Items.getItemByID(itemDef.itemID)
Line 128: Line 136:
-- If chance is less than 0.10% then show 2 significant figures, otherwise 2 decimal places
-- 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 fmt = (dropChance < 0.10 and '%.2g') or '%.2f'
table.insert(resultPart, '\r\n|-\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true}))
local row = html:tag('tr')
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
 
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Items.getValueText(item))
row:tag('td'):addClass('table-img'):wikitext(Icons.Icon({item.name, type='item', notext=true}))
table.insert(resultPart, '\r\n|style="text-align:right" data-sort-value="' .. itemDef.weight .. '"| ' .. Shared.fraction(itemDef.weight, totalWt))
row:tag('td'):wikitext(Icons.Icon({item.name, type='item', noicon=true}))
table.insert(resultPart, '\r\n|style="text-align:right"| ' .. string.format(fmt, dropChance) .. '%')
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
if lootValue[currID] == nil then
lootValue[currID] = 0
lootValue[currID] = 0
Line 139: Line 157:
end
end
end
end
table.insert(resultPart, '\r\n|}\r\nThe average value of a roll on the special fishing loot table is ' .. lootValueText(lootValue))
 
return table.concat(resultPart)
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.getFishingJunkTable(frame)
function p.getFishingJunkTable(frame)
local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
html:addClass('wikitable sortable stickyHeader')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
 
table.insert(resultPart, '\r\n!colspan="2"|Item!!Value')
-- 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 = {}
local itemArray = {}
Line 158: Line 181:
table.sort(itemArray, function(a, b) return a.name < b.name end)
table.sort(itemArray, function(a, b) return a.name < b.name end)


-- Add item rows
for i, item in ipairs(itemArray) do
for i, item in ipairs(itemArray) do
table.insert(resultPart, '\r\n|-')
local row = html:tag('tr')
table.insert(resultPart, '\r\n|class="table-img"| ' .. Icons.Icon({item.name, type='item', notext=true, size=50}))
row:tag('td'):addClass('table-img')
table.insert(resultPart, '\r\n| ' .. Icons.Icon({item.name, type='item', noicon=true}))
:wikitext(Icons.Icon({item.name, type='item', notext=true}))
table.insert(resultPart, '\r\n|data-sort-value="' .. item.sellsFor .. '"| ' .. Items.getValueText(item))
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
table.insert(resultPart, '\r\n|}')
 
return table.concat(resultPart)
return tostring(html)
end
end


Line 176: Line 203:
end
end
local skillID = 'Mining'
local skillID = 'Mining'
 
local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
:addClass("wikitable sortable stickyHeader")
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2"|Rock!!colspan=2|Ore!!Type!!Requirements')
html:tag('tr'):addClass("headerRow-0")
table.insert(resultPart, '!!XP!!Respawn Time!!Ore Value')
: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,
local mineData = GameData.getEntities(SkillData.Mining.rockData,
Line 194: Line 230:
local reqText = Skills.getRecipeRequirementText(SkillData.Mining.name, oreData)
local reqText = Skills.getRecipeRequirementText(SkillData.Mining.name, oreData)
local ore = Items.getItemByID(oreData.productId)
local ore = Items.getItemByID(oreData.productId)
local respawnStyle, respawnSort, respawnText = 'class="table-na"', 0, 'N/A'
local respawnSort, respawnText = 0, 'N/A'
if oreData.hasPassiveRegen then
if oreData.hasPassiveRegen then
respawnStyle = 'style="text-align:right;'
respawnSort = oreData.baseRespawnInterval / 1000
respawnSort = oreData.baseRespawnInterval / 1000
respawnText = Shared.timeString(respawnSort, true)
respawnText = Shared.timeString(respawnSort, true)
Line 208: Line 243:
local qtyText = (oreData.baseQuantity > 1 and '<b>' .. oreData.baseQuantity .. 'x</b> ') or ''
local qtyText = (oreData.baseQuantity > 1 and '<b>' .. oreData.baseQuantity .. 'x</b> ') or ''
table.insert(resultPart, '\n|-\n|class="table-img" data-sort-value="' .. rockName .. '"| '..Icons.Icon({oreData.name, type='rock', size='50', notext=true, nolink=true}))
local row = html:tag('tr')
table.insert(resultPart, '\n| data-sort-value="' ..rockName.. '"|'..Icons.getExpansionIcon(oreData.id) .. rockName)
row :tag('td'):wikitext(Icons.Icon({oreData.name, type='rock', notext=true, nolink=true}))
table.insert(resultPart, '\n|class="table-img" data-sort-value="' .. ore.name .. '"| '..Icons.Icon({ore.name, type='item', size='50', notext=true}))
  :addClass('table-img')
table.insert(resultPart, '\n| ' .. qtyText .. Icons.Icon({ore.name, type='item', noicon=true}))
  :attr('data-sort-value', rockName)
table.insert(resultPart, '\n| ' .. categoryName)
:tag('td'):wikitext(rockName)
table.insert(resultPart, '\n|data-sort-value="' .. level ..'"| ' .. reqText)
:tag('td'):wikitext(Icons.Icon({ore.name, type='item', notext=true}))
table.insert(resultPart, '\n|style="text-align:right"| '..Shared.formatnum(baseXP))
  :addClass('table-img')
table.insert(resultPart, '\n|' .. respawnStyle .. ' data-sort-value="'..respawnSort..'"| ' .. respawnText)
  :attr('data-sort-value', ore.name)
table.insert(resultPart, '\n|data-sort-value="'..ore.sellsFor..'"| '..Items.getValueText(ore))
: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


table.insert(resultPart, '\n|}')
return tostring(html)
return table.concat(resultPart)
end
end


Line 243: Line 296:
end
end
local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '{|class="wikitable sortable"')
:addClass('wikitable sortable')
table.insert(resultPart, '\n|- class="headerRow-0"')
 
table.insert(resultPart, '\n!colspan=2|Gem!!Gem Chance!!Gem Price')
-- 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
for i, gem in ipairs(gemData) do
local gemItem = Items.getItemByID(gem.itemID)
local gemItem = Items.getItemByID(gem.itemID)
local gemPct = gem.weight / totalWeight * 100
local gemPct = gem.weight / totalWeight * 100
table.insert(resultPart, '\n|-\n|class="table-img"| ')
local row = html:tag('tr')
table.insert(resultPart, Icons.Icon({gemItem.name, type='item', size='50', notext=true}))
 
table.insert(resultPart, '\n| data-sort-value="'..gemItem.name..'"|'..Icons.getExpansionIcon(gemItem.id) .. Icons.Icon({gemItem.name, type='item', noicon=true}))
row:tag('td'):addClass('table-img')
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. gemPct .. '" | ' .. string.format("%.1f%%", gemPct))
:wikitext(Icons.Icon({gemItem.name, type='item', notext=true}))
table.insert(resultPart, '\n|data-sort-value="' .. gemItem.sellsFor .. '"| ' .. Items.getValueText(gemItem))
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


table.insert(resultPart, '\n|}')
return tostring(html)
return table.concat(resultPart)
end
end


Line 276: Line 344:
local skillID = 'Fishing'
local skillID = 'Fishing'


local recipeList = GameData.getEntities(SkillData.Fishing.fish,
local recipeList = GameData.getEntities(SkillData.Fishing.fish, function(obj)
function(obj)
return Skills.getRecipeRealm(obj) == realm.id
return Skills.getRecipeRealm(obj) == realm.id
end)
end
)
table.sort(recipeList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
table.sort(recipeList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)


Line 295: Line 361:
end
end


local resultPart = {}
local html = mw.html.create('table')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
html:addClass('wikitable sortable stickyHeader')
table.insert(resultPart, '\n|- class="headerRow-0"')
 
table.insert(resultPart, '\n!colspan="2" rowspan="2"|Fish\n!rowspan="2"|Requirements')
-- Add header row
table.insert(resultPart, '\n!colspan="3"|Catch Time\n!rowspan="2"|XP\n!rowspan="2"|Value\n!rowspan="2"|XP/s\n!rowspan="2"|Price/s')
local headerRow0 = html:tag('tr'):addClass('headerRow-0')
table.insert(resultPart, '\n!rowspan="2"|' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' Level')
headerRow0:tag('th'):attr('colspan', '2'):attr('rowspan', '2'):wikitext('Fish')
table.insert(resultPart, '\n|- class="headerRow-1"\n!Min\n!Max\n!Avg')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext(Icons.Icon({'Fishing', type='skill', notext=true}) .. '<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('Value')
headerRow0:tag('th'):attr('rowspan', '2'):wikitext('XP/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
for i, recipe in ipairs(recipeList) do
local fish = Items.getItemByID(recipe.productId)
local fish = Items.getItemByID(recipe.productId)
Line 308: Line 386:
local timeAvg = (timeMin + timeMax) / 2
local timeAvg = (timeMin + timeMax) / 2
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000
local timeSortVal = (recipe.baseMinInterval + recipe.baseMaxInterval) / 2000
local timeStr = string.format("%.1fs - %.1fs", recipe.baseMinInterval / 1000, recipe.baseMaxInterval / 1000)
local level = Skills.getRecipeLevel(skillID, recipe)
local level = Skills.getRecipeLevel(skillID, recipe)
local reqText = Skills.getRecipeRequirementText(skillID, recipe)
local reqText = Skills.getRecipeRequirementText(skillID, recipe)
Line 314: Line 391:
local XPSec = baseXP / timeSortVal
local XPSec = baseXP / timeSortVal
local sellCurrency = fish.sellsForCurrency or 'melvorD:GP'
local sellCurrency = fish.sellsForCurrency or 'melvorD:GP'
local currPerSec = Shared.round(fish.sellsFor / timeSortVal, 2, 2)
 
local cookRecipe = cookRecipes[recipe.productId]
local row = html:tag('tr')
local cookLevel, cookStyle, cookStr = 0, 'class="table-na" ', 'N/A'
row:tag('td')
if cookRecipe ~= nil then
:addClass('table-img')
cookLevel = Skills.getRecipeLevel('Cooking', cookRecipe)
:wikitext(Icons.Icon({fish.name, type='item', notext=true}))
cookStyle = 'style="text-align:right" '
row:tag('td')
cookStr = Skills.getRecipeRequirementText('Cooking', cookRecipe)
:attr('data-sort-value', fish.name)
end
:wikitext('[[' .. fish.name .. ']]')
table.insert(resultPart, '\n|-')
row:tag('td')
table.insert(resultPart, '\n|class="table-img"| ' .. Icons.Icon({fish.name, type='item', size='50', notext=true}))
:css('text-align', 'center')
table.insert(resultPart, '\n|data-sort-value="'..fish.name..'"|'..Icons.getExpansionIcon(fish.id) .. Icons.Icon({fish.name, type='item', noicon=true}))
:attr('data-sort-value', level)
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. level .. '"| ' .. reqText)
:wikitext(level)
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. timeMin .. '"| ' .. string.format("%.1fs", timeMin))
row:tag('td')
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. timeMax .. '"| ' .. string.format("%.1fs", timeMax))
:css('text-align', 'center')
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. timeAvg .. '"| ' .. string.format("%.1fs", timeAvg))
:attr('data-sort-value', Icons.getExpansionID(fish.id))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. baseXP .. '"| ' .. Shared.formatnum(baseXP))
:wikitext(Icons.getDLCColumnIcon(fish.id))
table.insert(resultPart, '\n|data-sort-value="' .. fish.sellsFor .. '"| ' .. Items.getValueText(fish))
row:tag('td')
table.insert(resultPart, '\n|style="text-align:right"| ' .. Shared.round(XPSec, 2, 2))
:css('text-align', 'right')
table.insert(resultPart, '\n|data-sort-value="' .. currPerSec .. '"|' .. Icons._Currency(sellCurrency, currPerSec))
:attr('data-sort-value', timeMin)
table.insert(resultPart, '\n|' .. cookStyle .. 'data-sort-value="' .. cookLevel .. '"| ' .. cookStr)
:wikitext(string.format("%.1fs", timeMin))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', timeMax)
:wikitext(string.format("%.1fs", timeMax))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', timeAvg)
:wikitext(string.format("%.1fs", timeAvg))
row:tag('td')
:css('text-align', 'right')
:attr('data-sort-value', baseXP)
:wikitext(Num.formatnum(baseXP))
row:tag('td')
:attr('data-sort-value', fish.sellsFor)
:wikitext(Items.getValueText(fish))
row:tag('td')
:css('text-align', 'right')
:wikitext(Num.round(XPSec, 2, 2))
end
end
end
end
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
return tostring(html)
end
end


function p.getFishingAreasTable(frame)
function p.getFishingAreasTable(frame)
Line 349: Line 445:
local skillID = 'Fishing'
local skillID = 'Fishing'


local result = '{| class="wikitable sortable stickyHeader"'
local html = mw.html.create('table')
result = result..'\r\n|- class="headerRow-0"'
html:addClass('wikitable sortable stickyHeader')
result = result..'\r\n!Name\r\n!Fish\r\n!Fish Chance'
result = result..'\r\n!Junk Chance\r\n!Special Chance'


local fishAreas = GameData.getEntities(SkillData.Fishing.areas,
-- Add header row
function(obj)
local headerRow = html:tag('tr'):addClass('headerRow-0')
return Skills.getRecipeRealm(obj) == realm.id
headerRow:tag('th'):wikitext('Name')
end
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
for i, area in ipairs(fishAreas) do
result = result..'\r\n|-'
local row = html:tag('tr')
result = result..'\r\n| style ="text-align: left;" |'..Icons.getExpansionIcon(area.id)..area.name
row:tag('td')
:css('text-align', 'left')
:wikitext(area.name)


local fishArray = {}
local fishArray = {}
Line 371: Line 476:
end
end
end
end
result = result..'\r\n|'..table.concat(fishArray, '<br/>')
row:tag('td')
 
:wikitext(table.concat(fishArray, '<br/>'))
result = result..'\r\n| style="text-align:right"|'..area.fishChance..'%'
row:tag('td')
result = result..'\r\n| style="text-align:right"|'..area.junkChance..'%'
:css('text-align', 'center')
result = result..'\r\n| style="text-align:right"|'..area.specialChance..'%'
: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


result = result..'\r\n|}'
return tostring(html)
return result
end
end


function p.getThievingGeneralRareTable(frame)
function p.getThievingGeneralRareTable(frame)
Line 392: Line 507:
end
end


local rareTxt = '{|class="wikitable sortable"'
local html = mw.html.create('table')
rareTxt = rareTxt..'\r\n!Item!!Qty'
html:addClass('wikitable sortable')
rareTxt = rareTxt..'!!Price!!colspan="2"|Chance'
 
-- 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
for i, drop in ipairs(SkillData.Thieving.generalRareItems) do
-- If an npcID has been passed and the item is NPC specific, only display
-- 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
-- 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 npcMatch = (
local realmMatch = (npcRealm == nil or drop.realms == nil or Shared.contains(drop.realms, npcRealm))
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
if npcMatch and realmMatch then
local thisItem = Items.getItemByID(drop.itemID)
local thisItem = Items.getItemByID(drop.itemID)
local odds = drop.chance
local odds = drop.chance
 
rareTxt = rareTxt..'\r\n|-\r\n|data-sort-value="'..thisItem.name..'"|'..Icons.getExpansionIcon(thisItem.id)..Icons.Icon({thisItem.name, type='item'})
local row = html:tag('tr')
rareTxt = rareTxt..'||1||data-sort-value="'..thisItem.sellsFor..'"|'..Items.getValueText(thisItem)
row:tag('td')
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.fraction(1, Shared.round2(1/(odds/100), 0))
:attr('data-sort-value', thisItem.name)
rareTxt = rareTxt..'||style="text-align:right" data-sort-value="'..odds..'"|'..Shared.round(odds, 4, 4)..'%'
: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
end
rareTxt = rareTxt..'\r\n|}'
 
return rareTxt
return tostring(html)
end
end


function p._getThievingNPCCurrencyText(npc)
function p._getThievingNPCCurrencyText(npc)
Line 469: Line 603:


if loot.minQuantity ~= loot.maxQuantity then
if loot.minQuantity ~= loot.maxQuantity then
table.insert(normalTxt, Shared.formatnum(loot.minQuantity) .. ' - ' .. Shared.formatnum(loot.maxQuantity))
table.insert(normalTxt, Num.formatnum(loot.minQuantity) .. ' - ' .. Num.formatnum(loot.maxQuantity))
else
else
table.insert(normalTxt, Shared.formatnum(loot.maxQuantity))
table.insert(normalTxt, Num.formatnum(loot.maxQuantity))
end
end


Line 489: Line 623:
--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
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..loot.weight..'"')
table.insert(normalTxt, '||style="text-align:right" data-sort-value="'..loot.weight..'"')
table.insert(normalTxt, '|'..Shared.fraction(loot.weight * lootChance, totalWt * 100))
table.insert(normalTxt, '|'..Num.fraction(loot.weight * lootChance, totalWt * 100))
table.insert(normalTxt, '||')
table.insert(normalTxt, '||')
else
else
table.insert(normalTxt, '||colspan="2" data-sort-value="'..loot.weight..'"')
table.insert(normalTxt, '||colspan="2" data-sort-value="'..loot.weight..'"')
end
end
table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(dropChance, 2, 2)..'%')
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
Line 507: Line 641:
table.insert(normalTxt, '\r\n|-class="sortbottom" \r\n!colspan="3"|Total:')
table.insert(normalTxt, '\r\n|-class="sortbottom" \r\n!colspan="3"|Total:')
if lootChance < 100 then
if lootChance < 100 then
table.insert(normalTxt, '\r\n|style="text-align:right"|'..Shared.fraction(lootChance, 100)..'||')
table.insert(normalTxt, '\r\n|style="text-align:right"|'..Num.fraction(lootChance, 100)..'||')
else
else
table.insert(normalTxt, '\r\n|colspan="2" ')
table.insert(normalTxt, '\r\n|colspan="2" ')
end
end
table.insert(normalTxt, 'style="text-align:right"|'..Shared.round(lootChance, 2, 2)..'%')
table.insert(normalTxt, 'style="text-align:right"|'..Num.round(lootChance, 2, 2)..'%')
end
end
table.insert(normalTxt, '\r\n|}')
table.insert(normalTxt, '\r\n|}')
Line 545: Line 679:
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..'||data-sort-value="'..drop.quantity..'"| '..Shared.formatnum(drop.quantity)..'||data-sort-value="'..thisItem.sellsFor..'"|'..Items.getValueText(thisItem)
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"|'..Shared.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100))
lineTxt = lineTxt..'||style="text-align:right"|'..Num.fraction(1, 1/(SkillData.Thieving.baseAreaUniqueChance/100))
lineTxt = lineTxt..'||'..Shared.round(SkillData.Thieving.baseAreaUniqueChance, 2, 2)..'%'
lineTxt = lineTxt..'||'..Num.round(SkillData.Thieving.baseAreaUniqueChance, 2, 2)..'%'
dropLines[thisItem.name] = lineTxt
dropLines[thisItem.name] = lineTxt
end
end
Line 586: Line 720:


function p.getThievingNPCTable(frame)
function p.getThievingNPCTable(frame)
local args = frame.args ~= nil and frame.args or frame
    local args = frame.args or frame:getParent().args
local realmName = args.realm
    local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
    local realm = Skills.getRealmFromName(realmName)
if realm == nil then
   
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
    if realm == nil then
end
        return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
local skillID = 'Thieving'
    end
 
   
local result = '{| class="wikitable sortable stickyHeader"'
    local skillID = 'Thieving'
result = result..'\r\n|- class="headerRow-0"'
    local root = mw.html.create('table')
result = result..'\r\n!colspan="2"|Name!!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!Experience!!Max Hit!!Perception!!Currency!!Unique Drop'
        :addClass('wikitable sortable stickyHeader')
local npcArray = GameData.getEntities(SkillData.Thieving.npcs,
   
function(obj)
    -- Header row
return Skills.getRecipeRealm(obj) == realm.id
    local headerRow = root:tag('tr')
end
        :addClass('headerRow-0')
)
        :tag('th'):attr('colspan', '2'):wikitext('Name')
table.sort(npcArray, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
        :tag('th'):wikitext('Area')
for i, npc in ipairs(npcArray) do
        :tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level')
local level = Skills.getRecipeLevel(skillID, npc)
        :tag('th'):wikitext('[[DLC]]')
local baseXP = npc.baseAbyssalExperience or npc.baseExperience
        :tag('th'):wikitext('Experience')
local reqText = Skills.getRecipeRequirementText(SkillData.Thieving.name, npc)
        :tag('th'):wikitext('Max Hit')
local area = Skills.getThievingNPCArea(npc)
        :tag('th'):wikitext('Perception')
local currSortAmt = npc.currencyDrops[1].quantity
        :tag('th'):wikitext('Currency')
 
        :tag('th'):wikitext('Unique Drop')
result = result..'\r\n|-'
   
result = result..'\r\n|'..Icons.Icon({npc.name, type='thieving', size='50', notext=true})
    local npcArray = GameData.getEntities(SkillData.Thieving.npcs,
result = result..'||data-sort-value="'..npc.name..'"|'..Icons.getExpansionIcon(npc.id)..Icons.Icon({npc.name, type='thieving', noicon=true})
        function(obj)
 
            return Skills.getRecipeRealm(obj) == realm.id
result = result..'||'..area.name
        end
result = result..'||data-sort-value="' .. level .. '"| ' .. reqText
    )
result = result..'||style="text-align:right"|' .. Shared.formatnum(baseXP)
    table.sort(npcArray, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
result = result..'||style="text-align:right"|' .. Shared.formatnum(npc.maxHit * 10)
   
result = result..'||style="text-align:right" data-sort-value="' .. npc.perception .. '| '..Shared.formatnum(npc.perception)
    for i, npc in ipairs(npcArray) do
result = result..'||data-sort-value="' .. currSortAmt .. '"|' .. p._getThievingNPCCurrencyText(npc)
        local level = Skills.getRecipeLevel(skillID, npc)
if npc.uniqueDrop ~= nil then
        local baseXP = npc.baseAbyssalExperience or npc.baseExperience
local uniqueDrop = Items.getItemByID(npc.uniqueDrop.id)
        local area = Skills.getThievingNPCArea(npc)
if npc.uniqueDrop.quantity > 1 then
        local currSortAmt = npc.currencyDrops[1].quantity
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item', qty = npc.uniqueDrop.quantity})
       
else
        local row = root:tag('tr')
result = result..'||data-sort-value="'..uniqueDrop.name..'"|'..Icons.Icon({uniqueDrop.name, type='item'})
        row:tag('td'):wikitext(Icons.Icon({npc.name, type='thieving', notext=true}))
end
        row:tag('td'):attr('data-sort-value', npc.name)
else
            :wikitext('[[' .. npc.name .. ']]')
result = result..'|| '
        row:tag('td'):wikitext(area.name)
end
        row:tag('td'):wikitext(level)
end
        :css('text-align', 'center')
result = result..'\r\n|}'
        row:tag('td'):wikitext(Icons.getDLCColumnIcon(npc.id))
 
        :css('text-align', 'center')
return result
        :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
end


function p.getThievingAreaTable(frame)
function p.getThievingAreaTable(frame)
local args = frame.args ~= nil and frame.args or frame
    local args = frame.args or frame:getParent().args
local realmName = args.realm
    local realmName = args.realm
local realm = Skills.getRealmFromName(realmName)
    local realm = Skills.getRealmFromName(realmName)
if realm == nil then
   
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
    if realm == nil then
end
        return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
local skillID = 'Thieving'
    end
 
   
local resultPart = {}
    local skillID = 'Thieving'
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
    local root = mw.html.create('table')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
        :addClass('wikitable sortable stickyHeader')
table.insert(resultPart, '\r\n!Area!!'..Icons.Icon({'Thieving', type='skill', notext=true})..' Level!!NPCs!!Unique Drops')
   
 
    -- Header row
local areas = GameData.getEntities(SkillData.Thieving.areas,
    local headerRow = root:tag('tr')
function(obj)
        :addClass('headerRow-0')
return Skills.getRecipeRealm(obj) == realm.id
    headerRow:tag('th'):wikitext('Area')
end
    headerRow:tag('th'):wikitext(Icons.Icon({'Thieving', type='skill', notext=true}) .. '<br>Level')
)
    headerRow:tag('th'):wikitext('NPCs')
 
    headerRow:tag('th'):wikitext('Unique Drops')
for i, area in ipairs(areas) do
   
local minLevel, npcList, areaItemList = nil, {}, {}
    local areas = GameData.getEntities(SkillData.Thieving.areas,
-- Build NPC list & determine level for area, this is the minimum
        function(obj)
-- Thieving level required for all NPCs within that area
            return Skills.getRecipeRealm(obj) == realm.id
if area.npcIDs ~= nil and not Shared.tableIsEmpty(area.npcIDs) then
        end
for j, npcID in ipairs(area.npcIDs) do
    )
local npc = Skills.getThievingNPCByID(npcID)
   
local level = Skills.getRecipeLevel(skillID, npc)
    for i, area in ipairs(areas) do
if minLevel == nil or level < minLevel then
        local minLevel, npcList, areaItemList = nil, {}, {}
minLevel = level
       
end
        -- Build NPC list & determine minimum Thieving level
table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
        if area.npcIDs and not Shared.tableIsEmpty(area.npcIDs) then
end
            for j, npcID in ipairs(area.npcIDs) do
else
                local npc = Skills.getThievingNPCByID(npcID)
table.insert(npcList, '')
                local level = Skills.getRecipeLevel(skillID, npc)
end
                if not minLevel or level < minLevel then
 
                    minLevel = level
-- Build area unique item list
                end
if area.uniqueDrops ~= nil and Shared.tableCount(area.uniqueDrops) > 0 then
                table.insert(npcList, Icons.Icon({npc.name, type='thieving'}))
for k, drop in ipairs(area.uniqueDrops) do
            end
local areaItem = Items.getItemByID(drop.id)
        else
if areaItem == nil then
            table.insert(npcList, '')
table.insert(areaItemList, 'Unknown[[Category:Pages with script errors]]')
        end
else
       
local iconDef = {areaItem.name, type='item'}
        -- Build area unique item list
if drop.quantity > 1 then
        if area.uniqueDrops and Shared.tableCount(area.uniqueDrops) > 0 then
iconDef.qty = drop.quantity
            for k, drop in ipairs(area.uniqueDrops) do
end
                local areaItem = Items.getItemByID(drop.id)
table.insert(areaItemList, Icons.Icon(iconDef))
                if areaItem then
end
                    local iconDef = {areaItem.name, type='item'}
end
                    if drop.quantity > 1 then
else
                        iconDef.qty = drop.quantity
table.insert(areaItemList, '')
                    end
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)
        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


-- Generate table row
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|' .. area.name)
table.insert(resultPart, '\r\n|' .. Icons._SkillReq('Thieving', minLevel))
table.insert(resultPart, '\r\n|' .. table.concat(npcList, '<br/>'))
table.insert(resultPart, '\r\n|' .. table.concat(areaItemList, '<br/>'))
end
table.insert(resultPart, '\r\n|}')


return table.concat(resultPart)
function p._getFarmingTable(realmID, category)
end
 
function p._getFarmingTable(realmID, categoryName)
local category = GameData.getEntityByName(SkillData.Farming.categories, categoryName)
if category == nil then
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, Trees or Special')
end
local seedList = GameData.getEntities(SkillData.Farming.recipes,
local seedList = GameData.getEntities(SkillData.Farming.recipes,
function(recipe)
function(recipe)
Line 756: Line 919:
:tag('td'):wikitext(level)
:tag('td'):wikitext(level)
          :css('text-align', 'center')
          :css('text-align', 'center')
:tag('td'):wikitext(Icons.getExpansionIcon(seedItem.id))
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id))
  :css('text-align', 'center')
  :css('text-align', 'center')
  :attr('data-sort-value', Icons.getExpansionID(seedItem.id))
  :attr('data-sort-value', Icons.getExpansionID(seedItem.id))
:tag('td'):wikitext(Shared.formatnum(baseXP))
:tag('td'):wikitext(Num.formatnum(baseXP))
:tag('td'):attr('data-sort-value', (baseInt / 1000))
:tag('td'):attr('data-sort-value', (baseInt / 1000))
  :wikitext(Shared.timeString(baseInt / 1000, true))
  :wikitext(Shared.timeString(baseInt / 1000, true))
Line 765: Line 928:
      :wikitext(Items.getValueText(seedItem))
      :wikitext(Items.getValueText(seedItem))
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true}))
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true}))
:tag('td'):wikitext(Icons.getExpansionIcon(productItem.id) .. Icons.Icon({productItem.name, type='item', noicon=true}))
:tag('td'):wikitext('[[' .. productItem.name .. ']]')


if category.id == 'melvorD:Allotment' then
if category.id == 'melvorD:Allotment' then
Line 784: Line 947:
end
end


function p.getFarmingTable(frame)
function p._getSlimFarmingTable(realmID, category)
local args = frame.args ~= nil and frame.args or frame
local seedList = GameData.getEntities(SkillData.Farming.recipes,
local realmName = args.realm
function(recipe)
local realm = Skills.getRealmFromName(realmName)
return recipe.categoryID == category.id and Skills.getRecipeRealm(recipe) == realmID
if realm == nil then
end)
return Shared.printError('Failed to find a realm with name ' .. (realmName or 'nil'))
if Shared.tableIsEmpty(seedList) then
return ''
end
end
local category = args[1]


return p._getFarmingTable(realm.id, category)
local skillID = 'Farming'
end
 
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]]')


function p.getFarmingFoodTable(frame)
table.sort(seedList, function(a, b) return Skills.standardRecipeSort(skillID, a, b) end)
local result = '{| class="wikitable sortable stickyHeader"'
result = result..'\r\n|- class="headerRow-0"'
result = result..'\r\n!colspan="2"|Crop!!'..Icons.Icon({"Farming", type="skill", notext=true})..' Level'
result = result..'!!Healing!!Value'


local recipes = GameData.getEntities(SkillData.Farming.recipes,
for i, seed in ipairs(seedList) do
function(recipe)
local seedItem = Items.getItemByID(seed.seedCost.id)
local product = Items.getItemByID(recipe.productId)
local productItem = Items.getItemByID(seed.productId)
return product ~= nil and product.healsFor ~= nil and product.healsFor > 0
if seedItem ~= nil and productItem ~= nil then
end)
local level = Skills.getRecipeLevel(skillID, seed)
table.sort(recipes, function(a, b) return a.level < b.level end)


for i, recipe in ipairs(recipes) do
html =
local product = Items.getItemByID(recipe.productId)
html:tag('tr')
if product ~= nil and product.healsFor ~= nil and product.healsFor > 0 then
:tag('td'):wikitext(level)
result = result..'\r\n|-'
      :css('text-align', 'center')
result = result..'\r\n|'..Icons.Icon({product.name, type='item', notext='true', size='50'})
:tag('td'):wikitext(Icons.Icon({seedItem.name, type='item', notext=true}))
result = result..'|| ' .. Icons.getExpansionIcon(product.id) .. Icons.Icon({product.name, type='item', noicon=true})
:tag('td'):wikitext('[[' .. seedItem.name .. ']]')
result = result..'||style="text-align:right;"|'..recipe.level
:tag('td'):wikitext(Icons.Icon({productItem.name, type='item', notext=true}))
result = result..'||style="text-align:right" data-sort-value="'..product.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(product.healsFor * 10)
:tag('td'):wikitext('[[' .. productItem.name .. ']]')
result = result..'||style="text-align:right" data-sort-value="'..product.sellsFor..'"|'..Items.getValueText(product)
:tag('td'):wikitext(Icons.getDLCColumnIcon(seedItem.id))
  :css('text-align', 'center')
  :attr('data-sort-value', Icons.getExpansionID(seedItem.id))
:done()
end
end
end
end


result = result..'\r\n|}'
return tostring(tbl:done())
 
return result
end
end


function p.getFarmingPlotTable(frame)
function p.getFarmingTable(frame)
local skillID = 'Farming'
local args = frame.args ~= nil and frame.args or frame
local areaName = frame.args ~= nil and frame.args[1] or frame
local realmName = args.realm
local category = GameData.getEntityByName(SkillData.Farming.categories, areaName)
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
if category == nil then
return Shared.printError('Invalid farming category. Please choose Allotments, Herbs, or Trees')
return Shared.printError('Invalid farming category: ' .. categoryName .. '. Please choose Allotments, Herbs, Trees or Special')
end
end
local patches = GameData.getEntities(SkillData.Farming.plots,
 
function(plot)
if slim == true then
return plot.categoryID == category.id
return p._getSlimFarmingTable(realm.id, category)
end)
else
table.sort(patches,
return p._getFarmingTable(realm.id, category)
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
end
end


local result = '{|class="wikitable sortable stickyHeader"'
function p.getFarmingFoodTable(frame)
result = result..'\r\n!Plot!!Requirements!!Cost'
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


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 ~= nil and patch.currencyCosts[1].quantity) or 0


result = result..'\r\n|-\r\n|'..i
function p.getFarmingPlotTable(frame)
result = result..'||style="text-align:right;" data-sort-value="' .. level .. '"|'..reqText
    local skillID = 'Farming'
result = result..'||style="text-align:right;" data-sort-value="'..costVal..'"|'..costText
    local areaName = frame.args ~= nil and frame.args[1] or frame
end
    local category = GameData.getEntityByName(SkillData.Farming.categories, areaName)
 
   
result = result..'\r\n|}'
    if category == nil then
return result
        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
end


function p._buildAstrologyConstellationTable(realmID)
function p._buildAstrologyConstellationTable(realmID)
local modTypes = {
    local modTypes = {
{
        {
["name"] = 'Standard',
            name = 'Standard',
["modKey"] = 'standardModifiers',
            modKey = 'standardModifiers',
["levels"] = SkillData.Astrology.standardModifierLevels,
            levels = SkillData.Astrology.standardModifierLevels,
["inUse"] = false
            inUse = false
},
        },
{
        {
["name"] = 'Unique',
            name = 'Unique',
["modKey"] = 'uniqueModifiers',
            modKey = 'uniqueModifiers',
["levels"] = SkillData.Astrology.uniqueModifierLevels,
            levels = SkillData.Astrology.uniqueModifierLevels,
["inUse"] = false
            inUse = false
},
        },
{
        {
["name"] = 'Abyssal',
            name = 'Abyssal',
["modKey"] = 'abyssalModifiers',
            modKey = 'abyssalModifiers',
["levels"] = SkillData.Astrology.abyssalModifierLevels,
            levels = SkillData.Astrology.abyssalModifierLevels,
["inUse"] = false
            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 recipes = GameData.getEntities(SkillData.Astrology.recipes,
    local root = mw.html.create()
function(cons)
    local tableRoot = root:tag('table')
return Skills.getRecipeRealm(cons) == realmID
    tableRoot:addClass('wikitable stickyHeader')
end
)
table.sort(recipes,
function(a, b)
return Skills.getRecipeLevel('Astrology', a) < Skills.getRecipeLevel('Astrology', b)
end
)


-- Figure out which mod types are to be displayed
    -- Header rows
for _, recipe in ipairs(recipes) do
    local headerRow1 = tableRoot:tag('tr'):addClass('headerRow-0')
for _, modType in ipairs(modTypes) do
    headerRow1:tag('th'):attr('rowspan', 2)
if not modType.inUse then
    :attr('colspan', 2)
local recipeMods = recipe[modType.modKey]
    :wikitext('Constellation')
if recipeMods ~= nil and not Shared.tableIsEmpty(recipeMods) then
    headerRow1:tag('th'):attr('rowspan', 2)
modType.inUse = true
    :wikitext(Icons.Icon({ "Astrology", type='skill', notext='true' }) .. '<br>Level')
end
    headerRow1:tag('th'):attr('rowspan', 2)
end
    :wikitext('[[DLC]]')
end
    headerRow1:tag('th'):attr('rowspan', 2)
end
    :wikitext('XP')
    headerRow1:tag('th'):attr('rowspan', 2)
    :wikitext('Skills')


local resultPart = {}
    local headerRow2 = tableRoot:tag('tr'):addClass('headerRow-1')
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
    for _, modType in ipairs(modTypes) do
table.insert(resultPart, '\n|- class="headerRow-0"')
        if modType.inUse then
table.insert(resultPart, '\n!rowspan="2" colspan="2"|Constellation!!rowspan="2"| ' .. Icons.Icon({"Astrology", type='skill', notext='true'}) .. ' Level')
        local spanCount = modType.name == 'Abyssal' and 3 or 2
table.insert(resultPart, '!!rowspan="2"| XP!!rowspan="2"| Skills')
            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


local repCount = 0
    -- Data rows
for _, modType in ipairs(modTypes) do
    for _, cons in ipairs(recipes) do
if modType.inUse then
        local modData = Skills._getConstellationModifiers(cons)
repCount = repCount + 1
        local name = cons.name
table.insert(resultPart, '!!colspan="2"| ' .. modType.name .. ' Stars')
        local skillIconArray = {}
end
        for _, skillID in ipairs(cons.skillIDs) do
end
            table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
table.insert(resultPart, '\n|- class="headerRow-1"')
        end
table.insert(resultPart, string.rep('\n! ' .. Icons.Icon({'Mastery', notext=true}) .. 'Level\n! Modifiers', repCount))


for i, cons in ipairs(recipes) do
        -- Calculate maximum rows needed
local modData = Skills._getConstellationModifiers(cons)
        local maxRows = 1
local name = cons.name
        for _, modTypeData in pairs(modData) do
local skillIconArray = {}
            maxRows = math.max(maxRows, Shared.tableCount(modTypeData))
for j, skillID in ipairs(cons.skillIDs) do
        end
table.insert(skillIconArray, Icons.Icon({Constants.getSkillName(skillID), type='skill'}))
end
local maxRows = 1
for _, modTypeData in pairs(modData) do
maxRows = math.max(maxRows, Shared.tableCount(modTypeData))
end
local rowSpan = (maxRows > 1 and 'rowspan="' .. maxRows .. '"') or ''


table.insert(resultPart, '\n|-')
        -- Iterate through rows
table.insert(resultPart, '\n|' .. rowSpan .. ' data-sort-value="' .. name .. '" id="'..name..'"| ')
        for rowIdx = 1, maxRows do
table.insert(resultPart, Icons.Icon({name, type='constellation', size='50', notext=true}))
            local row = tableRoot:tag('tr')
table.insert(resultPart, '\n|' .. rowSpan .. '| ' .. Icons.getExpansionIcon(cons.id) .. name)
            if rowIdx == 1 then
table.insert(resultPart, '\n|' .. rowSpan .. ' style="text-align:right"| ' .. cons.level)
                row:tag('td'):attr('rowspan', maxRows)
table.insert(resultPart, '\n|' .. rowSpan .. ' style="text-align:right"| ' .. cons.baseExperience)
                :wikitext(Icons.Icon({name, type='constellation', size=50, notext=true}))
table.insert(resultPart, '\n|' .. rowSpan .. '| ' .. table.concat(skillIconArray, '<br/>'))
                :css('text-align', 'center')
                row:tag('td'):attr('rowspan', maxRows)
                :wikitext(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


-- Generate table text for modifiers
            -- Modifiers data
for row = 1, maxRows, 1 do
            for _, modType in ipairs(modTypes) do
for _, modType in ipairs(modTypes) do
                if modType.inUse then
if modType.inUse then
                    local masteryLevel = modType.levels[rowIdx]
local masteryLevel = modType.levels[row]
                    local rowModData = modData[modType.modKey][rowIdx]
local rowModData = modData[modType.modKey][row]
                    local rowConsData = cons[modType.modKey][rowIdx]
if masteryLevel == nil or rowModData == nil then
                    local cell1 = row:tag('td')
table.insert(resultPart, '\n|colspan="2" class="table-na"| ')
                    if modType.name == "Abyssal" then
else
                    local starUnlockReq = nil
table.insert(resultPart, '\n|style="text-align:right"| ' .. masteryLevel)
                    local cell2 = row:tag('td')
table.insert(resultPart, '\n| ' .. Modifiers.getModifiersText(rowModData, false, false, 10))
                    if rowConsData ~= nil and rowConsData.unlockRequirements ~= nil and Shared.tableCount(rowConsData.unlockRequirements) == 2 then
end
                    starUnlockReq = Common.getRequirementString({cons[modType.modKey][rowIdx].unlockRequirements[2]}, nil)
end
                    end
end
                    if starUnlockReq ~= nil then
if row < maxRows then
                    cell2:wikitext(starUnlockReq)
table.insert(resultPart, '\n|-')
                    :css('text-align', 'right')
end
                    else
end
                    cell2:wikitext('N/A')
end
                    :addClass('table-na')
table.insert(resultPart, '\n|}')
                    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 table.concat(resultPart)
return tostring(root)
end
end