Module:Shop: Difference between revisions

From Melvor Idle
(Tinkered with p.getItemShopTable to match new data formatting (and moved here from Items/SourceTables)
m (Fix typo)
(38 intermediate revisions by 3 users not shown)
Line 2: Line 2:


local ShopData = mw.loadData('Module:Shop/data')
local ShopData = mw.loadData('Module:Shop/data')
local ConstantData = mw.loadData('Module:Constants/data')
-- Data instead of Module:CombatAreas to avoid loop when 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 10:
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')


--Cape of Completion is a special purchase that has to be spoofed since it's not in the code
-- Overrides for various items, mostly relating to icon overrides
function p.getCapeOfCompletionPurchase()
local purchOverrides = {
  local item = Items.getItem('Cape of Completion')
["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 },
["Faster Golbin Spawns"] = { icon = {'Timer', nil}, link = nil, incCost = true},
["Golbin Crate"] = { icon = {'Golbin Crate', 'upgrade'}, link = nil, incCost = true}
}


  local purchase = { category = 'Skillcape', id = -1}
function p.getPurchase(purchaseName)
  purchase.name = 'Cape of Completion'
for categoryName, categoryData in pairs(ShopData.Shop) do
  purchase.description = item.description
for i, purchase in ipairs(categoryData) do
  purchase.cost = {gp = item.buysFor}
if purchase.name == purchaseName then
  purchase.unlockRequirements = {text = '100% Completion Log', items = {}}
return p.processPurchase(categoryName, i - 1)
  purchase.contains = {items = {{item.id, 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


  return purchase
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)
elseif stat == 'buyLimit' then
return p._getPurchaseBuyLimit(purchase, not displayInline)
else
return purchase[stat]
end
end
end


function p.processPurchase(category, purchaseID)
function p.getPurchaseStat(frame)
  local purchase = Shared.clone(ShopData.Shop[category][purchaseID + 1])
local args = frame.args ~= nil and frame.args or frame
  purchase.id = purchaseID
local purchaseName = args[1]
  purchase.category = category
local statName = args[2]
  return purchase
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
end


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


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


function p.getRequirementString(reqs)
function p.getRequirementString(reqs)
  if reqs == nil or Shared.tableCount(reqs) == 0 then
if reqs == nil or Shared.tableCount(reqs) == 0 then
    return "None"
return "None"
  end
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


  local reqArray = {}
function p._getPurchaseContents(purchase, asList)
  if reqs.slayerTaskCompletion ~= nil then
if asList == nil then asList = true end
    for i, taskReq in Shared.skpairs(reqs.slayerTaskCompletion) do
local containArray = {}
      local tierName = Constants.getSlayerTierName(taskReq[1])
local GPTotal = 0
      table.insert(reqArray, 'Complete '..taskReq[2]..' '..tierName..' Slayer Tasks')
if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0 then
    end
if not asList then
  end
table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
table.insert(containArray, '|- class="headerRow-0"')
table.insert(containArray, '! colspan="2" | Item !! Quantity !! Price')
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
local GPVal = item.sellsFor * itemLine[2]
GPTotal = GPTotal + GPVal
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]))
table.insert(containArray, '| data-sort-value="' .. GPVal .. '"| ' .. Icons.GP(GPVal))
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({purchase.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. purchase.charges .. '" style="text-align:right" | ' .. Shared.formatnum(purchase.charges))
table.insert(containArray, '| data-sort-value="0"| ' .. Icons.GP(0))
end
end
if not asList and Shared.tableCount(containArray) > 0 then
table.insert(containArray, '|- class="sortbottom"\r\n! colspan="3"| Total\r\n| ' .. Icons.GP(GPTotal) .. '\r\n|}')
end


  if reqs.dungeonCompletion ~= nil then
local delim = (asList and '<br/>' or '\r\n')
    for i, dungReq in Shared.skpairs(reqs.dungeonCompletion) do
return table.concat(containArray, delim)
      local dung = Areas.getAreaByID('dungeon', dungReq[1])
end
      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
function p.getPurchaseContents(frame)
    for i, skillReq in Shared.skpairs(reqs.skillLevel) do
local args = frame.args ~= nil and frame.args or frame
      local skillName = Constants.getSkillName(skillReq[1])
local purchaseName = args[1]
      table.insert(reqArray, Icons._SkillReq(skillName, skillReq[2]))
local asList = (args[2] ~= nil and string.upper(args[2]) == 'TRUE')
    end
local purchase = p.getPurchase(purchaseName)
  end
 
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
 
function p._getPurchaseBuyLimit(purchase, asList)
if asList == nil then asList = true end
if type(purchase.buyLimit) == 'table' then
local limitTable = {}
local gamemodeHasIcon = { 1, 2 }
-- Populate limitTable for each game mode to be included
for id, modeName in pairs(ConstantData.gamemode) do
if tonumber(id) ~= nil and string.upper(modeName) ~= 'CHAOS' then
local buyLimit = tostring(purchase.buyLimit[id + 1])
if limitTable[buyLimit] == nil then
limitTable[buyLimit] = {}
end
local gamemodeText = '[[Game Mode#' .. modeName .. '|' .. modeName .. ']]'
if Shared.contains(gamemodeHasIcon, id) then
gamemodeText = Icons.Icon({modeName, notext=(not asList or nil)})
end
table.insert(limitTable[buyLimit], gamemodeText)
end
end
local numLimits = Shared.tableCount(limitTable)
local resultPart = {}
for buyLimit, gameModes in Shared.skpairs(limitTable, true) do
local limitText = (buyLimit == '0' and 'Unlimited' or tostring(buyLimit))
if numLimits == 1 then
-- Buy limit is the same for all game modes
return limitText
else
table.insert(resultPart, limitText .. (asList and ' for ' or ' ') .. mw.text.listToText(gameModes, ', ', (asList and ' and ' or ', ')))
end
end
return table.concat(resultPart, (asList and ' or ' or '<br/>'))
end
end
 
function p.getPurchaseBuyLimit(frame)
local args = frame.args ~= nil and frame.args or frame
local purchaseName = args[1]
local asList = (args[2] ~= nil and string.upper(args[2]) == 'TRUE')
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._getPurchaseBuyLimit(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


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


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


  return table.concat(reqArray, '<br/>')
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
end


function p._getShopTable(Purchases)
function p._getShopTable(Purchases, options)
  local result = '{| class="wikitable sortable stickyHeader"'
local availableColumns = { 'Purchase', 'Type', 'Description', 'Cost', 'Requirements', 'Buy Limit' }
  result = result..'\r\n|- class="headerRow-0"'
local headerPropsDefault = {
  result = result..'\r\n!colspan="2"|Purchase!!Type!!Description!!style="min-width:100"|Cost!!Requirements'
["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


  for i, purchase in Shared.skpairs(Purchases) do
local purchIterator = nil
    result = result..'\r\n|-\r\n|'
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 isUpgrade = purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0
local purchType = p._getPurchaseType(purchase)
    local isBundle = purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1
local iconNoLink = nil
    local iconTxt = purchase.name
local purchLink = ''
    result = result..Icons.Icon({purchase.name, type=(isUpgrade and 'upgrade' or 'item'), notext=true, size='50'})
local costString = p.getCostString(purchase.cost, false)
    result = result..'||'..purchase.name..'||'
if purchOverride ~= nil then
    if isUpgrade then
if purchOverride.link == nil then
      result = result..'Upgrade'
iconNoLink = true
    elseif isBundle then
else
      result = result..'Item Bundle'
purchLink = purchOverride.link .. '|'
    else
end
      result = result..'Item'
if purchOverride.cost ~= nil then costString = purchOverride.cost end
    end
if purchOverride.incCost then
costString = costString .. '<br/>+' .. costString .. ' for each purchase'
end
end


    result = result..'||'..purchase.description..'||style="text-align:right;"'
local purchName = purchase.name
    if purchase.cost.gp ~= nil and purchase.cost.gp > 0 then
if iconNoLink == nil or iconNoLink ~= true then purchName = '[[' .. purchLink .. purchName .. ']]' end
      result = result..' data-sort-value="'..purchase.cost.gp..'"'
    elseif purchase.cost.slayerCoins ~= nil and purchase.cost.slayerCoins > 0 then
      result = result..' data-sort-value="'..purchase.cost.slayerCoins..'"'
    end
    result = result..'|'..p.getCostString(purchase.cost)..'||'..p.getRequirementString(purchase.unlockRequirements)
  end


  result = result..'\r\n|}'
table.insert(resultPart, '|-')
  return result
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))
elseif column == 'Buy Limit' then
local buyLimit = p._getPurchaseBuyLimit(purchase, false)
local sortValue = (tonumber(buyLimit) == nil and -1 or buyLimit)
table.insert(resultPart, '| data-sort-value="' .. sortValue .. '"| ' .. buyLimit)
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
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 shopCat = ShopData.Shop[cat]
local options = {}
  if shopCat == nil then
if frame.args ~= nil then
    return 'ERROR: Invalid category '..cat..'[[Category:Pages with script errors]]'
if frame.args.columns ~= nil then options.columns = Shared.splitString(frame.args.columns, ',') end
  else
if frame.args.purchaseHeader ~= nil then options.purchaseHeader = frame.args.purchaseHeader end
    return p._getShopTable(shopCat)
if frame.args.sortOrder ~= nil then options.sortOrder = frame.args.sortOrder end
  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
end


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


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


  return purchaseArray
return purchaseArray
end
end


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


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


  if itemID == 903 then --Special Cape of Completion thing
return purchaseArray
    table.insert(purchaseArray, p.getCapeOfCompletionPurchase())
end
  end


  return purchaseArray
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
end


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


  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'
  result = result..'\r\n|'..p.getRequirementString(purchase.unlockRequirements)
result = result..'\r\n|'..p.getRequirementString(purchase.unlockRequirements)


  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|'..table.concat(containArray, '<br/>')


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


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


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


  return table.concat(tableArray, '\r\n<br/>\r\n')
return table.concat(tableArray, '\r\n\r\n')
end
end


function p.getItemShopTable(frame)
function p.getItemShopTable(frame)
  local itemName = frame.args ~= nil and frame.args[1] or frame
local itemName = frame.args ~= nil and frame.args[1] or frame
  local item = Items.getItem(itemName)
local item = Items.getItem(itemName)
  if item == nil then
if item == nil then
    return "ERROR: No item named "..itemName.." exists in the data module"
return "ERROR: No item named "..itemName.." exists in the data module"
   end
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 p._getItemShopTable(item)
return table.concat(resultPart, '\r\n')
end
end


return p
return p

Revision as of 12:28, 18 April 2022

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

local p = {}

local ShopData = mw.loadData('Module:Shop/data')
local ConstantData = mw.loadData('Module:Constants/data')
-- Data instead of Module:CombatAreas to avoid loop when 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 },
	["Faster Golbin Spawns"] = { icon = {'Timer', nil}, link = nil, incCost = true},
	["Golbin Crate"] = { icon = {'Golbin Crate', 'upgrade'}, link = nil, incCost = true}
}

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)
	elseif stat == 'buyLimit' then
		return p._getPurchaseBuyLimit(purchase, not displayInline)
	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/>', '<br/>'
	if displayInline then
		sep = ', '
		lastSep = Shared.tableCount(costArray) > 2 and ', and ' or ' and '
	end
	return mw.text.listToText(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 = {}
	local GPTotal = 0
	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 !! Price')
		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
				local GPVal = item.sellsFor * itemLine[2]
				GPTotal = GPTotal + GPVal
				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]))
				table.insert(containArray, '| data-sort-value="' .. GPVal .. '"| ' .. Icons.GP(GPVal))
			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({purchase.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. purchase.charges .. '" style="text-align:right" | ' .. Shared.formatnum(purchase.charges))
			table.insert(containArray, '| data-sort-value="0"| ' .. Icons.GP(0))
		end
	end
	if not asList and Shared.tableCount(containArray) > 0 then
		table.insert(containArray, '|- class="sortbottom"\r\n! colspan="3"| Total\r\n| ' .. Icons.GP(GPTotal) .. '\r\n|}')
	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 string.upper(args[2]) == 'TRUE')
	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

function p._getPurchaseBuyLimit(purchase, asList)
	if asList == nil then asList = true end
	if type(purchase.buyLimit) == 'table' then
		local limitTable = {}
		local gamemodeHasIcon = { 1, 2 }
		-- Populate limitTable for each game mode to be included
		for id, modeName in pairs(ConstantData.gamemode) do
			if tonumber(id) ~= nil and string.upper(modeName) ~= 'CHAOS' then
				local buyLimit = tostring(purchase.buyLimit[id + 1])
				if limitTable[buyLimit] == nil then
					limitTable[buyLimit] = {}
				end
				local gamemodeText = '[[Game Mode#' .. modeName .. '|' .. modeName .. ']]'
				if Shared.contains(gamemodeHasIcon, id) then
					gamemodeText = Icons.Icon({modeName, notext=(not asList or nil)})
				end
				table.insert(limitTable[buyLimit], gamemodeText)
			end
		end
		
		local numLimits = Shared.tableCount(limitTable)
		local resultPart = {}
		for buyLimit, gameModes in Shared.skpairs(limitTable, true) do
			local limitText = (buyLimit == '0' and 'Unlimited' or tostring(buyLimit))
			if numLimits == 1 then
				-- Buy limit is the same for all game modes
				return limitText
			else
				table.insert(resultPart, limitText .. (asList and ' for ' or ' ') .. mw.text.listToText(gameModes, ', ', (asList and ' and ' or ', ')))
			end
		end
		return table.concat(resultPart, (asList and ' or ' or '<br/>'))
	end
end

function p.getPurchaseBuyLimit(frame)
	local args = frame.args ~= nil and frame.args or frame
	local purchaseName = args[1]
	local asList = (args[2] ~= nil and string.upper(args[2]) == 'TRUE')
	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._getPurchaseBuyLimit(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', 'Buy Limit' }
	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
			if purchOverride.incCost then
				costString = costString .. '<br/>+' .. costString .. ' for each purchase'
			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))
			elseif column == 'Buy Limit' then
				local buyLimit = p._getPurchaseBuyLimit(purchase, false)
				local sortValue = (tonumber(buyLimit) == nil and -1 or buyLimit)
				table.insert(resultPart, '| data-sort-value="' .. sortValue .. '"| ' .. buyLimit)
			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