Module:ModifierTables: Difference between revisions

From Melvor Idle
(Sort modifier tables alphabetically by default if more than one argument is passed in)
(Add Mastery Pool Bonuses to Modifier Tables)
 
(74 intermediate revisions by 6 users not shown)
Line 1: Line 1:
--Module that constructs tables for all things that can affect Player Modifiers
--Module that constructs tables for all things that can affect Player Modifiers
--This includes Agility, Equipment, and Pets right now
--This includes Agility, Equipment, Pets, Prayers, and Constellations right now


local p = {}
local p = {}
Line 6: Line 6:
local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local Num = require('Module:Number')
local Icons = require('Module:Icons')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local SkillTree = require('Module:SkillTree')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Pets = require('Module:Pets')
local Pets = require('Module:Pets')
local Items = require('Module:Items')
local Skills = require('Module:Skills')
local Agility = require('Module:Skills/Agility')
local Agility = require('Module:Skills/Agility')
local Shop = require('Module:Shop')
local Cartography = require('Module:Skills/Cartography')
local Icons = require('Module:Icons')
local Prayer = require('Module:Prayer')
local Summoning = require('Module:Skills/Summoning')
local Township = require('Module:Township')
 


--First up, functions to get all the things in a category that have a given modifier:
--First up, functions to get all the things in a category that have a given modifier:
function p.getModifierValue(modifiers, modifier, skill, getOpposites)
function p.getItemsWithModifier(modifierCriteria)
  --Sometimes nil modifier sets will get here, which is fine. Just return 0 immediately
local itemList = Items.getItems(
  if modifiers == nil then
function(item)
    return 0
if item.golbinRaidExclusive ~= nil and item.golbinRaidExclusive then
  end
return false
 
elseif item.modifiers == nil or Shared.tableIsEmpty(item.modifiers) then
  --Make sure we have the skillID and not the name
return false
  if skill == '' then
end
    skill = nil
local mods = Modifiers.getMatchingModifiers(item.modifiers, modifierCriteria)
  elseif type(skill) == 'string' then
return not Shared.tableIsEmpty(mods.matched)
    skill = Constants.getSkillID(skill)
end)
  end
return itemList
end


  --By default, attempt to add the increased and decreased prefixes to the modifier
function p.getSkillTreeNodesWithModifier(modifierCriteria)
  --But if getOpposites is false, only look for an exact match
    local nodesWithModifier = SkillTree.getSkillTreeNodes(
  local increaseMod, decreaseMod = '', ''
        function(node)
  if getOpposites == nil or getOpposites then
            if node.modifiers ~= nil then
    increaseMod = 'increased'..modifier
                local mods = Modifiers.getMatchingModifiers(node.modifiers, modifierCriteria)
    decreaseMod = 'decreased'..modifier
                if not Shared.tableIsEmpty(mods.matched) then
  else
                    return true
     increaseMod = modifier
                end
  end
            end
            return false
        end
     )
    return nodesWithModifier
end


  local increaseVal, decreaseVal = 0, 0
function p.getObstaclesWithModifier(modifierCriteria)
  if modifiers[increaseMod] ~= nil and modifiers[increaseMod] ~= nil then
local obstList = Agility.getObstacles(
    if type(modifiers[increaseMod]) == 'table' then
function(obst)
      for i, subVal in Shared.skpairs(modifiers[increaseMod]) do
if obst.modifiers ~= nil then
        if subVal[1] == skill then
local mods = Modifiers.getMatchingModifiers(obst.modifiers, modifierCriteria)
          increaseVal = subVal[2]
return not Shared.tableIsEmpty(mods.matched)
        end
end
      end
return false
    else
end)
      increaseVal = modifiers[increaseMod]
return obstList
    end
end
  end
 
function p.getConstellationsWithModifier(modifierCriteria)
local consList = Skills.getConstellations(
function(cons)
local consMods = Skills._getConstellationModifiers(cons)
for modType, modsForType in pairs(consMods) do
local modData = {}
-- Compile player modifiers
for _, mods in ipairs(modsForType) do
if mods.modifiers ~= nil then
for modKey, modDefn in pairs(mods.modifiers) do
if type(modData[modKey]) == 'table' and modData[modKey][1] ~= nil then
for _, v in ipairs(modDefn) do
table.insert(modData[modKey], v)
end
else
modData[modKey] = modDefn
end
end
end
end
local mods = Modifiers.getMatchingModifiers(modData, modifierCriteria)
if not Shared.tableIsEmpty(mods.matched) then
return true
end
end
return false
end)
return consList
end
 
 
function p.getPillarsWithModifier(modifierCriteria)
local pillarList = Agility.getPillars(
function(pillar)
if pillar.modifiers ~= nil then
local mods = Modifiers.getMatchingModifiers(pillar.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
end
return false
end)
return pillarList
end
 
function p.getPetsWithModifier(modifierCriteria)
local petList = Pets.getPets(
function(pet)
if pet.modifiers ~= nil then
local mods = Modifiers.getMatchingModifiers(pet.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
end
return false
end)
return petList
end
 
function p.getPrayersWithModifier(modifierCriteria)
local prayerList = Prayer.getPrayers(
function(prayer)
if prayer.modifiers ~= nil then
local mods = Modifiers.getMatchingModifiers(prayer.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
end
return false
end)
return prayerList
end
 
function p.getUpgradesWithModifier(modifierCriteria)
local upgradeList = Shop.getPurchases(
function(purchase)
if purchase.category == 'melvorD:GolbinRaid'
or purchase.contains == nil
or purchase.contains.modifiers == nil
or Shared.tableIsEmpty(purchase.contains.modifiers) then
return false
end
local mods = Modifiers.getMatchingModifiers(purchase.contains.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
end)
return upgradeList
end
 
function p.getPOIsWithModifier(modifierCriteria)
local POIList = Cartography.getPointsOfInterest(
function(POI)
if POI.activeStats == nil or POI.activeStats.modifiers == nil then
return false
end
local mods = Modifiers.getMatchingModifiers(POI.activeStats.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
end)
return POIList
end


  if modifiers[decreaseMod] ~= nil and modifiers[decreaseMod] ~= nil then
function p.getCartoMasteryBonusesWithModifier(modifierCriteria)
    if type(modifiers[decreaseMod]) == 'table' then
local bonusList = Cartography.getMasteryBonuses(
      for i, subVal in Shared.skpairs(modifiers[decreaseMod]) do
function(bonus)
        if subVal[1] == skill then
if bonus.modifiers == nil or Shared.tableIsEmpty(bonus.modifiers) then
          decreaseVal = subVal[2]
return false
        end
end
      end
local mods = Modifiers.getMatchingModifiers(bonus.modifiers, modifierCriteria)
    else
return not Shared.tableIsEmpty(mods.matched)
      decreaseVal = modifiers[decreaseMod]
end)
    end
  end


  return increaseVal - decreaseVal
return bonusList
end
end


function p.getItemsWithModifier(modifiers, skill, getOpposites)
function p.getAncientRelicsWithModifiers(modifierCriteria)
  if type(modifiers) == 'string' then
local matchedRelics = {}
    modifiers = {modifiers}
 
  end
for i, relic in ipairs(GameData.rawData.ancientRelics) do
  local itemList = Items.getItems(
if relic.modifiers ~= nil then
        function(item)
local mods = Modifiers.getMatchingModifiers(relic.modifiers, modifierCriteria)
          local result = false
if not Shared.tableIsEmpty(mods.matched) then
          for i, mod in Shared.skpairs(modifiers) do
table.insert(matchedRelics, relic)
            if p.getModifierValue(item.modifiers, mod, skill, getOpposites) ~= 0 then
end
              result = true
end
              break
end
            end
 
          end
return matchedRelics
          return result
        end)
  return itemList   
end
end


function p.getObstaclesWithModifier(modifiers, skill, getOpposites)
function p.getBuildingsWithModifiers(modifierCriteria)
  if type(modifiers) == 'string' then
local buildingList = Township.getBuildings(
    modifiers = {modifiers}
function(building)
  end
if building.modifiers == nil or Shared.tableIsEmpty(building.modifiers) then
  local obstList = Agility.getObstacles(
return false
        function(obst)
end
          local result = false
local mods = Modifiers.getMatchingModifiers(building.modifiers, modifierCriteria)
          for i, mod in Shared.skpairs(modifiers) do
return not Shared.tableIsEmpty(mods.matched)
            if p.getModifierValue(obst.modifiers, mod, skill, getOpposites) ~= 0 then
end)
              result = true
 
              break
return buildingList
            end
          end
          return result
        end)
  return obstList
end
end


function p.getPillarsWithModifier(modifiers, skill, getOpposites)
function p.getSeasonsWithModifiers(modifierCriteria)
  if type(modifiers) == 'string' then
local seasonList = Township.getSeasons(
    modifiers = {modifiers}
function(season)
  end
if season.modifiers == nil or Shared.tableIsEmpty(season.modifiers) then
  local pillarList = Agility.getPillars(
return false
        function(pillar)
end
          local result = false
local mods = Modifiers.getMatchingModifiers(season.modifiers, modifierCriteria)
          for i, mod in Shared.skpairs(modifiers) do
return not Shared.tableIsEmpty(mods.matched)
            if p.getModifierValue(pillar.modifiers, mod, skill, getOpposites) ~= 0 then
end)
              result = true
 
              break
return seasonList
            end
          end
          return result
        end)
  return pillarList
end
end


function p.getPetsWithModifier(modifiers, skill, getOpposites)
function p.getSummoningSynergiesWithModifiers(modifierCriteria)
  if type(modifiers) == 'string' then
local synergyList = Summoning.getSynergies(
    modifiers = {modifiers}
function(synergy)
  end
if synergy.modifiers == nil or Shared.tableIsEmpty(synergy.modifiers) then
  local petList = Pets.getPets(
return false
        function(pet)
end
          local result = false
local mods = Modifiers.getMatchingModifiers(synergy.modifiers, modifierCriteria)
          for i, mod in Shared.skpairs(modifiers) do
return not Shared.tableIsEmpty(mods.matched)
            if p.getModifierValue(pet.modifiers, mod, skill, getOpposites) ~= 0 then
end)
              result = true
 
              break
return synergyList
            end
          end
          return result
        end)
  return petList
end
end


function p.getUpgradesWithModifier(modifiers, skill, getOpposites)
function p.getMasteryPoolModifiers(modifierCriteria)
  if type(modifiers) == 'string' then
local masteryPoolList = {}
    modifiers = {modifiers}
  end
for skillName, skillData in pairs(SkillData) do
  local upgradeList = Shop.getPurchases(
if skillData.masteryPoolBonuses ~= nil then
        function(category, purchase)
for i, masteryPool in ipairs(skillData.masteryPoolBonuses) do
          local result = false
local mods = Modifiers.getMatchingModifiers(masteryPool.modifiers, modifierCriteria)
          for i, mod in Shared.skpairs(modifiers) do
if not Shared.tableIsEmpty(mods.matched) then
            if p.getModifierValue(purchase.contains.modifiers, mod, skill, getOpposites) ~= 0 then
table.insert(masteryPoolList, { [skillName] = masteryPool })
              result = true
end
              break
end
            end
end
          end
end
          return result
 
        end)
return masteryPoolList
return upgradeList
end
end


function p._getModifierTable(modifiers, skill, columnName, getOpposites)
function p._getModifierTable(modifiers, globalProps, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort, collapsed)
  local modifierNames = {}
local modifierIDs = {}
  if type(modifiers) == 'string' then
if type(modifiers) == 'string' then
    modifiers = {modifiers}
modifiers = {modifiers}
  end
end
  for i, modifier in pairs(modifiers) do
for i, modifier in ipairs(modifiers) do
    if getOpposites then
-- Ensure only the local ID is used in case the provided modifier ID is namespaced
      table.insert(modifierNames, 'increased'..modifier)
local modNS, modID = Shared.getLocalID(modifier.id)
      table.insert(modifierNames, 'decreased'..modifier)
-- Convert modifier & global property names to IDs
    else
local globalPropIDs = Modifiers.convertCriteriaNamesToIDs(globalProps or {})
      table.insert(modifierNames, modifier)
local modPropIDs = Modifiers.convertCriteriaNamesToIDs(modifier.props or {})
    end
-- Combine properties, mod props must be last as the highest priority
  end
local combinedProps = Modifiers.combineDataCriteria({ globalPropIDs, modPropIDs })
 
if Modifiers.getModifierByID(modID) ~= nil then
-- Provided ID is a modifier ID (as opposed to an alias)
table.insert(modifierIDs, {
["id"] = modID,
["type"] = 'id',
["props"] = combinedProps
})
else
-- Assume modID is a modifier alias
if getOpposites then
local incModAlias, decModAlias = 'increased' .. modID, 'decreased' .. modID
local incMod, decMod = Modifiers.getModifierByAlias(incModAlias), Modifiers.getModifierByAlias(decModAlias)
-- If neither alias resolves to a modifier, then let user know this is invalid
if incMod == nil and decMod == nil then
error('No such modifier alias: ' .. modID, 2)
end
-- Don't include increased or decreased variants if they don't resolve to a modifier,
-- as doing so will generate an error about an invalid alias later
if incMod ~= nil then
table.insert(modifierIDs, {
["id"] = incModAlias,
["type"] = 'alias',
["props"] = combinedProps
})
end
if decMod ~= nil then
table.insert(modifierIDs, {
["id"] = decModAlias,
["type"] = 'alias',
["props"] = combinedProps
})
end
else
table.insert(modifierIDs, {
["id"] = modID,
["type"] = 'alias',
["props"] = combinedProps
})
end
end
end
local modifierCriteria = Modifiers.getMatchCriteriaFromIDs(modifierIDs)
 
local hasOtherModifiers = false
local modifierCount = Shared.tableCount(modifiers)
 
local getModText =
function(modifiers)
local matchedMods = Modifiers.getMatchingModifiers(modifiers, modifierCriteria)
local mainModText = Modifiers.getModifiersText(matchedMods.matched, true, false, maxOtherMods)
local otherModText = Modifiers.getModifiersText(matchedMods.unmatched, true, false, maxOtherMods)
return mainModText, otherModText, matchedMods
end
 
local tableArray = {}
--Going through each type of thing to add to the array
 
local itemList = p.getItemsWithModifier(modifierCriteria)
for i, item in ipairs(itemList) do
local row = {}
row.name = item.name
row.icon = Icons.Icon({item.name, type='item'})
row.expIcon = Icons.getDLCColumnIcon(item.id)
row.expSort = Icons.getExpansionID(item.id)
row.type = 'Item'
row.typeText = row.type
--For equipment, show the slot they go in
if item.validSlots ~= nil and not Shared.tableIsEmpty(item.validSlots) then
local rowTypePart = {}
for j, slot in ipairs(item.validSlots) do
table.insert(rowTypePart, Common.getEquipmentSlotLink(slot))
end
row.typeText = row.typeText .. ' (' .. table.concat(item.validSlots, ', ') .. ')'
row.type = row.type .. ' (' .. table.concat(rowTypePart, ', ') .. ')'
end
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(item.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end


  local hasOtherModifiers = false
table.insert(tableArray, row)
  local modifierCount = Shared.tableCount(modifiers)
end


  if skill == '' then
local petList = p.getPetsWithModifier(modifierCriteria)
    skill = nil
for i, pet in Shared.skpairs(petList) do
  elseif type(skill) == 'string' then
local row = {}
    skill = Constants.getSkillID(skill)
row.name = pet.name
  end
row.icon = Icons.Icon({pet.name, type='pet'})
row.expIcon = Icons.getDLCColumnIcon(pet.id)
row.expSort = Icons.getExpansionID(pet.id)
row.type = '[[Pets|Pet]]'
row.typeText = 'Pet'


  local getModText =  
local objMods = nil
    function(modifiers)
row.modifierText, row.otherModifiers, objMods = getModText(pet.modifiers)
      local modTextArray = {}
row.val = Modifiers.getModifierValue(objMods.matched)
      local mainModText = {}
 
      for modName, modValue in Shared.skpairs(modifiers) do
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
        if Shared.contains(modifierNames, modName) then
hasOtherModifiers = true
          if type(modValue) == 'table' then
end
            for j, subVal in Shared.skpairs(modValue) do
 
              if subVal[1] == skill then
table.insert(tableArray, row)
                table.insert(mainModText, Constants._getModifierText(modName, subVal))
end
              else
                table.insert(modTextArray, Constants._getModifierText(modName, subVal))
local nodeList = p.getSkillTreeNodesWithModifier(modifierCriteria)
              end
for _, node in ipairs(nodeList) do
            end
    local row = {}
          else
    row.name = node.skillName .. " - " .. node.name
            table.insert(mainModText, Constants._getModifierText(modName, modValue))
   
          end
    local firstIcon = Icons.Icon({"", img='Skill Tree', nolink=true})
        else
    local secondIcon = Icons.Icon({node.skillName..'%23#Skill_Tree', node.name, type='skill', img=node.skillName})
          table.insert(modTextArray, Constants._getModifierText(modName, modValue))
    row.icon = firstIcon .. " " .. secondIcon
        end
   
      end
    row.expIcon = Icons.getDLCColumnIcon(node.id)
    row.expSort = Icons.getExpansionID(node.id)
    row.type = '[[Skill Tree#Nodes|Skill Tree Node]]'
    row.typeText = 'Skill Tree Node'
    local objMods = nil
    row.modifierText, row.otherModifiers, objMods = getModText(node.modifiers)
    row.val = Modifiers.getModifierValue(objMods.matched)
    if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
        hasOtherModifiers = true
    end
    table.insert(tableArray, row)
end
 
 
local obstList = p.getObstaclesWithModifier(modifierCriteria)
table.sort(obstList, function(a, b) return a.category < b.category end)
for i, obst in Shared.skpairs(obstList) do
local row = {}
row.name = obst.name
row.icon = Icons.Icon({'Agility%23'..string.gsub(obst.name, ' ', ''), obst.name, type='skill', img='Agility'})
row.expIcon = Icons.getDLCColumnIcon(obst.id)
row.expSort = Icons.getExpansionID(obst.id)
row.type = '[[Agility#Obstacles|Agility Obstacle '..tostring(tonumber(obst.category)+1)..']]'
row.typeText = 'Agility Obstacle '..string.format("%02d", (obst.category + 1))
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(obst.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
 
local pillarList = p.getPillarsWithModifier(modifierCriteria)
for i, pillar in ipairs(pillarList) do
local row = {}
row.name = pillar.name
row.icon = Icons.Icon({'Agility%23'..string.gsub(pillar.name, ' ', ''), pillar.name, type='skill', img='Agility'})
row.expIcon = Icons.getDLCColumnIcon(pillar.id)
row.expSort = Icons.getExpansionID(pillar.id)
row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
row.typeText = 'Agility Pillar'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(pillar.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
 
local prayerList = p.getPrayersWithModifier(modifierCriteria)
for i, prayer in ipairs(prayerList) do
local row = {}
row.name = prayer.name
row.icon = Icons.Icon({prayer.name, type='prayer'})
row.expIcon = Icons.getDLCColumnIcon(prayer.id)
row.expSort = Icons.getExpansionID(prayer.id)
row.type = [[Prayer]]
row.typeText = 'Prayer'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(prayer.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
 
local upgradeList = p.getUpgradesWithModifier(modifierCriteria)
for i, upgrade in ipairs(upgradeList) do
local row = {}
row.name = Shop._getPurchaseName(upgrade)
row.icon = Icons.Icon({row.name, type='upgrade'})
row.expIcon = Icons.getDLCColumnIcon(upgrade.id)
row.expSort = Icons.getExpansionID(upgrade.id)
row.type = '[[Shop|Upgrade]]'
row.typeText = 'Upgrade'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(upgrade.contains.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
 
local constellationList = p.getConstellationsWithModifier(modifierCriteria)
for i, cons in ipairs(constellationList) do
local modListByType = Skills._getConstellationModifiers(cons)
local modList = {}
-- Combine all mods into single list
for modType, modsForType in pairs(modListByType) do
for _, mod in ipairs(modsForType) do
if mod.modifiers ~= nil then
for modKey, modDefn in pairs(mod.modifiers) do
if type(modList[modKey]) == 'table' and modList[modKey][1] ~= nil then
for _, v in ipairs(modDefn) do
table.insert(modList[modKey], v)
end
else
modList[modKey] = modDefn
end
end
end
end
end
 
local row = {}
row.name = cons.name
row.icon = Icons.Icon({cons.name, type='constellation'})
row.expIcon = Icons.getDLCColumnIcon(cons.id)
row.expSort = Icons.getExpansionID(cons.id)
row.type = '[[Astrology#Constellations|Constellation]]'
row.typeText = 'Constellation'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(modList)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
local POIList = p.getPOIsWithModifier(modifierCriteria)
for i, POI in ipairs(POIList) do
local row = {}
row.name = POI.name
row.icon = Icons.Icon({POI.name, type='poi'})
row.expIcon = Icons.getDLCColumnIcon(POI.id)
row.expSort = Icons.getExpansionID(POI.id)
row.type = '[[Cartography|Point of Interest]]'
row.typeText = 'Point of Interest'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(POI.activeStats.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
 
table.insert(tableArray, row)
end
local cartoBonusList = p.getCartoMasteryBonusesWithModifier(modifierCriteria)
for i, bonus in ipairs(cartoBonusList) do
local row = {}
row.name = Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered'
row.icon = Icons.Icon({'Cartography', Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered', type='skill'})
row.expIcon = Icons.getDLCColumnIcon(bonus.id)
row.expSort = Icons.getExpansionID(bonus.id)
row.type = Icons.Icon({'Cartography', 'Mastery Bonus', section='Mastery Unlocks', type='skill', noicon=true})
row.typeText = 'Mastery Bonus'
 
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(bonus.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
 
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
table.insert(tableArray, row)
end


      return table.concat(mainModText, '<br/>'), table.concat(modTextArray, '<br/>')
local ancientRelicList = p.getAncientRelicsWithModifiers(modifierCriteria)
    end
for i, relic in ipairs(ancientRelicList) do
local row = {}
local relicName = relic.name
local sectionName = 'Melvor Realm'
if string.find(relic.id, 'Abyssal') ~= nil then
relicName = 'Abyssal ' .. relicName
sectionName = 'Abyssal Realm'
end
row.name = relicName
row.icon = Icons.Icon({'Ancient Relics', relicName, section=sectionName})
row.expIcon = Icons.getDLCColumnIcon(relic.id)
row.expSort = Icons.getExpansionID(relic.id)
row.type = Icons.Icon({'Ancient Relics', 'Ancient Relic', noicon=true})
row.typeText = 'Ancient Relic'


  local tableArray = {}
local objMods = nil
  --Going through each type of thing to add to the array
row.modifierText, row.otherModifiers, objMods = getModText(relic.modifiers)
  local itemList = p.getItemsWithModifier(modifiers, skill, getOpposites)
row.val = Modifiers.getModifierValue(objMods.matched)
  for i, item in Shared.skpairs(itemList) do
    local row = {}
    row.name = item.name
    row.icon = Icons.Icon({item.name, type='item'})
    row.type = 'Item'
    local totalVal = 0
    for i, mod in pairs(modifiers) do
      totalVal = totalVal + p.getModifierValue(item.modifiers, mod, skill, getOpposites)
    end
    row.val = totalVal


    row.modifierText, row.otherModifiers = getModText(item.modifiers)
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
table.insert(tableArray, row)
end


    if string.len(row.otherModifiers) > 0 then
local buildingList = p.getBuildingsWithModifiers(modifierCriteria)
      hasOtherModifiers = true
for i, building in ipairs(buildingList) do
    end
local row = {}
row.name = building.name
row.icon = Icons.Icon({building.name, type='building'})
row.expIcon = Icons.getDLCColumnIcon(building.id)
row.expSort = Icons.getExpansionID(building.id)
row.type = Icons.Icon({'Township', 'Building', section='Buildings', type='skill', noicon=true})
row.typeText = 'Building'


    table.insert(tableArray, row)
local modList = {}
  end
-- Multiply mod value by max upgrades to get the total modifier value
  local petList = p.getPetsWithModifier(modifiers, skill, getOpposites)
for modID, value in pairs(building.modifiers) do
  for i, pet in Shared.skpairs(petList) do
if type(value) == 'table' then
    local row = {}
modList[modID] = value[1].value * building.maxUpgrades
    row.name = pet.name
else
    row.icon = Icons.Icon({pet.name, type='pet'})
modList[modID] = value * building.maxUpgrades
    row.type = '[[Pets|Pet]]'
end
    local totalVal = 0
end
    for i, mod in pairs(modifiers) do
      totalVal = totalVal + p.getModifierValue(pet.modifiers, mod, skill, getOpposites)
    end
    row.val = totalVal


    row.modifierText, row.otherModifiers = getModText(pet.modifiers)
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(modList)
row.val = Modifiers.getModifierValue(objMods.matched)


    if string.len(row.otherModifiers) > 0 then
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
      hasOtherModifiers = true
hasOtherModifiers = true
    end
end
table.insert(tableArray, row)
end


    table.insert(tableArray, row)
local seasonList = p.getSeasonsWithModifiers(modifierCriteria)
  end
for i, season in ipairs(seasonList) do
  local obstList = p.getObstaclesWithModifier(modifiers, skill, getOpposites)
local row = {}
  for i, obst in Shared.skpairs(obstList) do
row.name = season.name
    local row = {}
row.icon = Icons.Icon({season.name, type='township'})
    row.name = obst.name
row.expIcon = Icons.getDLCColumnIcon(season.id)
    row.icon = Icons.Icon({'Agility', obst.name, type='skill'})
row.expSort = Icons.getExpansionID(season.id)
    row.type = '[[Agility#Obstacles|Agility Obstacle]]'
row.type = Icons.Icon({'Township', 'Season', section='Seasons', type='skill', noicon=true})
    local totalVal = 0
row.typeText = 'Season'
    for i, mod in pairs(modifiers) do
      totalVal = totalVal + p.getModifierValue(obst.modifiers, mod, skill, getOpposites)
    end
    row.val = totalVal


    row.modifierText, row.otherModifiers = getModText(obst.modifiers)
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(season.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)


    if string.len(row.otherModifiers) > 0 then
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
      hasOtherModifiers = true
hasOtherModifiers = true
    end
end
table.insert(tableArray, row)
end


    table.insert(tableArray, row)
local summoningSynergyList = p.getSummoningSynergiesWithModifiers(modifierCriteria)
  end
for i, synergy in ipairs(summoningSynergyList) do
local row = {}
local famProduct1 = Summoning.getFamiliarRecipeByID(synergy.summonIDs[1])
local familiar1 = nil
local famProduct2 = Summoning.getFamiliarRecipeByID(synergy.summonIDs[2])
local familiar2 = nil


  local pillarList = p.getPillarsWithModifier(modifiers, skill, getOpposites)
if famProduct1 ~= nil then
  for i, pillar in Shared.skpairs(pillarList) do
familiar1 = Items.getItemByID(famProduct1.productID)
    local row = {}
end
    row.name = pillar.name
if famProduct2 ~= nil then
    row.icon = Icons.Icon({'Agility', pillar.name, type='skill'})
familiar2 = Items.getItemByID(famProduct2.productID)
    row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
end
    local totalVal = 0
    for i, mod in pairs(modifiers) do
      totalVal = totalVal + p.getModifierValue(pillar.modifiers, mod, skill, getOpposites)
    end
    row.val = totalVal


    row.modifierText, row.otherModifiers = getModText(pillar.modifiers)
if familiar1 == nil or familiar2 == nil then
return
end


    if string.len(row.otherModifiers) > 0 then
row.name = familiar1.name .. familiar2.name .. ' Synergy'
      hasOtherModifiers = true
row.icon = Icons.Icon({familiar1.name, type='item'}) .. ' ' .. Icons.Icon({'SynergyIcon', notext=true, nolink=true}) .. ' ' .. Icons.Icon({familiar2.name, type='item'}) .. ' Synergy'
    end
row.expIcon = Icons.getDLCColumnIcon(familiar2.id)
row.expSort = Icons.getExpansionID(familiar2.id)
row.type = Icons.Icon({'Summoning', 'Summoning Synergy', section='Synergies', type='skill', noicon=true})
row.typeText = 'Synergy'


    table.insert(tableArray, row)
local objMods = nil
  end
row.modifierText, row.otherModifiers, objMods = getModText(synergy.modifiers)
  local upgradeList = p.getUpgradesWithModifier(modifiers, skill, getOpposites)
row.val = Modifiers.getModifierValue(objMods.matched)
  for i, upgrade in Shared.skpairs(upgradeList) do
    local row = {}
    row.name = upgrade.name
    row.icon = Icons.Icon({upgrade.name, type='upgrade'})
    row.type = '[[Shop|Upgrade]]'
    local totalVal = 0
    for i, mod in pairs(modifiers) do
      totalVal = totalVal + p.getModifierValue(upgrade.contains.modifiers, mod, skill, getOpposites)
    end
    row.val = totalVal


    row.modifierText, row.otherModifiers = getModText(upgrade.contains.modifiers)
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
table.insert(tableArray, row)
end
local masteryPools = p.getMasteryPoolModifiers(modifierCriteria)
for i, masteryPoolData in ipairs(masteryPools) do
for skillName, masteryPool in pairs(masteryPoolData) do
local row = {}
local isAbyssal = masteryPool.realm == 'melvorItA:Abyssal' and 'Abyssal ' or ''
row.name = isAbyssal .. masteryPool.percent .. '% Mastery Pool Bonus'
row.icon = Icons.Icon({skillName, row.name, section='Mastery Pool Checkpoints', type='skill'})
row.expIcon = Icons.getDLCColumnIcon(masteryPool.realm)
row.expSort = Icons.getExpansionID(masteryPool.realm)
row.type = Icons.Icon({skillName, 'Mastery Pool Bonus', section='Mastery Pool Checkpoints', type='skill', noicon=true})
row.typeText = 'Mastery Pool Bonus'
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(masteryPool.modifiers)
row.val = Modifiers.getModifierValue(objMods.matched)
if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
hasOtherModifiers = true
end
table.insert(tableArray, row)
end
end


    if string.len(row.otherModifiers) > 0 then
local html = mw.html.create('table')
      hasOtherModifiers = true
:addClass("wikitable sortable stickyHeader mw-collapsible")
    end
if collapsed == true then
html:addClass("mw-collapsed")
end
local header = html:tag('tr'):addClass("headerRow-0")
header:tag('th'):wikitext('Source')
  :tag('th'):wikitext('Type')
  :tag('th'):wikitext('[[DLC]]')
  :tag('th'):wikitext(columnName)


    table.insert(tableArray, row)
if hasOtherModifiers and displayOtherMods then
  end
header:tag('th'):wikitext('Other Modifiers')
end


  local result = '{| class="wikitable sortable stickyHeader"'
--Sort by value if only one modifier was passed in
  result = result..'\r\n|- class="headerRow-0"'
--Otherwise sort alphabetically by type, then name
  result = result..'\r\n!Source!!Type!!'..columnName
table.sort(tableArray, function(a, b)
  if hasOtherModifiers then result = result..'!!Other Modifiers' end
if (modifierCount == 1 or forceMagnitudeSort) and a.val ~= b.val then
return a.val > b.val
elseif a.typeText ~= b.typeText then
return a.typeText < b.typeText
else
return a.name < b.name
end
end)
for i, row in ipairs(tableArray) do
local datarow = html:tag('tr')
datarow:tag('td'):wikitext(row.icon)
  :attr('data-sort-value', row.name)
datarow:tag('td'):wikitext(row.type)
  :attr('data-sort-value', row.typeText)
datarow:tag('td'):wikitext(row.expIcon)
  :css('text-align', 'center')
  :attr('data-sort-value', row.expSort)
datarow:tag('td'):wikitext(row.modifierText)
  :attr('data-sort-value', row.val)


  --Sort by value if only one modifier was passed in
if hasOtherModifiers and displayOtherMods then
  --Otherwise sort alphabetically by name
datarow:tag('td'):wikitext(row.otherModifiers)
  if modifierCount == 1 then
end
    table.sort(tableArray, function(a, b)
end
                            if a.val ~= b.val then
                              return a.val > b.val
                            elseif a.name ~= b.name then
                              return a.name < b.name
                            else
                              return a.type < b.type
                            end
                          end)
  else
    table.sort(tableArray, function(a, b) return a.name < b.name end)
  end
  for i, row in Shared.skpairs(tableArray) do
    result = result..'\r\n|-'
    result = result..'\r\n|data-sort-value="'..row.name..'"|'..row.icon
    result = result..'||'..row.type..'||data-sort-value="'..row.val..'"|'..row.modifierText
    if hasOtherModifiers then
      result = result..'||'..row.otherModifiers
    end
  end


  result = result..'\r\n|}'
return tostring(html)
  return result
end
end


function p.getModifierTable(frame)
function p.getModifierTable(frame)
  local modifier = frame.args ~= nil and frame.args[1] or frame[1]
local args = frame.args ~= nil and frame.args or frame
  local skill = frame.args ~= nil and frame.args.skill or frame.skill
local modifier = args[1]
  local columnName = frame.args ~= nil and frame.args[2] or frame[2]
local columnName = args[2]
  local getOpposites = frame.args ~= nil and frame.args[3] or frame[3]
local getOpposites = args[3]
local displayOtherMods = args.displayOtherMods
local maxOtherMods = tonumber(args.maxOtherMods) or 5
local forceMagnitudeSort = string.upper(tostring(args.forceMagnitudeSort)) == 'TRUE' or false
local globalPropsText = args.filters
local collapsed = string.upper(tostring(args.collapsed)) == 'TRUE' or false
 
if getOpposites ~= nil then
getOpposites = string.upper(getOpposites) ~= 'FALSE'
else
getOpposites = true
end
 
if displayOtherMods ~= nil then
displayOtherMods = string.upper(displayOtherMods) ~= 'FALSE'
else
displayOtherMods = true
end


  if Shared.contains(modifier, ',') then
-- Given a text string following format 'key1=val1;key2=val2;...', returns
    modifier = Shared.splitString(modifier, ',')
-- a table: { key1 = val1, key2 = val2, ... }
  end
local function modPropTextToTable(propText)
local propTable = {}
local propParts = Shared.splitString(propText, ';')
for _, propPart in ipairs(propParts) do
local valueStartIdx, _ = string.find(propPart, ':')
if valueStartIdx ~= nil then
local k = string.sub(propPart, 1, valueStartIdx - 1)
local v = string.sub(propPart, valueStartIdx + 1, -1)
propTable[k] = v
end
end
return propTable
end


  if getOpposites ~= nil then
local globalProps = {}
    getOpposites = string.upper(getOpposites) ~= 'FALSE'
if globalPropsText ~= nil then
  else
globalProps = modPropTextToTable(globalPropsText)
    getOpposites = true
end
  end


 
local modifierList = {}
  return p._getModifierTable(modifier, skill, columnName, getOpposites)
local modifierListCrude = Shared.splitString(modifier, ',')
-- At this point, modifierListCrude requires further processing to separate modifier IDs
-- and properties
for _, modEntry in ipairs(modifierListCrude) do
local modID, modProps = nil, {}
local propStartIdx, _ = string.find(modEntry, '[[]') -- Matches plain text '['
if propStartIdx == nil then
-- No properties specified, the entire string is a modifier ID
modID = modEntry
else
modID = string.sub(modEntry, 1, propStartIdx - 1)
local modPropText = string.sub(modEntry, propStartIdx + 1, -2)
modProps = modPropTextToTable(modPropText)
end
table.insert(modifierList, {
["id"] = modID,
["props"] = modProps
})
end
 
return p._getModifierTable(modifierList, globalProps, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort, collapsed)
end
 
-- Function to list all available modifiers for the relevant templates.
function p.getAllModifiers()
local tabl = mw.html.create('table')
:addClass('mw-collapsible mw-collapsed')
tabl:tag('caption')
:css('min-width', '200px')
:tag('b')
:wikitext('All Modifiers List')
-- First, sort modifiers
local modifierNames = {}
for k, _ in pairs(Modifiers.ModifierIndex.localID) do
table.insert(modifierNames, k)
end
table.sort(modifierNames)
-- Then add modifiers to output table
for _, v in pairs(modifierNames) do
tabl:tag('tr')
:tag('td')
:tag('code')
:wikitext(tostring(v)):done()
:done()
:done()
end
return tostring(tabl)
end
--Function for console testing of modifier tables
function p.getModifierTableTest()
return p.getModifierTable({args = {'id:flatBaseRandomProductQuantity[item=Abyssal Stardust]', 'GP Boosts', filters = 'skill=Astrology'}})
end
end


return p
return p

Latest revision as of 23:08, 15 October 2024

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

--Module that constructs tables for all things that can affect Player Modifiers
--This includes Agility, Equipment, Pets, Prayers, and Constellations right now

local p = {}

local Constants = require('Module:Constants')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
local Num = require('Module:Number')
local Icons = require('Module:Icons')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local SkillTree = require('Module:SkillTree')
local Items = require('Module:Items')
local Shop = require('Module:Shop')
local Pets = require('Module:Pets')
local Skills = require('Module:Skills')
local Agility = require('Module:Skills/Agility')
local Cartography = require('Module:Skills/Cartography')
local Prayer = require('Module:Prayer')
local Summoning = require('Module:Skills/Summoning')
local Township = require('Module:Township')


--First up, functions to get all the things in a category that have a given modifier:
function p.getItemsWithModifier(modifierCriteria)
	local itemList = Items.getItems(
		function(item)
			if item.golbinRaidExclusive ~= nil and item.golbinRaidExclusive then
				return false
			elseif item.modifiers == nil or Shared.tableIsEmpty(item.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(item.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)
	return itemList
end

function p.getSkillTreeNodesWithModifier(modifierCriteria)
    local nodesWithModifier = SkillTree.getSkillTreeNodes(
        function(node)
            if node.modifiers ~= nil then
                local mods = Modifiers.getMatchingModifiers(node.modifiers, modifierCriteria)
                if not Shared.tableIsEmpty(mods.matched) then
                    return true
                end
            end
            return false
        end
    )
    return nodesWithModifier
end

function p.getObstaclesWithModifier(modifierCriteria)
	local obstList = Agility.getObstacles(
		function(obst)
			if obst.modifiers ~= nil then
				local mods = Modifiers.getMatchingModifiers(obst.modifiers, modifierCriteria)
				return not Shared.tableIsEmpty(mods.matched)
			end
			return false
		end)
	return obstList
end

function p.getConstellationsWithModifier(modifierCriteria)
	local consList = Skills.getConstellations(
			function(cons)
				local consMods = Skills._getConstellationModifiers(cons)
				for modType, modsForType in pairs(consMods) do
					local modData = {}
					-- Compile player modifiers
					for _, mods in ipairs(modsForType) do
						if mods.modifiers ~= nil then
							for modKey, modDefn in pairs(mods.modifiers) do
								if type(modData[modKey]) == 'table' and modData[modKey][1] ~= nil then
									for _, v in ipairs(modDefn) do
										table.insert(modData[modKey], v)
									end
								else
									modData[modKey] = modDefn
								end
							end
						end
					end
					local mods = Modifiers.getMatchingModifiers(modData, modifierCriteria)
					if not Shared.tableIsEmpty(mods.matched) then
						return true
					end
				end
				return false
			end)
	return consList
end


function p.getPillarsWithModifier(modifierCriteria)
	local pillarList = Agility.getPillars(
		function(pillar)
			if pillar.modifiers ~= nil then
				local mods = Modifiers.getMatchingModifiers(pillar.modifiers, modifierCriteria)
				return not Shared.tableIsEmpty(mods.matched)
			end
			return false
		end)
	return pillarList
end

function p.getPetsWithModifier(modifierCriteria)
	local petList = Pets.getPets(
		function(pet)
			if pet.modifiers ~= nil then
				local mods = Modifiers.getMatchingModifiers(pet.modifiers, modifierCriteria)
				return not Shared.tableIsEmpty(mods.matched)
			end
			return false
		end)
	return petList
end

function p.getPrayersWithModifier(modifierCriteria)
	local prayerList = Prayer.getPrayers(
		function(prayer)
			if prayer.modifiers ~= nil then
				local mods = Modifiers.getMatchingModifiers(prayer.modifiers, modifierCriteria)
				return not Shared.tableIsEmpty(mods.matched)
			end
			return false
		end)
	return prayerList
end

function p.getUpgradesWithModifier(modifierCriteria)
	local upgradeList = Shop.getPurchases(
		function(purchase)
			if purchase.category == 'melvorD:GolbinRaid'
				or purchase.contains == nil
				or purchase.contains.modifiers == nil
				or Shared.tableIsEmpty(purchase.contains.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(purchase.contains.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)
	return upgradeList
end

function p.getPOIsWithModifier(modifierCriteria)
	local POIList = Cartography.getPointsOfInterest(
		function(POI)
			if POI.activeStats == nil or POI.activeStats.modifiers == nil then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(POI.activeStats.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)
		
	return POIList
end

function p.getCartoMasteryBonusesWithModifier(modifierCriteria)
	local bonusList = Cartography.getMasteryBonuses(
		function(bonus)
			if bonus.modifiers == nil or Shared.tableIsEmpty(bonus.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(bonus.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)

	return bonusList
end

function p.getAncientRelicsWithModifiers(modifierCriteria)
	local matchedRelics = {}

	for i, relic in ipairs(GameData.rawData.ancientRelics) do
		if relic.modifiers ~= nil then
			local mods = Modifiers.getMatchingModifiers(relic.modifiers, modifierCriteria)
			if not Shared.tableIsEmpty(mods.matched) then
				table.insert(matchedRelics, relic)
			end
		end
	end

	return matchedRelics
end

function p.getBuildingsWithModifiers(modifierCriteria)
	local buildingList = Township.getBuildings(
		function(building)
			if building.modifiers == nil or Shared.tableIsEmpty(building.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(building.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)

	return buildingList
end

function p.getSeasonsWithModifiers(modifierCriteria)
	local seasonList = Township.getSeasons(
		function(season)
			if season.modifiers == nil or Shared.tableIsEmpty(season.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(season.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)

	return seasonList
end

function p.getSummoningSynergiesWithModifiers(modifierCriteria)
	local synergyList = Summoning.getSynergies(
		function(synergy)
			if synergy.modifiers == nil or Shared.tableIsEmpty(synergy.modifiers) then
				return false
			end
			local mods = Modifiers.getMatchingModifiers(synergy.modifiers, modifierCriteria)
			return not Shared.tableIsEmpty(mods.matched)
		end)

	return synergyList
end

function p.getMasteryPoolModifiers(modifierCriteria)
	local masteryPoolList = {}
	
	for skillName, skillData in pairs(SkillData) do
		if skillData.masteryPoolBonuses ~= nil then
			for i, masteryPool in ipairs(skillData.masteryPoolBonuses) do
				local mods = Modifiers.getMatchingModifiers(masteryPool.modifiers, modifierCriteria)
				if not Shared.tableIsEmpty(mods.matched) then
					table.insert(masteryPoolList, { [skillName] = masteryPool })
				end
			end
		end
	end

	return masteryPoolList
end

function p._getModifierTable(modifiers, globalProps, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort, collapsed)
	local modifierIDs = {}
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	for i, modifier in ipairs(modifiers) do
		-- Ensure only the local ID is used in case the provided modifier ID is namespaced
		local modNS, modID = Shared.getLocalID(modifier.id)
		-- Convert modifier & global property names to IDs
		local globalPropIDs = Modifiers.convertCriteriaNamesToIDs(globalProps or {})
		local modPropIDs = Modifiers.convertCriteriaNamesToIDs(modifier.props or {})
		-- Combine properties, mod props must be last as the highest priority
		local combinedProps = Modifiers.combineDataCriteria({ globalPropIDs, modPropIDs })

		if Modifiers.getModifierByID(modID) ~= nil then
			-- Provided ID is a modifier ID (as opposed to an alias)
			table.insert(modifierIDs, {
				["id"] = modID,
				["type"] = 'id',
				["props"] = combinedProps
			})
		else
			-- Assume modID is a modifier alias
			if getOpposites then
				local incModAlias, decModAlias = 'increased' .. modID, 'decreased' .. modID
				local incMod, decMod = Modifiers.getModifierByAlias(incModAlias), Modifiers.getModifierByAlias(decModAlias)
				-- If neither alias resolves to a modifier, then let user know this is invalid
				if incMod == nil and decMod == nil then
					error('No such modifier alias: ' .. modID, 2)
				end
				-- Don't include increased or decreased variants if they don't resolve to a modifier,
				-- as doing so will generate an error about an invalid alias later
				if incMod ~= nil then
					table.insert(modifierIDs, {
						["id"] = incModAlias,
						["type"] = 'alias',
						["props"] = combinedProps
					})
				end
				if decMod ~= nil then
					table.insert(modifierIDs, {
						["id"] = decModAlias,
						["type"] = 'alias',
						["props"] = combinedProps
					})
				end
			else
				table.insert(modifierIDs, {
					["id"] = modID,
					["type"] = 'alias',
					["props"] = combinedProps
				})
			end
		end
	end
	local modifierCriteria = Modifiers.getMatchCriteriaFromIDs(modifierIDs)

	local hasOtherModifiers = false
	local modifierCount = Shared.tableCount(modifiers)

	local getModText =
		function(modifiers)
			local matchedMods = Modifiers.getMatchingModifiers(modifiers, modifierCriteria)
			local mainModText = Modifiers.getModifiersText(matchedMods.matched, true, false, maxOtherMods)
			local otherModText = Modifiers.getModifiersText(matchedMods.unmatched, true, false, maxOtherMods)
			return mainModText, otherModText, matchedMods
		end

	local tableArray = {}
	--Going through each type of thing to add to the array

	local itemList = p.getItemsWithModifier(modifierCriteria)
	for i, item in ipairs(itemList) do
		local row = {}
		row.name = item.name
		row.icon = Icons.Icon({item.name, type='item'})
		row.expIcon = Icons.getDLCColumnIcon(item.id)
		row.expSort = Icons.getExpansionID(item.id)
		row.type = 'Item'
		row.typeText = row.type
		--For equipment, show the slot they go in
		if item.validSlots ~= nil and not Shared.tableIsEmpty(item.validSlots) then
			local rowTypePart = {}
			for j, slot in ipairs(item.validSlots) do
				table.insert(rowTypePart, Common.getEquipmentSlotLink(slot))
			end
			row.typeText = row.typeText .. ' (' .. table.concat(item.validSlots, ', ') .. ')'
			row.type = row.type .. ' (' .. table.concat(rowTypePart, ', ') .. ')'
		end

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(item.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local petList = p.getPetsWithModifier(modifierCriteria)
	for i, pet in Shared.skpairs(petList) do
		local row = {}
		row.name = pet.name
		row.icon = Icons.Icon({pet.name, type='pet'})
		row.expIcon = Icons.getDLCColumnIcon(pet.id)
		row.expSort = Icons.getExpansionID(pet.id)
		row.type = '[[Pets|Pet]]'
		row.typeText = 'Pet'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(pet.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end
	
	local nodeList = p.getSkillTreeNodesWithModifier(modifierCriteria)
	for _, node in ipairs(nodeList) do
	    local row = {}
	    row.name = node.skillName .. " - " .. node.name
	    
	    local firstIcon = Icons.Icon({"", img='Skill Tree', nolink=true})
	    local secondIcon = Icons.Icon({node.skillName..'%23#Skill_Tree', node.name, type='skill', img=node.skillName})
	    row.icon = firstIcon .. " " .. secondIcon
	    
	    row.expIcon = Icons.getDLCColumnIcon(node.id)
	    row.expSort = Icons.getExpansionID(node.id)
	    row.type = '[[Skill Tree#Nodes|Skill Tree Node]]'
	    row.typeText = 'Skill Tree Node'
	
	    local objMods = nil
	    row.modifierText, row.otherModifiers, objMods = getModText(node.modifiers)
	    row.val = Modifiers.getModifierValue(objMods.matched)
	
	    if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
	        hasOtherModifiers = true
	    end
	
	    table.insert(tableArray, row)
	end


	local obstList = p.getObstaclesWithModifier(modifierCriteria)
	table.sort(obstList, function(a, b) return a.category < b.category end)
	for i, obst in Shared.skpairs(obstList) do
		local row = {}
		row.name = obst.name
		row.icon = Icons.Icon({'Agility%23'..string.gsub(obst.name, ' ', ''), obst.name, type='skill', img='Agility'})
		row.expIcon = Icons.getDLCColumnIcon(obst.id)
		row.expSort = Icons.getExpansionID(obst.id)
		row.type = '[[Agility#Obstacles|Agility Obstacle '..tostring(tonumber(obst.category)+1)..']]'
		row.typeText = 'Agility Obstacle '..string.format("%02d", (obst.category + 1))

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(obst.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local pillarList = p.getPillarsWithModifier(modifierCriteria)
	for i, pillar in ipairs(pillarList) do
		local row = {}
		row.name = pillar.name
		row.icon = Icons.Icon({'Agility%23'..string.gsub(pillar.name, ' ', ''), pillar.name, type='skill', img='Agility'})
		row.expIcon = Icons.getDLCColumnIcon(pillar.id)
		row.expSort = Icons.getExpansionID(pillar.id)
		row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
		row.typeText = 'Agility Pillar'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(pillar.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local prayerList = p.getPrayersWithModifier(modifierCriteria)
	for i, prayer in ipairs(prayerList) do
		local row = {}
		row.name = prayer.name
		row.icon = Icons.Icon({prayer.name, type='prayer'})
		row.expIcon = Icons.getDLCColumnIcon(prayer.id)
		row.expSort = Icons.getExpansionID(prayer.id)
		row.type = [[Prayer]]
		row.typeText = 'Prayer'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(prayer.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local upgradeList = p.getUpgradesWithModifier(modifierCriteria)
	for i, upgrade in ipairs(upgradeList) do
		local row = {}
		row.name = Shop._getPurchaseName(upgrade)
		row.icon = Icons.Icon({row.name, type='upgrade'})
		row.expIcon = Icons.getDLCColumnIcon(upgrade.id)
		row.expSort = Icons.getExpansionID(upgrade.id)
		row.type = '[[Shop|Upgrade]]'
		row.typeText = 'Upgrade'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(upgrade.contains.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local constellationList = p.getConstellationsWithModifier(modifierCriteria)
	for i, cons in ipairs(constellationList) do
		local modListByType = Skills._getConstellationModifiers(cons)
		local modList = {}
		-- Combine all mods into single list
		for modType, modsForType in pairs(modListByType) do
			for _, mod in ipairs(modsForType) do
				if mod.modifiers ~= nil then
					for modKey, modDefn in pairs(mod.modifiers) do
						if type(modList[modKey]) == 'table' and modList[modKey][1] ~= nil then
							for _, v in ipairs(modDefn) do
								table.insert(modList[modKey], v)
							end
						else
							modList[modKey] = modDefn
						end
					end
				end
			end
		end

		local row = {}
		row.name = cons.name
		row.icon = Icons.Icon({cons.name, type='constellation'})
		row.expIcon = Icons.getDLCColumnIcon(cons.id)
		row.expSort = Icons.getExpansionID(cons.id)
		row.type = '[[Astrology#Constellations|Constellation]]'
		row.typeText = 'Constellation'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(modList)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end
	
	local POIList = p.getPOIsWithModifier(modifierCriteria)
	for i, POI in ipairs(POIList) do
		local row = {}
		row.name = POI.name
		row.icon = Icons.Icon({POI.name, type='poi'})
		row.expIcon = Icons.getDLCColumnIcon(POI.id)
		row.expSort = Icons.getExpansionID(POI.id)
		row.type = '[[Cartography|Point of Interest]]'
		row.typeText = 'Point of Interest'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(POI.activeStats.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end
	
	local cartoBonusList = p.getCartoMasteryBonusesWithModifier(modifierCriteria)
	for i, bonus in ipairs(cartoBonusList) do
		local row = {}
		row.name = Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered'
		row.icon = Icons.Icon({'Cartography', Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered', type='skill'})
		row.expIcon = Icons.getDLCColumnIcon(bonus.id)
		row.expSort = Icons.getExpansionID(bonus.id)
		row.type = Icons.Icon({'Cartography', 'Mastery Bonus', section='Mastery Unlocks', type='skill', noicon=true})
		row.typeText = 'Mastery Bonus'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(bonus.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end
		
		table.insert(tableArray, row)
	end

	local ancientRelicList = p.getAncientRelicsWithModifiers(modifierCriteria)
	for i, relic in ipairs(ancientRelicList) do
		local row = {}
		local relicName = relic.name
		local sectionName = 'Melvor Realm'
		if string.find(relic.id, 'Abyssal') ~= nil then
			relicName = 'Abyssal ' .. relicName
			sectionName = 'Abyssal Realm'
		end
		row.name = relicName
		row.icon = Icons.Icon({'Ancient Relics', relicName, section=sectionName})
		row.expIcon = Icons.getDLCColumnIcon(relic.id)
		row.expSort = Icons.getExpansionID(relic.id)
		row.type = Icons.Icon({'Ancient Relics', 'Ancient Relic', noicon=true})
		row.typeText = 'Ancient Relic'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(relic.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end
		
		table.insert(tableArray, row)
	end

	local buildingList = p.getBuildingsWithModifiers(modifierCriteria)
	for i, building in ipairs(buildingList) do
		local row = {}
		row.name = building.name
		row.icon = Icons.Icon({building.name, type='building'})
		row.expIcon = Icons.getDLCColumnIcon(building.id)
		row.expSort = Icons.getExpansionID(building.id)
		row.type = Icons.Icon({'Township', 'Building', section='Buildings', type='skill', noicon=true})
		row.typeText = 'Building'

		local modList = {}
		-- Multiply mod value by max upgrades to get the total modifier value
		for modID, value in pairs(building.modifiers) do
			if type(value) == 'table' then
				modList[modID] = value[1].value * building.maxUpgrades
			else
				modList[modID] = value * building.maxUpgrades
			end
		end

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(modList)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end
		
		table.insert(tableArray, row)
	end

	local seasonList = p.getSeasonsWithModifiers(modifierCriteria)
	for i, season in ipairs(seasonList) do
		local row = {}
		row.name = season.name
		row.icon = Icons.Icon({season.name, type='township'})
		row.expIcon = Icons.getDLCColumnIcon(season.id)
		row.expSort = Icons.getExpansionID(season.id)
		row.type = Icons.Icon({'Township', 'Season', section='Seasons', type='skill', noicon=true})
		row.typeText = 'Season'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(season.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end
		
		table.insert(tableArray, row)
	end

	local summoningSynergyList = p.getSummoningSynergiesWithModifiers(modifierCriteria)
	for i, synergy in ipairs(summoningSynergyList) do
		local row = {}
		local famProduct1 = Summoning.getFamiliarRecipeByID(synergy.summonIDs[1])
		local familiar1 = nil
		local famProduct2 = Summoning.getFamiliarRecipeByID(synergy.summonIDs[2])
		local familiar2 = nil

		if famProduct1 ~= nil then
			familiar1 = Items.getItemByID(famProduct1.productID)
		end
		if famProduct2 ~= nil then
			familiar2 = Items.getItemByID(famProduct2.productID)
		end

		if familiar1 == nil or familiar2 == nil then
			return
		end

		row.name = familiar1.name .. familiar2.name .. ' Synergy'
		row.icon = Icons.Icon({familiar1.name, type='item'}) .. ' ' .. Icons.Icon({'SynergyIcon', notext=true, nolink=true}) .. ' ' .. Icons.Icon({familiar2.name, type='item'}) .. ' Synergy'
		row.expIcon = Icons.getDLCColumnIcon(familiar2.id)
		row.expSort = Icons.getExpansionID(familiar2.id)
		row.type = Icons.Icon({'Summoning', 'Summoning Synergy', section='Synergies', type='skill', noicon=true})
		row.typeText = 'Synergy'

		local objMods = nil
		row.modifierText, row.otherModifiers, objMods = getModText(synergy.modifiers)
		row.val = Modifiers.getModifierValue(objMods.matched)

		if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
			hasOtherModifiers = true
		end
		
		table.insert(tableArray, row)
	end
	
	local masteryPools = p.getMasteryPoolModifiers(modifierCriteria)
	for i, masteryPoolData in ipairs(masteryPools) do
		for skillName, masteryPool in pairs(masteryPoolData) do
			local row = {}
			local isAbyssal = masteryPool.realm == 'melvorItA:Abyssal' and 'Abyssal ' or ''
			row.name = isAbyssal .. masteryPool.percent .. '% Mastery Pool Bonus'
			row.icon = Icons.Icon({skillName, row.name, section='Mastery Pool Checkpoints', type='skill'})
			row.expIcon = Icons.getDLCColumnIcon(masteryPool.realm)
			row.expSort = Icons.getExpansionID(masteryPool.realm)
			row.type = Icons.Icon({skillName, 'Mastery Pool Bonus', section='Mastery Pool Checkpoints', type='skill', noicon=true})
			row.typeText = 'Mastery Pool Bonus'
	
			local objMods = nil
			row.modifierText, row.otherModifiers, objMods = getModText(masteryPool.modifiers)
			row.val = Modifiers.getModifierValue(objMods.matched)
	
			if not hasOtherModifiers and not Shared.tableIsEmpty(objMods.unmatched) then
				hasOtherModifiers = true
			end
	
			table.insert(tableArray, row)
		end
	end

	local html = mw.html.create('table')
		:addClass("wikitable sortable stickyHeader mw-collapsible")
	if collapsed == true then
		html:addClass("mw-collapsed")
	end
	
	local header = html:tag('tr'):addClass("headerRow-0")
	header:tag('th'):wikitext('Source')
		  :tag('th'):wikitext('Type')
		  :tag('th'):wikitext('[[DLC]]')
		  :tag('th'):wikitext(columnName)

	if hasOtherModifiers and displayOtherMods then 
		header:tag('th'):wikitext('Other Modifiers')
	end

	--Sort by value if only one modifier was passed in
	--Otherwise sort alphabetically by type, then name
	table.sort(tableArray, function(a, b)
			if (modifierCount == 1 or forceMagnitudeSort) and a.val ~= b.val then
				return a.val > b.val
			elseif a.typeText ~= b.typeText then
				return a.typeText < b.typeText
			else
				return a.name < b.name
			end
		end)
	for i, row in ipairs(tableArray) do
		local datarow = html:tag('tr')
		datarow:tag('td'):wikitext(row.icon)
			   :attr('data-sort-value', row.name)
		datarow:tag('td'):wikitext(row.type)
			   :attr('data-sort-value', row.typeText)
		datarow:tag('td'):wikitext(row.expIcon)
			   :css('text-align', 'center')
			   :attr('data-sort-value', row.expSort)
		datarow:tag('td'):wikitext(row.modifierText)
			   :attr('data-sort-value', row.val)

		if hasOtherModifiers and displayOtherMods then
			datarow:tag('td'):wikitext(row.otherModifiers)
		end
	end

	return tostring(html)
end

function p.getModifierTable(frame)
	local args = frame.args ~= nil and frame.args or frame
	local modifier = args[1]
	local columnName = args[2]
	local getOpposites = args[3]
	local displayOtherMods = args.displayOtherMods
	local maxOtherMods = tonumber(args.maxOtherMods) or 5
	local forceMagnitudeSort = string.upper(tostring(args.forceMagnitudeSort)) == 'TRUE' or false
	local globalPropsText = args.filters
	local collapsed = string.upper(tostring(args.collapsed)) == 'TRUE' or false

	if getOpposites ~= nil then
		getOpposites = string.upper(getOpposites) ~= 'FALSE'
	else
		getOpposites = true
	end

	if displayOtherMods ~= nil then
		displayOtherMods = string.upper(displayOtherMods) ~= 'FALSE'
	else
		displayOtherMods = true
	end

	-- Given a text string following format 'key1=val1;key2=val2;...', returns
	-- a table: { key1 = val1, key2 = val2, ... }
	local function modPropTextToTable(propText)
		local propTable = {}
		local propParts = Shared.splitString(propText, ';')
		for _, propPart in ipairs(propParts) do
			local valueStartIdx, _ = string.find(propPart, ':')
			if valueStartIdx ~= nil then
				local k = string.sub(propPart, 1, valueStartIdx - 1)
				local v = string.sub(propPart, valueStartIdx + 1, -1)
				propTable[k] = v
			end
		end
		return propTable
	end

	local globalProps = {}
	if globalPropsText ~= nil then
		globalProps = modPropTextToTable(globalPropsText)
	end

	local modifierList = {}
	local modifierListCrude = Shared.splitString(modifier, ',')
	-- At this point, modifierListCrude requires further processing to separate modifier IDs
	-- and properties
	for _, modEntry in ipairs(modifierListCrude) do
		local modID, modProps = nil, {}
		local propStartIdx, _ = string.find(modEntry, '[[]') -- Matches plain text '['
		if propStartIdx == nil then
			-- No properties specified, the entire string is a modifier ID
			modID = modEntry
		else
			modID = string.sub(modEntry, 1, propStartIdx - 1)
			local modPropText = string.sub(modEntry, propStartIdx + 1, -2)
			modProps = modPropTextToTable(modPropText)
		end
		table.insert(modifierList, {
			["id"] = modID,
			["props"] = modProps
 		})
	end

	return p._getModifierTable(modifierList, globalProps, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort, collapsed)
end

-- Function to list all available modifiers for the relevant templates.
function p.getAllModifiers()
	local tabl = mw.html.create('table')
		:addClass('mw-collapsible mw-collapsed')
		
	tabl:tag('caption')
		:css('min-width', '200px')
		:tag('b')
		:wikitext('All Modifiers List')
	
	-- First, sort modifiers
	local modifierNames = {}
	for k, _ in pairs(Modifiers.ModifierIndex.localID) do
		table.insert(modifierNames, k)
	end
	table.sort(modifierNames)
	
	-- Then add modifiers to output table
	for _, v in pairs(modifierNames) do
		tabl:tag('tr')
			:tag('td')
				:tag('code')
					:wikitext(tostring(v)):done()
				:done()
			:done()
	end
	
	return tostring(tabl)
end
--Function for console testing of modifier tables
function p.getModifierTableTest()
	return p.getModifierTable({args = {'id:flatBaseRandomProductQuantity[item=Abyssal Stardust]', 'GP Boosts', filters = 'skill=Astrology'}})
end

return p