Module:ItemTables: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Instead of removing duplicates, keep all stats and use rowspan to merge the item info across multiple rows. Added FervorMovespeed to move speed
capacitor move slow
 
(22 intermediate revisions by 5 users not shown)
Line 1: Line 1:
local p = {};
local p = {};
local data = mw.loadJsonData("Data:ItemData.json")
local get_cost = require("Module:ItemData")._get_cost
local get_cost = require("Module:ItemData")._get_cost
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")
local GameData = require("Module:GameData")


--- Copies original table with its children as deep as possible. Does not handle metatables. (Copy is needed because Lua passes by reference, not value)
-- Translate internal attribute names to plain text for module calls
-- @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 = {}
local friendly_to_internal = {}
friendly_to_internal["ammo"] = { "BonusClipSizePercent", "BonusClipSize", "ActiveReloadPercent" }
friendly_to_internal["ammo"] = { "BonusClipSizePercent", "BonusClipSize", "ActiveReloadPercent" }
Line 35: Line 18:
friendly_to_internal["spirit resist reduction"] = { "TechArmorDamageReduction", "MagicResistReduction" }
friendly_to_internal["spirit resist reduction"] = { "TechArmorDamageReduction", "MagicResistReduction" }
friendly_to_internal["bonus health"] = { "BonusHealth" }
friendly_to_internal["bonus health"] = { "BonusHealth" }
friendly_to_internal["bonus base health"] = { "BonusBaseHealth", "MaxHealthLossPercent" }
friendly_to_internal["health regen"] = { "BonusHealthRegen" }
friendly_to_internal["health regen"] = { "BonusHealthRegen" }
friendly_to_internal["regeneration"] = { "Regeneration" }
friendly_to_internal["regeneration"] = { "Regeneration" }
Line 40: Line 24:
friendly_to_internal["out of combat health regen"] = { "OutOfCombatHealthRegen" }
friendly_to_internal["out of combat health regen"] = { "OutOfCombatHealthRegen" }
friendly_to_internal["max health regen"] = { "HealLifePercentOutOfCombat" }
friendly_to_internal["max health regen"] = { "HealLifePercentOutOfCombat" }
friendly_to_internal["fire rate"] = { "BonusFireRate", "FireRateWhenShielded", "FireRateBonus", "FervorFireRate", "AmbushBonusFireRate", "ActiveBonusFireRate" }
friendly_to_internal["fire rate"] = { "BonusFireRate", "FireRateWhenShielded", "FireRateBonus", "FervorFireRate", "AmbushBonusFireRate", "ActiveBonusFireRate", "ActivatedFireRate" }
friendly_to_internal["fire rate slow"] = { "FireRateSlow" }
friendly_to_internal["fire rate slow"] = { "FireRateSlow" }
friendly_to_internal["bullet velocity"] = { "BonusBulletSpeedPercent" }
friendly_to_internal["bullet velocity"] = { "BonusBulletSpeedPercent" }
Line 46: Line 30:
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", "FervorMovespeed"}
friendly_to_internal["move speed"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "FervorMovespeed",}
friendly_to_internal["sprint speed"] = { "BonusSprintSpeed" }
friendly_to_internal["sprint speed"] = { "BonusSprintSpeed", "InvisMoveSpeedMod" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow", "MaxSlowPercent" }
friendly_to_internal["reload time"] = { "ReloadSpeedMultipler" }
friendly_to_internal["reload time"] = { "ReloadSpeedMultipler" }
friendly_to_internal["heavy melee distance"] = { "MeleeDistanceScale" }
friendly_to_internal["heavy melee distance"] = { "MeleeDistanceScale" }
friendly_to_internal["bonus heavy damage"] = { "BonusHeavyMeleeDamage" }
friendly_to_internal["bonus heavy damage"] = { "BonusHeavyMeleeDamage" }
friendly_to_internal["bonus melee damage"] = { "BonusMeleeDamagePercent" }
friendly_to_internal["bonus melee damage"] = { "BonusMeleeDamagePercent", "AmbushBonusMeleeDamage" }
friendly_to_internal["melee resist"] = { "MeleeResistPercent" }
friendly_to_internal["melee resist"] = { "MeleeResistPercent" }
friendly_to_internal["stamina"] = { "Stamina" }
friendly_to_internal["stamina"] = { "Stamina" }
Line 62: Line 46:
friendly_to_internal["item cooldown reduction"] = { "ItemCooldownReduction" }
friendly_to_internal["item cooldown reduction"] = { "ItemCooldownReduction" }
friendly_to_internal["charge cooldown reduction"] = { "CooldownReductionOnChargedAbilities" }
friendly_to_internal["charge cooldown reduction"] = { "CooldownReductionOnChargedAbilities" }
friendly_to_internal["debuff resist"] = { "StatusResistancePercent" }
friendly_to_internal["debuff resist"] = { "StatusResistancePercent", "FervorStatusResistancePercent" }
friendly_to_internal["ability range"] = { "TechRangeMultiplier", "ImbuedTechRangeMultiplier"}
friendly_to_internal["ability range"] = { "TechRangeMultiplier", "ImbuedTechRangeMultiplier"}
friendly_to_internal["falloff range"] = { "BonusAttackRangePercent" }
friendly_to_internal["falloff range"] = { "BonusAttackRangePercent" }
friendly_to_internal["spirit amp"] = { "ShreddersTechAmp", "MagicIncreasePerStack" }
friendly_to_internal["spirit amp"] = { "ShreddersTechAmp", "MagicIncreasePerStack" }
friendly_to_internal["spirit damage reduction"] = { "TechDamageReduction" }
friendly_to_internal["spirit damage reduction"] = { "TechDamageReduction" }
friendly_to_internal["dash distance"] = { "GroundDashReductionPercent" }
friendly_to_internal["dash distance"] = { "GroundDashReductionPercent", "AirMoveIncreasePercent" }
friendly_to_internal["damage reduction"] = { "OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction" }
friendly_to_internal["damage reduction"] = { "OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction" }
friendly_to_internal["gravity scale"] = { "GravityScale" }
-- Override dead/hidden stats that exist in data but don't actually work in‑game.
-- Keys are item Names, values are tables of { [internalStatName] = replacementValue }
local deadStatOverrides = {
    ["Witchmail"] = { CooldownReduction = "0" },
    ["Refresher"] = { BulletResist = "0", TechResist = "0" },
    ["Glass Cannon"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
    ["Majestic Leap"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
    ["Improved Spirit"] = { BonusSprintSpeed = "0"},
}
--- 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
-- Helper to resolve a stat value, applying any dead‑stat override if defined.
local function resolveStatValue(itemName, t)
    if deadStatOverrides[itemName.Name] and deadStatOverrides[itemName.Name][t] ~= nil then
        return deadStatOverrides[itemName.Name][t]
    end
    return itemName[t]
end


-- Returns a table of internal properties  
-- Returns a table of internal properties  
Line 87: Line 108:
"BaseAttackDamagePercent", "CloseRangeBonusWeaponPower", "LongRangeBonusWeaponPower", "AttackDamageWhenShielded",
"BaseAttackDamagePercent", "CloseRangeBonusWeaponPower", "LongRangeBonusWeaponPower", "AttackDamageWhenShielded",
"TechResist", "BulletResist",
"TechResist", "BulletResist",
"BonusFireRate", "FireRateWhenShielded", "FervorFireRate", "FireRateBonus", "AmbushBonusFireRate", "ActiveBonusFireRate", "FireRateSlow",
"BonusFireRate", "FireRateWhenShielded", "FervorFireRate", "FireRateBonus", "AmbushBonusFireRate", "ActiveBonusFireRate", "ActivatedFireRate", "FireRateSlow",
"LocalBulletArmorReduction", "ReturnFireBulletResist", "BulletArmorReduction", "BulletResistReduction", "BulletResistPerStack",
"LocalBulletArmorReduction", "ReturnFireBulletResist", "BulletArmorReduction", "BulletResistReduction", "BulletResistPerStack",
"TechArmorDamageReduction", "MagicResistReduction",
"TechArmorDamageReduction", "MagicResistReduction",
Line 94: Line 115:
"HealLifePercentOutOfCombat",
"HealLifePercentOutOfCombat",
"ReloadSpeedMultipler",
"ReloadSpeedMultipler",
"MeleeDistanceScale", "BonusHeavyMeleeDamage", "BonusMeleeDamagePercent", "MeleeResistPercent",
"MeleeDistanceScale", "BonusHeavyMeleeDamage", "BonusMeleeDamagePercent", "MeleeResistPercent", "AmbushBonusMeleeDamage",
"SlideScale",
"SlideScale",
"StaminaCooldownReduction",
"StaminaCooldownReduction",
"SlowPercent", "MovementSpeedSlow",
"SlowPercent", "MovementSpeedSlow", "MaxSlowPercent",
"HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent",
"HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent",
"ImbuedBonusDuration", "NonImbuedBonusDuration", "BonusAbilityDurationPercent",
"ImbuedBonusDuration", "NonImbuedBonusDuration", "BonusAbilityDurationPercent",
Line 109: Line 130:
"TechDamageReduction",
"TechDamageReduction",
"OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction", "TechPowerPercent",
"OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction", "TechPowerPercent",
"GroundDashReductionPercent"
"GroundDashReductionPercent", "AirMoveIncreasePercent",
"GravityScale",
"BonusBaseHealth", "MaxHealthLossPercent"
     },
     },
["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed", "FervorMovespeed"},
["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed", "FervorMovespeed", "InvisMoveSpeedMod" },
[" HP"] = {"CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier"}
[" HP"] = {"CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier"}
}
}
Line 175: Line 198:
listofKeysTable = friendlyNames(property)  
listofKeysTable = friendlyNames(property)  


local filteredData = {}
local filteredData = GameData.get_entities(GameData.Dataset.ITEMS, 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 item-stat-table')
:tag('caption'):wikitext("List of items"):done()
    :tag('caption'):done()


local soulIcon = frame:expandTemplate{ title = 'Souls' }
local soulIcon = frame:expandTemplate{ title = 'Souls' }
Line 204: Line 218:
local rowspanTracker = {}
local rowspanTracker = {}


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


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


local firstStat = true
local firstStat = true
for _, t in pairs(listofKeysTable) do
for _, t in pairs(listofKeysTable) do
if(itemName[t] ~= nil and itemName[t] ~= "0") then
local statValue = resolveStatValue(itemName, t)
if(statValue ~= nil and statValue ~= "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
if type(statValue) == 'table' and statValue.Value ~= nil then
-- scaling info (e.g., "BonusFireRate": { "Value": 10, "Scale": ... }).
statValue = statValue.Value
-- This code checks if the stat is a table and extracts the base numerical value from its 'Value' key.
end
local statValue = itemName[t]
 
if type(statValue) == 'table' and statValue.Value ~= nil then
copyVar = copyVar:gsub("^%l", string.upper)
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 categoryDisplay
local tableData = mw.html.create('tr')
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(
(get_cost(itemName["Name"]) == 9999)
and "Legendary"
or commas._add(get_cost(itemName["Name"]))
):done()
:tag('td'):attr('rowspan', statCount):wikitext(categoryDisplay):done()
firstStat = false
end


if firstStat then
tableData:tag('td'):wikitext(signPrefix(statValue) .. appendSuffix(t) .. " " .. copyVar):done()
tableData
table.insert(requirements, 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
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
        createTable:node(row)
    end


for _, row in ipairs(requirements) do
    local edit_url = mw.uri.fullUrl("Module:ItemTables", { action = "edit" })
createTable:node(row)
    local disclaimer = frame:preprocess(
end
        '<div class="item-stat-table-disclaimer">'
        .. 'This is a dynamic list '
        .. '<span class="item-stat-table-list-name">"' .. property .. '"</span>. '
        .. '[' .. tostring(edit_url) .. ' Edit contents].'
        .. '</div>'
    )


return tostring(createTable)
    return disclaimer .. tostring(createTable)
end
end


return p
return p

Latest revision as of 15:11, 24 August 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 get_cost = require("Module:ItemData")._get_cost
local get_type = require("Module:ItemData")._get_type
local commas = require("Module:Addcommas")
local GameData = require("Module:GameData")

 -- Translate internal attribute names to plain text for module calls
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["bonus base health"] = { "BonusBaseHealth", "MaxHealthLossPercent" }
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", "ActivatedFireRate" }
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", "InvisMoveSpeedMod" }
friendly_to_internal["movement slow"] = { "SlowPercent", "MovementSpeedSlow", "MaxSlowPercent" }
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", "AmbushBonusMeleeDamage" }
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", "FervorStatusResistancePercent" }
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", "AirMoveIncreasePercent" }
friendly_to_internal["damage reduction"] = { "OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction" }
friendly_to_internal["gravity scale"] = { "GravityScale" }

-- Override dead/hidden stats that exist in data but don't actually work in‑game.
-- Keys are item Names, values are tables of { [internalStatName] = replacementValue }
local deadStatOverrides = {
    ["Witchmail"] = { CooldownReduction = "0" },
    ["Refresher"] = { BulletResist = "0", TechResist = "0" },
    ["Glass Cannon"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
    ["Majestic Leap"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
    ["Improved Spirit"] = { BonusSprintSpeed = "0"},
}

--- 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

-- Helper to resolve a stat value, applying any dead‑stat override if defined.
local function resolveStatValue(itemName, t)
    if deadStatOverrides[itemName.Name] and deadStatOverrides[itemName.Name][t] ~= nil then
        return deadStatOverrides[itemName.Name][t]
    end
    return itemName[t]
end

-- 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", "ActivatedFireRate", "FireRateSlow",
		"LocalBulletArmorReduction", "ReturnFireBulletResist", "BulletArmorReduction", "BulletResistReduction", "BulletResistPerStack",
		"TechArmorDamageReduction", "MagicResistReduction",
		"BonusBulletSpeedPercent",
		"BulletLifestealPercent", "ActiveBonusLifesteal", "AbilityLifestealPercentHero",
		"HealLifePercentOutOfCombat",
		"ReloadSpeedMultipler",
		"MeleeDistanceScale", "BonusHeavyMeleeDamage", "BonusMeleeDamagePercent", "MeleeResistPercent", "AmbushBonusMeleeDamage",
		"SlideScale",
		"StaminaCooldownReduction",
		"SlowPercent", "MovementSpeedSlow", "MaxSlowPercent",
		"HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent",
		"ImbuedBonusDuration", "NonImbuedBonusDuration", "BonusAbilityDurationPercent",
		"ImbuedCooldownReduction", "CooldownReduction",
		"ItemCooldownReduction",
		"CooldownReductionOnChargedAbilities",
		"StatusResistancePercent",
		"TechRangeMultiplier", "ImbuedTechRangeMultiplier",
		"BonusAttackRangePercent",
		"ShreddersTechAmp", "BonusAmpPerHeadshot", "MagicIncreasePerStack",
		"TechDamageReduction",
		"OutgoingDamagePenaltyPercent", "DeathImmunityDamageReduction", "TechPowerPercent",
		"GroundDashReductionPercent", "AirMoveIncreasePercent",
		"GravityScale",
		"BonusBaseHealth", "MaxHealthLossPercent"
    	},
	["m/s"] = { "BonusMoveSpeed", "ActiveBonusMoveSpeed", "BonusSprintSpeed", "FervorMovespeed", "InvisMoveSpeedMod" },
	[" 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 = GameData.get_entities(GameData.Dataset.ITEMS, listofKeysTable)
	table.sort(filteredData, defaultSort())

	local createTable = mw.html.create('table')
    :addClass('wikitable sortable item-stat-table')
    :tag('caption'):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
			local statVal = resolveStatValue(itemName, t)
			if(statVal ~= nil and statVal ~= "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
			local statValue = resolveStatValue(itemName, t)
			if(statValue ~= nil and statValue ~= "0") then
				-- Handles complex stat values from the JSON data.
				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(
							(get_cost(itemName["Name"]) == 9999)
							and "Legendary"
							or 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

    local edit_url = mw.uri.fullUrl("Module:ItemTables", { action = "edit" })
    local disclaimer = frame:preprocess(
        '<div class="item-stat-table-disclaimer">'
        .. 'This is a dynamic list '
        .. '<span class="item-stat-table-list-name">"' .. property .. '"</span>. '
        .. '[' .. tostring(edit_url) .. ' Edit contents].'
        .. '</div>'
    )

    return disclaimer .. tostring(createTable)
end

return p