Module:Shop: Difference between revisions

From Melvor Idle
(_getShopTable: Minor amends to disable links on certain Golbin raid purchases; enforce minimum width on image column to prevent image disappearing completely on some mobile resolutions)
(getShopMiscUpgradeTable: Include requirements column)
(15 intermediate revisions by the same user not shown)
Line 2: Line 2:


local ShopData = mw.loadData('Module:Shop/data')
local ShopData = mw.loadData('Module:Shop/data')
-- Data instead of Module:CombatAreas to avoid loop whne that module attempts to require Module:Shop
local AreaData = require('Module:CombatAreas/data')


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
Line 7: Line 9:
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Areas = require('Module:CombatAreas')
 
-- Overrides for various items, mostly relating to icon overrides
local purchOverrides = {
  ["Extra Bank Slot"] = { icon = {'Bank Slot', 'upgrade'}, link = 'Bank Slot', cost = Icons.Icon({'Coins', size = 25, notext = true}) .. ' <span style="font-size:127%; font-family: MathJax_Math; font-style: italic;">C<sub>b</sub></span>*' },
  -- Golbin Raid items
  ["Reduce Wave Skip Cost"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Food Bonus"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Ammo Gatherer"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Rune Pouch"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Increase Starting Prayer Points"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Unlock Combat Passive Slot"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Prayer"] = { icon = {'Prayer', 'skill'}, link = nil },
  ["Increase Prayer Level"] = { icon = {'Prayer', 'skill'}, link = nil },
  ["Increase Prayer Points gained per Wave Completion"] = { icon = {'Prayer', 'skill'}, link = nil }
}
 
function p.getPurchase(purchaseName)
  for categoryName, categoryData in pairs(ShopData.Shop) do
    for i, purchase in ipairs(categoryData) do
      if purchase.name == purchaseName then
        return p.processPurchase(categoryName, i - 1)
      end
    end
  end
end


function p.processPurchase(category, purchaseID)
function p.processPurchase(category, purchaseID)
Line 16: Line 42:
end
end


function p.getCostString(cost)
function p._getPurchaseStat(purchase, stat, inline)
  local displayInline = (inline ~= nil and inline or false)
  if stat == 'cost' then
    return p.getCostString(purchase.cost, displayInline)
  elseif stat == 'requirements' then
    return p.getRequirementString(purchase.unlockRequirements)
  elseif stat == 'contents' then
    return p._getPurchaseContents(purchase, true)
  elseif stat == 'type' then
    return p._getPurchaseType(purchase)
  else
    return purchase[stat]
  end
end
 
function p.getPurchaseStat(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local statName = args[2]
  local displayInline = (args['inline'] ~= nil and string.lower(args['inline']) == 'true' or false)
  -- Hack for some purchases existing twice with varying costs (e.g. 'Extra Equipment Set')
  local purchaseList = {}
  if statName == 'cost' then
    purchaseList = p.getPurchases(function(cat, purch) return purch.name == purchaseName end)
  else
    purchaseList = {p.getPurchase(purchaseName)}
  end
 
  if Shared.tableCount(purchaseList) == 0 then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    local resultPart = {}
    for i, purchase in ipairs(purchaseList) do
      table.insert(resultPart, p._getPurchaseStat(purchase, statName, displayInline))
    end
    return table.concat(resultPart, ' or ')
  end
end
 
function p.getCostString(cost, inline)
  local displayInline = (inline ~= nil and inline or false)
   local costArray = {}
   local costArray = {}
   if cost.gp ~= nil and cost.gp > 0 then
   if cost.gp ~= nil and cost.gp > 0 then
Line 31: Line 97:
     for i, itemCost in Shared.skpairs(cost.items) do
     for i, itemCost in Shared.skpairs(cost.items) do
       local item = Items.getItemByID(itemCost[1])
       local item = Items.getItemByID(itemCost[1])
       table.insert(itemArray, Icons.Icon({item.name, type="item", notext=true, qty=itemCost[2]}))
       table.insert(itemArray, Icons.Icon({item.name, type="item", notext=(not displayInline and true or nil), qty=itemCost[2]}))
     end
     end


Line 39: Line 105:
   end
   end


   return table.concat(costArray, "<br/>")
  local sep, lastSep = '<br/>', nil
  if displayInline then
    sep = ', '
    lastSep = Shared.tableCount(costArray) > 2 and ', and ' or ' and '
  end
   return Shared.joinList(costArray, sep, lastSep)
end
end


Line 57: Line 128:
   if reqs.dungeonCompletion ~= nil then
   if reqs.dungeonCompletion ~= nil then
     for i, dungReq in Shared.skpairs(reqs.dungeonCompletion) do
     for i, dungReq in Shared.skpairs(reqs.dungeonCompletion) do
       local dung = Areas.getAreaByID('dungeon', dungReq[1])
       local dung = AreaData['dungeons'][dungReq[1] + 1]
       local dungStr = 'Complete '..Icons.Icon({dung.name, type='dungeon'})
       local dungStr = 'Complete '..Icons.Icon({dung.name, type='dungeon'})
       if dungReq[2] > 1 then
       if dungReq[2] > 1 then
Line 92: Line 163:
end
end


function p._getShopTable(Purchases)
function p._getPurchaseType(purchase)
   local result = '{| class="wikitable sortable stickyHeader"'
   if purchase.contains == nil then
   result = result..'\r\n|- class="headerRow-0"'
    return 'Unknown'
   result = result..'\r\n!colspan="2"|Purchase!!Type!!Description!!style="min-width:90px"|Cost!!Requirements'
  elseif purchase.contains.pet ~= nil then
    return 'Pet'
   elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
    return 'Upgrade'
   elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
    return 'Item Bundle'
  else
    return 'Item'
  end
end


function p._getPurchaseContents(purchase, asList)
  if asList == nil then asList = true end
  local containArray = {}
  if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0 then
    if not asList then
      table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
      table.insert(containArray, '|- class="headerRow-0"')
      table.insert(containArray, '! colspan="2" | Item !! Quantity')
    end
    for i, itemLine in Shared.skpairs(purchase.contains.items) do
      local item = Items.getItemByID(itemLine[1])
      if asList then
        table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemLine[2]}))
      else
        table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size='25'}))
        table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. '\r\n| data-sort-value="' .. itemLine[2] .. '" style="text-align:right" | ' .. Shared.formatnum(itemLine[2]))
      end
    end
  end
  if purchase.charges ~= nil and purchase.charges > 0 then
    if asList then
      table.insert(containArray, '+'..purchase.charges..' '..Icons.Icon({purchase.name, type='item'})..' Charges')
    else
      table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({purchase.name, type='item', notext=true, size='25'}))
      table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. purchase.charges .. '" style="text-align:right" | ' .. Shared.formatnum(purchase.charges))
    end
  end
  if not asList and Shared.tableCount(containArray) > 0 then table.insert(containArray, '|}') end
  local delim = (asList and '<br/>' or '\r\n')
  return table.concat(containArray, delim)
end
function p.getPurchaseContents(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local asList = ((args[2] ~= nil and args[2] == 'true') or false)
  local purchase = p.getPurchase(purchaseName)
  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    return p._getPurchaseContents(purchase, asList)
  end
end
-- Accept similar arguments to Icons.Icon
function p._getPurchaseIcon(iconArgs)
  local purchase = iconArgs[1]
  local override = purchOverrides[purchase.name]
  local purchType = p._getPurchaseType(purchase)
  -- Amend iconArgs before passing to Icons.Icon()
  iconArgs[1] = ((override ~= nil and override.icon[1]) or purchase.name)
  if override ~= nil then
    iconArgs['type'] = override.icon[2]
    if override.link == nil then
      iconArgs['nolink'] = true
    end
  else
    iconArgs['type'] = (purchType == 'Item Bundle' and 'item') or string.lower(purchType)
  end
  return Icons.Icon(iconArgs)
end
function p.getPurchaseIcon(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local purchase = p.getPurchase(purchaseName)
  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    args[1] = purchase
    return p._getPurchaseIcon(args)
  end
end
function p._getPurchaseSortValue(purchase)
   local costCurrencies = {'gp', 'slayerCoins', 'raidCoins'}
   local costCurrencies = {'gp', 'slayerCoins', 'raidCoins'}
   for i, purchase in Shared.skpairs(Purchases) do
   for j, curr in ipairs(costCurrencies) do
     result = result..'\r\n|-\r\n|'
    local costAmt = purchase.cost[curr]
    if costAmt ~= nil and costAmt > 0 then
      return costAmt
    end
  end
end
 
function p._getShopTable(Purchases, options)
  local availableColumns = { 'Purchase', 'Type', 'Description', 'Cost', 'Requirements' }
  local headerPropsDefault = {
    ["Purchase"] = 'colspan="2"',
    ["Cost"] = 'style="min-width:100px"'
  }
  local usedColumns, purchHeader, sortOrder, headerProps = {}, 'Purchase', nil, {}
 
  -- Process options if specified
  if options ~= nil and type(options) == 'table' then
    -- Custom columns
    if options.columns ~= nil and type(options.columns) == 'table' then
      for i, column in ipairs(options.columns) do
        if Shared.contains(availableColumns, column) then
          table.insert(usedColumns, column)
        end
      end
    end
    -- Purchase column header text
     if options.purchaseHeader ~= nil and type(options.purchaseHeader) == 'string' then
      purchHeader = options.purchaseHeader
    end
    -- Custom sort order
    if options.sortOrder ~= nil and type(options.sortOrder) == 'function' then
      sortOrder = options.sortOrder
    end
    -- Header properties
    if options.headerProps ~= nil and type(options.headerProps) == 'table' then
      headerProps = options.headerProps
    end
  end
  -- Use default columns if no custom columns specified
  if Shared.tableCount(usedColumns) == 0 then
    usedColumns = availableColumns
  end
  if Shared.tableCount(headerProps) == 0 then
    headerProps = headerPropsDefault
  end


    local purchType = 'Item'
  -- Begin output generation
    if purchase.contains.pet ~= nil then
  local resultPart = {}
      purchType = 'Pet'
  -- Generate header
    elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
      purchType = 'Upgrade'
  table.insert(resultPart, '|- class="headerRow-0"')
     elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
  for i, column in ipairs(usedColumns) do
       purchType = 'Item Bundle'
    local prop = headerProps[column]
    table.insert(resultPart, '!' .. (prop and prop .. '| ' or ' ') .. (column == 'Purchase' and purchHeader or column))
  end
 
  local purchIterator = nil
  if sortOrder == nil then
    purchIterator = Shared.skpairs
  else
    table.sort(Purchases, sortOrder)
    purchIterator = ipairs
  end
  for i, purchase in purchIterator(Purchases) do
    local purchOverride = nil
     if purchOverrides ~= nil then
       purchOverride = purchOverrides[purchase.name]
     end
     end


     -- Translate certain media strings to icons, mostly for Golbin Raid shop objects
     local purchType = p._getPurchaseType(purchase)
     local iconOverrides = {
     local iconNoLink = nil
      ["assets/media/main/logo.svg"] = { icon = 'Melvor Logo', type = nil, noLink = true },
    local purchLink = ''
       ["assets/media/skills/prayer/prayer.svg"] = { icon = 'Prayer', type = 'skill', noLink = true }
    local costString = p.getCostString(purchase.cost, false)
    }
    if purchOverride ~= nil then
    local iconName, iconType, iconNoLink = purchase.name, (purchType == 'Item Bundle' and 'item' or string.lower(purchType)), nil
       if purchOverride.link == nil then
    if iconOverrides[purchase.media] ~= nil then
        iconNoLink = true
      iconName, iconType, iconNoLink = iconOverrides[purchase.media].icon, iconOverrides[purchase.media].type, iconOverrides[purchase.media].noLink
      else
        purchLink = purchOverride.link .. '|'
      end
      if purchOverride.cost ~= nil then costString = purchOverride.cost end
     end
     end


     local purchName = purchase.name
     local purchName = purchase.name
     if iconNoLink == nil or iconNoLink ~= true then purchName = '[[' .. purchName .. ']]' end
     if iconNoLink == nil or iconNoLink ~= true then purchName = '[[' .. purchLink .. purchName .. ']]' end
    result = result..'style="min-width:25px"|'..Icons.Icon({iconName, type=iconType, notext=true, nolink=iconNoLink, size='50'})
    result = result..'||'..purchName..'||'..purchType
    result = result..'||'..purchase.description..'||style="text-align:right;"'


     for j, curr in Shared.skpairs(costCurrencies) do
    table.insert(resultPart, '|-')
       local costAmt = purchase.cost[curr]
     for j, column in ipairs(usedColumns) do
       if costAmt ~= nil and costAmt > 0 then
      if column == 'Purchase' then
        result = result..' data-sort-value="'..costAmt..'"'
        table.insert(resultPart, '|style="min-width:25px"|' .. p._getPurchaseIcon({purchase, notext=true, size='50'}))
         break
        --table.insert(resultPart, '|style="min-width:25px"|' .. Icons.Icon({iconName, type=iconType, notext=true, nolink=iconNoLink, size='50'}))
        table.insert(resultPart, '| ' .. purchName)
      elseif column == 'Type' then
        table.insert(resultPart, '| ' .. purchType)
       elseif column == 'Description' then
        table.insert(resultPart, '| ' .. purchase.description)
       elseif column == 'Cost' then
        local cellProp = '|style="text-align:right;"'
        local sortValue = p._getPurchaseSortValue(purchase)
        if sortValue ~= nil then cellProp = cellProp .. ' data-sort-value="' .. sortValue .. '"' end
        table.insert(resultPart, cellProp .. '| ' .. costString)
      elseif column == 'Requirements' then
         table.insert(resultPart, '| ' .. p.getRequirementString(purchase.unlockRequirements))
      else
        -- Shouldn't be reached, but will prevent the resulting table becoming horribly mis-aligned if it ever happens
        table.insert(resultPart, '| ')
       end
       end
     end
     end
    result = result..'|'..p.getCostString(purchase.cost)..'||'..p.getRequirementString(purchase.unlockRequirements)
   end
   end
  table.insert(resultPart, '|}')


   result = result..'\r\n|}'
   return table.concat(resultPart, '\r\n')
  return result
end
end


-- getShopTable parameter definition:
--  columns:        Comma separated values indicating which columns are to be included & the order
--                  in which they are displayed.
--                  Values can be any of: Purchase, Type, Description, Cost, Requirements
--  columnProps:    Comma separated values indicating formatting to be applied to each column. Each
--                  value must be in the format column:property, e.g. Purchase:colspan="2"
--  sortOrder:      A function determining the order in which table items appear
--  purchaseHeader: Specifies header text for the Purchase column if not 'Purchase'
function p.getShopTable(frame)
function p.getShopTable(frame)
   local cat = frame.args ~= nil and frame.args[1] or frame
   local cat = frame.args ~= nil and frame.args[1] or frame
  local options = {}
  if frame.args ~= nil then
    if frame.args.columns ~= nil then options.columns = Shared.splitString(frame.args.columns, ',') end
    if frame.args.purchaseHeader ~= nil then options.purchaseHeader = frame.args.purchaseHeader end
    if frame.args.sortOrder ~= nil then options.sortOrder = frame.args.sortOrder end
    if frame.args.columnProps ~= nil then
      local columnPropValues = Shared.splitString(frame.args.columnProps, ',')
      local columnProps = {}
      for i, prop in pairs(columnPropValues) do
        local propName, propValue = string.match(prop, '^([^:]+):(.*)$')
        if propName ~= nil then
          columnProps[propName] = propValue
        end
      end
      if Shared.tableCount(columnProps) > 0 then options.headerProps = columnProps end
    end
  end
   local shopCat = ShopData.Shop[cat]
   local shopCat = ShopData.Shop[cat]
   if shopCat == nil then
   if shopCat == nil then
     return 'ERROR: Invalid category '..cat..'[[Category:Pages with script errors]]'
     return 'ERROR: Invalid category '..cat..'[[Category:Pages with script errors]]'
   else
   else
     return p._getShopTable(shopCat)
     return p._getShopTable(shopCat, options)
   end
   end
end
end
Line 196: Line 452:
   for category, purchaseArray in Shared.skpairs(ShopData.Shop) do
   for category, purchaseArray in Shared.skpairs(ShopData.Shop) do
     for i, purchase in Shared.skpairs(purchaseArray) do
     for i, purchase in Shared.skpairs(purchaseArray) do
       if checkFunc(purchase) then
       if checkFunc(category, purchase) then
         table.insert(purchaseList, p.processPurchase(category, i - 1))
         table.insert(purchaseList, p.processPurchase(category, i - 1))
       end
       end
Line 212: Line 468:


   result = result..'\r\n|-\r\n!style="text-align:right;"|Cost'
   result = result..'\r\n|-\r\n!style="text-align:right;"|Cost'
   result = result..'\r\n|'..p.getCostString(purchase.cost)
   result = result..'\r\n|'..p.getCostString(purchase.cost, false)


   result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements'
   result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements'
Line 218: Line 474:


   result = result..'\r\n|-\r\n!style="text-align:right;"|Contains'
   result = result..'\r\n|-\r\n!style="text-align:right;"|Contains'
  local containArray = {}
   result = result..'\r\n|style="text-align:right;"|'..p._getPurchaseContents(purchase, true)
  if purchase.contains.items ~= nil then
    for i, itemLine in Shared.skpairs(purchase.contains.items) do
      local item = Items.getItemByID(itemLine[1])
      table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemLine[2]}))
    end
  end
  if purchase.charges ~= nil and purchase.charges > 0 then
    table.insert(containArray, '+'..purchase.charges..' '..Icons.Icon({purchase.name, type='item'})..' Charges')
  end
   result = result..'\r\n|style="text-align:right;"|'..table.concat(containArray, '<br/>')


   result = result..'\r\n|}'
   result = result..'\r\n|}'
Line 253: Line 499:


   return p._getItemShopTable(item)
   return p._getItemShopTable(item)
end
function p.getShopMiscUpgradeTable()
  local purchList = p.getPurchases(function(cat, purch) return cat == 'General' and string.find(purch.name, '^Auto Eat') == nil end)
  return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
end
function p.getShopSkillcapeTable()
  local capeList = p.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
  local sortOrderFunc = function(a, b)
                      if a.cost.gp == b.cost.gp then
                        return a.name < b.name
                      else
                        return a.cost.gp < b.cost.gp
                      end
                    end
  return p._getShopTable(capeList,
    { columns = { 'Purchase', 'Description', 'Cost' },
      purchaseHeader = 'Cape',
      sortOrder = sortOrderFunc,
      headerProps = {["Purchase"] = 'colspan="2" style="width:200px;"', ["Cost"] = 'style=width:120px;'}
    })
end
function p.getAutoEatTable()
  local resultPart = {}
  local purchasesAE = p.getPurchases(function(cat, purch) return string.find(purch.name, '^Auto Eat') ~= nil end)
  -- Table header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '|- class="headerRow-0"')
  table.insert(resultPart, '!colspan="2"|Auto Eat Tier!!Minimum Threshold!!Efficiency!!Max Healing!!Cost')
  -- Rows for each Auto Eat tier
  local mods = {["increasedAutoEatEfficiency"] = 0, ["increasedAutoEatHPLimit"] = 0, ["increasedAutoEatThreshold"] = 0}
  for i, purchase in ipairs(purchasesAE) do
    -- Modifiers must be accumulated as we go
    for modName, modValue in pairs(mods) do
      if purchase.contains.modifiers[modName] ~= nil then
        mods[modName] = mods[modName] + purchase.contains.modifiers[modName]
      end
    end
    local costAmt = p._getPurchaseSortValue(purchase)
    table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. purchase.name .. '"| ' .. Icons.Icon({purchase.name, type='upgrade', size=50, notext=true}))
    table.insert(resultPart, '| ' .. Icons.Icon({purchase.name, type='upgrade', noicon=true}))
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatThreshold .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatThreshold, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatEfficiency .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatEfficiency, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatHPLimit .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatHPLimit, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. costAmt .. '" | ' .. Icons.GP(costAmt))
  end
  table.insert(resultPart, '|}')
  return table.concat(resultPart, '\r\n')
end
function p.getGodUpgradeTable()
  local resultPart = {}
  -- Obtain list of God upgrades: look for skill upgrades which have a dungeon completion
  --  requirement for an area whose name ends with 'God Dungeon'
  local getGodDungeon =
    function(reqs)
      if reqs.dungeonCompletion ~= nil then
        for i, areaReq in ipairs(reqs.dungeonCompletion) do
          local dung = AreaData['dungeons'][areaReq[1] + 1]
          if string.find(dung.name, 'God Dungeon$') ~= nil then return dung end
        end
      end
    end
  local upgradeList = p.getPurchases(
    function(cat, purch)
      if cat == 'SkillUpgrades' and purch.unlockRequirements ~= nil then
        return getGodDungeon(purch.unlockRequirements) ~= nil
      end
      return false
    end)
  if Shared.tableCount(upgradeList) == 0 then return '' end
  -- Table header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '|- class="headerRow-0"')
  table.insert(resultPart, '!colspan="2"|God Upgrade!!Effect!!Dungeon!!Cost')
  -- Rows for each God upgrade
  for i, upgrade in ipairs(upgradeList) do
    local dung = getGodDungeon(upgrade.unlockRequirements)
    local costSortValue = p._getPurchaseSortValue(upgrade)
    table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. upgrade.name .. '"| ' .. Icons.Icon({upgrade.name, type='upgrade', size=50, notext=true}))
    table.insert(resultPart, '| ' .. Icons.Icon({upgrade.name, type='upgrade', noicon=true}))
    table.insert(resultPart, '| ' .. upgrade.description)
    table.insert(resultPart, '| data-sort-value="' .. dung.name .. '"| ' .. Icons.Icon({dung.name, type='dungeon'}))
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. costSortValue .. '"| ' .. p.getCostString(upgrade.cost, false))
  end
  table.insert(resultPart, '|}')
  return table.concat(resultPart, '\r\n')
end
function p.getCookingUtilityTable(frame)
local category = nil
if frame ~= nil then category = frame.args ~= nil and frame.args[1] or frame end
local validCategories = {'Cooking Fire', 'Furnace', 'Pot'}
if category == nil or not Shared.contains({'Cooking Fire', 'Furnace', 'Pot'}, category) then
return 'ERROR: Invalid category specified. Must be one of the following: ' .. mw.text.listToText(validCategories, ', ', ' or ')
end
local categoryShort = string.match(category, '[^%s]+$')
local bonusSkillID = Constants.getSkillID('Cooking')
local bonusColMod, bonusColName = nil, nil
if category == 'Cooking Fire' then
bonusColMod = 'increasedSkillXP'
bonusColName = 'Bonus ' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' XP'
else
bonusColMod = 'increasedChanceToDoubleItemsSkill'
bonusColName = 'Double Items Chance'
end
local modsPerfectChance = {'increasedChancePerfectCookFire', 'increasedChancePerfectCookFurnace',
                      'increasedChancePerfectCookPot', 'increasedChancePerfectCookGlobal'}
local totalBonusVal, totalPerfectChance = 0, 0
local utilityList = p.getPurchases(function(cat, purch) return cat == 'SkillUpgrades' and string.find(purch.name, category .. '$') ~= nil end)
local resultPart = {}
    -- Table header
    table.insert(resultPart, '{| class="wikitable stickyHeader"')
    table.insert(resultPart, '|- class="headerRow-0"')
    table.insert(resultPart, '!colspan="4"| !!colspan="2"|' .. bonusColName .. '!!colspan="2"|Bonus Perfect Chance')
    table.insert(resultPart, '|- class="headerRow-1"')
    table.insert(resultPart, '!colspan="2"|Name!!Level!!Cost' .. string.rep('!!This ' .. categoryShort .. '!!Total', 2))
   
    -- Row for each upgrade
    for i, utility in ipairs(utilityList) do
    -- First determine bonus XP/doubling chance and perfect chance
    local bonusVal, perfectChance = 0, 0
    if type(utility.contains) == 'table' then
    if type(utility.contains.modifiers) == 'table' then
    for modName, modVal in pairs(utility.contains.modifiers) do
    if modName == bonusColMod and type(modVal) == 'table' then
    -- Bonus XP/doubling
    for skID, skVal in pairs(modVal) do
    if skVal[1] == bonusSkillID then bonusVal = bonusVal + skVal[2] end
    end
    elseif Shared.contains(modsPerfectChance, modName) then
    -- Perfect chance
    perfectChance = perfectChance + modVal
    end
    end
    end
    end
    totalBonusVal = totalBonusVal + bonusVal
    totalPerfectChance = totalPerfectChance + perfectChance
   
    -- Mangle unlockRequirements so that it only includes skillLevels
    local unlockReqs = {}
    if type(utility.unlockRequirements) == 'table' then
    unlockReqs['skillLevel'] = utility.unlockRequirements.skillLevel
    end
   
    table.insert(resultPart, '|-')
    table.insert(resultPart, '|style="min-width:25px"|' .. Icons.Icon({utility.name, type='upgrade', size='50', notext=true}))
    table.insert(resultPart, '|' .. utility.name)
    table.insert(resultPart, '|style="text-align:right"|' .. p.getRequirementString(unlockReqs))
    table.insert(resultPart, '|style="text-align:right"|' .. p.getCostString(utility.cost, false))
    table.insert(resultPart, '|style="text-align:right"|' .. '+' .. bonusVal .. '%')
    table.insert(resultPart, '|style="text-align:right"|' .. '+' .. totalBonusVal .. '%')
    table.insert(resultPart, '|style="text-align:right"|' .. '+' .. perfectChance .. '%')
    table.insert(resultPart, '|style="text-align:right"|' .. '+' .. totalPerfectChance .. '%')
    end
    table.insert(resultPart, '|}')
return table.concat(resultPart, '\r\n')
end
end


return p
return p

Revision as of 00:37, 17 November 2021

Documentation for this module may be created at Module:Shop/doc

local p = {}

local ShopData = mw.loadData('Module:Shop/data')
-- Data instead of Module:CombatAreas to avoid loop whne that module attempts to require Module:Shop
local AreaData = require('Module:CombatAreas/data')

local Shared = require('Module:Shared')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Constants = require('Module:Constants')

-- Overrides for various items, mostly relating to icon overrides
local purchOverrides = {
  ["Extra Bank Slot"] = { icon = {'Bank Slot', 'upgrade'}, link = 'Bank Slot', cost = Icons.Icon({'Coins', size = 25, notext = true}) .. ' <span style="font-size:127%; font-family: MathJax_Math; font-style: italic;">C<sub>b</sub></span>*' },
  -- Golbin Raid items
  ["Reduce Wave Skip Cost"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Food Bonus"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Ammo Gatherer"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Rune Pouch"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Increase Starting Prayer Points"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Unlock Combat Passive Slot"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Prayer"] = { icon = {'Prayer', 'skill'}, link = nil },
  ["Increase Prayer Level"] = { icon = {'Prayer', 'skill'}, link = nil },
  ["Increase Prayer Points gained per Wave Completion"] = { icon = {'Prayer', 'skill'}, link = nil }
}

function p.getPurchase(purchaseName)
  for categoryName, categoryData in pairs(ShopData.Shop) do
    for i, purchase in ipairs(categoryData) do
      if purchase.name == purchaseName then
        return p.processPurchase(categoryName, i - 1)
      end
    end
  end
end

function p.processPurchase(category, purchaseID)
  local purchase = Shared.clone(ShopData.Shop[category][purchaseID + 1])
  purchase.id = purchaseID
  purchase.category = category
  return purchase
end

function p._getPurchaseStat(purchase, stat, inline)
  local displayInline = (inline ~= nil and inline or false)
  if stat == 'cost' then
    return p.getCostString(purchase.cost, displayInline)
  elseif stat == 'requirements' then
    return p.getRequirementString(purchase.unlockRequirements)
  elseif stat == 'contents' then
    return p._getPurchaseContents(purchase, true)
  elseif stat == 'type' then
    return p._getPurchaseType(purchase)
  else
    return purchase[stat]
  end
end

function p.getPurchaseStat(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local statName = args[2]
  local displayInline = (args['inline'] ~= nil and string.lower(args['inline']) == 'true' or false)
  -- Hack for some purchases existing twice with varying costs (e.g. 'Extra Equipment Set')
  local purchaseList = {}
  if statName == 'cost' then
    purchaseList = p.getPurchases(function(cat, purch) return purch.name == purchaseName end)
  else
    purchaseList = {p.getPurchase(purchaseName)}
  end

  if Shared.tableCount(purchaseList) == 0 then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    local resultPart = {}
    for i, purchase in ipairs(purchaseList) do
      table.insert(resultPart, p._getPurchaseStat(purchase, statName, displayInline))
    end
    return table.concat(resultPart, ' or ')
  end
end

function p.getCostString(cost, inline)
  local displayInline = (inline ~= nil and inline or false)
  local costArray = {}
  if cost.gp ~= nil and cost.gp > 0 then
    table.insert(costArray, Icons.GP(cost.gp))
  end
  if cost.slayerCoins ~= nil and cost.slayerCoins > 0 then
    table.insert(costArray, Icons.SC(cost.slayerCoins))
  end
  if cost.raidCoins ~= nil and cost.raidCoins > 0 then
    table.insert(costArray, Icons.RC(cost.raidCoins))
  end
  local itemArray = {}
  if cost.items ~= nil then
    for i, itemCost in Shared.skpairs(cost.items) do
      local item = Items.getItemByID(itemCost[1])
      table.insert(itemArray, Icons.Icon({item.name, type="item", notext=(not displayInline and true or nil), qty=itemCost[2]}))
    end

    if Shared.tableCount(itemArray) > 0 then
      table.insert(costArray, table.concat(itemArray, ", "))
    end
  end

  local sep, lastSep = '<br/>', nil
  if displayInline then
    sep = ', '
    lastSep = Shared.tableCount(costArray) > 2 and ', and ' or ' and '
  end
  return Shared.joinList(costArray, sep, lastSep)
end

function p.getRequirementString(reqs)
  if reqs == nil or Shared.tableCount(reqs) == 0 then
    return "None"
  end

  local reqArray = {}
  if reqs.slayerTaskCompletion ~= nil then
    for i, taskReq in Shared.skpairs(reqs.slayerTaskCompletion) do
      local tierName = Constants.getSlayerTierName(taskReq[1])
      table.insert(reqArray, 'Complete '..taskReq[2]..' '..tierName..' Slayer Tasks')
    end
  end

  if reqs.dungeonCompletion ~= nil then
    for i, dungReq in Shared.skpairs(reqs.dungeonCompletion) do
      local dung = AreaData['dungeons'][dungReq[1] + 1]
      local dungStr = 'Complete '..Icons.Icon({dung.name, type='dungeon'})
      if dungReq[2] > 1 then
        dungStr = dungStr..' '..dungReq[2]..' times'
      end
      table.insert(reqArray, dungStr)
    end
  end

  if reqs.skillLevel ~= nil then
    for i, skillReq in Shared.skpairs(reqs.skillLevel) do
      local skillName = Constants.getSkillName(skillReq[1])
      table.insert(reqArray, Icons._SkillReq(skillName, skillReq[2]))
    end
  end

  if reqs.shopItemPurchased ~= nil then
    for i, shopReq in Shared.skpairs(reqs.shopItemPurchased) do
      local purchase = ShopData.Shop[shopReq[1]][shopReq[2] + 1]
      local isUpgrade = purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0
      table.insert(reqArray, Icons.Icon({purchase.name, type=(isUpgrade and 'upgrade' or 'item')})..' Purchased')
    end
  end

  if reqs.completionPercentage ~= nil then
    table.insert(reqArray, tostring(reqs.completionPercentage) .. '% Completion Log')
  end

  if reqs.text ~= nil then
    table.insert(reqArray, reqs.text)
  end

  return table.concat(reqArray, '<br/>')
end

function p._getPurchaseType(purchase)
  if purchase.contains == nil then
    return 'Unknown'
  elseif purchase.contains.pet ~= nil then
    return 'Pet'
  elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
    return 'Upgrade'
  elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
    return 'Item Bundle'
  else
    return 'Item'
  end
end

function p._getPurchaseContents(purchase, asList)
  if asList == nil then asList = true end
  local containArray = {}
  if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0 then
    if not asList then
      table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
      table.insert(containArray, '|- class="headerRow-0"')
      table.insert(containArray, '! colspan="2" | Item !! Quantity')
    end
    for i, itemLine in Shared.skpairs(purchase.contains.items) do
      local item = Items.getItemByID(itemLine[1])
      if asList then
        table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemLine[2]}))
      else
        table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size='25'}))
        table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. '\r\n| data-sort-value="' .. itemLine[2] .. '" style="text-align:right" | ' .. Shared.formatnum(itemLine[2]))
      end
    end
  end
  if purchase.charges ~= nil and purchase.charges > 0 then
    if asList then
      table.insert(containArray, '+'..purchase.charges..' '..Icons.Icon({purchase.name, type='item'})..' Charges')
    else
      table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({purchase.name, type='item', notext=true, size='25'}))
      table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. purchase.charges .. '" style="text-align:right" | ' .. Shared.formatnum(purchase.charges))
    end
  end
  if not asList and Shared.tableCount(containArray) > 0 then table.insert(containArray, '|}') end

  local delim = (asList and '<br/>' or '\r\n')
  return table.concat(containArray, delim)
end

function p.getPurchaseContents(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local asList = ((args[2] ~= nil and args[2] == 'true') or false)
  local purchase = p.getPurchase(purchaseName)

  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    return p._getPurchaseContents(purchase, asList)
  end
end

-- Accept similar arguments to Icons.Icon
function p._getPurchaseIcon(iconArgs)
  local purchase = iconArgs[1]
  local override = purchOverrides[purchase.name]
  local purchType = p._getPurchaseType(purchase)
  -- Amend iconArgs before passing to Icons.Icon()
  iconArgs[1] = ((override ~= nil and override.icon[1]) or purchase.name)
  if override ~= nil then
    iconArgs['type'] = override.icon[2]
    if override.link == nil then
      iconArgs['nolink'] = true
    end
  else
    iconArgs['type'] = (purchType == 'Item Bundle' and 'item') or string.lower(purchType)
  end

  return Icons.Icon(iconArgs)
end

function p.getPurchaseIcon(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local purchase = p.getPurchase(purchaseName)

  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    args[1] = purchase
    return p._getPurchaseIcon(args)
  end
end

function p._getPurchaseSortValue(purchase)
  local costCurrencies = {'gp', 'slayerCoins', 'raidCoins'}
  for j, curr in ipairs(costCurrencies) do
    local costAmt = purchase.cost[curr]
    if costAmt ~= nil and costAmt > 0 then
      return costAmt
    end
  end
end

function p._getShopTable(Purchases, options)
  local availableColumns = { 'Purchase', 'Type', 'Description', 'Cost', 'Requirements' }
  local headerPropsDefault = {
    ["Purchase"] = 'colspan="2"',
    ["Cost"] = 'style="min-width:100px"'
  }
  local usedColumns, purchHeader, sortOrder, headerProps = {}, 'Purchase', nil, {}

  -- Process options if specified
  if options ~= nil and type(options) == 'table' then
    -- Custom columns
    if options.columns ~= nil and type(options.columns) == 'table' then
      for i, column in ipairs(options.columns) do
        if Shared.contains(availableColumns, column) then
          table.insert(usedColumns, column)
        end
      end
    end
    -- Purchase column header text
    if options.purchaseHeader ~= nil and type(options.purchaseHeader) == 'string' then
      purchHeader = options.purchaseHeader
    end
    -- Custom sort order
    if options.sortOrder ~= nil and type(options.sortOrder) == 'function' then
      sortOrder = options.sortOrder
    end
    -- Header properties
    if options.headerProps ~= nil and type(options.headerProps) == 'table' then
      headerProps = options.headerProps
    end
  end
  -- Use default columns if no custom columns specified
  if Shared.tableCount(usedColumns) == 0 then
    usedColumns = availableColumns
  end
  if Shared.tableCount(headerProps) == 0 then
    headerProps = headerPropsDefault
  end

  -- Begin output generation
  local resultPart = {}
  -- Generate header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '|- class="headerRow-0"')
  for i, column in ipairs(usedColumns) do
    local prop = headerProps[column]
    table.insert(resultPart, '!' .. (prop and prop .. '| ' or ' ') .. (column == 'Purchase' and purchHeader or column))
  end

  local purchIterator = nil
  if sortOrder == nil then
    purchIterator = Shared.skpairs
  else
    table.sort(Purchases, sortOrder)
    purchIterator = ipairs
  end
  for i, purchase in purchIterator(Purchases) do
    local purchOverride = nil
    if purchOverrides ~= nil then
      purchOverride = purchOverrides[purchase.name]
    end

    local purchType = p._getPurchaseType(purchase)
    local iconNoLink = nil
    local purchLink = ''
    local costString = p.getCostString(purchase.cost, false)
    if purchOverride ~= nil then
      if purchOverride.link == nil then
        iconNoLink = true
      else
        purchLink = purchOverride.link .. '|'
      end
      if purchOverride.cost ~= nil then costString = purchOverride.cost end
    end

    local purchName = purchase.name
    if iconNoLink == nil or iconNoLink ~= true then purchName = '[[' .. purchLink .. purchName .. ']]' end

    table.insert(resultPart, '|-')
    for j, column in ipairs(usedColumns) do
      if column == 'Purchase' then
        table.insert(resultPart, '|style="min-width:25px"|' .. p._getPurchaseIcon({purchase, notext=true, size='50'}))
        --table.insert(resultPart, '|style="min-width:25px"|' .. Icons.Icon({iconName, type=iconType, notext=true, nolink=iconNoLink, size='50'}))
        table.insert(resultPart, '| ' .. purchName)
      elseif column == 'Type' then
        table.insert(resultPart, '| ' .. purchType)
      elseif column == 'Description' then
        table.insert(resultPart, '| ' .. purchase.description)
      elseif column == 'Cost' then
        local cellProp = '|style="text-align:right;"'
        local sortValue = p._getPurchaseSortValue(purchase)
        if sortValue ~= nil then cellProp = cellProp .. ' data-sort-value="' .. sortValue .. '"' end
        table.insert(resultPart, cellProp .. '| ' .. costString)
      elseif column == 'Requirements' then
        table.insert(resultPart, '| ' .. p.getRequirementString(purchase.unlockRequirements))
      else
        -- Shouldn't be reached, but will prevent the resulting table becoming horribly mis-aligned if it ever happens
        table.insert(resultPart, '| ')
      end
    end
  end
  table.insert(resultPart, '|}')

  return table.concat(resultPart, '\r\n')
end

-- getShopTable parameter definition:
--   columns:        Comma separated values indicating which columns are to be included & the order
--                   in which they are displayed.
--                   Values can be any of: Purchase, Type, Description, Cost, Requirements
--   columnProps:    Comma separated values indicating formatting to be applied to each column. Each
--                   value must be in the format column:property, e.g. Purchase:colspan="2"
--   sortOrder:      A function determining the order in which table items appear
--   purchaseHeader: Specifies header text for the Purchase column if not 'Purchase'
function p.getShopTable(frame)
  local cat = frame.args ~= nil and frame.args[1] or frame
  local options = {}
  if frame.args ~= nil then
    if frame.args.columns ~= nil then options.columns = Shared.splitString(frame.args.columns, ',') end
    if frame.args.purchaseHeader ~= nil then options.purchaseHeader = frame.args.purchaseHeader end
    if frame.args.sortOrder ~= nil then options.sortOrder = frame.args.sortOrder end
    if frame.args.columnProps ~= nil then
      local columnPropValues = Shared.splitString(frame.args.columnProps, ',')
      local columnProps = {}
      for i, prop in pairs(columnPropValues) do
        local propName, propValue = string.match(prop, '^([^:]+):(.*)$')
        if propName ~= nil then
          columnProps[propName] = propValue
        end
      end
      if Shared.tableCount(columnProps) > 0 then options.headerProps = columnProps end
    end
  end
  local shopCat = ShopData.Shop[cat]
  if shopCat == nil then
    return 'ERROR: Invalid category '..cat..'[[Category:Pages with script errors]]'
  else
    return p._getShopTable(shopCat, options)
  end
end

function p.getItemCostArray(itemID)
  local purchaseArray = {}

  for catName, cat in Shared.skpairs(ShopData.Shop) do
    for j, purchase in Shared.skpairs(cat) do
      if purchase.cost.items ~= nil then
        for k, costLine in Shared.skpairs(purchase.cost.items) do
          if costLine[1] == itemID then
            local temp = p.processPurchase(catName, j - 1)
            temp.qty = costLine[2]
            table.insert(purchaseArray, temp)
            break
          end
        end
      end
    end
  end

  return purchaseArray
end

function p.getItemSourceArray(itemID)
  local purchaseArray = {}

  for catName, cat in Shared.skpairs(ShopData.Shop) do
    for j, purchase in Shared.skpairs(cat) do
      if purchase.contains.items ~= nil and purchase.contains.items ~= nil then
        for k, containsLine in Shared.skpairs(purchase.contains.items) do
          if containsLine [1] == itemID then
            local temp = p.processPurchase(catName, j - 1)
            temp.qty = containsLine[2]
            table.insert(purchaseArray, temp)
            break
          end
        end
      end
    end
  end

  return purchaseArray
end

function p.getPurchases(checkFunc)
  local purchaseList = {}
  for category, purchaseArray in Shared.skpairs(ShopData.Shop) do
    for i, purchase in Shared.skpairs(purchaseArray) do
      if checkFunc(category, purchase) then
        table.insert(purchaseList, p.processPurchase(category, i - 1))
      end
    end
  end
  return purchaseList
end

function p._getPurchaseTable(purchase)
  local result = '{| class="wikitable"\r\n|-'
  result = result..'\r\n!colspan="2"|'..Icons.Icon({'Shop'})..' Purchase'
  if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
    result = result..' - '..Icons.Icon({purchase.name, type='item'})
  end

  result = result..'\r\n|-\r\n!style="text-align:right;"|Cost'
  result = result..'\r\n|'..p.getCostString(purchase.cost, false)

  result = result..'\r\n|-\r\n!style="text-align:right;"|Requirements'
  result = result..'\r\n|'..p.getRequirementString(purchase.unlockRequirements)

  result = result..'\r\n|-\r\n!style="text-align:right;"|Contains'
  result = result..'\r\n|style="text-align:right;"|'..p._getPurchaseContents(purchase, true)

  result = result..'\r\n|}'
  return result
end

function p._getItemShopTable(item)
  local tableArray = {}
  local purchaseArray = p.getItemSourceArray(item.id)

  for i, purchase in Shared.skpairs(purchaseArray) do
    table.insert(tableArray, p._getPurchaseTable(purchase))
  end

  return table.concat(tableArray, '\r\n\r\n')
end

function p.getItemShopTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
  if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
  end

  return p._getItemShopTable(item)
end

function p.getShopMiscUpgradeTable()
  local purchList = p.getPurchases(function(cat, purch) return cat == 'General' and string.find(purch.name, '^Auto Eat') == nil end)
  return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
end

function p.getShopSkillcapeTable()
  local capeList = p.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
  local sortOrderFunc = function(a, b)
                      if a.cost.gp == b.cost.gp then
                        return a.name < b.name
                      else
                        return a.cost.gp < b.cost.gp
                      end
                    end
  return p._getShopTable(capeList,
    { columns = { 'Purchase', 'Description', 'Cost' },
      purchaseHeader = 'Cape',
      sortOrder = sortOrderFunc,
      headerProps = {["Purchase"] = 'colspan="2" style="width:200px;"', ["Cost"] = 'style=width:120px;'}
    })
end

function p.getAutoEatTable()
  local resultPart = {}
  local purchasesAE = p.getPurchases(function(cat, purch) return string.find(purch.name, '^Auto Eat') ~= nil end)

  -- Table header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '|- class="headerRow-0"')
  table.insert(resultPart, '!colspan="2"|Auto Eat Tier!!Minimum Threshold!!Efficiency!!Max Healing!!Cost')
  -- Rows for each Auto Eat tier
  local mods = {["increasedAutoEatEfficiency"] = 0, ["increasedAutoEatHPLimit"] = 0, ["increasedAutoEatThreshold"] = 0}
  for i, purchase in ipairs(purchasesAE) do
    -- Modifiers must be accumulated as we go
    for modName, modValue in pairs(mods) do
      if purchase.contains.modifiers[modName] ~= nil then
        mods[modName] = mods[modName] + purchase.contains.modifiers[modName]
      end
    end

    local costAmt = p._getPurchaseSortValue(purchase)
    table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. purchase.name .. '"| ' .. Icons.Icon({purchase.name, type='upgrade', size=50, notext=true}))
    table.insert(resultPart, '| ' .. Icons.Icon({purchase.name, type='upgrade', noicon=true}))
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatThreshold .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatThreshold, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatEfficiency .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatEfficiency, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. mods.increasedAutoEatHPLimit .. '" | ' .. Shared.formatnum(Shared.round(mods.increasedAutoEatHPLimit, 0, 0)) .. '%')
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. costAmt .. '" | ' .. Icons.GP(costAmt))
  end
  table.insert(resultPart, '|}')

  return table.concat(resultPart, '\r\n')
end

function p.getGodUpgradeTable()
  local resultPart = {}
  -- Obtain list of God upgrades: look for skill upgrades which have a dungeon completion
  --   requirement for an area whose name ends with 'God Dungeon'
  local getGodDungeon =
    function(reqs)
      if reqs.dungeonCompletion ~= nil then
        for i, areaReq in ipairs(reqs.dungeonCompletion) do
          local dung = AreaData['dungeons'][areaReq[1] + 1]
          if string.find(dung.name, 'God Dungeon$') ~= nil then return dung end
        end
      end
    end

  local upgradeList = p.getPurchases(
    function(cat, purch)
      if cat == 'SkillUpgrades' and purch.unlockRequirements ~= nil then
        return getGodDungeon(purch.unlockRequirements) ~= nil
      end
      return false
    end)
  if Shared.tableCount(upgradeList) == 0 then return '' end

  -- Table header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
  table.insert(resultPart, '|- class="headerRow-0"')
  table.insert(resultPart, '!colspan="2"|God Upgrade!!Effect!!Dungeon!!Cost')

  -- Rows for each God upgrade
  for i, upgrade in ipairs(upgradeList) do
    local dung = getGodDungeon(upgrade.unlockRequirements)
    local costSortValue = p._getPurchaseSortValue(upgrade)
    table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. upgrade.name .. '"| ' .. Icons.Icon({upgrade.name, type='upgrade', size=50, notext=true}))
    table.insert(resultPart, '| ' .. Icons.Icon({upgrade.name, type='upgrade', noicon=true}))
    table.insert(resultPart, '| ' .. upgrade.description)
    table.insert(resultPart, '| data-sort-value="' .. dung.name .. '"| ' .. Icons.Icon({dung.name, type='dungeon'}))
    table.insert(resultPart, '| style="text-align:right;" data-sort-value="' .. costSortValue .. '"| ' .. p.getCostString(upgrade.cost, false))
  end
  table.insert(resultPart, '|}')

  return table.concat(resultPart, '\r\n')
end

function p.getCookingUtilityTable(frame)
	local category = nil
	if frame ~= nil then category = frame.args ~= nil and frame.args[1] or frame end
	local validCategories = {'Cooking Fire', 'Furnace', 'Pot'}
	if category == nil or not Shared.contains({'Cooking Fire', 'Furnace', 'Pot'}, category) then
		return 'ERROR: Invalid category specified. Must be one of the following: ' .. mw.text.listToText(validCategories, ', ', ' or ')
	end
	
	local categoryShort = string.match(category, '[^%s]+$')
	local bonusSkillID = Constants.getSkillID('Cooking')
	local bonusColMod, bonusColName = nil, nil
	if category == 'Cooking Fire' then
		bonusColMod = 'increasedSkillXP'
		bonusColName = 'Bonus ' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' XP'
	else
		bonusColMod = 'increasedChanceToDoubleItemsSkill'
		bonusColName = 'Double Items Chance'
	end
	local modsPerfectChance = {'increasedChancePerfectCookFire', 'increasedChancePerfectCookFurnace',
		                       'increasedChancePerfectCookPot', 'increasedChancePerfectCookGlobal'}
	local totalBonusVal, totalPerfectChance = 0, 0
	local utilityList = p.getPurchases(function(cat, purch) return cat == 'SkillUpgrades' and string.find(purch.name, category .. '$') ~= nil end)
	local resultPart = {}
	
    -- Table header
    table.insert(resultPart, '{| class="wikitable stickyHeader"')
    table.insert(resultPart, '|- class="headerRow-0"')
    table.insert(resultPart, '!colspan="4"| !!colspan="2"|' .. bonusColName .. '!!colspan="2"|Bonus Perfect Chance')
    table.insert(resultPart, '|- class="headerRow-1"')
    table.insert(resultPart, '!colspan="2"|Name!!Level!!Cost' .. string.rep('!!This ' .. categoryShort .. '!!Total', 2))
    
    -- Row for each upgrade
    for i, utility in ipairs(utilityList) do
    	-- First determine bonus XP/doubling chance and perfect chance
    	local bonusVal, perfectChance = 0, 0
    	if type(utility.contains) == 'table' then
	    	if type(utility.contains.modifiers) == 'table' then
	    		for modName, modVal in pairs(utility.contains.modifiers) do
	    			if modName == bonusColMod and type(modVal) == 'table' then
	    				-- Bonus XP/doubling
	    				for skID, skVal in pairs(modVal) do
	    					if skVal[1] == bonusSkillID then bonusVal = bonusVal + skVal[2] end
	    				end
	    			elseif Shared.contains(modsPerfectChance, modName) then
	    				-- Perfect chance
	    				perfectChance = perfectChance + modVal
	    			end
	    		end
	    	end
	    end
    	totalBonusVal = totalBonusVal + bonusVal
    	totalPerfectChance = totalPerfectChance + perfectChance
    	
    	-- Mangle unlockRequirements so that it only includes skillLevels
    	local unlockReqs = {}
    	if type(utility.unlockRequirements) == 'table' then
    		unlockReqs['skillLevel'] = utility.unlockRequirements.skillLevel
    	end
    	
    	table.insert(resultPart, '|-')
    	table.insert(resultPart, '|style="min-width:25px"|' .. Icons.Icon({utility.name, type='upgrade', size='50', notext=true}))
    	table.insert(resultPart, '|' .. utility.name)
    	table.insert(resultPart, '|style="text-align:right"|' .. p.getRequirementString(unlockReqs))
    	table.insert(resultPart, '|style="text-align:right"|' .. p.getCostString(utility.cost, false))
    	table.insert(resultPart, '|style="text-align:right"|' .. '+' .. bonusVal .. '%')
    	table.insert(resultPart, '|style="text-align:right"|' .. '+' .. totalBonusVal .. '%')
    	table.insert(resultPart, '|style="text-align:right"|' .. '+' .. perfectChance .. '%')
    	table.insert(resultPart, '|style="text-align:right"|' .. '+' .. totalPerfectChance .. '%')
    end
    table.insert(resultPart, '|}')
	
	return table.concat(resultPart, '\r\n')
end

return p