Module:ModifierTables: Difference between revisions

From Melvor Idle
(I believe I have poked agility obstacles to properly link to where they are on the page)
(Attempting to add Points of Interest to Modifier tables)
 
(One intermediate revision by one other user not shown)
Line 13: Line 13:
local Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Cartography = require('Module:Skills/Cartography')


--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:
Line 195: Line 196:
end)
end)
return upgradeList
return upgradeList
end
function p.getPOIsWithModifier(modifiers, skill, getOpposites)
if type(modifiers) == 'string' then
modifiers = {modifiers}
end
local POIList = Cartography.getPointsOfInterest(
function(POI)
if POI.activeModifiers == nil then
return false
end
for i, mod in ipairs(modifiers) do
if p.getModifierValue(POI.activeModifiers, mod, skill, getOpposites) ~= 0 then
return true
end
end
return false
end)
return POIList
end
end


Line 356: Line 378:
local row = {}
local row = {}
row.name = pillar.name
row.name = pillar.name
row.icon = Icons.Icon({'Agility', pillar.name, type='skill'})
row.icon = Icons.Icon({'Agility%23'..string.gsub(pillar.name, ' ', ''), pillar.name, type='skill', img='Agility'})
row.expIcon = Icons.getExpansionIcon(pillar.id)
row.expIcon = Icons.getExpansionIcon(pillar.id)
row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
Line 437: Line 459:
end
end
row.val = totalVal
row.val = totalVal
if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
hasOtherModifiers = true
end
table.insert(tableArray, row)
end
local POIList = p.getPOIsWithModifier(modifiers, skill, getOpposites)
for i, POI in ipairs(POIList) do
local row = {}
row.name = POI.name
row.icon = Icons.Icon({POI.name, type='poi'})
row.expIcon = Icons.getExpansionIcon(POI.id)
row.type = '[[Cartography|Point of Interest]]'
row.typeText = 'Point of Interest'
local totalVal = 0
for i, mod in ipairs(modifiers) do
totalVal = totalVal + p.getModifierValue(POI.activeModifiers, mod, skill, getOpposites)
end
row.val = totalVal
row.modifierText, row.otherModifiers = getModText(POI.activeModifiers)


if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then

Latest revision as of 19:16, 22 September 2023

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 Pets = require('Module:Pets')
local Items = require('Module:Items')
local Skills = require('Module:Skills')
local Agility = require('Module:Skills/Agility')
local Prayer = require('Module:Prayer')
local Shop = require('Module:Shop')
local Icons = require('Module:Icons')
local Cartography = require('Module:Skills/Cartography')

--First up, functions to get all the things in a category that have a given modifier:
function p.getModifierValue(modifiers, modifier, skill, getOpposites)
	--Sometimes nil modifier sets will get here, which is fine. Just return 0 immediately
	if modifiers == nil then
		return 0
	end

	--Make sure we have the skillID and not the name
	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 0
		end
	end

	--By default, attempt to add the increased and decreased prefixes to the modifier
	--But if getOpposites is false, only look for an exact match
	
	local mods = {}
	if getOpposites == nil or getOpposites then
		mods.inc = 'increased'..modifier
		mods.dec = 'decreased'..modifier
	else
		mods.inc = modifier
	end
	
	local magnitude = { inc = 0, dec = 0 }
	for modType, modName in pairs(mods) do
		if modifiers[modName] ~= nil then
			local valueArray = nil
            if type(modifiers[modName]) ~= 'table' then
                valueArray = {modifiers[modName]}
            else
                valueArray = modifiers[modName]
            end

            for i, subVal in ipairs(valueArray) do
                if type(subVal) == 'table' then
					if  subVal.skillID ~= nil then
						-- Modifier value is skill specific
						if skill == nil or skill == '' or subVal.skillID == skill then
							magnitude[modType] = magnitude[modType] + subVal.value
						end
					else
						-- Modifier value is a table of two numbers representing a range. Take the largest value
						magnitude[modType] = magnitude[modType] + (subVal[2] or 0)
					end
                else
                    magnitude[modType] = magnitude[modType] + subVal
                end
            end
		end
	end

    return magnitude.inc - magnitude.dec
end

function p.getItemsWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	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
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(item.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return itemList
end

function p.getObstaclesWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local obstList = Agility.getObstacles(
		function(obst)
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(obst.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return obstList
end

function p.getConstellationsWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local consList = Skills.getConstellations(
			function(cons)
				local consMods = Skills._buildAstrologyModifierArray(cons, 1, true, true, true, true)
				for i, modifier in ipairs(modifiers) do
					if p.getModifierValue(consMods, modifier, skill, getOpposites) ~= 0 then
						return true
					end
				end
				return false
			end)
	return consList
end

function p.getPillarsWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local pillarList = Agility.getPillars(
		function(pillar)
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(pillar.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return pillarList
end

function p.getPetsWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local petList = Pets.getPets(
		function(pet)
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(pet.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return petList
end

function p.getPrayersWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local prayerList = Prayer.getPrayers(
		function(prayer)
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(prayer.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return prayerList
end

function p.getUpgradesWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local upgradeList = Shop.getPurchases(
		function(purchase)
			if purchase.category == 'melvorD:GolbinRaid' then
				return false
			end
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(purchase.contains.modifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
	return upgradeList
end

function p.getPOIsWithModifier(modifiers, skill, getOpposites)
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	local POIList = Cartography.getPointsOfInterest(
		function(POI)
			if POI.activeModifiers == nil then
				return false
			end
			
			for i, mod in ipairs(modifiers) do
				if p.getModifierValue(POI.activeModifiers, mod, skill, getOpposites) ~= 0 then
					return true
				end
			end
			return false
		end)
		
	return POIList
end

function p._getModifierTable(modifiers, skill, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort)
	local modifierNames = {}
	if type(modifiers) == 'string' then
		modifiers = {modifiers}
	end
	for i, modifier in pairs(modifiers) do
		if getOpposites then
			table.insert(modifierNames, 'increased'..modifier)
			table.insert(modifierNames, 'decreased'..modifier)
		else
			table.insert(modifierNames, modifier)
		end
	end

	local hasOtherModifiers = false
	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 =
		function(modifiers)
			local modTextArray = { ["visible"] = {}, ["overflow"] = {} }
			local otherModCount = 0
			local mainModText = {}
			for modName, modValue in Shared.skpairs(modifiers) do
                local includedMod = Shared.contains(modifierNames, modName)
                local valueArray = nil
                if type(modValue) ~= 'table' then
                    valueArray = {modValue}
                else
                    valueArray = modValue
                end

                for j, subVal in ipairs(valueArray) do
                    local includeInMainText = includedMod
                    if type(subVal) == 'table' and subVal.skillID ~= nil then
                        -- Modifier value is skill specific
                        if includeInMainText then
                            -- If the skill doesn't match then don't include in the main text
                            includeInMainText = skill == nil or skill == '' or subVal.skillID == skill
                        end
                        subVal = {subVal}
                    end

                    if includeInMainText then
                        table.insert(mainModText, Constants._getModifierText(modName, subVal))
                    else
                        otherModCount = otherModCount + 1
                        local key = ((maxOtherMods == nil or otherModCount <= maxOtherMods) and 'visible') or 'overflow'
                        table.insert(modTextArray[key], Constants._getModifierText(modName, subVal))
                    end
                end
            end

			local overflowModCount = Shared.tableCount(modTextArray['overflow'])
			if overflowModCount == 1 then
				table.insert(modTextArray['visible'], modTextArray['overflow'][1])
			end
			local otherModText = {table.concat(modTextArray['visible'], '<br/>')}

			if overflowModCount > 1 then
				-- Number of other modifiers for the object exceed the specified maximum
				table.insert(otherModText, '<br/><span class="mw-collapsible mw-collapsed" ')
				table.insert(otherModText, 'data-expandtext="Show ' .. Shared.formatnum(overflowModCount) .. ' more modifiers", data-collapsetext="Hide">')
				table.insert(otherModText, table.concat(modTextArray['overflow'], '<br/>') .. '</span>')
			end
			return table.concat(mainModText, '<br/>'), table.concat(otherModText)
		end

	local tableArray = {}
	--Going through each type of thing to add to the array
	local itemList = p.getItemsWithModifier(modifiers, skill, getOpposites)
	for i, item in ipairs(itemList) do
		local row = {}
		row.name = item.name
		row.icon = Icons.Icon({item.name, type='item'})
		row.expIcon = Icons.getExpansionIcon(item.id)
		row.type = 'Item'
		--For equipment, show the slot they go in
		if item.validSlots ~= nil then
			row.type = row.type..' ('..table.concat(Shared.clone(item.validSlots), ', ')..')'
		end
		row.typeText = row.type
		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 string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local petList = p.getPetsWithModifier(modifiers, skill, getOpposites)
	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.getExpansionIcon(pet.id)
		row.type = '[[Pets|Pet]]'
		row.typeText = 'Pet'
		local totalVal = 0
		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)

		if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local obstList = p.getObstaclesWithModifier(modifiers, skill, getOpposites)
	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.getExpansionIcon(obst.id)
		row.type = '[[Agility#Obstacles|Agility Obstacle '..tostring(tonumber(obst.category)+1)..']]'
		row.typeText = 'Agility Obstacle '..string.format("%02d", (obst.category + 1))
		local totalVal = 0
		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)

		if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local pillarList = p.getPillarsWithModifier(modifiers, skill, getOpposites)
	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.getExpansionIcon(pillar.id)
		row.type = '[[Agility#Passive Pillars|Agility Pillar]]'
		row.typeText = 'Agility Pillar'
		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 not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local prayerList = p.getPrayersWithModifier(modifiers, skill, getOpposites)
	for i, prayer in ipairs(prayerList) do
		local row = {}
		row.name = prayer.name
		row.icon = Icons.Icon({prayer.name, type='prayer'})
		row.expIcon = Icons.getExpansionIcon(prayer.id)
		row.type = [[Prayer]]
		row.typeText = 'Prayer'
		local totalVal = 0
		for i, mod in ipairs(modifiers) do
			totalVal = totalVal + p.getModifierValue(prayer.modifiers, mod, skill, getOpposites)
		end
		row.val = totalVal

		row.modifierText, row.otherModifiers = getModText(prayer.modifiers)

		if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local upgradeList = p.getUpgradesWithModifier(modifiers, skill, getOpposites)
	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.getExpansionIcon(upgrade.id)
		row.type = '[[Shop|Upgrade]]'
		row.typeText = '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 string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local constellationList = p.getConstellationsWithModifier(modifiers, skill, getOpposites)
	for i, cons in ipairs(constellationList) do
		local modList = Skills._buildAstrologyModifierArray(cons, nil, true, true, true, true)
		local row = {}
		row.name = cons.name
		row.icon = Icons.Icon({cons.name, type='constellation'})
		row.expIcon = Icons.getExpansionIcon(cons.id)
		row.type = '[[Astrology#Constellations|Constellation]]'
		row.typeText = 'Constellation'
		row.modifierText, row.otherModifiers = getModText(modList)
		
		local totalVal = 0
		for i, mod in pairs(modifiers) do
			totalVal = totalVal + p.getModifierValue(modList, mod, skill, getOpposites)
		end
		row.val = totalVal

		if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end
	
	local POIList = p.getPOIsWithModifier(modifiers, skill, getOpposites)
	for i, POI in ipairs(POIList) do
		local row = {}
		row.name = POI.name
		row.icon = Icons.Icon({POI.name, type='poi'})
		row.expIcon = Icons.getExpansionIcon(POI.id)
		row.type = '[[Cartography|Point of Interest]]'
		row.typeText = 'Point of Interest'
		local totalVal = 0
		for i, mod in ipairs(modifiers) do
			totalVal = totalVal + p.getModifierValue(POI.activeModifiers, mod, skill, getOpposites)
		end
		row.val = totalVal

		row.modifierText, row.otherModifiers = getModText(POI.activeModifiers)

		if not hasOtherModifiers and string.len(row.otherModifiers) > 0 then
			hasOtherModifiers = true
		end

		table.insert(tableArray, row)
	end

	local result = '{| class="wikitable sortable stickyHeader"'
	result = result..'\r\n|- class="headerRow-0"'
	result = result..'\r\n!Source!!Type!!'..columnName
	if hasOtherModifiers and displayOtherMods then result = result..'!!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
		result = result..'\r\n|-'
		result = result..'\r\n|data-sort-value="'..row.name..'"|'..row.icon
		result = result..'|| data-sort-value="'..row.typeText..'" | '..row.expIcon..row.type..'||data-sort-value="'..row.val..'"| '..row.modifierText
		if hasOtherModifiers and displayOtherMods then
			result = result..'|| '..row.otherModifiers
		end
	end

	result = result..'\r\n|}'
	return result
end

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

	if Shared.contains(modifier, ',') then
		modifier = Shared.splitString(modifier, ',')
	end

	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

	return p._getModifierTable(modifier, skill, columnName, getOpposites, displayOtherMods, maxOtherMods, forceMagnitudeSort)
end

--Function for console testing of modifier tables
function p.getModifierTableTest()
	return p.getModifierTable({args = {'MeleeMaxHit', 'GP Boosts'}})
end

return p