Anonymous

Module:Shop: Difference between revisions

From Melvor Idle
11,738 bytes added ,  17 November 2021
getShopMiscUpgradeTable: Include requirements column
(_getShopTable: Overhaul to allow for greater control over output format; getShopSkillcapeTable: Use _getShopTable; getShopMiscUpgradeTable: Initial implementation)
(getShopMiscUpgradeTable: Include requirements column)
(10 intermediate revisions by the same user not shown)
Line 2: Line 2:


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


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


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


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


Line 39: Line 105:
   end
   end


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


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


   return table.concat(reqArray, '<br/>')
   return table.concat(reqArray, '<br/>')
end
function p._getPurchaseType(purchase)
  if purchase.contains == nil then
    return 'Unknown'
  elseif purchase.contains.pet ~= nil then
    return 'Pet'
  elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
    return 'Upgrade'
  elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
    return 'Item Bundle'
  else
    return 'Item'
  end
end
function p._getPurchaseContents(purchase, asList)
  if asList == nil then asList = true end
  local containArray = {}
  if purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 0 then
    if not asList then
      table.insert(containArray, '{| class="wikitable sortable stickyHeader"')
      table.insert(containArray, '|- class="headerRow-0"')
      table.insert(containArray, '! colspan="2" | Item !! Quantity')
    end
    for i, itemLine in Shared.skpairs(purchase.contains.items) do
      local item = Items.getItemByID(itemLine[1])
      if asList then
        table.insert(containArray, Icons.Icon({item.name, type='item', qty=itemLine[2]}))
      else
        table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({item.name, type='item', notext=true, size='25'}))
        table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. '\r\n| data-sort-value="' .. itemLine[2] .. '" style="text-align:right" | ' .. Shared.formatnum(itemLine[2]))
      end
    end
  end
  if purchase.charges ~= nil and purchase.charges > 0 then
    if asList then
      table.insert(containArray, '+'..purchase.charges..' '..Icons.Icon({purchase.name, type='item'})..' Charges')
    else
      table.insert(containArray, '|-\r\n| style="min-width:25px"| ' .. Icons.Icon({purchase.name, type='item', notext=true, size='25'}))
      table.insert(containArray, '| ' .. Icons.Icon({item.name, type='item', noicon=true}) .. ' Charges\r\n| data-sort-value="' .. purchase.charges .. '" style="text-align:right" | ' .. Shared.formatnum(purchase.charges))
    end
  end
  if not asList and Shared.tableCount(containArray) > 0 then table.insert(containArray, '|}') end
  local delim = (asList and '<br/>' or '\r\n')
  return table.concat(containArray, delim)
end
function p.getPurchaseContents(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local asList = ((args[2] ~= nil and args[2] == 'true') or false)
  local purchase = p.getPurchase(purchaseName)
  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    return p._getPurchaseContents(purchase, asList)
  end
end
-- Accept similar arguments to Icons.Icon
function p._getPurchaseIcon(iconArgs)
  local purchase = iconArgs[1]
  local override = purchOverrides[purchase.name]
  local purchType = p._getPurchaseType(purchase)
  -- Amend iconArgs before passing to Icons.Icon()
  iconArgs[1] = ((override ~= nil and override.icon[1]) or purchase.name)
  if override ~= nil then
    iconArgs['type'] = override.icon[2]
    if override.link == nil then
      iconArgs['nolink'] = true
    end
  else
    iconArgs['type'] = (purchType == 'Item Bundle' and 'item') or string.lower(purchType)
  end
  return Icons.Icon(iconArgs)
end
function p.getPurchaseIcon(frame)
  local args = frame.args ~= nil and frame.args or frame
  local purchaseName = args[1]
  local purchase = p.getPurchase(purchaseName)
  if purchase == nil then
    return "ERROR: Couldn't find purchase with name '" .. purchaseName .. "'[[Category:Pages with script errors]]"
  else
    args[1] = purchase
    return p._getPurchaseIcon(args)
  end
end
end


Line 106: Line 269:
   local headerPropsDefault = {
   local headerPropsDefault = {
     ["Purchase"] = 'colspan="2"',
     ["Purchase"] = 'colspan="2"',
     ["Cost"] = 'style="min-width:90px"'
     ["Cost"] = 'style="min-width:100px"'
   }
   }
   local usedColumns, purchHeader, sortOrder, headerProps = {}, 'Purchase', nil, {}
   local usedColumns, purchHeader, sortOrder, headerProps = {}, 'Purchase', nil, {}
Line 141: Line 304:
   end
   end


  -- Various overrides for certain shop items
  local purchOverrides = {
    ["Extra Bank Slot"] = { icon = {'Bank Slot', 'upgrade'}, link = 'Bank Slot', cost = Icons.Icon({'Coins', size = 25, notext = true}) .. ' <math>C_b</math>*' },
    -- 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 }
  }
   -- Begin output generation
   -- Begin output generation
   local resultPart = {}
   local resultPart = {}
Line 178: Line 327:
     end
     end


     local purchType = 'Item'
     local purchType = p._getPurchaseType(purchase)
    if purchase.contains.pet ~= nil then
      purchType = 'Pet'
    elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
      purchType = 'Upgrade'
    elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
      purchType = 'Item Bundle'
    end
 
    local iconName = purchase.name
    local iconType = (purchType == 'Item Bundle' and 'item' or string.lower(purchType))
     local iconNoLink = nil
     local iconNoLink = nil
     local purchLink = ''
     local purchLink = ''
     local costString = p.getCostString(purchase.cost)
     local costString = p.getCostString(purchase.cost, false)
     if purchOverride ~= nil then
     if purchOverride ~= nil then
      if purchOverride.icon ~= nil then
        iconName = purchOverride.icon[1]
        iconType = purchOverride.icon[2]
      end
       if purchOverride.link == nil then
       if purchOverride.link == nil then
         iconNoLink = true
         iconNoLink = true
Line 211: Line 346:
     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"|' .. Icons.Icon({iconName, type=iconType, notext=true, nolink=iconNoLink, 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, '| ' .. purchName)
         table.insert(resultPart, '| ' .. purchName)
       elseif column == 'Type' then
       elseif column == 'Type' then
Line 235: Line 371:
end
end


-- getShopTable parameter definition:
--  columns:        Comma separated values indicating which columns are to be included & the order
--                  in which they are displayed.
--                  Values can be any of: Purchase, Type, Description, Cost, Requirements
--  columnProps:    Comma separated values indicating formatting to be applied to each column. Each
--                  value must be in the format column:property, e.g. Purchase:colspan="2"
--  sortOrder:      A function determining the order in which table items appear
--  purchaseHeader: Specifies header text for the Purchase column if not 'Purchase'
function p.getShopTable(frame)
function p.getShopTable(frame)
   local cat = frame.args ~= nil and frame.args[1] or frame
   local cat = frame.args ~= nil and frame.args[1] or frame
Line 242: Line 386:
     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
      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
   end
   local shopCat = ShopData.Shop[cat]
   local shopCat = ShopData.Shop[cat]
Line 313: Line 468:


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


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


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


   result = result..'\r\n|}'
   result = result..'\r\n|}'
Line 358: Line 503:
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(cat, purch) return cat == 'General' and string.find(purch.name, '^Auto Eat') == nil end)
   return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost' }, purchaseHeader = 'Upgrade' })
   return p._getShopTable(purchList, { columns = { 'Purchase', 'Description', 'Cost', 'Requirements' }, purchaseHeader = 'Upgrade' })
end
end


Line 398: Line 543:
     local costAmt = p._getPurchaseSortValue(purchase)
     local costAmt = p._getPurchaseSortValue(purchase)
     table.insert(resultPart, '|-\r\n|style="min-width:25px; text-align:center;" data-sort-value="' .. purchase.name .. '"| ' .. Icons.Icon({purchase.name, type='upgrade', size=50, notext=true}))
     table.insert(resultPart, '|-\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, '| [[' .. purchase.name .. ']]')
     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)) .. '%')
Line 407: Line 552:


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


return p
return p