Module:ItemTables: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
No edit summary
Instead of removing duplicates, keep all stats and use rowspan to merge the item info across multiple rows. Added FervorMovespeed to move speed
Line 4: Line 4:
local get_type = require("Module:ItemData")._get_type
local get_type = require("Module:ItemData")._get_type
local commas = require("Module:Addcommas")
local commas = require("Module:Addcommas")
-- Returns an array of item tables that have the same properties
-- @function  get_similar_items_array
-- @param      {array}
-- @return    {array of tables}
local function get_similar_items_array(property)
local similarItems = {}
local hash = {}
local noDuplicates = {}
for _, v in pairs(data) do
for _, value in ipairs(property) do
if (v[value] ~= nil and v["IsDisabled"] == false and v["Name"] ~= nil and v[value] ~= "0" and v[value] ~= "0m" and v[value] ~= "") then
table.insert(similarItems, v)
end
end
end
-- remove duplicate items if that item contains multiple keys of the same stat increase (eg. Fleetfoot has both "BonusMoveSpeed", "ActiveBonusMoveSpeed" in move speed).
-- there might be a more efficient way to prevent adding duplicate items in the first place in the above for loop
for _, v in pairs(similarItems) do
if (not hash[v]) then
noDuplicates[#noDuplicates+1] = v
hash[v] = true
end
end
return noDuplicates
end


--- Copies original table with its children as deep as possible. Does not handle metatables. (Copy is needed because Lua passes by reference, not value)
--- Copies original table with its children as deep as possible. Does not handle metatables. (Copy is needed because Lua passes by reference, not value)
Line 73: Line 46:
friendly_to_internal["bullet lifesteal"] = { "BulletLifestealPercent", "ActiveBonusLifesteal" }
friendly_to_internal["bullet lifesteal"] = { "BulletLifestealPercent", "ActiveBonusLifesteal" }
friendly_to_internal["spirit lifesteal"] = { "AbilityLifestealPercentHero" }
friendly_to_internal["spirit lifesteal"] = { "AbilityLifestealPercentHero" }
friendly_to_internal["move speed"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed" }
friendly_to_internal["move speed"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "FervorMovespeed"}
friendly_to_internal["sprint speed"] = { "BonusSprintSpeed" }
friendly_to_internal["sprint speed"] = { "BonusSprintSpeed" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow" }
Line 138: Line 111:
"GroundDashReductionPercent"
"GroundDashReductionPercent"
     },
     },
["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed" },
["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed", "FervorMovespeed"},
[" HP"] = {"CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier"}
[" HP"] = {"CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier"}
}
}
Line 172: Line 145:
-- @return    {string} The formatted wikitext string.
-- @return    {string} The formatted wikitext string.
local function signPrefix(value)
local function signPrefix(value)
-- Returns an empty string if the input is nil to prevent errors.
if value == nil then
if value == nil then
return ""
return ""
end
end
-- Converts the input to a string to safely use string methods. This allows
-- the function to handle numbers from JSON (e.g., 10) that are not strings.
value = tostring(value)
value = tostring(value)
-- Removes the 'm' suffix if it exists (e.g., from range values like "7m").
value = value:gsub("m", "")
value = value:gsub("m", "")
-- Convert the cleaned string into a number for comparison.
value = tonumber(value)
value = tonumber(value)
-- Returns an empty string if the value cannot be converted to a number,
-- which prevents errors and malformed output.
if not value then
if not value then
return ""
return ""
end
end
if (value >= 0) then
if (value >= 0) then
return "+<b>" .. value .. "</b>"
return "+<b>" .. value .. "</b>"
Line 207: Line 169:
local requirements = {}
local requirements = {}
local listofKeysTable = {}
local listofKeysTable = {}
local listofItems = ""
local property = frame:getParent().args[1] or mw.title.getCurrentTitle().text
local property = frame:getParent().args[1] or mw.title.getCurrentTitle().text
local copyVar = property
local copyVar = property
-- Get internal keys corresponding to the friendly property name
listofKeysTable = friendlyNames(property)  
listofKeysTable = friendlyNames(property)  
local filteredData = {}
local filteredData = {}
filteredData = get_similar_items_array(listofKeysTable)
for _, v in pairs(data) do
for _, t in ipairs(listofKeysTable) do
if v[t] ~= nil and v["IsDisabled"] == false and v["Name"] ~= nil and v[t] ~= "0" and v[t] ~= "0m" and v[t] ~= "" then
table.insert(filteredData, v)
break
end
end
end
 
table.sort(filteredData, defaultSort())
table.sort(filteredData, defaultSort())
 
local createTable = mw.html.create('table')
local createTable = mw.html.create('table')
:addClass('wikitable sortable')
:addClass('wikitable sortable')
:tag('caption'):wikitext("List of items"):done()
:tag('caption'):wikitext("List of items"):done()
 
local soulIcon = frame:expandTemplate{ title = 'Souls' }
local soulIcon = frame:expandTemplate{ title = 'Souls' }
 
local createTableHeader = mw.html.create('tr')
local createTableHeader = mw.html.create('tr')
createTableHeader
createTableHeader
Line 227: Line 199:
:tag('th'):wikitext('Category'):done()
:tag('th'):wikitext('Category'):done()
:tag('th'):wikitext('Stat change'):done()
:tag('th'):wikitext('Stat change'):done()
createTable:node(createTableHeader)
createTable:node(createTableHeader)
 
-- query all the filtered items. itemName is the item table.
-- Track rowspans for items with multiple stats
for internalName, itemName in ipairs(filteredData) do  
local rowspanTracker = {}
local display = frame:expandTemplate{
 
title = 'ItemIcon',
for _, itemName in ipairs(filteredData) do  
args = {
local display = frame:expandTemplate{
itemName["Name"]
title = 'ItemIcon',
}
args = { itemName["Name"] }
}
}
 
for k, t in pairs(listofKeysTable) do
local statCount = 0
-- filter out items that: don't have the property and the value isn't zero
for _, t in pairs(listofKeysTable) do
if(itemName[t] ~= nil and itemName[t] ~= "0" ) then
if(itemName[t] ~= nil and itemName[t] ~= "0") then
statCount = statCount + 1
end
end
if statCount == 0 then statCount = 1 end
rowspanTracker[itemName["Name"]] = statCount
 
local firstStat = true
for _, t in pairs(listofKeysTable) do
if(itemName[t] ~= nil and itemName[t] ~= "0") then
-- Handles complex stat values from the JSON data.
-- Handles complex stat values from the JSON data.
-- A stat can be a simple value (e.g., "BonusMoveSpeed": "1") or a table containing
-- A stat can be a simple value (e.g., "BonusMoveSpeed": "1") or a table containing
-- scaling info (e.g., "BonusFireRate": { "Value": 10, "Scale": ... }).
-- scaling info (e.g., "BonusFireRate": { "Value": 10, "Scale": ... }).
-- This code checks if the stat is a table and extracts the base numerical value from its 'Value' key.
-- This code checks if the stat is a table and extracts the base numerical value from its 'Value' key.
local statValue = itemName[t]
local statValue = itemName[t]
if type(statValue) == 'table' and statValue.Value ~= nil then
if type(statValue) == 'table' and statValue.Value ~= nil then
statValue = statValue.Value
statValue = statValue.Value
end
end
 
listofItems = listofItems .. itemName["Name"] .. "<br/>"
copyVar = copyVar:gsub("^%l", string.upper)
copyVar = copyVar:gsub("^%l", string.upper)
 
local categoryDisplay
-- Get category display using ItemType template with appropriate parameters
if itemName["Slot"] == "Armor" then
local categoryDisplay
categoryDisplay = frame:expandTemplate{
if itemName["Slot"] == "Armor" then
title = 'ItemType',
categoryDisplay = frame:expandTemplate{
args = { "Armor", "Vitality" }
title = 'ItemType',
}
args = { "Armor", "Vitality" }
elseif itemName["Slot"] == "Tech" then
}
categoryDisplay = frame:expandTemplate{
elseif itemName["Slot"] == "Tech" then
title = 'ItemType',
categoryDisplay = frame:expandTemplate{
args = { "Tech", "Spirit" }
title = 'ItemType',
}
args = { "Tech", "Spirit" }
else
}
categoryDisplay = frame:expandTemplate{
else -- Weapon
title = 'ItemType',
categoryDisplay = frame:expandTemplate{
args = { "Weapon", "Weapon" }
title = 'ItemType',
}
args = { "Weapon", "Weapon" }
end
}
 
end
local tableData = mw.html.create('tr')
 
local tableData = mw.html.create('tr')
if firstStat then
tableData
tableData
:tag('td'):wikitext(display):done()
:tag('td'):attr('rowspan', statCount):wikitext(display):done()
:tag('td'):wikitext(commas._add(get_cost(itemName["Name"]))):done()
:tag('td'):attr('rowspan', statCount):wikitext(commas._add(get_cost(itemName["Name"]))):done()
:tag('td'):wikitext(categoryDisplay):done()
:tag('td'):attr('rowspan', statCount):wikitext(categoryDisplay):done()
-- Construct the stat change string.
firstStat = false
:tag('td'):wikitext(signPrefix(statValue) .. appendSuffix(t) .. " " .. copyVar):done()
table.insert(requirements, tableData)
end
end
tableData:tag('td'):wikitext(signPrefix(statValue) .. appendSuffix(t) .. " " .. copyVar):done()
table.insert(requirements, tableData)
end
end
end
end
end
 
 
for _, row in ipairs(requirements) do
for _, row in ipairs(requirements) do
createTable:node(row)
createTable:node(row)
end
end
 
return tostring(createTable)
return tostring(createTable)
end
end


return p
return p

Revision as of 17:17, 18 January 2026

Module used to create item tables with Template:Item stat table.

To add a new stat, add the name of the stat as "friendly to internal", followed by the property as listed on Data:ItemData.json, as well as adding the property to the "local unitSuffix" list corresponding to its suffix.


local p = {};
local data = mw.loadJsonData("Data:ItemData.json")
local get_cost = require("Module:ItemData")._get_cost
local get_type = require("Module:ItemData")._get_type
local commas = require("Module:Addcommas")

--- Copies original table with its children as deep as possible. Does not handle metatables. (Copy is needed because Lua passes by reference, not value)
-- @function   Deepcopy
-- @param      {n-D table}
-- @return     {n-D table}
function Deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[Deepcopy(orig_key)] = Deepcopy(orig_value)
        end
    else
        copy = orig
    end
    return copy
end

local friendly_to_internal = {}
friendly_to_internal["ammo"] = { "BonusClipSizePercent", "BonusClipSize", "ActiveReloadPercent" }
friendly_to_internal["weapon damage"] = { "BaseAttackDamagePercent", "CloseRangeBonusWeaponPower", "LongRangeBonusWeaponPower", "AttackDamageWhenShielded" }
friendly_to_internal["spirit damage"] = { "ProcBonusMagicDamage" }
friendly_to_internal["bullet shield health"] = { "BulletShieldMaxHealth", "BulletShieldOnCast", "SaviorBulletShieldHealth", "FlyingBulletShield", "VexBarrierBulletMaxHealth" }
friendly_to_internal["spirit shield health"] = { "TechShieldMaxHealth", "TechShieldOnCast", "SaviorMagicShieldHealth", "FlyingTechShield", "VexBarrierTechMaxHealth" }
friendly_to_internal["barrier"] = { "CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier" }
friendly_to_internal["bullet resist"] = { "BulletResist", "LocalBulletArmorReduction", "ReturnFireBulletResist", "BulletResistPerStack" }
friendly_to_internal["bullet resist reduction"] = { "BulletArmorReduction", "BulletResistReduction" }
friendly_to_internal["spirit resist"] = { "TechResist" }
friendly_to_internal["spirit resist reduction"] = { "TechArmorDamageReduction", "MagicResistReduction" }
friendly_to_internal["bonus health"] = { "BonusHealth" }
friendly_to_internal["health regen"] = { "BonusHealthRegen" }
friendly_to_internal["regeneration"] = { "Regeneration" }
friendly_to_internal["regeneration duration"] = { "RegenerationDuration" }
friendly_to_internal["out of combat health regen"] = { "OutOfCombatHealthRegen" }
friendly_to_internal["max health regen"] = { "HealLifePercentOutOfCombat" }
friendly_to_internal["fire rate"] = { "BonusFireRate", "FireRateWhenShielded", "FireRateBonus", "FervorFireRate", "AmbushBonusFireRate", "ActiveBonusFireRate" }
friendly_to_internal["fire rate slow"] = { "FireRateSlow" }
friendly_to_internal["bullet velocity"] = { "BonusBulletSpeedPercent" }
friendly_to_internal["spirit power"] = { "TechPower", "BonusSpirit", "SpiritPower", "BonusSpiritWithMagicShield", "ImbuedTechPower", "AmbushBonusTechPower", "TechPowerPercent" }
friendly_to_internal["bullet lifesteal"] = { "BulletLifestealPercent", "ActiveBonusLifesteal" }
friendly_to_internal["spirit lifesteal"] = { "AbilityLifestealPercentHero" }
friendly_to_internal["move speed"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "FervorMovespeed"}
friendly_to_internal["sprint speed"] = { "BonusSprintSpeed" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow" }
friendly_to_internal["reload time"] = { "ReloadSpeedMultipler" }
friendly_to_internal["heavy melee distance"] = { "MeleeDistanceScale" }
friendly_to_internal["bonus heavy damage"] = { "BonusHeavyMeleeDamage" }
friendly_to_internal["bonus melee damage"] = { "BonusMeleeDamagePercent" }
friendly_to_internal["melee resist"] = { "MeleeResistPercent" }
friendly_to_internal["stamina"] = { "Stamina" }
friendly_to_internal["slide distance"] = { "SlideScale" }
friendly_to_internal["stamina recovery"] = { "StaminaCooldownReduction" }
friendly_to_internal["healing reduction"] = { "HealAmpRegenPenaltyPercent" }
friendly_to_internal["ability duration"] = { "ImbuedBonusDuration", "BonusAbilityDurationPercent" }
friendly_to_internal["ability cooldown reduction"] = { "ImbuedCooldownReduction", "CooldownReduction" }
friendly_to_internal["item cooldown reduction"] = { "ItemCooldownReduction" }
friendly_to_internal["charge cooldown reduction"] = { "CooldownReductionOnChargedAbilities" }
friendly_to_internal["debuff resist"] = { "StatusResistancePercent" }
friendly_to_internal["ability range"] = { "TechRangeMultiplier", "ImbuedTechRangeMultiplier"}
friendly_to_internal["falloff range"] = { "BonusAttackRangePercent" }
friendly_to_internal["spirit amp"] = { "ShreddersTechAmp", "MagicIncreasePerStack" }
friendly_to_internal["spirit damage reduction"] = { "TechDamageReduction" }
friendly_to_internal["dash distance"] = { "GroundDashReductionPercent" }
friendly_to_internal["damage reduction"] = { "OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction" }

-- Returns a table of internal properties 
-- @function   friendlyNames
-- @param      {string}
-- @return     {table}
local function friendlyNames(desc)
	local keysTable = {}
	desc = string.lower(desc or "")
	for link, patterns in pairs(friendly_to_internal) do
		if (desc == link) then keysTable = patterns end 
	end
	return keysTable
end

local unitSuffix = {
	["%"] = { "BonusClipSizePercent", "ActiveReloadPercent", 
		"BaseAttackDamagePercent", "CloseRangeBonusWeaponPower", "LongRangeBonusWeaponPower", "AttackDamageWhenShielded",
		"TechResist", "BulletResist",
		"BonusFireRate", "FireRateWhenShielded", "FervorFireRate", "FireRateBonus", "AmbushBonusFireRate", "ActiveBonusFireRate", "FireRateSlow",
		"LocalBulletArmorReduction", "ReturnFireBulletResist", "BulletArmorReduction", "BulletResistReduction", "BulletResistPerStack",
		"TechArmorDamageReduction", "MagicResistReduction",
		"BonusBulletSpeedPercent",
		"BulletLifestealPercent", "ActiveBonusLifesteal", "AbilityLifestealPercentHero",
		"HealLifePercentOutOfCombat",
		"ReloadSpeedMultipler",
		"MeleeDistanceScale", "BonusHeavyMeleeDamage", "BonusMeleeDamagePercent", "MeleeResistPercent",
		"SlideScale",
		"StaminaCooldownReduction",
		"SlowPercent", "MovementSpeedSlow",
		"HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent",
		"ImbuedBonusDuration", "NonImbuedBonusDuration", "BonusAbilityDurationPercent",
		"ImbuedCooldownReduction", "CooldownReduction",
		"ItemCooldownReduction",
		"CooldownReductionOnChargedAbilities",
		"StatusResistancePercent",
		"TechRangeMultiplier", "ImbuedTechRangeMultiplier",
		"BonusAttackRangePercent",
		"ShreddersTechAmp", "BonusAmpPerHeadshot", "MagicIncreasePerStack",
		"TechDamageReduction",
		"OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction", "TechPowerPercent",
		"GroundDashReductionPercent"
    	},
	["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed", "FervorMovespeed"},
	[" HP"] = {"CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier"}
}

local function appendSuffix(internal)
	local unitName = ""
	for key, s in pairs(unitSuffix) do
		for _, pattern in pairs(s) do
			if (internal == pattern) then return key end
		end
	end
	return unitName
end

local sortByWeapon = {}
sortByWeapon["Weapon"] = 1
sortByWeapon["Armor"] = 10000
sortByWeapon["Tech"] = 100000000

-- Sort the order of items in a preset as it appears when no filtered ordering is applied. 
-- Ordered first by slot and cost increasing, then alphabetical within same slot and cost.
function defaultSort()
    return function(a, b)
    	if (get_cost(a["Name"]) == get_cost(b["Name"]) and sortByWeapon[a["Slot"]] == sortByWeapon[b["Slot"]]) then return a["Name"] < b["Name"] end
        return get_cost(a["Name"]) * sortByWeapon[a["Slot"]] < get_cost(b["Name"]) * sortByWeapon[b["Slot"]]
    end
end

-- Formats a stat value with a leading + or - sign and makes it bold.
-- The function is robust and handles nil, number, string, or table inputs safely.
-- @function   signPrefix
-- @param      {any} The stat value to format.
-- @return     {string} The formatted wikitext string.
local function signPrefix(value)
	if value == nil then
		return ""
	end
	value = tostring(value)
	value = value:gsub("m", "")
	value = tonumber(value)
	if not value then
		return ""
	end
	if (value >= 0) then
		return "+<b>" .. value .. "</b>"
	elseif (value < 0) then
		value = math.abs(value)
		return "-<b>" .. value .. "</b>"
	end
end

-- Outputs a wikitable of items that increase a specified stat. Invoked by {{Item stat table}}
-- @function   p.itemPropTable
-- @param      {string}
-- @return     {string}
p.itemPropTable = function(frame)
	local requirements = {}
	local listofKeysTable = {}
	local property = frame:getParent().args[1] or mw.title.getCurrentTitle().text
	local copyVar = property

	-- Get internal keys corresponding to the friendly property name
	listofKeysTable = friendlyNames(property) 

	local filteredData = {}
	for _, v in pairs(data) do
		for _, t in ipairs(listofKeysTable) do
			if v[t] ~= nil and v["IsDisabled"] == false and v["Name"] ~= nil and v[t] ~= "0" and v[t] ~= "0m" and v[t] ~= "" then
				table.insert(filteredData, v)
				break
			end
		end
	end

	table.sort(filteredData, defaultSort())

	local createTable = mw.html.create('table')
		:addClass('wikitable sortable')
		:tag('caption'):wikitext("List of items"):done()

	local soulIcon = frame:expandTemplate{ title = 'Souls' }

	local createTableHeader = mw.html.create('tr')
	createTableHeader
		:tag('th'):wikitext('Name'):done()
		:tag('th'):wikitext(soulIcon .. 'Cost'):done()
		:tag('th'):wikitext('Category'):done()
		:tag('th'):wikitext('Stat change'):done()
	createTable:node(createTableHeader)

	-- Track rowspans for items with multiple stats
	local rowspanTracker = {}

for _, itemName in ipairs(filteredData) do 
	local display = frame:expandTemplate{
		title = 'ItemIcon',
		args = { itemName["Name"] }
	}

	local statCount = 0
	for _, t in pairs(listofKeysTable) do
		if(itemName[t] ~= nil and itemName[t] ~= "0") then
			statCount = statCount + 1
		end
	end
	if statCount == 0 then statCount = 1 end
	rowspanTracker[itemName["Name"]] = statCount

	local firstStat = true
	for _, t in pairs(listofKeysTable) do
		if(itemName[t] ~= nil and itemName[t] ~= "0") then
				-- Handles complex stat values from the JSON data.
				-- A stat can be a simple value (e.g., "BonusMoveSpeed": "1") or a table containing
				-- scaling info (e.g., "BonusFireRate": { "Value": 10, "Scale": ... }).
				-- This code checks if the stat is a table and extracts the base numerical value from its 'Value' key.
			local statValue = itemName[t]
			if type(statValue) == 'table' and statValue.Value ~= nil then
				statValue = statValue.Value
			end

			copyVar = copyVar:gsub("^%l", string.upper)

			local categoryDisplay
			if itemName["Slot"] == "Armor" then
				categoryDisplay = frame:expandTemplate{
					title = 'ItemType',
					args = { "Armor", "Vitality" }
				}
			elseif itemName["Slot"] == "Tech" then
				categoryDisplay = frame:expandTemplate{
					title = 'ItemType',
					args = { "Tech", "Spirit" }
				}
			else
				categoryDisplay = frame:expandTemplate{
					title = 'ItemType',
					args = { "Weapon", "Weapon" }
				}
			end

			local tableData = mw.html.create('tr')

			if firstStat then
				tableData
					:tag('td'):attr('rowspan', statCount):wikitext(display):done()
					:tag('td'):attr('rowspan', statCount):wikitext(commas._add(get_cost(itemName["Name"]))):done()
					:tag('td'):attr('rowspan', statCount):wikitext(categoryDisplay):done()
				firstStat = false
			end

			tableData:tag('td'):wikitext(signPrefix(statValue) .. appendSuffix(t) .. " " .. copyVar):done()
			table.insert(requirements, tableData)
		end
	end
end


	for _, row in ipairs(requirements) do
		createTable:node(row)
	end

	return tostring(createTable)
end

return p