Module:Skills/Artisan: Difference between revisions
From Melvor Idle
(Update for v1.0.2) |
(Update for v1.0.3) |
||
Line 11: | Line 11: | ||
function p.getCookedItemsTable(frame) | function p.getCookedItemsTable(frame) | ||
local category = frame.args ~= nil and frame.args[1] or frame | local category = frame.args ~= nil and frame.args[1] or frame | ||
local | local categoryMap = { | ||
["Cooking Fire"] = 0, | |||
["Furnace"] = 1, | |||
["Pot"] = 2 | |||
} | |||
local categoryID = categoryMap[category] | |||
local recipeArray = {} | |||
-- Find recipes for the relevant categories | |||
for i, recipe in ipairs(SkillData.Cooking.Recipes) do | |||
-- Note: Excludes Lemon cake | |||
if (categoryID == nil or recipe.category == categoryID) and recipe.masteryID >= 0 then | |||
table.insert(recipeArray, recipe) | |||
end | |||
end | end | ||
table.sort( | table.sort(recipeArray, function(a, b) return (a.level == b.level and a.masteryID < b.masteryID) or a.level < b.level end) | ||
-- Logic for generating some cells of the table which are consistent for normal & perfect items | -- Logic for generating some cells of the table which are consistent for normal & perfect items | ||
Line 40: | Line 44: | ||
end | end | ||
local | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!colspan="3" rowspan="2"|Cooked Item!!rowspan="2"|'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level') | |||
table.insert(resultPart, '!!rowspan="2"|Cook Time!!rowspan="2"|XP!!colspan="2"|Healing!!colspan="2"|Value!!rowspan="2"|Ingredients') | |||
table.insert(resultPart, '\r\n|- class="headerRow-1"') | |||
table.insert(resultPart, '\r\n!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true})) | |||
table.insert(resultPart, '!!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true})) | |||
for i, item | for i, recipe in ipairs(recipeArray) do | ||
local item = Items.getItemByID(recipe.itemID) | |||
local perfectItem = nil | local perfectItem = nil | ||
if | if recipe.perfectCookID ~= nil then | ||
perfectItem = Items.getItemByID( | perfectItem = Items.getItemByID(recipe.perfectCookID) | ||
end | end | ||
local qty = | local qty = recipe.baseQuantity or 1 | ||
table.insert(resultPart, '\r\n|-') | |||
table.insert(resultPart, '\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'})) | |||
table.insert(resultPart, '\r\n|style="min-width:25px"| ') | |||
if perfectItem ~= nil then | if perfectItem ~= nil then | ||
table.insert(resultPart, Icons.Icon({perfectItem.name, type='item', notext=true, size='50'})) | |||
end | end | ||
table.insert(resultPart, '||') | |||
if qty > 1 then | if qty > 1 then | ||
table.insert(resultPart, qty..'x ') | |||
end | end | ||
table.insert(resultPart, Icons.Icon({item.name, type='item', noicon = true})) | |||
table.insert(resultPart, '||style="text-align:right"|' .. recipe.level) | |||
table.insert(resultPart, '||style="text-align:right" data-sort-value="' .. recipe.baseInterval .. '"|' .. Shared.timeString(recipe.baseInterval / 1000, true)) | |||
table.insert(resultPart, '||style="text-align:right" data-sort-value="' .. recipe.baseXP .. '"|' .. Shared.formatnum(recipe.baseXP)) | |||
table.insert(resultPart, '||'..getHealingCell(item, qty)..'||'..getHealingCell(perfectItem, qty)) | |||
table.insert(resultPart, '||'..getSaleValueCell(item, qty)..'||'..getSaleValueCell(perfectItem, qty)) | |||
local matArray = {} | local matArray = {} | ||
for j, | for j, mat in ipairs(recipe.itemCosts) do | ||
local matItem = Items.getItemByID(mat.id) | |||
if matItem ~= nil then | |||
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty})) | table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty})) | ||
end | end | ||
end | end | ||
table.insert(resultPart, '\r\n|'..table.concat(matArray, ' ')) | |||
end | end | ||
table.insert(resultPart, '\r\n|}') | |||
return | return table.concat(resultPart) | ||
end | end | ||
Line 103: | Line 108: | ||
end | end | ||
local | local resultPart = {} | ||
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"') | |||
table.insert(resultPart, '\r\n|- class="headerRow-0"') | |||
table.insert(resultPart, '\r\n!Potion!!'..Icons.Icon({'Herblore', type='skill', notext=true})..' Level') | |||
table.insert(resultPart, '!!XP!!Ingredients!!style="width:30px;"|Tier!!Value!!Charges!!Effect') | |||
table.sort(potionArray, function(a, b) return a.level < b.level end) | table.sort(potionArray, function(a, b) return a.level < b.level end) | ||
for i, potion in ipairs(potionArray) do | for i, potion in ipairs(potionArray) do | ||
table.insert(resultPart, '\r\n|-') | |||
if potion.name == 'Bird Nests Potion' then | if potion.name == 'Bird Nests Potion' then | ||
table.insert(resultPart, '\r\n|rowspan="4"|[[Bird Nest Potion]]') | |||
else | else | ||
table.insert(resultPart, '\r\n|rowspan="4"|[['..potion.name..']]') | |||
end | end | ||
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.level) | |||
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.baseXP) | |||
local matArray = {} | local matArray = {} | ||
Line 125: | Line 131: | ||
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty})) | table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty})) | ||
end | end | ||
table.insert(resultPart, '||rowspan="4"|'..table.concat(matArray, ', ')..'||') | |||
local tierRows = {} | local tierRows = {} | ||
for j, potionID in ipairs(potion.potionIDs) do | for j, potionID in ipairs(potion.potionIDs) do | ||
local rowTxt = {} | |||
local tierPot = Items.getItemByID(potionID) | local tierPot = Items.getItemByID(potionID) | ||
table.insert(rowTxt, Icons.Icon({tierPot.name, type='item', notext=true})) | |||
rowTxt | table.insert(rowTxt, Icons.Icon({tierPot.name, tierSuffix[j], type = 'item', noicon = true})) | ||
rowTxt | table.insert(rowTxt, '||style="text-align:right;" data-sort-value="'..tierPot.sellsFor..'"|'..Icons.GP(tierPot.sellsFor)) | ||
rowTxt | table.insert(rowTxt, '||style="text-align:right;"|'..tierPot.potionCharges..'||'..tierPot.description) | ||
table.insert(tierRows, rowTxt) | table.insert(tierRows, table.concat(rowTxt)) | ||
end | end | ||
table.insert(resultPart, table.concat(tierRows, '\r\n|-\r\n|')) | |||
end | end | ||
table.insert(resultPart, '\r\n|}') | |||
return | return table.concat(resultPart) | ||
end | end | ||
Line 150: | Line 157: | ||
function p.getRunecraftingTable(frame) | function p.getRunecraftingTable(frame) | ||
local category = frame.args ~= nil and frame.args[1] or frame | local category = frame.args ~= nil and frame.args[1] or frame | ||
return p._getRecipeTable('Runecrafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients', 'SkillXPSec', 'GPSec'}) | |||
end | |||
function p.getFletchingTable(frame) | |||
local category = frame.args ~= nil and frame.args[1] or frame | |||
return p._getRecipeTable('Fletching', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'}) | |||
end | end | ||
function p. | function p.getCraftingTable(frame) | ||
local category = frame.args ~= nil and frame.args[1] or frame | local category = frame.args ~= nil and frame.args[1] or frame | ||
return p._getRecipeTable('Crafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'}) | |||
end | |||
-- Given a skill name, category, and column list, produces a table of recipes for that | |||
-- skill & category combination. If categoryName is '', all recipes are returned. | |||
-- Note: This only supports a number of skills with consistent recipe data structures, being: | |||
-- Fletching, Crafting, and Runecrafting | |||
elseif | -- Valid column list options are: Item, SkillLevel, SkillXP, GP, Ingredients, SkillXPSec, GPSec | ||
function p._getRecipeTable(skillName, categoryName, columnList) | |||
-- Validation: Parameters | |||
if type(skillName) ~= 'string' then | |||
elseif | return 'ERROR: skillName must be a string' | ||
elseif not Shared.contains({'string', 'nil'}, type(categoryName)) then | |||
return 'ERROR: category must be a string or nil' | |||
elseif type(columnList) ~= 'table' or Shared.tableCount(columnList) == 0 then | |||
return 'ERROR: columnList must be a table with 1+ elements' | |||
end | end | ||
if | local categoryMap = { | ||
return | ["Runecrafting"] = { | ||
["Runes"] = 0, | |||
["ComboRunes"] = 1, | |||
["Weapons"] = 2, | |||
["AirGear"] = 3, | |||
["WaterGear"] = 4, | |||
["EarthGear"] = 5, | |||
["FireGear"] = 6 | |||
}, | |||
["Fletching"] = { | |||
["Arrows"] = 0, | |||
["Shortbows"] = 1, | |||
["Longbows"] = 2, | |||
["Bolts"] = 3, | |||
["Crossbows"] = 4, | |||
["Javelins"] = 5 | |||
}, | |||
["Crafting"] = { | |||
["Leather"] = 0, | |||
["Dragonhide"] = 1, | |||
["Rings"] = 2, | |||
["Necklaces"] = 3, | |||
["Bags"] = 4 | |||
} | |||
} | |||
local actionIntervals = { | |||
["Runecrafting"] = 2, | |||
["Fletching"] = 2, | |||
["Crafting"] = 2 | |||
} | |||
-- Validation: Category | |||
if categoryMap[skillName] == nil then | |||
return 'ERROR: The ' .. skillName .. ' skill is not supported by this function' | |||
elseif categoryName ~= '' and categoryMap[skillName][categoryName] == nil then | |||
local catNames = {} | |||
for catName, catID in pairs(categoryMap[skillName]) do | |||
table.insert(catNames, catName) | |||
end | |||
return 'ERROR: No such category ' .. categoryName .. ' for skill ' .. skillName .. ', the following are available: ' .. table.concat(catNames, ', ') | |||
end | end | ||
local categoryID = categoryMap[skillName][categoryName] | |||
local actionInterval = actionIntervals[skillName] | |||
local | -- Validation: Skill data | ||
local recipeKey = 'Recipes' | |||
if SkillData[skillName] == nil then | |||
return 'ERROR: Could not locate skill data for ' .. skillName | |||
elseif SkillData[skillName][recipeKey] == nil then | |||
return 'ERROR: Could not locate recipe data for ' .. skillName | |||
end | |||
-- Validation: Column list | |||
local columnDef = { | |||
["Item"] = {["header"] = 'Item\r\n!Name', ["altRepeat"] = true}, | |||
["SkillLevel"] = {["header"] = Icons.Icon({skillName, type='skill', notext=true}) .. ' Level', ["altRepeat"] = false}, | |||
["SkillXP"] = {["header"] = 'XP', altRepeat = false}, | |||
["GP"] = {["header"] = 'Value', ["altRepeat"] = true}, | |||
["Ingredients"] = {["header"] = 'Ingredients', ["altRepeat"] = true}, | |||
["SkillXPSec"] = {["header"] = 'XP/s', ["altRepeat"] = false}, | |||
["GPSec"] = {["header"] = 'GP/s', ["altRepeat"] = true} | |||
} | |||
-- Build the table header while we're here | |||
local resultPart = {} | |||
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"\r\n|- class="headerRow-0"') | |||
for i, colID in ipairs(columnList) do | |||
if columnDef[colID] == nil then | |||
table.insert( | return 'ERROR: Invalid column ' .. colID .. ' requested' | ||
else | |||
table.insert(resultPart, '\r\n! ' .. columnDef[colID].header) | |||
end | end | ||
end | end | ||
-- Determine which recipes to include | |||
local recipeList = {} | |||
for i, recipe in ipairs(SkillData[skillName][recipeKey]) do | |||
if categoryID == nil or recipe.category == categoryID then | |||
table.insert(recipeList, recipe) | |||
end | |||
end | end | ||
if Shared.tableCount(recipeList) == 0 then | |||
if | return '' | ||
return | |||
end | end | ||
table.sort(recipeList, function(a, b) return (a.level == b.level and a.masteryID < b.masteryID) or a.level < b.level end) | |||
-- Build rows based on recipes | |||
for i, recipe in ipairs(recipeList) do | |||
local item = Items.getItemByID(recipe.itemID) | |||
if item ~= nil then | |||
-- Some recipes have alternative costs, so the recipe may require multiple rows | |||
local costList = nil | |||
if recipe.alternativeCosts ~= nil and Shared.tableCount(recipe.alternativeCosts) > 0 then | |||
costList = recipe.alternativeCosts | |||
else | |||
costList = {{["itemCosts"] = recipe.itemCosts}} | |||
end | |||
local costCount = Shared.tableCount(costList) | |||
-- Build one row per element within costList | |||
for recipeRow, costDef in ipairs(costList) do | |||
local rowspanStr = (recipeRow == 1 and costCount > 1 and 'rowspan="' .. costCount .. '" ') or '' | |||
local qty = (costDef.quantityMultiplier or 1) * (recipe.baseQuantity or 1) | |||
table.insert(resultPart, '\r\n|-') | |||
for j, colID in ipairs(columnList) do | |||
local altRepeat = columnDef[colID].altRepeat | |||
-- All columns must be generated if this is the very first row for a recipe, | |||
-- for subsequent rows only columns marked as altRepeat = true are generated | |||
if recipeRow == 1 or altRepeat then | |||
local spanStr = (not altRepeat and rowspanStr) or '' | |||
if colID == 'Item' then | |||
local namePrefix = spanStr .. (qty > 1 and 'data-sort-value="' .. item.name .. '"') | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:center;min-width:25px"| ' .. Icons.Icon({item.name, type='item', size='50', notext=true})) | |||
table.insert(resultPart, '\r\n|'.. (namePrefix ~= '' and namePrefix .. '| ' or ' ') .. (qty > 1 and '<b>' .. qty .. 'x</b> ' or '') .. Icons.Icon({item.name, type='item', noicon=true})) | |||
elseif colID == 'SkillLevel' then | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. recipe.level) | |||
elseif colID == 'SkillXP' then | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. recipe.baseXP) | |||
elseif colID == 'GP' then | |||
local val = math.floor(item.sellsFor) | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'data-sort-value="' .. (val * qty) .. '"| ' .. Icons.GP(val) .. (qty > 1 and ' (x' .. qty .. ')' or '')) | |||
elseif colID == 'Ingredients' then | |||
local matArray = {} | |||
for k, mat in ipairs(costDef.itemCosts) do | |||
local matItem = Items.getItemByID(mat.id) | |||
if matItem ~= nil then | |||
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty})) | |||
end | |||
end | |||
table.insert(resultPart, '\r\n|' .. (spanStr ~= '' and spanStr .. '| ' or ' ') .. table.concat(matArray, ', ')) | |||
elseif colID == 'SkillXPSec' then | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. string.format('%.2f', recipe.baseXP / actionInterval)) | |||
elseif colID == 'GPSec' then | |||
local val = math.floor(item.sellsFor) * qty / actionInterval | |||
table.insert(resultPart, '\r\n|' .. spanStr .. 'data-sort-value="' .. val .. '"| ' .. Icons.GP(string.format('%.2f', val))) | |||
else | |||
table.insert(resultPart, '\r\n| ') | |||
end | |||
end | |||
end | |||
end | |||
end | end | ||
end | end | ||
table.insert(resultPart, '\r\n|}') | |||
return table.concat(resultPart) | |||
return | |||
end | end | ||
return p | return p |
Revision as of 12:25, 5 March 2022
Documentation for this module may be created at Module:Skills/Artisan/doc
--Splitting some functions into here to avoid bloating a single file
--Contains function for skills that consume resources (ie smithing, cooking, herblore, etc.)
local p = {}
local SkillData = mw.loadData('Module:Skills/data')
local Shared = require('Module:Shared')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
function p.getCookedItemsTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
local categoryMap = {
["Cooking Fire"] = 0,
["Furnace"] = 1,
["Pot"] = 2
}
local categoryID = categoryMap[category]
local recipeArray = {}
-- Find recipes for the relevant categories
for i, recipe in ipairs(SkillData.Cooking.Recipes) do
-- Note: Excludes Lemon cake
if (categoryID == nil or recipe.category == categoryID) and recipe.masteryID >= 0 then
table.insert(recipeArray, recipe)
end
end
table.sort(recipeArray, function(a, b) return (a.level == b.level and a.masteryID < b.masteryID) or a.level < b.level end)
-- Logic for generating some cells of the table which are consistent for normal & perfect items
local getHealingCell = function(item, qty)
if item ~= nil then
return 'data-sort-value="'..(math.floor(item.healsFor) * qty)..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..math.floor(item.healsFor * 10)..(qty > 1 and ' (x'..qty..')' or '')
else
return ' '
end
end
local getSaleValueCell = function(item, qty)
if item ~= nil then
return 'data-sort-value="'..(math.floor(item.sellsFor) * qty)..'"|'..Icons.GP(math.floor(item.sellsFor))..(qty > 1 and ' (x'..qty..')' or '')
else
return ' '
end
end
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!colspan="3" rowspan="2"|Cooked Item!!rowspan="2"|'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level')
table.insert(resultPart, '!!rowspan="2"|Cook Time!!rowspan="2"|XP!!colspan="2"|Healing!!colspan="2"|Value!!rowspan="2"|Ingredients')
table.insert(resultPart, '\r\n|- class="headerRow-1"')
table.insert(resultPart, '\r\n!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true}))
table.insert(resultPart, '!!Normal!!' .. Icons.Icon({'Perfect', type='bonus', ext='png', notext=true, nolink=true}))
for i, recipe in ipairs(recipeArray) do
local item = Items.getItemByID(recipe.itemID)
local perfectItem = nil
if recipe.perfectCookID ~= nil then
perfectItem = Items.getItemByID(recipe.perfectCookID)
end
local qty = recipe.baseQuantity or 1
table.insert(resultPart, '\r\n|-')
table.insert(resultPart, '\r\n|style="min-width:25px"|'..Icons.Icon({item.name, type='item', notext=true, size='50'}))
table.insert(resultPart, '\r\n|style="min-width:25px"| ')
if perfectItem ~= nil then
table.insert(resultPart, Icons.Icon({perfectItem.name, type='item', notext=true, size='50'}))
end
table.insert(resultPart, '||')
if qty > 1 then
table.insert(resultPart, qty..'x ')
end
table.insert(resultPart, Icons.Icon({item.name, type='item', noicon = true}))
table.insert(resultPart, '||style="text-align:right"|' .. recipe.level)
table.insert(resultPart, '||style="text-align:right" data-sort-value="' .. recipe.baseInterval .. '"|' .. Shared.timeString(recipe.baseInterval / 1000, true))
table.insert(resultPart, '||style="text-align:right" data-sort-value="' .. recipe.baseXP .. '"|' .. Shared.formatnum(recipe.baseXP))
table.insert(resultPart, '||'..getHealingCell(item, qty)..'||'..getHealingCell(perfectItem, qty))
table.insert(resultPart, '||'..getSaleValueCell(item, qty)..'||'..getSaleValueCell(perfectItem, qty))
local matArray = {}
for j, mat in ipairs(recipe.itemCosts) do
local matItem = Items.getItemByID(mat.id)
if matItem ~= nil then
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
end
end
table.insert(resultPart, '\r\n|'..table.concat(matArray, ' '))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
local tierSuffix = { 'I', 'II', 'III', 'IV' }
function p._getHerblorePotionTable(category)
if string.upper(category) == 'COMBAT' then
category = 0
elseif string.upper(category) == 'SKILL' then
category = 1
elseif type(category) == 'string' then
category = tonumber(category)
end
local potionArray = {}
for i, potion in Shared.skpairs(SkillData.Herblore.Potions) do
if potion.category == category then
table.insert(potionArray, potion)
end
end
local resultPart = {}
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\r\n|- class="headerRow-0"')
table.insert(resultPart, '\r\n!Potion!!'..Icons.Icon({'Herblore', type='skill', notext=true})..' Level')
table.insert(resultPart, '!!XP!!Ingredients!!style="width:30px;"|Tier!!Value!!Charges!!Effect')
table.sort(potionArray, function(a, b) return a.level < b.level end)
for i, potion in ipairs(potionArray) do
table.insert(resultPart, '\r\n|-')
if potion.name == 'Bird Nests Potion' then
table.insert(resultPart, '\r\n|rowspan="4"|[[Bird Nest Potion]]')
else
table.insert(resultPart, '\r\n|rowspan="4"|[['..potion.name..']]')
end
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.level)
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.baseXP)
local matArray = {}
for j, mat in ipairs(potion.itemCosts) do
local matItem = Items.getItemByID(mat.id)
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
end
table.insert(resultPart, '||rowspan="4"|'..table.concat(matArray, ', ')..'||')
local tierRows = {}
for j, potionID in ipairs(potion.potionIDs) do
local rowTxt = {}
local tierPot = Items.getItemByID(potionID)
table.insert(rowTxt, Icons.Icon({tierPot.name, type='item', notext=true}))
table.insert(rowTxt, Icons.Icon({tierPot.name, tierSuffix[j], type = 'item', noicon = true}))
table.insert(rowTxt, '||style="text-align:right;" data-sort-value="'..tierPot.sellsFor..'"|'..Icons.GP(tierPot.sellsFor))
table.insert(rowTxt, '||style="text-align:right;"|'..tierPot.potionCharges..'||'..tierPot.description)
table.insert(tierRows, table.concat(rowTxt))
end
table.insert(resultPart, table.concat(tierRows, '\r\n|-\r\n|'))
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
function p.getHerblorePotionTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
return p._getHerblorePotionTable(category)
end
function p.getRunecraftingTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
return p._getRecipeTable('Runecrafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients', 'SkillXPSec', 'GPSec'})
end
function p.getFletchingTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
return p._getRecipeTable('Fletching', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'})
end
function p.getCraftingTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
return p._getRecipeTable('Crafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'})
end
-- Given a skill name, category, and column list, produces a table of recipes for that
-- skill & category combination. If categoryName is '', all recipes are returned.
-- Note: This only supports a number of skills with consistent recipe data structures, being:
-- Fletching, Crafting, and Runecrafting
-- Valid column list options are: Item, SkillLevel, SkillXP, GP, Ingredients, SkillXPSec, GPSec
function p._getRecipeTable(skillName, categoryName, columnList)
-- Validation: Parameters
if type(skillName) ~= 'string' then
return 'ERROR: skillName must be a string'
elseif not Shared.contains({'string', 'nil'}, type(categoryName)) then
return 'ERROR: category must be a string or nil'
elseif type(columnList) ~= 'table' or Shared.tableCount(columnList) == 0 then
return 'ERROR: columnList must be a table with 1+ elements'
end
local categoryMap = {
["Runecrafting"] = {
["Runes"] = 0,
["ComboRunes"] = 1,
["Weapons"] = 2,
["AirGear"] = 3,
["WaterGear"] = 4,
["EarthGear"] = 5,
["FireGear"] = 6
},
["Fletching"] = {
["Arrows"] = 0,
["Shortbows"] = 1,
["Longbows"] = 2,
["Bolts"] = 3,
["Crossbows"] = 4,
["Javelins"] = 5
},
["Crafting"] = {
["Leather"] = 0,
["Dragonhide"] = 1,
["Rings"] = 2,
["Necklaces"] = 3,
["Bags"] = 4
}
}
local actionIntervals = {
["Runecrafting"] = 2,
["Fletching"] = 2,
["Crafting"] = 2
}
-- Validation: Category
if categoryMap[skillName] == nil then
return 'ERROR: The ' .. skillName .. ' skill is not supported by this function'
elseif categoryName ~= '' and categoryMap[skillName][categoryName] == nil then
local catNames = {}
for catName, catID in pairs(categoryMap[skillName]) do
table.insert(catNames, catName)
end
return 'ERROR: No such category ' .. categoryName .. ' for skill ' .. skillName .. ', the following are available: ' .. table.concat(catNames, ', ')
end
local categoryID = categoryMap[skillName][categoryName]
local actionInterval = actionIntervals[skillName]
-- Validation: Skill data
local recipeKey = 'Recipes'
if SkillData[skillName] == nil then
return 'ERROR: Could not locate skill data for ' .. skillName
elseif SkillData[skillName][recipeKey] == nil then
return 'ERROR: Could not locate recipe data for ' .. skillName
end
-- Validation: Column list
local columnDef = {
["Item"] = {["header"] = 'Item\r\n!Name', ["altRepeat"] = true},
["SkillLevel"] = {["header"] = Icons.Icon({skillName, type='skill', notext=true}) .. ' Level', ["altRepeat"] = false},
["SkillXP"] = {["header"] = 'XP', altRepeat = false},
["GP"] = {["header"] = 'Value', ["altRepeat"] = true},
["Ingredients"] = {["header"] = 'Ingredients', ["altRepeat"] = true},
["SkillXPSec"] = {["header"] = 'XP/s', ["altRepeat"] = false},
["GPSec"] = {["header"] = 'GP/s', ["altRepeat"] = true}
}
-- Build the table header while we're here
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"\r\n|- class="headerRow-0"')
for i, colID in ipairs(columnList) do
if columnDef[colID] == nil then
return 'ERROR: Invalid column ' .. colID .. ' requested'
else
table.insert(resultPart, '\r\n! ' .. columnDef[colID].header)
end
end
-- Determine which recipes to include
local recipeList = {}
for i, recipe in ipairs(SkillData[skillName][recipeKey]) do
if categoryID == nil or recipe.category == categoryID then
table.insert(recipeList, recipe)
end
end
if Shared.tableCount(recipeList) == 0 then
return ''
end
table.sort(recipeList, function(a, b) return (a.level == b.level and a.masteryID < b.masteryID) or a.level < b.level end)
-- Build rows based on recipes
for i, recipe in ipairs(recipeList) do
local item = Items.getItemByID(recipe.itemID)
if item ~= nil then
-- Some recipes have alternative costs, so the recipe may require multiple rows
local costList = nil
if recipe.alternativeCosts ~= nil and Shared.tableCount(recipe.alternativeCosts) > 0 then
costList = recipe.alternativeCosts
else
costList = {{["itemCosts"] = recipe.itemCosts}}
end
local costCount = Shared.tableCount(costList)
-- Build one row per element within costList
for recipeRow, costDef in ipairs(costList) do
local rowspanStr = (recipeRow == 1 and costCount > 1 and 'rowspan="' .. costCount .. '" ') or ''
local qty = (costDef.quantityMultiplier or 1) * (recipe.baseQuantity or 1)
table.insert(resultPart, '\r\n|-')
for j, colID in ipairs(columnList) do
local altRepeat = columnDef[colID].altRepeat
-- All columns must be generated if this is the very first row for a recipe,
-- for subsequent rows only columns marked as altRepeat = true are generated
if recipeRow == 1 or altRepeat then
local spanStr = (not altRepeat and rowspanStr) or ''
if colID == 'Item' then
local namePrefix = spanStr .. (qty > 1 and 'data-sort-value="' .. item.name .. '"')
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:center;min-width:25px"| ' .. Icons.Icon({item.name, type='item', size='50', notext=true}))
table.insert(resultPart, '\r\n|'.. (namePrefix ~= '' and namePrefix .. '| ' or ' ') .. (qty > 1 and '<b>' .. qty .. 'x</b> ' or '') .. Icons.Icon({item.name, type='item', noicon=true}))
elseif colID == 'SkillLevel' then
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. recipe.level)
elseif colID == 'SkillXP' then
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. recipe.baseXP)
elseif colID == 'GP' then
local val = math.floor(item.sellsFor)
table.insert(resultPart, '\r\n|' .. spanStr .. 'data-sort-value="' .. (val * qty) .. '"| ' .. Icons.GP(val) .. (qty > 1 and ' (x' .. qty .. ')' or ''))
elseif colID == 'Ingredients' then
local matArray = {}
for k, mat in ipairs(costDef.itemCosts) do
local matItem = Items.getItemByID(mat.id)
if matItem ~= nil then
table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
end
end
table.insert(resultPart, '\r\n|' .. (spanStr ~= '' and spanStr .. '| ' or ' ') .. table.concat(matArray, ', '))
elseif colID == 'SkillXPSec' then
table.insert(resultPart, '\r\n|' .. spanStr .. 'style="text-align:right"| ' .. string.format('%.2f', recipe.baseXP / actionInterval))
elseif colID == 'GPSec' then
local val = math.floor(item.sellsFor) * qty / actionInterval
table.insert(resultPart, '\r\n|' .. spanStr .. 'data-sort-value="' .. val .. '"| ' .. Icons.GP(string.format('%.2f', val)))
else
table.insert(resultPart, '\r\n| ')
end
end
end
end
end
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
return p