Module:ModifierTables: Difference between revisions

From Melvor Idle
(Fix constellation modifier handling)
(Add Mastery Pool Bonuses to Modifier Tables)
 
(20 intermediate revisions by 4 users not shown)
Line 7: Line 7:
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Common = require('Module:Common')
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 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 Skills = require('Module:Skills')
local Agility = require('Module:Skills/Agility')
local Agility = require('Module:Skills/Agility')
local Cartography = require('Module:Skills/Cartography')
local Prayer = require('Module:Prayer')
local Prayer = require('Module:Prayer')
local Shop = require('Module:Shop')
local Summoning = require('Module:Skills/Summoning')
local Icons = require('Module:Icons')
local Township = require('Module:Township')
local Cartography = require('Module:Skills/Cartography')
 
local GameData = require('Module:GameData')


--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.getItemsWithModifier(modifierNames, skill)
function p.getItemsWithModifier(modifierCriteria)
local itemList = Items.getItems(
local itemList = Items.getItems(
function(item)
function(item)
Line 27: Line 33:
return false
return false
end
end
local mods = Modifiers.getMatchingModifiers(item.modifiers, modifierNames, skill)
local mods = Modifiers.getMatchingModifiers(item.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
return not Shared.tableIsEmpty(mods.matched)
end)
end)
Line 33: Line 39:
end
end


function p.getObstaclesWithModifier(modifierNames, skill)
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(
local obstList = Agility.getObstacles(
function(obst)
function(obst)
if obst.modifiers ~= nil then
if obst.modifiers ~= nil then
local mods = Modifiers.getMatchingModifiers(obst.modifiers, modifierNames, skill)
local mods = Modifiers.getMatchingModifiers(obst.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
return not Shared.tableIsEmpty(mods.matched)
end
end
Line 45: Line 66:
end
end


function p.getConstellationsWithModifier(modifierNames, skill)
function p.getConstellationsWithModifier(modifierCriteria)
local consList = Skills.getConstellations(
local consList = Skills.getConstellations(
function(cons)
function(cons)
Line 54: Line 75:
for _, mods in ipairs(modsForType) do
for _, mods in ipairs(modsForType) do
if mods.modifiers ~= nil then
if mods.modifiers ~= nil then
table.insert(modData, mods.modifiers)
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
end
end
local mods = Modifiers.getMatchingModifiers(modData, modifierNames, skill)
local mods = Modifiers.getMatchingModifiers(modData, modifierCriteria)
if not Shared.tableIsEmpty(mods.matched) then
if not Shared.tableIsEmpty(mods.matched) then
return true
return true
Line 68: Line 97:




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


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


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


function p.getUpgradesWithModifier(modifierNames, skill)
function p.getUpgradesWithModifier(modifierCriteria)
local upgradeList = Shop.getPurchases(
local upgradeList = Shop.getPurchases(
function(purchase)
function(purchase)
Line 113: Line 142:
return false
return false
end
end
local mods = Modifiers.getMatchingModifiers(purchase.contains.modifiers, modifierNames, skill)
local mods = Modifiers.getMatchingModifiers(purchase.contains.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
return not Shared.tableIsEmpty(mods.matched)
end)
end)
Line 119: Line 148:
end
end


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


function p.getCartoMasteryBonusesWithModifier(modifierNames, skill)
function p.getCartoMasteryBonusesWithModifier(modifierCriteria)
local bonusList = Cartography.getMasteryBonuses(
local bonusList = Cartography.getMasteryBonuses(
function(bonus)
function(bonus)
Line 138: Line 167:
return false
return false
end
end
local mods = Modifiers.getMatchingModifiers(bonus.modifiers, modifierNames, skill)
local mods = Modifiers.getMatchingModifiers(bonus.modifiers, modifierCriteria)
return not Shared.tableIsEmpty(mods.matched)
return not Shared.tableIsEmpty(mods.matched)
end)
end)
Line 145: Line 174:
end
end


function p._getModifierTable(modifiers, skill, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort)
function p.getAncientRelicsWithModifiers(modifierCriteria)
local modifierByType = {
local matchedRelics = {}
["id"] = {},
 
["alias"] = {}
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
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
local isNamespaced = string.find(modifier, ':') ~= nil
-- Ensure only the local ID is used in case the provided modifier ID is namespaced
if isNamespaced then
local modNS, modID = Shared.getLocalID(modifier.id)
-- Assume 'modifier' contains a modifier ID
-- Convert modifier & global property names to IDs
table.insert(modifierByType.id, modifier)
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
else
-- Assume 'modifier' contains a modifier alias
-- Assume modID is a modifier alias
if getOpposites then
if getOpposites then
local incModAlias, decModAlias = 'increased' .. modifier, 'decreased' .. modifier
local incModAlias, decModAlias = 'increased' .. modID, 'decreased' .. modID
local incMod, decMod = Modifiers.getModifierByAlias(incModAlias), Modifiers.getModifierByAlias(decModAlias)
local incMod, decMod = Modifiers.getModifierByAlias(incModAlias), Modifiers.getModifierByAlias(decModAlias)
-- If neither alias resolves to a modifier, then let user know this is invalid
-- If neither alias resolves to a modifier, then let user know this is invalid
if incMod == nil and decMod == nil then
if incMod == nil and decMod == nil then
error('No such modifier alias: ' .. modifier, 2)
error('No such modifier alias: ' .. modID, 2)
end
end
-- Don't include increased or decreased variants if they don't resolve to a modifier,
-- 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
-- as doing so will generate an error about an invalid alias later
if incMod ~= nil then
if incMod ~= nil then
table.insert(modifierByType.alias, incModAlias)
table.insert(modifierIDs, {
["id"] = incModAlias,
["type"] = 'alias',
["props"] = combinedProps
})
end
end
if decMod ~= nil then
if decMod ~= nil then
table.insert(modifierByType.alias, decModAlias)
table.insert(modifierIDs, {
["id"] = decModAlias,
["type"] = 'alias',
["props"] = combinedProps
})
end
end
else
else
table.insert(modifierByType.alias, modifier)
table.insert(modifierIDs, {
["id"] = modID,
["type"] = 'alias',
["props"] = combinedProps
})
end
end
end
end
end
end
local modifierCriteria = Modifiers.getMatchCriteriaFromIDs(modifierByType.id, modifierByType.alias)
local modifierCriteria = Modifiers.getMatchCriteriaFromIDs(modifierIDs)


local hasOtherModifiers = false
local hasOtherModifiers = false
local modifierCount = Shared.tableCount(modifiers)
local modifierCount = Shared.tableCount(modifiers)
if skill ~= nil then
if skill == '' then
skill = nil
elseif Constants.getSkillID(skill) ~= nil then
-- skill is a skill name
skill = Constants.getSkillID(skill)
elseif Constants.getSkillName(skill) == nil then
-- skill is neither a skill name or ID
return Shared.printError('Failed to find a skill ID for "' .. skill .. '"')
end
end


local getModText =
local getModText =
function(modifiers)
function(modifiers)
local matchedMods = Modifiers.getMatchingModifiers(modifiers, modifierCriteria, skill)
local matchedMods = Modifiers.getMatchingModifiers(modifiers, modifierCriteria)
local mainModText = Modifiers.getModifiersText(matchedMods.matched, true, false, maxOtherMods)
local mainModText = Modifiers.getModifiersText(matchedMods.matched, true, false, maxOtherMods)
local otherModText = Modifiers.getModifiersText(matchedMods.unmatched, true, false, maxOtherMods)
local otherModText = Modifiers.getModifiersText(matchedMods.unmatched, true, false, maxOtherMods)
Line 208: Line 316:
--Going through each type of thing to add to the array
--Going through each type of thing to add to the array


local itemList = p.getItemsWithModifier(modifierCriteria, skill)
local itemList = p.getItemsWithModifier(modifierCriteria)
for i, item in ipairs(itemList) do
for i, item in ipairs(itemList) do
local row = {}
local row = {}
Line 238: Line 346:
end
end


local petList = p.getPetsWithModifier(modifierCriteria, skill)
local petList = p.getPetsWithModifier(modifierCriteria)
for i, pet in Shared.skpairs(petList) do
for i, pet in Shared.skpairs(petList) do
local row = {}
local row = {}
Line 257: Line 365:


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


local obstList = p.getObstaclesWithModifier(modifierCriteria, skill)
 
local obstList = p.getObstaclesWithModifier(modifierCriteria)
table.sort(obstList, function(a, b) return a.category < b.category end)
table.sort(obstList, function(a, b) return a.category < b.category end)
for i, obst in Shared.skpairs(obstList) do
for i, obst in Shared.skpairs(obstList) do
Line 281: Line 415:
end
end


local pillarList = p.getPillarsWithModifier(modifierCriteria, skill)
local pillarList = p.getPillarsWithModifier(modifierCriteria)
for i, pillar in ipairs(pillarList) do
for i, pillar in ipairs(pillarList) do
local row = {}
local row = {}
Line 302: Line 436:
end
end


local prayerList = p.getPrayersWithModifier(modifierCriteria, skill)
local prayerList = p.getPrayersWithModifier(modifierCriteria)
for i, prayer in ipairs(prayerList) do
for i, prayer in ipairs(prayerList) do
local row = {}
local row = {}
Line 323: Line 457:
end
end


local upgradeList = p.getUpgradesWithModifier(modifierCriteria, skill)
local upgradeList = p.getUpgradesWithModifier(modifierCriteria)
for i, upgrade in ipairs(upgradeList) do
for i, upgrade in ipairs(upgradeList) do
local row = {}
local row = {}
Line 344: Line 478:
end
end


local constellationList = p.getConstellationsWithModifier(modifierCriteria, skill)
local constellationList = p.getConstellationsWithModifier(modifierCriteria)
for i, cons in ipairs(constellationList) do
for i, cons in ipairs(constellationList) do
local modListByType = Skills._getConstellationModifiers(cons)
local modListByType = Skills._getConstellationModifiers(cons)
Line 352: Line 486:
for _, mod in ipairs(modsForType) do
for _, mod in ipairs(modsForType) do
if mod.modifiers ~= nil then
if mod.modifiers ~= nil then
table.insert(modList, mod.modifiers)
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
end
Line 376: Line 518:
end
end
local POIList = p.getPOIsWithModifier(modifierCriteria, skill)
local POIList = p.getPOIsWithModifier(modifierCriteria)
for i, POI in ipairs(POIList) do
for i, POI in ipairs(POIList) do
local row = {}
local row = {}
Line 387: Line 529:


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


Line 397: Line 539:
end
end
local cartoBonusList = p.getCartoMasteryBonusesWithModifier(modifierCriteria, skill)
local cartoBonusList = p.getCartoMasteryBonusesWithModifier(modifierCriteria)
for i, bonus in ipairs(cartoBonusList) do
for i, bonus in ipairs(cartoBonusList) do
local row = {}
local row = {}
row.name = Shared.formatnum(bonus.masteredHexes) .. ' Hexes Mastered'
row.name = Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered'
row.icon = Icons.Icon({'Cartography', Shared.formatnum(bonus.masteredHexes) .. ' Hexes Mastered', type='skill'})
row.icon = Icons.Icon({'Cartography', Num.formatnum(bonus.masteredHexes) .. ' Hexes Mastered', type='skill'})
row.expIcon = Icons.getDLCColumnIcon(bonus.id)
row.expIcon = Icons.getDLCColumnIcon(bonus.id)
row.expSort = Icons.getExpansionID(bonus.id)
row.expSort = Icons.getExpansionID(bonus.id)
Line 416: Line 558:
table.insert(tableArray, row)
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
end


local html = mw.html.create('table')
local html = mw.html.create('table')
:addClass("wikitable sortable stickyHeader mw-collapsible")
:addClass("wikitable sortable stickyHeader mw-collapsible")
if collapsed == true then
html:addClass("mw-collapsed")
end
local header = html:tag('tr'):addClass("headerRow-0")
local header = html:tag('tr'):addClass("headerRow-0")
Line 463: Line 748:


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 = frame.args ~= nil and frame.args.displayOtherMods or frame.displayOtherMods
local displayOtherMods = args.displayOtherMods
local maxOtherMods = frame.args ~= nil and tonumber(frame.args.maxOtherMods) or 5
local maxOtherMods = tonumber(args.maxOtherMods) or 5
local forceMagnitudeSort = frame.args ~= nil and string.upper(tostring(frame.args.forceMagnitudeSort)) == 'TRUE' or false
local forceMagnitudeSort = string.upper(tostring(args.forceMagnitudeSort)) == 'TRUE' or false
 
local globalPropsText = args.filters
if Shared.contains(modifier, ',') then
local collapsed = string.upper(tostring(args.collapsed)) == 'TRUE' or false
modifier = Shared.splitString(modifier, ',')
end


if getOpposites ~= nil then
if getOpposites ~= nil then
Line 487: Line 770:
end
end


return p._getModifierTable(modifier, skill, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort)
-- 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
end


Line 502: Line 827:
-- First, sort modifiers
-- First, sort modifiers
local modifierNames = {}
local modifierNames = {}
for k, _ in pairs(GameData.rawData.modifierData) do
for k, _ in pairs(Modifiers.ModifierIndex.localID) do
table.insert(modifierNames, tostring(k))
table.insert(modifierNames, k)
end
end
table.sort(modifierNames)
table.sort(modifierNames)
Line 521: Line 846:
--Function for console testing of modifier tables
--Function for console testing of modifier tables
function p.getModifierTableTest()
function p.getModifierTableTest()
return p.getModifierTable({args = {'MeleeMaxHit', 'GP Boosts'}})
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