Module:ModifierTables: Difference between revisions

From Melvor Idle
(Add Ancient Relics, Summoning Synergies, Township Buildings and Township Seasons to ModifierTables)
(Add Mastery Pool Bonuses to Modifier Tables)
 
(3 intermediate revisions by the same user not shown)
Line 10: Line 10:
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local GameData = require('Module:GameData')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Modifiers = require('Module:Modifiers')
local SkillTree = require('Module:SkillTree')
local SkillTree = require('Module:SkillTree')
Line 225: Line 226:


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


Line 576: Line 594:
row.expIcon = Icons.getDLCColumnIcon(building.id)
row.expIcon = Icons.getDLCColumnIcon(building.id)
row.expSort = Icons.getExpansionID(building.id)
row.expSort = Icons.getExpansionID(building.id)
row.type = Icons.Icon({'Township', 'Building', section='Buildings', type='Skill', noicon=true})
row.type = Icons.Icon({'Township', 'Building', section='Buildings', type='skill', noicon=true})
row.typeText = 'Building'
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
local objMods = nil
row.modifierText, row.otherModifiers, objMods = getModText(building.modifiers)
row.modifierText, row.otherModifiers, objMods = getModText(modList)
row.val = Modifiers.getModifierValue(objMods.matched)
row.val = Modifiers.getModifierValue(objMods.matched)


Line 597: Line 625:
row.expIcon = Icons.getDLCColumnIcon(season.id)
row.expIcon = Icons.getDLCColumnIcon(season.id)
row.expSort = Icons.getExpansionID(season.id)
row.expSort = Icons.getExpansionID(season.id)
row.type = Icons.Icon({'Township', 'Season', section='Seasons', type='Skill', noicon=true})
row.type = Icons.Icon({'Township', 'Season', section='Seasons', type='skill', noicon=true})
row.typeText = 'Season'
row.typeText = 'Season'


Line 631: Line 659:


row.name = familiar1.name .. familiar2.name .. ' Synergy'
row.name = familiar1.name .. familiar2.name .. ' Synergy'
row.icon = Icons.Icon({familiar1.name, type='item'}) .. ' ' .. Icons.Icon({'SynergyIcon', notext=true}) .. ' ' .. Icons.Icon({familiar2.name, type='item'}) .. ' 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.expIcon = Icons.getDLCColumnIcon(familiar2.id)
row.expSort = Icons.getExpansionID(familiar2.id)
row.expSort = Icons.getExpansionID(familiar2.id)
row.type = Icons.Icon({'Summoning', 'Summoning Synergy', section='Synergies', type='Skill', noicon=true})
row.type = Icons.Icon({'Summoning', 'Summoning Synergy', section='Synergies', type='skill', noicon=true})
row.typeText = 'Synergy'
row.typeText = 'Synergy'


Line 646: Line 674:
table.insert(tableArray, row)
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
end



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