Difference between revisions of "Module:Skills/Artisan"

From Melvor Idle
(getCraftingTable)
(_getRecipeTable: Support GP & SC costs)
 
(23 intermediate revisions by 4 users not shown)
Line 4: Line 4:
  
 
local SkillData = mw.loadData('Module:Skills/data')
 
local SkillData = mw.loadData('Module:Skills/data')
local ShopData = mw.loadData('Module:Shop/data')
 
  
 
local Shared = require('Module:Shared')
 
local Shared = require('Module:Shared')
 
local Items = require('Module:Items')
 
local Items = require('Module:Items')
 
local Icons = require('Module:Icons')
 
local Icons = require('Module:Icons')
local Shop = require('Module:Shop')
 
  
 
function p.getCookedItemsTable(frame)
 
function p.getCookedItemsTable(frame)
  local result = '{| class="wikitable sortable stickyHeader"'
+
local category = frame.args ~= nil and frame.args[1] or frame
  result = result..'\r\n|- class="headerRow-0"'
+
local categoryMap = {
  result = result..'\r\n!colspan="2"|Cooked Item!!'..Icons.Icon({'Cooking', type='skill', notext=true})..' Level'
+
["Cooking Fire"] = 0,
  result = result..'!!XP!!Healing!!Value!!Ingredients'
+
["Furnace"] = 1,
 +
["Pot"] = 2
 +
}
 +
local categoryID = categoryMap[category]
 +
local recipeArray = {}
  
  local itemArray = Items.getItems(function(item) return item.cookingID ~= nil end)
+
-- Find recipes for the relevant categories
  table.sort(itemArray, function(a, b) return a.cookingLevel < b.cookingLevel end)
+
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)
  
  for i, item in Shared.skpairs(itemArray) do
+
-- Logic for generating some cells of the table which are consistent for normal & perfect items
    local cookedItem = Items.getItemByID(item.cookedItemID)
+
local getHealingCell = function(item, qty)
    result = result..'\r\n|-'
+
if item ~= nil then
    result = result..'\r\n|style="min-width:25px"|'..Icons.Icon({cookedItem.name, type='item', notext='true', size='50'})..'||[['..cookedItem.name..']]'
+
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 '')
    result = result..'||style="text-align:right"|'..item.cookingLevel
+
else
    result = result..'||style="text-align:right"|'..item.cookingXP
+
return ' '
    result = result..'||style="text-align:right" data-sort-value="'..cookedItem.healsFor..'"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..(cookedItem.healsFor * 10)
+
end
    result = result..'||style="text-align:right" data-sort-value="'..cookedItem.sellsFor..'"|'..Icons.GP(cookedItem.sellsFor)
+
end
    result = result..'||'..Icons.Icon({item.name, type='item', qty = 1})
+
local getSaleValueCell = function(item, qty)
  end
+
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
  
  result = result..'\r\n|}'
+
local resultPart = {}
  return result
+
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
 
end
  
 
local tierSuffix = { 'I', 'II', 'III', 'IV' }
 
local tierSuffix = { 'I', 'II', 'III', 'IV' }
 
function p._getHerblorePotionTable(category)
 
function p._getHerblorePotionTable(category)
  if string.upper(category) == 'COMBAT' then
+
if string.upper(category) == 'COMBAT' then
    category = 0
+
category = 0
  elseif string.upper(category) == 'SKILL' then
+
elseif string.upper(category) == 'SKILL' then
    category = 1
+
category = 1
  elseif type(category) == 'string' then
+
elseif type(category) == 'string' then
    category = tonumber(category)
+
category = tonumber(category)
  end
+
end
  
  local potionArray = {}
+
local potionArray = {}
  for i, potion in Shared.skpairs(SkillData.Herblore.ItemData) do
+
for i, potion in Shared.skpairs(SkillData.Herblore.Potions) do
    if potion.category == category then
+
if potion.category == category then
      table.insert(potionArray, potion)
+
table.insert(potionArray, potion)
    end
+
end
  end
+
end
  
  local result = '{|class = "wikitable sortable stickyHeader"'
+
local resultPart = {}
  result = result..'\r\n|- class="headerRow-0"'
+
table.insert(resultPart, '{|class="wikitable sortable stickyHeader"')
  result = result..'\r\n!Potion!!'..Icons.Icon({'Herblore', type='skill', notext=true})..' Level'
+
table.insert(resultPart, '\r\n|- class="headerRow-0"')
  result = result..'!!XP!!Ingredients!!colspan="2"|Tier!!Value!!Charges!!Effect'
+
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.herbloreLevel < b.herbloreLevel end)
+
table.sort(potionArray, function(a, b) return a.level < b.level end)
  
  for i, potion in Shared.skpairs(potionArray) do
+
for i, potion in ipairs(potionArray) do
    local tierPots = {}
+
table.insert(resultPart, '\r\n|-')
    for j = 1, 4, 1 do
+
if potion.name == 'Bird Nests Potion' then
      table.insert(tierPots, Items.getItemByID(potion.itemID[j]))
+
table.insert(resultPart, '\r\n|rowspan="4"|[[Bird Nest Potion]]')
    end
+
else
    result = result..'\r\n|-'
+
table.insert(resultPart, '\r\n|rowspan="4"|[['..potion.name..']]')
    if potion.name == 'Bird Nests Potion' then
+
end
      result = result..'\r\n|rowspan="4"|[[Bird Nest Potion]]'
+
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.level)
    else
+
table.insert(resultPart, '||rowspan="4" style="text-align:right"|'..potion.baseXP)
      result = result..'\r\n|rowspan="4"|[['..potion.name..']]'
 
    end
 
    result = result..'||rowspan="4" style="text-align:right"|'..potion.herbloreLevel
 
    result = result..'||rowspan="4" style="text-align:right"|'..potion.herbloreXP
 
  
    local matArray = {}
+
local matArray = {}
    for j, mat in Shared.skpairs(tierPots[1].herbloreReq) do
+
for j, mat in ipairs(potion.itemCosts) do
      local matItem = Items.getItemByID(mat.id)
+
local matItem = Items.getItemByID(mat.id)
      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
    result = result..'||rowspan="4"|'..table.concat(matArray, ', ')..'||'
+
table.insert(resultPart, '||rowspan="4"|'..table.concat(matArray, ', ')..'||')
   
 
    local tierRows = {}
 
    for j, tierPot in Shared.skpairs(tierPots) do
 
      local rowTxt = Icons.Icon({tierPot.name, type='item', notext=true})
 
      rowTxt = rowTxt..'||[['..tierPot.name..'|'..tierSuffix[j]..']]'
 
      rowTxt = rowTxt..'||style="text-align:right;" data-sort-value="'..tierPot.sellsFor..'"|'..Icons.GP(tierPot.sellsFor)
 
      rowTxt = rowTxt..'||style="text-align:right;"|'..tierPot.potionCharges..'||'..tierPot.description
 
      table.insert(tierRows, rowTxt)
 
    end
 
    result = result..table.concat(tierRows, '\r\n|-\r\n|')
 
  end
 
  
  result = result..'\r\n|}'
+
local tierRows = {}
  return result
+
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
 
end
  
 
function p.getHerblorePotionTable(frame)
 
function p.getHerblorePotionTable(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._getHerblorePotionTable(category)
+
return p._getHerblorePotionTable(category)
end
 
 
 
function p.getCookingFireTable(frame)
 
  local toolArray = {}
 
  for i, upgrade in Shared.skpairs(ShopData.Shop.SkillUpgrades) do
 
    if Shared.contains(upgrade.name, 'Cooking Fire') then
 
      table.insert(toolArray, upgrade)
 
    end
 
  end
 
 
 
  local result = '{| class="wikitable"'
 
  result = result..'\r\n!colspan="4"| !!colspan="2"|Bonus '..Icons.Icon({'Cooking', type='skill', notext=true})..' XP'
 
  result = result..'\r\n|- class="headerRow-0"'
 
  result = result..'\r\n!colspan="2"|Name!!'..Icons.Icon({'Firemaking', type='skill', notext=true})..' Level'
 
  result = result..'!!Cost!!This Fire!!Total'
 
 
 
  local total = 0
 
  local total2 = 0
 
 
 
  for i, tool in Shared.skpairs(toolArray) do
 
    result = result..'\r\n|-'
 
    result = result..'\r\n|style="min-width:25px" data-sort-value="'..tool.name..'"|'..Icons.Icon({tool.name, type='upgrade', size='50', notext=true})
 
    result = result..'||'..tool.name
 
    local level = 1
 
    if tool.unlockRequirements ~= nil and tool.unlockRequirements.skillLevel ~= nil then
 
      --Gonna be lazy and assume there's only the one skill level and it's the one we care about
 
      level = tool.unlockRequirements.skillLevel[1][2]
 
    end
 
    result = result..'||style="text-align:right"|'..level
 
    result = result..'||style="text-align:right" data-sort-value="'..tool.cost.gp..'"|'..Shop.getCostString(tool.cost)
 
 
 
    local bonusXP = tool.contains.modifiers.increasedSkillXP[1][2]
 
    total = total + bonusXP
 
 
 
    result = result..'||style="text-align:right"|+'..bonusXP..'%'
 
    result = result..'||style="text-align:right"|+'..total..'%'
 
  end
 
 
 
  result = result..'\r\n|}'
 
  return result
 
 
end
 
end
  
 
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
  local data = nil
+
return p._getRecipeTable('Runecrafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients', 'SkillXPSec', 'GPSec'})
  if    category == "Runes"      then data = SkillData.Runecrafting.Runes
 
  elseif category == "ComboRunes" then data = SkillData.Runecrafting.ComboRunes
 
  elseif category == "Weapons"    then data = SkillData.Runecrafting.Weapons
 
  elseif category == "AirGear"    then data = SkillData.Runecrafting.AirGear
 
  elseif category == "WaterGear"  then data = SkillData.Runecrafting.WaterGear
 
  elseif category == "EarthGear"  then data = SkillData.Runecrafting.EarthGear
 
  elseif category == "FireGear"  then data = SkillData.Runecrafting.FireGear
 
  end
 
 
 
  if data == nil then
 
    return "ERROR: Invalid Runecrafting category name.[[Category:Pages with script errors]]"
 
  end
 
 
 
  local result = '{| class="wikitable sortable stickyHeader"'
 
  result = result..'\r\n|- class="headerRow-0"'
 
  result = result..'\r\n!Item\r\n!Name\r\n!Runecrafting Level\r\n!Experience'
 
  result = result..'\r\n!Item Price\r\n!Ingredients\r\n!XP/s\r\n!GP/s'
 
 
 
  local rcArray = {}
 
  for i, rc in Shared.skpairs(data) do
 
    table.insert(rcArray, rc)
 
  end
 
  table.sort(rcArray, function(a, b) return a.runecraftingLevel < b.runecraftingLevel end)
 
 
 
  for i, rune in Shared.skpairs(rcArray) do
 
    result = result..'\r\n|-'
 
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({rune.name, type='item', size='50', notext=true})
 
    result = result..'\r\n| style ="text-align: left;" |[['..rune.name..']]'
 
    result = result..'\r\n| style="text-align:right"|'..rune.runecraftingLevel
 
    result = result..'\r\n| style="text-align:right"|'..rune.runecraftingXP
 
    result = result..'\r\n| style="text-align:right"|'..rune.sellsFor
 
 
 
    local matArray = {}
 
    for j, mat in Shared.skpairs(rune.runecraftReq) do
 
      local matItem = Items.getItemByID(mat.id)
 
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
 
    end
 
    result = result..'\r\n|'..table.concat(matArray, ' ')
 
 
 
    local rcCraftTime = 2.00
 
    local xps = rune.runecraftingXP / rcCraftTime
 
    local gps = rune.sellsFor / rcCraftTime
 
    result = result..'\r\n| style="text-align:right"|'..string.format("%.2f", xps)
 
    result = result..'\r\n| style="text-align:right"|'..string.format("%.2f", gps)
 
  end
 
 
 
  result = result..'\r\n|}'
 
  return result
 
 
end
 
end
  
 
function p.getFletchingTable(frame)
 
function p.getFletchingTable(frame)
  local category = frame.args ~= nil and frame.args[1] or frame
+
local category = frame.args ~= nil and frame.args[1] or frame
  local data = nil
+
return p._getRecipeTable('Fletching', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'})
  if    category == "Arrows"    then data = SkillData.Fletching.Arrows
 
  elseif category == "Shortbows" then data = SkillData.Fletching.Shortbows
 
  elseif category == "Longbows"  then data = SkillData.Fletching.Longbows
 
  elseif category == "Bolts"    then data = SkillData.Fletching.Bolts
 
  elseif category == "Crossbows" then data = SkillData.Fletching.Crossbows
 
  elseif category == "Javelins"  then data = SkillData.Fletching.Javelins
 
  end
 
 
 
  if data == nil then
 
    return "ERROR: Invalid Fletching category name.[[Category:Pages with script errors]]"
 
  end
 
 
 
  local result = '{| class="wikitable sortable stickyHeader"'
 
  result = result..'\r\n|- class="headerRow-0"'
 
  result = result..'\r\n!Item\r\n!Name\r\n!Fletching Level\r\n!Experience'
 
  result = result..'\r\n!Quantity\r\n!Sells For\r\n!Ingredients'
 
 
 
  local fletchArray = {}
 
  for i, fc in Shared.skpairs(data) do
 
    table.insert(fletchArray, fc)
 
  end
 
  table.sort(fletchArray, function(a, b) return a.fletchingLevel < b.fletchingLevel end)
 
 
 
  for i, fletch in Shared.skpairs(fletchArray) do
 
    result = result..'\r\n|-'
 
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({fletch.name, type='item', size='50', notext=true})
 
    result = result..'\r\n| style ="text-align: left;" |[['..fletch.name..']]'
 
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchingLevel
 
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchingXP
 
    result = result..'\r\n| style="text-align:right"|'..fletch.fletchQty
 
    result = result..'\r\n| style="text-align:right"|'..fletch.sellsFor
 
 
 
    local matArray = {}
 
    for j, mat in Shared.skpairs(fletch.fletchReq) do
 
      local matItem = Items.getItemByID(mat.id)
 
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
 
    end
 
    result = result..'\r\n|'..table.concat(matArray, ' ')
 
  end
 
 
 
  result = result..'\r\n|}'
 
  return result
 
 
end
 
end
  
 
function p.getCraftingTable(frame)
 
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
  local data = nil
+
return p._getRecipeTable('Crafting', category, {'Item', 'SkillLevel', 'SkillXP', 'GP', 'Ingredients'})
 
+
end
  if    category == "Leather"    then data =
 
        Items.getItems(function(item) return item.tier == "Leather" or item.tier == "Hard Leather" end)
 
  elseif category == "Dragonhide" then data =
 
        Items.getItems(function(item) return item.tier == "Dragonhide" and item.craftingLevel ~= nil end)
 
  elseif category == "Rings"      then data =
 
        Items.getItems(function(item) return item.type == "Ring" and item.craftingLevel ~= nil end)
 
  elseif category == "Necklaces"  then data =
 
        Items.getItems(function(item) return item.type == "Amulet" and item.craftingLevel ~= nil end)
 
  end
 
  
  if data == nil then
+
-- Given a skill name, category, and column list, produces a table of recipes for that
    return "ERROR: Invalid Crafting category name.[[Category:Pages with script errors]]"
+
-- skill & category combination. If categoryName is '', all recipes are returned.
  end
+
-- 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
  
  table.sort(data, function(a, b) return (a.craftingLevel == b.craftingLevel and a.id < b.id)  
+
local categoryMap = {
                                      or a.craftingLevel <  b.craftingLevel end)
+
["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]
  
  local result = '{| class="wikitable sortable stickyHeader"'
+
-- Validation: Skill data
  result = result..'\r\n|- class="headerRow-0"'
+
local recipeKey = 'Recipes'
  result = result..'\r\n!Item\r\n!Name\r\n!Crafting Level\r\n!Experience'
+
if SkillData[skillName] == nil then
  result = result..'\r\n!Item Price\r\n!Ingredients'
+
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
  
  for i, craft in Shared.skpairs(data) do
+
-- Validation: Column list
    result = result..'\r\n|-'
+
local columnDef = {
    result = result..'\r\n| style="text-align: left;" | '..Icons.Icon({craft.name, type='item', size='50', notext=true})
+
["Item"] = {["header"] = 'Item\r\n!Name', ["altRepeat"] = true},
    result = result..'\r\n| style ="text-align: left;" |[['..craft.name..']]'
+
["SkillLevel"] = {["header"] = Icons.Icon({skillName, type='skill', notext=true}) .. ' Level', ["altRepeat"] = false},
    result = result..'\r\n| style="text-align:right"|'..craft.craftingLevel
+
["SkillXP"] = {["header"] = 'XP', altRepeat = false},
    result = result..'\r\n| style="text-align:right"|'..craft.craftingXP
+
["GP"] = {["header"] = 'Value', ["altRepeat"] = true},
    result = result..'\r\n| style="text-align:right"|'..craft.sellsFor
+
["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
  
    local matArray = {}
+
-- Determine which recipes to include
    for j, mat in Shared.skpairs(craft.craftReq) do
+
local recipeList = {}
      local matItem = Items.getItemByID(mat.id)
+
for i, recipe in ipairs(SkillData[skillName][recipeKey]) do
      table.insert(matArray, Icons.Icon({matItem.name, type='item', notext=true, qty=mat.qty}))
+
if categoryID == nil or recipe.category == categoryID then
    end
+
table.insert(recipeList, recipe)
    result = result..'\r\n|'..table.concat(matArray, ' ')
+
end
  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)
  
  result = result..'\r\n|}'
+
-- Build rows based on recipes
  return result
+
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
 +
if qty > 1 then
 +
namePrefix = namePrefix .. 'data-sort-value="' .. item.name .. '"'
 +
end
 +
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
 +
if recipe.gpCost ~= nil and recipe.gpCost > 0 then
 +
table.insert(matArray, Icons.GP(recipe.gpCost))
 +
end
 +
if recipe.scCost ~= nil and recipe.scCost > 0 then
 +
table.insert(matArray, Icons.SC(recipe.gpCost))
 +
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
 
end
  
 
return p
 
return p

Latest revision as of 22:06, 17 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
							if qty > 1 then
								namePrefix = namePrefix .. 'data-sort-value="' .. item.name .. '"'
							end
							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
							if recipe.gpCost ~= nil and recipe.gpCost > 0 then
								table.insert(matArray, Icons.GP(recipe.gpCost))
							end
							if recipe.scCost ~= nil and recipe.scCost > 0 then
								table.insert(matArray, Icons.SC(recipe.gpCost))
							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