Module:Shop: Difference between revisions

From Melvor Idle
(getPurchaseBuyLimit: Initial implementation; _getShopTable: Support "Buy Limit" column)
(Use printError function)
(45 intermediate revisions by 4 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local ShopData = mw.loadData('Module:Shop/data')
local ConstantData = mw.loadData('Module:Constants/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')
local Constants = require('Module:Constants')
local GameData = require('Module:GameData')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Constants = require('Module:Constants')
local Pets = require('Module:Pets')


-- Overrides for various items, mostly relating to icon overrides
-- Overrides for various items, mostly relating to icon overrides
local purchOverrides = {
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>*' },
["Extra Bank Slot"] = { icon = {'Bank Slot', 'upgrade'}, link = 'Bank Slot' },
  -- Golbin Raid items
-- Golbin Raid items
  ["Reduce Wave Skip Cost"] = { icon = {'Melvor Logo', nil}, link = nil },
["Reduce Wave Skip Cost"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Food Bonus"] = { icon = {'Melvor Logo', nil}, link = nil },
["Food Bonus"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Ammo Gatherer"] = { icon = {'Melvor Logo', nil}, link = nil },
["Ammo Gatherer"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Rune Pouch"] = { icon = {'Melvor Logo', nil}, link = nil },
["Rune Pouch"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Increase Starting Prayer Points"] = { 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 },
["Unlock Combat Passive Slot"] = { icon = {'Melvor Logo', nil}, link = nil },
  ["Prayer"] = { icon = {'Prayer', 'skill'}, link = nil },
["Prayer"] = { icon = {'Prayer', 'skill'}, link = nil },
  ["Increase Prayer Level"] = { 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 }
["Increase Prayer Points gained per Wave Completion"] = { icon = {'Prayer', 'skill'}, link = nil },
["Faster Golbin Spawns"] = { icon = {'Timer', nil}, link = nil },
["Golbin Crate"] = { icon = {'Golbin Crate', 'upgrade'}, link = nil }
}
}


function p.getPurchase(purchaseName)
function p.getPurchase(purchaseName)
  for categoryName, categoryData in pairs(ShopData.Shop) do
    local purchList = p.getPurchases(function(purch) return p._getPurchaseName(purch) == purchaseName end)
     for i, purchase in ipairs(categoryData) do
     if purchList ~= nil and not Shared.tableIsEmpty(purchList) then
      if purchase.name == purchaseName then
         return purchList[1]
         return p.processPurchase(categoryName, i - 1)
      end
     end
     end
  end
end
end


function p.processPurchase(category, purchaseID)
function p.getPurchaseByID(id)
  local purchase = Shared.clone(ShopData.Shop[category][purchaseID + 1])
return GameData.getEntityByID('shopPurchases', id)
  purchase.id = purchaseID
end
  purchase.category = category
 
  return purchase
function p.getPurchases(checkFunc)
    return GameData.getEntities('shopPurchases', checkFunc)
end
end


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


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


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


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


     if Shared.tableCount(itemArray) > 0 then
function p._getPurchaseExpansionIcon(purch)
      table.insert(costArray, table.concat(itemArray, ", "))
     if purch.id ~= nil then
        return Icons.getExpansionIcon(purch.id)
    elseif purch.contains ~= nil then
    local item = nil
        if purch.contains.items ~= nil and not Shared.tableIsEmpty(purch.contains.items) then
            return Icons.getExpansionIcon(purch.contains.items[1].id)
        elseif purch.contains.itemCharges ~= nil and not Shared.tableIsEmpty(purch.contains.itemCharges) then
        return Icons.getExpansionIcon(purch.contains.itemCharges.id)
        end
       
        if purch.contains.petID ~= nil then
return Icons.getExpansionIcon(purch.contains.petID)
        end
     end
     end
  end
    return ''
end


  local sep, lastSep = '<br/>', nil
function p._getPurchaseDescription(purch)
  if displayInline then
if purch.customDescription ~= nil then
    sep = ', '
local templateData = p._getPurchaseTemplateData(purch)
    lastSep = Shared.tableCount(costArray) > 2 and ', and ' or ' and '
return Shared.applyTemplateData(purch.customDescription, templateData)
  end
elseif purch.contains ~= nil then
  return Shared.joinList(costArray, sep, lastSep)
local item = nil
if purch.contains.modifiers ~= nil then
return Constants.getModifiersText(purch.contains.modifiers, false)
elseif purch.contains.petID ~= nil then
            local pet = Pets.getPetByID(purch.contains.petID)
            return Pets._getPetEffect(pet)
elseif purch.contains.items ~= nil and Shared.tableCount(purch.contains.items) == 1 then
item = Items.getItemByID(purch.contains.items[1].id)
elseif purch.contains.itemCharges ~= nil then
item = Items.getItemByID(purch.contains.itemCharges.id)
end
if item ~= nil then
if item.customDescription ~= nil then
return item.customDescription
elseif item.modifiers ~= nil then
return Constants.getModifiersText(item.modifiers, false)
end
end
end
return ''
end
end


function p.getRequirementString(reqs)
function p.getCostString(cost, inline)
  if reqs == nil or Shared.tableCount(reqs) == 0 then
local displayInline = (inline ~= nil and inline or false)
    return "None"
local costArray = {}
  end
    local currencies = {'gp', 'slayerCoins', 'raidCoins'}
    for i, currency in ipairs(currencies) do
        if cost[currency] ~= nil then
            local costStr = p.getCurrencyCostString(cost[currency], currency)
            if costStr ~= nil then
                table.insert(costArray, costStr)
            end
        end
    end
if cost.items ~= nil and not Shared.tableIsEmpty(cost.items) then
        local itemArray = {}
for i, itemCost in ipairs(cost.items) do
local item = Items.getItemByID(itemCost.id)
            if item ~= nil then
    table.insert(itemArray, Icons.Icon({item.name, type="item", notext=(not displayInline and true or nil), qty=itemCost.quantity}))
            end
end
        if not Shared.tableIsEmpty(itemArray) then
            table.insert(costArray, table.concat(itemArray, ', '))
        end
end


  local reqArray = {}
    if not Shared.tableIsEmpty(costArray) then
  if reqs.slayerTaskCompletion ~= nil then
        local sep, lastSep = '<br/>', '<br/>'
    for i, taskReq in Shared.skpairs(reqs.slayerTaskCompletion) do
        if displayInline then
      local tierName = Constants.getSlayerTierName(taskReq[1])
            sep = ', '
      table.insert(reqArray, 'Complete '..taskReq[2]..' '..tierName..' Slayer Tasks')
            lastSep = Shared.tableCount(costArray) > 2 and ', and ' or ' and '
        end
        return mw.text.listToText(costArray, sep, lastSep)
     end
     end
  end
end


  if reqs.dungeonCompletion ~= nil then
-- Generates description template data. See: shop.js, getDescriptionTemplateData()
    for i, dungReq in Shared.skpairs(reqs.dungeonCompletion) do
function p._getPurchaseTemplateData(purchase)
      local dung = AreaData['dungeons'][dungReq[1] + 1]
-- qty is a static value of 1 for Bank slots
      local dungStr = 'Complete '..Icons.Icon({dung.name, type='dungeon'})
local templateData = { qty = 1 }
      if dungReq[2] > 1 then
if purchase.contains ~= nil and purchase.contains.items ~= nil then
         dungStr = dungStr..' '..dungReq[2]..' times'
for i, itemDef in ipairs(purchase.contains.items) do
      end
templateData['qty' .. i] = itemDef.quantity
      table.insert(reqArray, dungStr)
end
end
return templateData
end
 
function p.getCurrencyCostString(cost, currency)
    local decoratorList = {
        ["gp"] = Icons.GP,
         ["slayerCoins"] = Icons.SC,
        ["raidCoins"] = Icons.RC
    }
    local decorator = nil
    if currency ~= nil then
        decorator = decoratorList[currency]
     end
     end
  end
    if decorator == nil then
 
        decorator = function(cost) return cost 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
  end


  if reqs.shopItemPurchased ~= nil then
    if cost.type == 'BankSlot' then
    for i, shopReq in Shared.skpairs(reqs.shopItemPurchased) do
        -- Unusual bit of code that basically evaluates wikitext '<math>C_b</math>*'
      local purchase = ShopData.Shop[shopReq[1]][shopReq[2] + 1]
        return mw.getCurrentFrame():callParserFunction('#tag:math', {'C_b'}) .. '*'
      local isUpgrade = purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0
    elseif cost.type == 'Linear' and (cost.initial > 0 or cost.scaling > 0) then
      table.insert(reqArray, Icons.Icon({purchase.name, type=(isUpgrade and 'upgrade' or 'item')})..' Purchased')
        return decorator(cost.initial) .. '<br/>+' .. decorator(cost.scaling) .. ' for each purchase'
    elseif cost.type == 'Glove' or cost.type == 'Fixed' and cost.cost > 0 then
        -- Type Glove exists in game so the Merchan's Permit cost reduction can be applied,
        -- it makes no difference here
        return decorator(cost.cost)
     end
     end
  end
end


  if reqs.completionPercentage ~= nil then
function p.getRequirementString(reqs)
    table.insert(reqArray, tostring(reqs.completionPercentage) .. '% Completion Log')
if reqs == nil or Shared.tableIsEmpty(reqs) then
  end
return 'None'
end


  if reqs.text ~= nil then
local reqArray = {}
    table.insert(reqArray, reqs.text)
    for i, req in ipairs(reqs) do
  end
        if req.type == 'SkillLevel' then
            local skillName = Constants.getSkillName(req.skillID)
            if skillName ~= nil then
                table.insert(reqArray, Icons._SkillReq(skillName, req.level))
            end
        elseif req.type == 'DungeonCompletion' then
            local dung = GameData.getEntityByID('dungeons', req.dungeonID)
            if dung ~= nil then
                local dungStr = 'Complete ' .. Icons.Icon({dung.name, type='dungeon'})
                if req.count > 1 then
                    dungStr = dungStr .. ' ' .. Shared.formatnum(req.count) .. ' times'
                end
                table.insert(reqArray, dungStr)
            end
        elseif req.type == 'SlayerTask' then
            table.insert(reqArray, 'Complete ' .. Shared.formatnum(req.count) .. ' ' .. req.tier .. ' Slayer Tasks')
        elseif req.type == 'TownshipTask' then
            table.insert(reqArray, 'Complete ' .. Shared.formatnum(req.count) .. ' Township Tasks')
        elseif req.type == 'TownshipBuilding' then
            local tsData = GameData.getSkillData('melvorD:Township')
            if tsData ~= nil and tsData.buildings ~= nil then
                local building = GameData.getEntityByID(tsData.buildings, req.buildingID)
                if building ~= nil then
                    table.insert(reqArray, 'Have ' .. Shared.formatnum(req.count) .. ' ' .. building.name .. ' actively built in Township')
                end
            end
        elseif req.type == 'ShopPurchase' then
        local shopPurch = p.getPurchaseByID(req.purchaseID)
        if shopPurch ~= nil then
        table.insert(reqArray, p._getPurchaseIcon({shopPurch}) .. ' Purchased')
        end
        elseif req.type == 'Completion' then
            local ns = GameData.getEntityByName('namespaces', req.namespace)
            if ns ~= nil then
                table.insert(reqArray, req.percent .. '% ' .. ns.displayName .. ' Completion')
            end
        elseif req.type == 'AllSkillLevels' then
            local reqText = 'Level ' .. req.level .. ' in all skills'
            if req.exceptions ~= nil and not Shared.tableIsEmpty(req.exceptions) then
                local exceptSkills = {}
                for i, skillID in ipairs(req.exceptions) do
                    local skillName = Constants.getSkillName(skillID)
                    if skillName ~= nil then
                        table.insert(exceptSkills, Icons.Icon({skillName, type='skill'}))
                    end
                end
                reqText = reqText .. ' except for ' .. table.concat(exceptSkills, ', ')
            end
            table.insert(reqArray, reqText)
        else
            table.insert(reqArray, Shared.printError('Unknown requirement: ' .. (req.type or 'nil')))
        end
    end


  return table.concat(reqArray, '<br/>')
    if Shared.tableIsEmpty(reqArray) then
        return 'None'
    else
    return table.concat(reqArray, '<br/>')
    end
end
end


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


function p._getPurchaseContents(purchase, asList)
function p._getPurchaseContents(purchase, asList)
  if asList == nil then asList = true end
if asList == nil then asList = true end
  local containArray = {}
local containArray = {}
  if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0 then
local GPTotal = 0
    if not asList then
if purchase.contains ~= nil then
      table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
if purchase.contains.items ~= nil and not Shared.tableIsEmpty(purchase.contains.items) then
      table.insert(containArray, '|- class="headerRow-0"')
if not asList then
      table.insert(containArray, '! colspan="2" | Item !! Quantity')
table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
    end
table.insert(containArray, '|- class="headerRow-0"')
    for i, itemLine in Shared.skpairs(purchase.contains.items) do
table.insert(containArray, '! colspan="2" | Item !! Quantity !! Price')
      local item = Items.getItemByID(itemLine[1])
end
      if asList then
for i, itemLine in ipairs(purchase.contains.items) do
        table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemLine[2]}))
local item = Items.getItemByID(itemLine.id)
      else
            local itemQty = itemLine.quantity
        table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size='25'}))
if asList then
        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, Icons.Icon({item.name, type='item', qty=itemQty}))
      end
else
    end
local GPVal = item.sellsFor * itemQty
  end
GPTotal = GPTotal + GPVal
  if purchase.charges ~= nil and purchase.charges > 0 then
table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size='25'}))
    if asList then
table.insert(containArray, '|data-sort-value="'..item.name..'"|'.. Icons.getExpansionIcon(item.id) .. Icons.Icon({item.name, type='item', noicon=true}) .. '\r\n| data-sort-value="' .. itemQty .. '" style="text-align:right" | ' .. Shared.formatnum(itemQty))
      table.insert(containArray, '+'..purchase.charges..' '..Icons.Icon({purchase.name, type='item'})..' Charges')
table.insert(containArray, '| data-sort-value="' .. GPVal .. '"| ' .. Icons.GP(GPVal))
    else
end
      table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({purchase.name, type='item', notext=true, size='25'}))
end
      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 purchase.contains.itemCharges ~= nil and purchase.contains.itemCharges.quantity > 0 then
  end
        local gloveItem = Items.getItemByID(purchase.contains.itemCharges.id)
  if not asList and Shared.tableCount(containArray) > 0 then table.insert(containArray, '|}') end
        local chargeQty = purchase.contains.itemCharges.quantity
        if gloveItem ~= nil then
            if asList then
                table.insert(containArray, '+'..Shared.formatnum(chargeQty)..' '..Icons.Icon({gloveItem.name, type='item'})..' Charges')
            else
                table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({gloveItem.name, type='item', notext=true, size='25'}))
                table.insert(containArray, '| ' .. Icons.Icon({gloveItem.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. chargeQty .. '" style="text-align:right" | ' .. Shared.formatnum(chargeQty))
                table.insert(containArray, '| data-sort-value="0"| ' .. Icons.GP(0))
            end
        end
end
end
if not asList and not Shared.tableIsEmpty(containArray) 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')
local delim = (asList and '<br/>' or '\r\n')
  return table.concat(containArray, delim)
return table.concat(containArray, delim)
end
end


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


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


function p._getPurchaseBuyLimit(purchase, asList)
function p._getPurchaseBuyLimit(purchase, asList)
if asList == nil then asList = true end
if asList == nil then asList = true end
if type(purchase.buyLimit) == 'table' then
    local defaultLimit = (purchase.defaultBuyLimit == 0 and 'Unlimited') or Shared.formatnum(purchase.defaultBuyLimit)
local limitTable = {}
    if purchase.buyLimitOverrides == nil or Shared.tableIsEmpty(purchase.buyLimitOverrides) then
local gamemodeHasIcon = { 1, 2 }
        -- Same limit for all game modes
-- Populate limitTable for each game mode to be included
        return defaultLimit
for id, modeName in pairs(ConstantData.gamemode) do
    else
if tonumber(id) ~= nil and string.upper(modeName) ~= 'CHAOS' then
        -- The limit varies depending on game mode
local buyLimit = tostring(purchase.buyLimit[id + 1])
        local limitTable = {}
if limitTable[buyLimit] == nil then
        local gamemodeHasIcon = { 'melvorF:Hardcore', 'melvorF:Adventure' }
limitTable[buyLimit] = {}
        for i, buyLimit in ipairs(purchase.buyLimitOverrides) do
end
            local gamemode = GameData.getEntityByID('gamemodes', buyLimit.gamemodeID)
local gamemodeText = '[[Game Mode#' .. modeName .. '|' .. modeName .. ']]'
            if gamemode ~= nil then
if Shared.contains(gamemodeHasIcon, id) then
                local gamemodeText = nil
gamemodeText = Icons.Icon({modeName, notext=(not asList or nil)})
                if Shared.contains(gamemodeHasIcon, gamemode.id) then
end
                    gamemodeText = Icons.Icon({gamemode.name, notext=(not asList or nil)})
table.insert(limitTable[buyLimit], gamemodeText)
                else
end
                    gamemodeText = '[[Game Mode#' .. gamemode.name .. '|' .. gamemode.name .. ']]'
end
                end
                local limitText = (buyLimit.maximum == 0 and 'Unlimited') or Shared.formatnum(buyLimit.maximum)
local numLimits = Shared.tableCount(limitTable)
                table.insert(limitTable, limitText .. (asList and ' for ' or ' ') .. gamemodeText)
local resultPart = {}
            end
for buyLimit, gameModes in Shared.skpairs(limitTable, true) do
        end
local limitText = (buyLimit == '0' and 'Unlimited' or tostring(buyLimit))
        table.insert(limitTable, defaultLimit .. (asList and ' for ' or ' ') .. 'All other game modes')
if numLimits == 1 then
        return table.concat(limitTable, (asList and ' or ' or '<br/>'))
-- Buy limit is the same for all game modes
    end
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
end


Line 266: Line 405:
if purchase == nil then
if purchase == nil then
return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
return Shared.printError("Couldn't find purchase with name '" .. purchaseName .. "'")
else
else
return p._getPurchaseBuyLimit(purchase, asList)
return p._getPurchaseBuyLimit(purchase, asList)
Line 274: Line 413:
-- Accept similar arguments to Icons.Icon
-- Accept similar arguments to Icons.Icon
function p._getPurchaseIcon(iconArgs)
function p._getPurchaseIcon(iconArgs)
  local purchase = iconArgs[1]
local purchase = iconArgs[1]
  local override = purchOverrides[purchase.name]
    local purchaseName = p._getPurchaseName(purchase)
  local purchType = p._getPurchaseType(purchase)
local override = purchOverrides[purchaseName]
  -- Amend iconArgs before passing to Icons.Icon()
local purchType = p._getPurchaseType(purchase)
  iconArgs[1] = ((override ~= nil and override.icon[1]) or purchase.name)
-- Amend iconArgs before passing to Icons.Icon()
  if override ~= nil then
iconArgs[1] = ((override ~= nil and override.icon[1]) or purchaseName)
    iconArgs['type'] = override.icon[2]
if override ~= nil then
    if override.link == nil then
iconArgs['type'] = override.icon[2]
      iconArgs['nolink'] = true
if override.link == nil then
    end
iconArgs['nolink'] = true
  else
end
    iconArgs['type'] = (purchType == 'Item Bundle' and 'item') or string.lower(purchType)
else
  end
iconArgs['type'] = (purchType == 'Item Bundle' and 'item') or string.lower(purchType)
end


  return Icons.Icon(iconArgs)
return Icons.Icon(iconArgs)
end
end


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


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


function p._getPurchaseSortValue(purchase)
function p._getPurchaseSortValue(purchase)
  local costCurrencies = {'gp', 'slayerCoins', 'raidCoins'}
local costCurrencies = {'gp', 'slayerCoins', 'raidCoins'}
  for j, curr in ipairs(costCurrencies) do
for j, curr in ipairs(costCurrencies) do
    local costAmt = purchase.cost[curr]
local costAmt = purchase.cost[curr]
    if costAmt ~= nil and costAmt > 0 then
        if costAmt.type == 'BankSlot' then
      return costAmt
            return -1
        elseif costAmt.type == 'Linear' then
            return costAmt.initial
        elseif costAmt.type == 'Glove' or costAmt.type == 'Fixed' and costAmt.cost > 0 then
            return costAmt.cost
        end
     end
     end
  end
end
end


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


  -- Process options if specified
-- Process options if specified
  if options ~= nil and type(options) == 'table' then
if options ~= nil and type(options) == 'table' then
    -- Custom columns
-- Custom columns
    if options.columns ~= nil and type(options.columns) == 'table' then
if options.columns ~= nil and type(options.columns) == 'table' then
      for i, column in ipairs(options.columns) do
for i, column in ipairs(options.columns) do
        if Shared.contains(availableColumns, column) then
if Shared.contains(availableColumns, column) then
          table.insert(usedColumns, column)
table.insert(usedColumns, column)
        end
end
      end
end
    end
end
    -- Purchase column header text
-- Purchase column header text
    if options.purchaseHeader ~= nil and type(options.purchaseHeader) == 'string' then
if options.purchaseHeader ~= nil and type(options.purchaseHeader) == 'string' then
      purchHeader = options.purchaseHeader
purchHeader = options.purchaseHeader
    end
end
    -- Custom sort order
-- Custom sort order
    if options.sortOrder ~= nil and type(options.sortOrder) == 'function' then
if options.sortOrder ~= nil and type(options.sortOrder) == 'function' then
      sortOrder = options.sortOrder
sortOrder = options.sortOrder
    end
end
    -- Header properties
-- Header properties
    if options.headerProps ~= nil and type(options.headerProps) == 'table' then
if options.headerProps ~= nil and type(options.headerProps) == 'table' then
      headerProps = options.headerProps
headerProps = options.headerProps
    end
end
  end
-- Sticky header class
  -- Use default columns if no custom columns specified
if options.stickyHeader ~= nil then
  if Shared.tableCount(usedColumns) == 0 then
if type(options.stickyHeader) == 'boolean' then
    usedColumns = availableColumns
stickyHeader = options.stickyHeader
  end
elseif type(options.stickyHeader) == 'string' and string.lower(options.stickyHeader) == 'false' then
  if Shared.tableCount(headerProps) == 0 then
stickyHeader = false
    headerProps = headerPropsDefault
end
  end
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
-- Begin output generation
  local resultPart = {}
local resultPart = {}
  -- Generate header
-- Generate header
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '{| class="wikitable sortable' .. (stickyHeader and ' stickyHeader' or '') .. '"')
  table.insert(resultPart, '|- class="headerRow-0"')
table.insert(resultPart, '|- class="headerRow-0"')
  for i, column in ipairs(usedColumns) do
for i, column in ipairs(usedColumns) do
    local prop = headerProps[column]
local prop = headerProps[column]
    table.insert(resultPart, '!' .. (prop and prop .. '| ' or ' ') .. (column == 'Purchase' and purchHeader or column))
table.insert(resultPart, '!' .. (prop and prop .. '| ' or ' ') .. (column == 'Purchase' and purchHeader or column))
  end
end


  local purchIterator = nil
if sortOrder == nil then
  if sortOrder == nil then
Purchases = GameData.sortByOrderTable(Purchases, GameData.rawData.shopDisplayOrder, true)
    purchIterator = Shared.skpairs
else
  else
table.sort(Purchases, sortOrder)
    table.sort(Purchases, sortOrder)
end
    purchIterator = ipairs
for i, purchase in ipairs(Purchases) do
  end
        local purchName = p._getPurchaseName(purchase)
  for i, purchase in purchIterator(Purchases) do
        local purchExpIcon = p._getPurchaseExpansionIcon(purchase)
    local purchOverride = nil
local purchOverride = nil
    if purchOverrides ~= nil then
if purchOverrides ~= nil then
      purchOverride = purchOverrides[purchase.name]
purchOverride = purchOverrides[purchName]
    end
end


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


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


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


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


Line 432: Line 583:
--  sortOrder:      A function determining the order in which table items appear
--  sortOrder:      A function determining the order in which table items appear
--  purchaseHeader: Specifies header text for the Purchase column if not 'Purchase'
--  purchaseHeader: Specifies header text for the Purchase column if not 'Purchase'
-- stickyHeader:  Specifies if the table will have a sticky header or not
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 = {}
local options = {}
  if frame.args ~= nil then
if frame.args ~= nil then
    if frame.args.columns ~= nil then options.columns = Shared.splitString(frame.args.columns, ',') end
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.purchaseHeader ~= nil then options.purchaseHeader = frame.args.purchaseHeader end
    if frame.args.sortOrder ~= nil then options.sortOrder = frame.args.sortOrder end
if frame.args.sortOrder ~= nil then options.sortOrder = frame.args.sortOrder end
    if frame.args.columnProps ~= nil then
if frame.args.stickyHeader ~= nil then options.stickyHeader = frame.args.stickyHeader end
      local columnPropValues = Shared.splitString(frame.args.columnProps, ',')
if frame.args.columnProps ~= nil then
      local columnProps = {}
local columnPropValues = Shared.splitString(frame.args.columnProps, ',')
      for i, prop in pairs(columnPropValues) do
local columnProps = {}
        local propName, propValue = string.match(prop, '^([^:]+):(.*)$')
for i, prop in pairs(columnPropValues) do
        if propName ~= nil then
local propName, propValue = string.match(prop, '^([^:]+):(.*)$')
          columnProps[propName] = propValue
if propName ~= nil then
        end
columnProps[propName] = propValue
      end
end
      if Shared.tableCount(columnProps) > 0 then options.headerProps = columnProps end
end
    end
if Shared.tableCount(columnProps) > 0 then options.headerProps = columnProps end
  end
end
  local shopCat = ShopData.Shop[cat]
end
  if shopCat == nil then
    local shopCat = GameData.getEntityByName('shopCategories', cat)
    return 'ERROR: Invalid category '..cat..'[[Category:Pages with script errors]]'
if shopCat == nil then
  else
return Shared.printError('Invalid category ' .. cat)
    return p._getShopTable(shopCat, options)
else
  end
        local catPurchases = p.getPurchases(function(purch) return purch.category == shopCat.id end)
return p._getShopTable(catPurchases, options)
end
end
end


function p.getItemCostArray(itemID)
function p.getItemCostArray(itemID)
  local purchaseArray = {}
    local purchaseArray = {}
 
    for i, purchase in ipairs(GameData.rawData.shopPurchases) do
  for catName, cat in Shared.skpairs(ShopData.Shop) do
        if purchase.cost ~= nil and purchase.cost.items ~= nil then
    for j, purchase in Shared.skpairs(cat) do
            for j, itemCost in ipairs(purchase.cost.items) do
      if purchase.cost.items ~= nil then
                if itemCost.id == itemID then
        for k, costLine in Shared.skpairs(purchase.cost.items) do
                    table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = itemCost.quantity })
          if costLine[1] == itemID then
                    break
            local temp = p.processPurchase(catName, j - 1)
                end
            temp.qty = costLine[2]
            end
            table.insert(purchaseArray, temp)
            break
          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 i, purchase in ipairs(GameData.rawData.shopPurchases) do
  for catName, cat in Shared.skpairs(ShopData.Shop) do
        if purchase.contains ~= nil then
    for j, purchase in Shared.skpairs(cat) do
        if purchase.contains.items ~= nil then
      if purchase.contains.items ~= nil and purchase.contains.items ~= nil then
            for j, itemContains in ipairs(purchase.contains.items) do
        for k, containsLine in Shared.skpairs(purchase.contains.items) do
                if itemContains.id == itemID then
          if containsLine [1] == itemID then
                    table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = itemContains.quantity })
            local temp = p.processPurchase(catName, j - 1)
                    break
            temp.qty = containsLine[2]
                end
            table.insert(purchaseArray, temp)
            end
            break
        end
          end
        if purchase.contains.itemCharges ~= nil and purchase.contains.itemCharges.id == itemID then
        table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = 1 })
        end
         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
  end
    return purchaseArray
  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"|'..Icons.Icon({'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..' - '..p._getPurchaseExpansionIcon(purchase)..Icons.Icon({p._getPurchaseName(purchase), 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, false)
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.purchaseRequirements)


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


  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 ipairs(purchaseArray) do
    table.insert(tableArray, p._getPurchaseTable(purchase))
table.insert(tableArray, p._getPurchaseTable(purchase.purchase))
  end
end


  return table.concat(tableArray, '\r\n\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 Shared.printError('No item named ' .. itemName .. ' exists in the data module')
  end
end


  return p._getItemShopTable(item)
return p._getItemShopTable(item)
end
end


function p.getShopMiscUpgradeTable()
function p.getShopMiscUpgradeTable()
  local purchList = p.getPurchases(function(cat, purch) return cat == 'General' and string.find(purch.name, '^Auto Eat') == nil end)
local purchList = p.getPurchases(function(purch) return (purch.category == 'melvorD:General' and string.find(purch.id, '^melvorD:Auto_Eat') == nil) or Shared.contains({'melvorTotH:SignOfTheStars', 'melvorTotH:SummonersAltar'}, purch.id) end)
  return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
 
return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
end
end


function p.getShopSkillcapeTable()
function p._getShopSkillcapeTable(showSuperior)
  local capeList = p.getPurchases(function(cat, purch) return cat == 'Skillcapes' end)
local categoryID = (showSuperior and 'melvorTotH:SuperiorSkillcapes') or 'melvorD:Skillcapes'
  local sortOrderFunc = function(a, b)
local capeList = p.getPurchases(function(purch) return purch.category == categoryID end)
                      if a.cost.gp == b.cost.gp then
local sortOrderFunc =
                        return a.name < b.name
function(a, b)
                      else
local costA, costB = p._getPurchaseSortValue(a), p._getPurchaseSortValue(b)
                        return a.cost.gp < b.cost.gp
if costA == costB then
                      end
return p._getPurchaseName(a) < p._getPurchaseName(b)
                    end
else
  return p._getShopTable(capeList,
return costA < costB
    { columns = { 'Purchase', 'Description', 'Cost' },
end
      purchaseHeader = 'Cape',
end
      sortOrder = sortOrderFunc,
return p._getShopTable(capeList, {
      headerProps = {["Purchase"] = 'colspan="2" style="width:200px;"', ["Cost"] = 'style=width:120px;'}
columns = { 'Purchase', 'Description', 'Cost' },
    })
purchaseHeader = 'Cape',
sortOrder = sortOrderFunc,
stickyHeader = false,
headerProps = {["Purchase"] = 'colspan="2" style="width:200px;"', ["Cost"] = 'style=width:120px;'}
})
end
 
function p.getShopSkillcapeTable(frame)
local capeCategory = frame.args ~= nil and frame.args[1] or frame
local showSuperior = string.lower(capeCategory) == 'superior'
return p._getShopSkillcapeTable(showSuperior)
end
 
function p.getSkillcapeTable(frame)
local skillName = frame.args ~= nil and frame.args[1] or frame
local capeList = p.getPurchases(function(purch) return Shared.contains({'melvorD:Skillcapes', 'melvorTotH:SuperiorSkillcapes'}, purch.category) and string.find(p._getPurchaseName(purch), skillName) end)
if Shared.tableIsEmpty(capeList) then
return ''
else
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"\r\n')
table.insert(resultPart, '!Skillcape!!Name!!Requirements!!Effect')
for i, cape in ipairs(capeList) do
local capeItem = Items.getItemByID(cape.contains.items[1].id)
if capeItem ~= nil then
table.insert(resultPart, '\r\n|-\r\n| ' .. Icons.Icon({capeItem.name, type='item', size='60', notext=true}))
table.insert(resultPart, '\r\n| data-sort-value="'..capeItem.name..'"|'..Icons.getExpansionIcon(capeItem.id) .. Icons.Icon({capeItem.name, type='item', noicon=true}))
table.insert(resultPart, '\r\n| ' .. p.getRequirementString(cape.purchaseRequirements))
table.insert(resultPart, '\r\n| ' .. p._getPurchaseDescription(cape))
end
end
table.insert(resultPart, '\r\n|}')
return table.concat(resultPart)
end
end
end


function p.getAutoEatTable()
function p.getAutoEatTable()
  local resultPart = {}
local resultPart = {}
  local purchasesAE = p.getPurchases(function(cat, purch) return string.find(purch.name, '^Auto Eat') ~= nil end)
local purchasesAE = p.getPurchases(function(purch) return purch.category == 'melvorD:General' and string.find(purch.id, '^melvorD:Auto_Eat') ~= nil end)


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


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


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


function p.getGodUpgradeTable()
function p.getGodUpgradeTable()
  local resultPart = {}
local resultPart = {}
  -- Obtain list of God upgrades: look for skill upgrades which have a dungeon completion
-- Obtain list of God upgrades: look for skill upgrades which have a dungeon completion
  --  requirement for an area whose name ends with 'God Dungeon'
--  requirement for an area whose name ends with 'God Dungeon'
  local getGodDungeon =
local getGodDungeon =
    function(reqs)
function(reqs)
      if reqs.dungeonCompletion ~= nil then
            for i, req in ipairs(reqs) do
        for i, areaReq in ipairs(reqs.dungeonCompletion) do
                if req.type == 'DungeonCompletion' and string.find(req.dungeonID, 'God_Dungeon$') ~= nil then
          local dung = AreaData['dungeons'][areaReq[1] + 1]
                    return GameData.getEntityByID('dungeons', req.dungeonID)
          if string.find(dung.name, 'God Dungeon$') ~= nil then return dung end
                end
            end
         end
         end
      end
 
local upgradeList = p.getPurchases(
function(purch)
if purch.category == 'melvorD:SkillUpgrades' and purch.purchaseRequirements ~= nil then
return getGodDungeon(purch.purchaseRequirements) ~= nil
end
return false
end)
if Shared.tableIsEmpty(upgradeList) then
        return ''
     end
     end


  local upgradeList = p.getPurchases(
-- Table header
    function(cat, purch)
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
      if cat == 'SkillUpgrades' and purch.unlockRequirements ~= nil then
table.insert(resultPart, '|- class="headerRow-0"')
        return getGodDungeon(purch.unlockRequirements) ~= nil
table.insert(resultPart, '!colspan="2"|God Upgrade!!Effect!!Dungeon!!Cost')
      end
      return false
    end)
  if Shared.tableCount(upgradeList) == 0 then return '' end


  -- Table header
-- Rows for each God upgrade
  table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
for i, upgrade in ipairs(upgradeList) do
  table.insert(resultPart, '|- class="headerRow-0"')
        local upgradeName = p._getPurchaseName(upgrade)
  table.insert(resultPart, '!colspan="2"|God Upgrade!!Effect!!Dungeon!!Cost')
local dung = getGodDungeon(upgrade.purchaseRequirements)
local costSortValue = p._getPurchaseSortValue(upgrade)
table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. upgradeName .. '"| ' ..p._getPurchaseExpansionIcon(upgrade).. Icons.Icon({upgradeName, type='upgrade', size=50, notext=true}))
table.insert(resultPart, '| ' .. Icons.Icon({upgradeName, type='upgrade', noicon=true}))
table.insert(resultPart, '| ' .. p._getPurchaseDescription(upgrade))
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, '|}')


  -- Rows for each God upgrade
return table.concat(resultPart, '\r\n')
  for i, upgrade in ipairs(upgradeList) do
end
    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')
function p.getToolTable(toolName, searchString, modifiers, skillID)
local skillName = nil
if type(skillID) == 'string' then
skillName = Constants.getSkillName(skillID)
end
local toolArray = p.getPurchases(
function(purch)
return purch.category == 'melvorD:SkillUpgrades' and string.find(purch.id, searchString) ~= nil
end)
 
if Shared.tableIsEmpty(toolArray) then
return ''
end
if modifiers == nil then
modifiers = {}
end
 
local modTotal = {}
for i, modDef in ipairs(modifiers) do
modTotal[modDef.name] = 0
end
 
local headerRowSpan = (Shared.tableIsEmpty(toolArray) and 1) or 2
local resultPart = {}
table.insert(resultPart, '{| class="wikitable"')
table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '" colspan="2"| Name')
table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '"| ' .. (skillName == nil and 'Requirements' or Icons.Icon({skillName, type='skill', notext=true}) .. ' Level'))
table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '"| Cost')
for i, modDef in ipairs(modifiers) do
modTotal[modDef.name] = 0
table.insert(resultPart, '\n!colspan="2"| ' .. modDef.header)
end
if headerRowSpan > 1 then
table.insert(resultPart, '\n|-' .. string.rep('\n!This ' .. toolName .. '\n!Total', Shared.tableCount(modifiers)))
end
 
for i, tool in ipairs(toolArray) do
local toolName = p._getPurchaseName(tool)
local toolCost = p.getCostString(tool.cost, false)
local toolCostSort = p._getPurchaseSortValue(tool) or 0
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|style="min-width:25px" data-sort-value="' .. toolName .. '"| ' .. Icons.Icon({toolName, type='upgrade', size='50', notext=true}))
table.insert(resultPart, '\n| data-sort-value="' .. toolName.. '"|' .. Icons.getExpansionIcon(tool.id) .. toolName)
local level, levelStyle = nil, nil
if skillID == nil then
level = 'None'
levelStyle = '|class="table-na"'
else
level = 1
levelStyle = '|style="text-align:right"'
end
if tool.purchaseRequirements ~= nil and not Shared.tableIsEmpty(tool.purchaseRequirements) then
if skillID == nil then
-- Return all requirements
level = p.getRequirementString(tool.purchaseRequirements)
if level ~= 'None' then
levelStyle = ''
end
else
-- Return level requirement for just the specified skill
for i, purchReq in ipairs(tool.purchaseRequirements) do
if purchReq.type == 'SkillLevel' and purchReq.skillID == skillID then
level = purchReq.level
break
end
end
end
end
table.insert(resultPart, '\n' .. levelStyle .. '| '..level)
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. toolCostSort .. '"| ' .. toolCost)
 
local cellStart = '\n|style="text-align:right"'
if tool.contains ~= nil and tool.contains.modifiers ~= nil then
for j, modDef in ipairs(modifiers) do
local modName = modDef.name
local modVal = tool.contains.modifiers[modName]
if modVal ~= nil then
if type(modVal) == 'table' and type(modVal[1]) == 'table' and modVal[1].skillID ~= nil and (modDef.skillID == nil or modDef.skillID == modVal[1].skillID) then
modVal = modVal[1].value
end
modTotal[modName] = modTotal[modName] + modVal
else
modVal = 0
end
local cellStartVal = cellStart .. ((modVal == 0 and ' class="table-na"') or '')
local cellStartTot = cellStart .. ((modTotal[modName] == 0 and ' class="table-na"') or '')
table.insert(resultPart, cellStartVal .. '| ' .. (modVal == 0 and '' or modDef.sign) .. modVal .. modDef.suffix)
table.insert(resultPart, cellStartTot .. '| ' .. (modTotal[modName] == 0 and '' or modDef.sign) .. modTotal[modName] .. modDef.suffix)
end
end
end
 
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
 
function p.getAxeTable(frame)
local modifiers = {
{ name = 'decreasedSkillIntervalPercent', header = 'Cut Time Decrease', sign = '-', suffix = '%' },
{ name = 'increasedChanceToDoubleItemsSkill', header = 'Double Items Chance', sign = '+', suffix = '%' },
{ name = 'increasedBirdNestDropRate', header = Icons.Icon({'Bird Nest', 'Drop Chance', type='item', nolink=true}), sign = '+', suffix = '%' },
{ name = 'increasedChanceForAshInWoodcutting', header = Icons.Icon({'Ash', 'Drop Chance', type='item', nolink=true}), sign = '+', suffix = '%' }
}
return p.getToolTable('Axe', '_Axe$', modifiers, 'melvorD:Woodcutting')
end
 
function p.getPickaxeTable(frame)
local modifiers = {
{ name = 'decreasedSkillIntervalPercent', header = 'Mining Time Decrease', sign = '-', suffix = '%' },
{ name = 'increasedChanceToDoubleOres', header = '2x Ore Chance', sign = '+', suffix = '%' },
{ name = 'increasedChanceForOneExtraOre', header = '+1 Ore Chance', sign = '+', suffix = '%' },
{ name = 'increasedChanceForQualitySuperiorGem', header = 'Superior Gem Chance', sign = '+', suffix = '%' },
{ name = 'increasedMeteoriteOre', header = 'Increased ' .. Icons.Icon({'Meteorite Ore', type='item', notext=true}), sign = '+', suffix = '' }
}
 
return p.getToolTable('Pickaxe', '_Pickaxe$', modifiers, 'melvorD:Mining')
end
 
function p.getRodTable(frame)
local modifiers = {
{ name = 'decreasedSkillIntervalPercent', header = 'Catch Time Decrease', sign = '-', suffix = '%' },
{ name = 'increasedChanceForOneExtraFish', header = '+1 Fish Chance', sign = '+', suffix = '%' },
{ name = 'increasedChanceToFindLostChest', header = Icons.Icon({'Lost Chest', type='item', notext=true}) .. ' Chance', sign = '+', suffix = '%' },
{ name = 'increasedFishingCookedChance', header = 'Cooked Fish Chance', sign = '+', suffix = '%' }
}
 
return p.getToolTable('Rod', '_Rod$', modifiers, 'melvorD:Fishing')
end
end


Line 655: Line 958:
local validCategories = {'Cooking Fire', 'Furnace', 'Pot'}
local validCategories = {'Cooking Fire', 'Furnace', 'Pot'}
if category == nil or not Shared.contains({'Cooking Fire', 'Furnace', 'Pot'}, category) then
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 ')
return Shared.printError('Invalid category specified. Must be one of the following: ' .. mw.text.listToText(validCategories, ', ', ' or '))
end
end
 
local categoryShort = string.match(category, '[^%s]+$')
local categoryShort = string.match(category, '[^%s]+$')
local bonusSkillID = Constants.getSkillID('Cooking')
local modifiers = {
local bonusColMod, bonusColName = nil, nil
['Cooking Fire'] = {
if category == 'Cooking Fire' then
{ name = 'increasedSkillXP', skillID = 'melvorD:Cooking', header = 'Bonus ' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' XP', sign = '+', suffix = '%' },
bonusColMod = 'increasedSkillXP'
{ name = 'increasedChancePerfectCookFire', header = 'Fire Perfect Cook Chance', sign ='+', suffix = '%' },
bonusColName = 'Bonus ' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' XP'
{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
else
{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
bonusColMod = 'increasedChanceToDoubleItemsSkill'
{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' }
bonusColName = 'Double Items Chance'
},
end
['Furnace'] = {
local modsPerfectChance = {'increasedChancePerfectCookFire', 'increasedChancePerfectCookFurnace',
{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
                      'increasedChancePerfectCookPot', 'increasedChancePerfectCookGlobal'}
{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
local totalBonusVal, totalPerfectChance = 0, 0
{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' },
local utilityList = p.getPurchases(function(cat, purch) return cat == 'SkillUpgrades' and string.find(purch.name, category .. '$') ~= nil end)
{ name = 'increasedChanceAdditionalSkillResource', skillID = 'melvorD:Cooking', header = '+1 Item Chance', sign = '+', suffix = '%' }
local resultPart = {}
},
['Pot'] = {
    -- Table header
{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
    table.insert(resultPart, '{| class="wikitable stickyHeader"')
{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
    table.insert(resultPart, '|- class="headerRow-0"')
{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' },
    table.insert(resultPart, '!colspan="4"| !!colspan="2"|' .. bonusColName .. '!!colspan="2"|Bonus Perfect Chance')
{ name = 'increasedChanceAdditionalSkillResource', skillID = 'melvorD:Cooking', header = '+1 Item Chance', sign = '+', suffix = '%' }
    table.insert(resultPart, '|- class="headerRow-1"')
}
    table.insert(resultPart, '!colspan="2"|Name!!Level!!Cost' .. string.rep('!!This ' .. categoryShort .. '!!Total', 2))
}
   
 
    -- Row for each upgrade
return p.getToolTable(categoryShort, categoryShort .. '$', modifiers[category], nil)
    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 19:23, 27 December 2022

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

local p = {}

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

-- Overrides for various items, mostly relating to icon overrides
local purchOverrides = {
	["Extra Bank Slot"] = { icon = {'Bank Slot', 'upgrade'}, link = 'Bank Slot' },
	-- 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 },
	["Golbin Crate"] = { icon = {'Golbin Crate', 'upgrade'}, link = nil }
}

function p.getPurchase(purchaseName)
    local purchList = p.getPurchases(function(purch) return p._getPurchaseName(purch) == purchaseName end)
    if purchList ~= nil and not Shared.tableIsEmpty(purchList) then
        return purchList[1]
    end
end

function p.getPurchaseByID(id)
	return GameData.getEntityByID('shopPurchases', id)
end

function p.getPurchases(checkFunc)
    return GameData.getEntities('shopPurchases', checkFunc)
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.purchaseRequirements)
	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)
	elseif stat == 'description' then
		return p._getPurchaseDescription(purchase)
	elseif stat =='expansionicon' then
		return p._getPurchaseExpansionIcon(purchase)
	else
		return purchase[stat]
	end
end

function p.getPurchaseStat(frame)
	local args = frame.args ~= nil and frame.args or frame
	local purchaseName = Shared.fixPagename(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(purch) return p._getPurchaseName(purch) == purchaseName end)
	else
		purchaseList = {p.getPurchase(purchaseName)}
	end

	if Shared.tableIsEmpty(purchaseList) then
		return Shared.printError("Couldn't find purchase with name '" .. purchaseName .. "'")
	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._getPurchaseName(purch)
    if purch.customName ~= nil then
        return purch.customName
    elseif purch.contains ~= nil then
    	local item = nil
        if purch.contains.items ~= nil and not Shared.tableIsEmpty(purch.contains.items) then
            item = Items.getItemByID(purch.contains.items[1].id)
        elseif purch.contains.itemCharges ~= nil and not Shared.tableIsEmpty(purch.contains.itemCharges) then
        	item = Items.getItemByID(purch.contains.itemCharges.id)
        end
        if item ~= nil then
            return item.name
        end
        if purch.contains.petID ~= nil then
            local pet = Pets.getPetByID(purch.contains.petID)
            if pet ~= nil then
                return pet.name
            end
        end
    end
    return ''
end

function p._getPurchaseExpansionIcon(purch)
    if purch.id ~= nil then
        return Icons.getExpansionIcon(purch.id)
    elseif purch.contains ~= nil then
    	local item = nil
        if purch.contains.items ~= nil and not Shared.tableIsEmpty(purch.contains.items) then
            return Icons.getExpansionIcon(purch.contains.items[1].id)
        elseif purch.contains.itemCharges ~= nil and not Shared.tableIsEmpty(purch.contains.itemCharges) then
        	return Icons.getExpansionIcon(purch.contains.itemCharges.id)
        end
        
        if purch.contains.petID ~= nil then
			return Icons.getExpansionIcon(purch.contains.petID)
        end
    end
    return ''
end

function p._getPurchaseDescription(purch)
	if purch.customDescription ~= nil then
		local templateData = p._getPurchaseTemplateData(purch)
		return Shared.applyTemplateData(purch.customDescription, templateData)
	elseif purch.contains ~= nil then
		local item = nil
		if purch.contains.modifiers ~= nil then
			return Constants.getModifiersText(purch.contains.modifiers, false)
		elseif purch.contains.petID ~= nil then
            local pet = Pets.getPetByID(purch.contains.petID)
            return Pets._getPetEffect(pet)
		elseif purch.contains.items ~= nil and Shared.tableCount(purch.contains.items) == 1 then
			item = Items.getItemByID(purch.contains.items[1].id)
		elseif purch.contains.itemCharges ~= nil then
			item = Items.getItemByID(purch.contains.itemCharges.id)
		end
		if item ~= nil then
			if item.customDescription ~= nil then
				return item.customDescription
			elseif item.modifiers ~= nil then
				return Constants.getModifiersText(item.modifiers, false)
			end
		end
	end
	return ''
end

function p.getCostString(cost, inline)
	local displayInline = (inline ~= nil and inline or false)
	local costArray = {}
    local currencies = {'gp', 'slayerCoins', 'raidCoins'}
    for i, currency in ipairs(currencies) do
        if cost[currency] ~= nil then
            local costStr = p.getCurrencyCostString(cost[currency], currency)
            if costStr ~= nil then
                table.insert(costArray, costStr)
            end
        end
    end
	if cost.items ~= nil and not Shared.tableIsEmpty(cost.items) then
        local itemArray = {}
		for i, itemCost in ipairs(cost.items) do
			local item = Items.getItemByID(itemCost.id)
            if item ~= nil then
			    table.insert(itemArray, Icons.Icon({item.name, type="item", notext=(not displayInline and true or nil), qty=itemCost.quantity}))
            end
		end
        if not Shared.tableIsEmpty(itemArray) then
            table.insert(costArray, table.concat(itemArray, ', '))
        end
	end

    if not Shared.tableIsEmpty(costArray) then
        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

-- Generates description template data. See: shop.js, getDescriptionTemplateData()
function p._getPurchaseTemplateData(purchase)
	-- qty is a static value of 1 for Bank slots
	local templateData = { qty = 1 }
	if purchase.contains ~= nil and purchase.contains.items ~= nil then
		for i, itemDef in ipairs(purchase.contains.items) do
			templateData['qty' .. i] = itemDef.quantity
		end
	end
	return templateData
end

function p.getCurrencyCostString(cost, currency)
    local decoratorList = {
        ["gp"] = Icons.GP,
        ["slayerCoins"] = Icons.SC,
        ["raidCoins"] = Icons.RC
    }
    local decorator = nil
    if currency ~= nil then
        decorator = decoratorList[currency]
    end
    if decorator == nil then
        decorator = function(cost) return cost end
    end

    if cost.type == 'BankSlot' then
        -- Unusual bit of code that basically evaluates wikitext '<math>C_b</math>*'
        return mw.getCurrentFrame():callParserFunction('#tag:math', {'C_b'}) .. '*'
    elseif cost.type == 'Linear' and (cost.initial > 0 or cost.scaling > 0) then
        return decorator(cost.initial) .. '<br/>+' .. decorator(cost.scaling) .. ' for each purchase'
    elseif cost.type == 'Glove' or cost.type == 'Fixed' and cost.cost > 0 then
        -- Type Glove exists in game so the Merchan's Permit cost reduction can be applied,
        -- it makes no difference here
        return decorator(cost.cost)
    end
end

function p.getRequirementString(reqs)
	if reqs == nil or Shared.tableIsEmpty(reqs) then
		return 'None'
	end

	local reqArray = {}
    for i, req in ipairs(reqs) do
        if req.type == 'SkillLevel' then
            local skillName = Constants.getSkillName(req.skillID)
            if skillName ~= nil then
                table.insert(reqArray, Icons._SkillReq(skillName, req.level))
            end
        elseif req.type == 'DungeonCompletion' then
            local dung = GameData.getEntityByID('dungeons', req.dungeonID)
            if dung ~= nil then
                local dungStr = 'Complete ' .. Icons.Icon({dung.name, type='dungeon'})
                if req.count > 1 then
                    dungStr = dungStr .. ' ' .. Shared.formatnum(req.count) .. ' times'
                end
                table.insert(reqArray, dungStr)
            end
        elseif req.type == 'SlayerTask' then
            table.insert(reqArray, 'Complete ' .. Shared.formatnum(req.count) .. ' ' .. req.tier .. ' Slayer Tasks')
        elseif req.type == 'TownshipTask' then
            table.insert(reqArray, 'Complete ' .. Shared.formatnum(req.count) .. ' Township Tasks')
        elseif req.type == 'TownshipBuilding' then
            local tsData = GameData.getSkillData('melvorD:Township')
            if tsData ~= nil and tsData.buildings ~= nil then
                local building = GameData.getEntityByID(tsData.buildings, req.buildingID)
                if building ~= nil then
                    table.insert(reqArray, 'Have ' .. Shared.formatnum(req.count) .. ' ' .. building.name .. ' actively built in Township')
                end
            end
        elseif req.type == 'ShopPurchase' then
        	local shopPurch = p.getPurchaseByID(req.purchaseID)
        	if shopPurch ~= nil then
        		table.insert(reqArray, p._getPurchaseIcon({shopPurch}) .. ' Purchased')
        	end
        elseif req.type == 'Completion' then
            local ns = GameData.getEntityByName('namespaces', req.namespace)
            if ns ~= nil then
                table.insert(reqArray, req.percent .. '% ' .. ns.displayName .. ' Completion')
            end
        elseif req.type == 'AllSkillLevels' then
            local reqText = 'Level ' .. req.level .. ' in all skills'
            if req.exceptions ~= nil and not Shared.tableIsEmpty(req.exceptions) then
                local exceptSkills = {}
                for i, skillID in ipairs(req.exceptions) do
                    local skillName = Constants.getSkillName(skillID)
                    if skillName ~= nil then
                        table.insert(exceptSkills, Icons.Icon({skillName, type='skill'}))
                    end
                end
                reqText = reqText .. ' except for ' .. table.concat(exceptSkills, ', ')
            end
            table.insert(reqArray, reqText)
        else
            table.insert(reqArray, Shared.printError('Unknown requirement: ' .. (req.type or 'nil')))
        end
    end

    if Shared.tableIsEmpty(reqArray) then
        return 'None'
    else
	    return table.concat(reqArray, '<br/>')
    end
end

function p._getPurchaseType(purchase)
	if purchase.contains == nil then
		return 'Unknown'
	elseif purchase.contains.petID ~= nil then
		return 'Pet'
	elseif purchase.contains.itemCharges ~= nil then
		return 'Item'
	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 ~= nil then
		if purchase.contains.items ~= nil and not Shared.tableIsEmpty(purchase.contains.items) 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 ipairs(purchase.contains.items) do
				local item = Items.getItemByID(itemLine.id)
	            local itemQty = itemLine.quantity
				if asList then
					table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemQty}))
				else
					local GPVal = item.sellsFor * itemQty
					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, '|data-sort-value="'..item.name..'"|'.. Icons.getExpansionIcon(item.id) .. Icons.Icon({item.name, type='item', noicon=true}) .. '\r\n| data-sort-value="' .. itemQty .. '" style="text-align:right" | ' .. Shared.formatnum(itemQty))
					table.insert(containArray, '| data-sort-value="' .. GPVal .. '"| ' .. Icons.GP(GPVal))
				end
			end
		end
		if purchase.contains.itemCharges ~= nil and purchase.contains.itemCharges.quantity > 0 then
	        local gloveItem = Items.getItemByID(purchase.contains.itemCharges.id)
	        local chargeQty = purchase.contains.itemCharges.quantity
	        if gloveItem ~= nil then
	            if asList then
	                table.insert(containArray, '+'..Shared.formatnum(chargeQty)..' '..Icons.Icon({gloveItem.name, type='item'})..' Charges')
	            else
	                table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({gloveItem.name, type='item', notext=true, size='25'}))
	                table.insert(containArray, '| ' .. Icons.Icon({gloveItem.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. chargeQty .. '" style="text-align:right" | ' .. Shared.formatnum(chargeQty))
	                table.insert(containArray, '| data-sort-value="0"| ' .. Icons.GP(0))
	            end
	        end
		end
	end
	if not asList and not Shared.tableIsEmpty(containArray) 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 Shared.printError("Couldn't find purchase with name '" .. purchaseName .. "'")
	else
		return p._getPurchaseContents(purchase, asList)
	end
end

function p._getPurchaseBuyLimit(purchase, asList)
	if asList == nil then asList = true end
    local defaultLimit = (purchase.defaultBuyLimit == 0 and 'Unlimited') or Shared.formatnum(purchase.defaultBuyLimit)
    if purchase.buyLimitOverrides == nil or Shared.tableIsEmpty(purchase.buyLimitOverrides) then
        -- Same limit for all game modes
        return defaultLimit
    else
        -- The limit varies depending on game mode
        local limitTable = {}
        local gamemodeHasIcon = { 'melvorF:Hardcore', 'melvorF:Adventure' }
        for i, buyLimit in ipairs(purchase.buyLimitOverrides) do
            local gamemode = GameData.getEntityByID('gamemodes', buyLimit.gamemodeID)
            if gamemode ~= nil then
                local gamemodeText = nil
                if Shared.contains(gamemodeHasIcon, gamemode.id) then
                    gamemodeText = Icons.Icon({gamemode.name, notext=(not asList or nil)})
                else
                    gamemodeText = '[[Game Mode#' .. gamemode.name .. '|' .. gamemode.name .. ']]'
                end
                local limitText = (buyLimit.maximum == 0 and 'Unlimited') or Shared.formatnum(buyLimit.maximum)
                table.insert(limitTable, limitText .. (asList and ' for ' or ' ') .. gamemodeText)
            end
        end
        table.insert(limitTable, defaultLimit .. (asList and ' for ' or ' ') .. 'All other game modes')
        return table.concat(limitTable, (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 Shared.printError("Couldn't find purchase with name '" .. purchaseName .. "'")
	else
		return p._getPurchaseBuyLimit(purchase, asList)
	end
end

-- Accept similar arguments to Icons.Icon
function p._getPurchaseIcon(iconArgs)
	local purchase = iconArgs[1]
    local purchaseName = p._getPurchaseName(purchase)
	local override = purchOverrides[purchaseName]
	local purchType = p._getPurchaseType(purchase)
	-- Amend iconArgs before passing to Icons.Icon()
	iconArgs[1] = ((override ~= nil and override.icon[1]) or purchaseName)
	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 = Shared.fixPagename(args[1])
	local purchase = p.getPurchase(purchaseName)

	if purchase == nil then
		return Shared.printError("Couldn't find purchase with name '" .. tostring(purchaseName) .. "'")
	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.type == 'BankSlot' then
            return -1
        elseif costAmt.type == 'Linear' then
            return costAmt.initial
        elseif costAmt.type == 'Glove' or costAmt.type == 'Fixed' and costAmt.cost > 0 then
            return costAmt.cost
        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, stickyHeader = {}, 'Purchase', nil, {}, true

	-- 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
		-- Sticky header class
		if options.stickyHeader ~= nil then
			if type(options.stickyHeader) == 'boolean' then
				stickyHeader = options.stickyHeader
			elseif type(options.stickyHeader) == 'string' and string.lower(options.stickyHeader) == 'false' then
				stickyHeader = false
			end
		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 and ' stickyHeader' or '') .. '"')
	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

	if sortOrder == nil then
		Purchases = GameData.sortByOrderTable(Purchases, GameData.rawData.shopDisplayOrder, true)
	else
		table.sort(Purchases, sortOrder)
	end
	for i, purchase in ipairs(Purchases) do
        local purchName = p._getPurchaseName(purchase)
        local purchExpIcon = p._getPurchaseExpansionIcon(purchase)
		local purchOverride = nil
		if purchOverrides ~= nil then
			purchOverride = purchOverrides[purchName]
		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
		end

        local purchSortName = purchName
		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, '| data-sort-value="'..purchSortName..'"|'..purchExpIcon .. purchName)
			elseif column == 'Type' then
				table.insert(resultPart, '| ' .. purchType)
			elseif column == 'Description' then
				table.insert(resultPart, '| ' .. p._getPurchaseDescription(purchase))
			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.purchaseRequirements))
			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'
--	 stickyHeader:   Specifies if the table will have a sticky header or not
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.stickyHeader ~= nil then options.stickyHeader = frame.args.stickyHeader 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 = GameData.getEntityByName('shopCategories', cat)
	if shopCat == nil then
		return Shared.printError('Invalid category ' .. cat)
	else
        local catPurchases = p.getPurchases(function(purch) return purch.category == shopCat.id end)
		return p._getShopTable(catPurchases, options)
	end
end

function p.getItemCostArray(itemID)
    local purchaseArray = {}
    for i, purchase in ipairs(GameData.rawData.shopPurchases) do
        if purchase.cost ~= nil and purchase.cost.items ~= nil then
            for j, itemCost in ipairs(purchase.cost.items) do
                if itemCost.id == itemID then
                    table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = itemCost.quantity })
                    break
                end
            end
        end
    end
    return purchaseArray
end

function p.getItemSourceArray(itemID)
    local purchaseArray = {}
    for i, purchase in ipairs(GameData.rawData.shopPurchases) do
        if purchase.contains ~= nil then
        	if purchase.contains.items ~= nil then
	            for j, itemContains in ipairs(purchase.contains.items) do
	                if itemContains.id == itemID then
	                    table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = itemContains.quantity })
	                    break
	                end
	            end
	        end
	        if purchase.contains.itemCharges ~= nil and purchase.contains.itemCharges.id == itemID then
	        	table.insert(purchaseArray, { ["purchase"] = purchase, ["qty"] = 1 })
	        end
        end
    end
    return purchaseArray
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..' - '..p._getPurchaseExpansionIcon(purchase)..Icons.Icon({p._getPurchaseName(purchase), 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.purchaseRequirements)

	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 ipairs(purchaseArray) do
		table.insert(tableArray, p._getPurchaseTable(purchase.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 Shared.printError('No item named ' .. itemName .. ' exists in the data module')
	end

	return p._getItemShopTable(item)
end

function p.getShopMiscUpgradeTable()
	local purchList = p.getPurchases(function(purch) return (purch.category == 'melvorD:General' and string.find(purch.id, '^melvorD:Auto_Eat') == nil) or Shared.contains({'melvorTotH:SignOfTheStars', 'melvorTotH:SummonersAltar'}, purch.id) end)

	return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
end

function p._getShopSkillcapeTable(showSuperior)
	local categoryID = (showSuperior and 'melvorTotH:SuperiorSkillcapes') or 'melvorD:Skillcapes'
	local capeList = p.getPurchases(function(purch) return purch.category == categoryID end)
	local sortOrderFunc =
		function(a, b)
			local costA, costB = p._getPurchaseSortValue(a), p._getPurchaseSortValue(b)
			if costA == costB then
				return p._getPurchaseName(a) < p._getPurchaseName(b)
			else
				return costA < costB
			end
		end
	return p._getShopTable(capeList, {
			columns = { 'Purchase', 'Description', 'Cost' },
			purchaseHeader = 'Cape',
			sortOrder = sortOrderFunc,
			stickyHeader = false,
			headerProps = {["Purchase"] = 'colspan="2" style="width:200px;"', ["Cost"] = 'style=width:120px;'}
		})
end

function p.getShopSkillcapeTable(frame)
	local capeCategory = frame.args ~= nil and frame.args[1] or frame
	local showSuperior = string.lower(capeCategory) == 'superior'
	
	return p._getShopSkillcapeTable(showSuperior)
end

function p.getSkillcapeTable(frame)
	local skillName = frame.args ~= nil and frame.args[1] or frame
	local capeList = p.getPurchases(function(purch) return Shared.contains({'melvorD:Skillcapes', 'melvorTotH:SuperiorSkillcapes'}, purch.category) and string.find(p._getPurchaseName(purch), skillName) end)
	if Shared.tableIsEmpty(capeList) then
		return ''
	else
		local resultPart = {}
		table.insert(resultPart, '{| class="wikitable"\r\n')
		table.insert(resultPart, '!Skillcape!!Name!!Requirements!!Effect')
		for i, cape in ipairs(capeList) do
			local capeItem = Items.getItemByID(cape.contains.items[1].id)
			if capeItem ~= nil then
				table.insert(resultPart, '\r\n|-\r\n| ' .. Icons.Icon({capeItem.name, type='item', size='60', notext=true}))
				table.insert(resultPart, '\r\n| data-sort-value="'..capeItem.name..'"|'..Icons.getExpansionIcon(capeItem.id) .. Icons.Icon({capeItem.name, type='item', noicon=true}))
				table.insert(resultPart, '\r\n| ' .. p.getRequirementString(cape.purchaseRequirements))
				table.insert(resultPart, '\r\n| ' .. p._getPurchaseDescription(cape))
			end
		end
		
		table.insert(resultPart, '\r\n|}')
		return table.concat(resultPart)
	end
end

function p.getAutoEatTable()
	local resultPart = {}
	local purchasesAE = p.getPurchases(function(purch) return purch.category == 'melvorD:General' and string.find(purch.id, '^melvorD: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
        local purchaseName = p._getPurchaseName(purchase)
		-- 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

		table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. purchaseName .. '"| ' .. Icons.Icon({purchaseName, type='upgrade', size=50, notext=true}))
		table.insert(resultPart, '| ' .. Icons.Icon({purchaseName, 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="' .. p._getPurchaseSortValue(purchase) .. '" | ' .. p.getCostString(purchase.cost, false))
	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)
            for i, req in ipairs(reqs) do
                if req.type == 'DungeonCompletion' and string.find(req.dungeonID, 'God_Dungeon$') ~= nil then
                    return GameData.getEntityByID('dungeons', req.dungeonID)
                end
            end
        end

	local upgradeList = p.getPurchases(
		function(purch)
			if purch.category == 'melvorD:SkillUpgrades' and purch.purchaseRequirements ~= nil then
				return getGodDungeon(purch.purchaseRequirements) ~= nil
			end
			return false
		end)
	if Shared.tableIsEmpty(upgradeList) 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 upgradeName = p._getPurchaseName(upgrade)
		local dung = getGodDungeon(upgrade.purchaseRequirements)
		local costSortValue = p._getPurchaseSortValue(upgrade)
		table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. upgradeName .. '"| ' ..p._getPurchaseExpansionIcon(upgrade).. Icons.Icon({upgradeName, type='upgrade', size=50, notext=true}))
		table.insert(resultPart, '| ' .. Icons.Icon({upgradeName, type='upgrade', noicon=true}))
		table.insert(resultPart, '| ' .. p._getPurchaseDescription(upgrade))
		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.getToolTable(toolName, searchString, modifiers, skillID)
	local skillName = nil
	if type(skillID) == 'string' then
		skillName = Constants.getSkillName(skillID)
	end
	local toolArray = p.getPurchases(
		function(purch)
			return purch.category == 'melvorD:SkillUpgrades' and string.find(purch.id, searchString) ~= nil
		end)

	if Shared.tableIsEmpty(toolArray) then
		return ''
	end
	if modifiers == nil then
		modifiers = {}
	end

	local modTotal = {}
	for i, modDef in ipairs(modifiers) do
		modTotal[modDef.name] = 0
	end

	local headerRowSpan = (Shared.tableIsEmpty(toolArray) and 1) or 2
	local resultPart = {}
	table.insert(resultPart, '{| class="wikitable"')
	table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '" colspan="2"| Name')
	table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '"| ' .. (skillName == nil and 'Requirements' or Icons.Icon({skillName, type='skill', notext=true}) .. ' Level'))
	table.insert(resultPart, '\n!rowspan="' .. headerRowSpan .. '"| Cost')
	for i, modDef in ipairs(modifiers) do
		modTotal[modDef.name] = 0
		table.insert(resultPart, '\n!colspan="2"| ' .. modDef.header)
	end
	if headerRowSpan > 1 then
		table.insert(resultPart, '\n|-' .. string.rep('\n!This ' .. toolName .. '\n!Total', Shared.tableCount(modifiers)))
	end

	for i, tool in ipairs(toolArray) do
		local toolName = p._getPurchaseName(tool)
		local toolCost = p.getCostString(tool.cost, false)
		local toolCostSort = p._getPurchaseSortValue(tool) or 0
		table.insert(resultPart, '\n|-')
		table.insert(resultPart, '\n|style="min-width:25px" data-sort-value="' .. toolName .. '"| ' .. Icons.Icon({toolName, type='upgrade', size='50', notext=true}))
		table.insert(resultPart, '\n| data-sort-value="' .. toolName.. '"|' .. Icons.getExpansionIcon(tool.id) .. toolName)
		local level, levelStyle = nil, nil
		if skillID == nil then
			level = 'None'
			levelStyle = '|class="table-na"'
		else
			level = 1
			levelStyle = '|style="text-align:right"'
		end
		if tool.purchaseRequirements ~= nil and not Shared.tableIsEmpty(tool.purchaseRequirements) then
			if skillID == nil then
				-- Return all requirements
				level = p.getRequirementString(tool.purchaseRequirements)
				if level ~= 'None' then
					levelStyle = ''
				end
			else
				-- Return level requirement for just the specified skill
				for i, purchReq in ipairs(tool.purchaseRequirements) do
					if purchReq.type == 'SkillLevel' and purchReq.skillID == skillID then
						level = purchReq.level
						break
					end
				end
			end
		end
		table.insert(resultPart, '\n' .. levelStyle .. '| '..level)
		table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. toolCostSort .. '"| ' .. toolCost)

		local cellStart = '\n|style="text-align:right"'
		if tool.contains ~= nil and tool.contains.modifiers ~= nil then
			for j, modDef in ipairs(modifiers) do
				local modName = modDef.name
				local modVal = tool.contains.modifiers[modName]
				if modVal ~= nil then
					if type(modVal) == 'table' and type(modVal[1]) == 'table' and modVal[1].skillID ~= nil and (modDef.skillID == nil or modDef.skillID == modVal[1].skillID) then
						modVal = modVal[1].value
					end
					modTotal[modName] = modTotal[modName] + modVal
				else
					modVal = 0
				end
				local cellStartVal = cellStart .. ((modVal == 0 and ' class="table-na"') or '')
				local cellStartTot = cellStart .. ((modTotal[modName] == 0 and ' class="table-na"') or '')
				table.insert(resultPart, cellStartVal .. '| ' .. (modVal == 0 and '' or modDef.sign) .. modVal .. modDef.suffix)
				table.insert(resultPart, cellStartTot .. '| ' .. (modTotal[modName] == 0 and '' or modDef.sign) .. modTotal[modName] .. modDef.suffix)
			end
		end
	end

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

function p.getAxeTable(frame)
	local modifiers = {
		{ name = 'decreasedSkillIntervalPercent', header = 'Cut Time Decrease', sign = '-', suffix = '%' },
		{ name = 'increasedChanceToDoubleItemsSkill', header = 'Double Items Chance', sign = '+', suffix = '%' },
		{ name = 'increasedBirdNestDropRate', header = Icons.Icon({'Bird Nest', 'Drop Chance', type='item', nolink=true}), sign = '+', suffix = '%' },
		{ name = 'increasedChanceForAshInWoodcutting', header = Icons.Icon({'Ash', 'Drop Chance', type='item', nolink=true}), sign = '+', suffix = '%' }
		}
	
	return p.getToolTable('Axe', '_Axe$', modifiers, 'melvorD:Woodcutting')
end

function p.getPickaxeTable(frame)
	local modifiers = {
		{ name = 'decreasedSkillIntervalPercent', header = 'Mining Time Decrease', sign = '-', suffix = '%' },
		{ name = 'increasedChanceToDoubleOres', header = '2x Ore Chance', sign = '+', suffix = '%' },
		{ name = 'increasedChanceForOneExtraOre', header = '+1 Ore Chance', sign = '+', suffix = '%' },
		{ name = 'increasedChanceForQualitySuperiorGem', header = 'Superior Gem Chance', sign = '+', suffix = '%' },
		{ name = 'increasedMeteoriteOre', header = 'Increased ' .. Icons.Icon({'Meteorite Ore', type='item', notext=true}), sign = '+', suffix = '' }
		}

	return p.getToolTable('Pickaxe', '_Pickaxe$', modifiers, 'melvorD:Mining')
end

function p.getRodTable(frame)
	local modifiers = {
		{ name = 'decreasedSkillIntervalPercent', header = 'Catch Time Decrease', sign = '-', suffix = '%' },
		{ name = 'increasedChanceForOneExtraFish', header = '+1 Fish Chance', sign = '+', suffix = '%' },
		{ name = 'increasedChanceToFindLostChest', header = Icons.Icon({'Lost Chest', type='item', notext=true}) .. ' Chance', sign = '+', suffix = '%' },
		{ name = 'increasedFishingCookedChance', header = 'Cooked Fish Chance', sign = '+', suffix = '%' }
		}

	return p.getToolTable('Rod', '_Rod$', modifiers, 'melvorD:Fishing')
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 Shared.printError('Invalid category specified. Must be one of the following: ' .. mw.text.listToText(validCategories, ', ', ' or '))
	end

	local categoryShort = string.match(category, '[^%s]+$')
	local modifiers = {
		['Cooking Fire'] = {
			{ name = 'increasedSkillXP', skillID = 'melvorD:Cooking', header = 'Bonus ' .. Icons.Icon({'Cooking', type='skill', notext=true}) .. ' XP', sign = '+', suffix = '%' },
			{ name = 'increasedChancePerfectCookFire', header = 'Fire Perfect Cook Chance', sign ='+', suffix = '%' },
			{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
			{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
			{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' }
		},
		['Furnace'] = {
			{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
			{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
			{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' },
			{ name = 'increasedChanceAdditionalSkillResource', skillID = 'melvorD:Cooking', header = '+1 Item Chance', sign = '+', suffix = '%' }
		},
		['Pot'] = {
			{ name = 'increasedChanceToDoubleItemsSkill', skillID = 'melvorD:Cooking', header = '2x Items Chance', sign = '+', suffix = '%' },
			{ name = 'decreasedPassiveCookInterval', header = 'Passive Cook Time Decrease', sign = '-', suffix = '%' },
			{ name = 'decreasedSkillIntervalPercent', skillID = 'melvorD:Cooking', header = 'Active Cook Time Decrease', sign = '-', suffix = '%' },
			{ name = 'increasedChanceAdditionalSkillResource', skillID = 'melvorD:Cooking', header = '+1 Item Chance', sign = '+', suffix = '%' }
		}
	}

	return p.getToolTable(categoryShort, categoryShort .. '$', modifiers[category], nil)
end

return p