Module:ItemTables: Difference between revisions

add styling and more patterns
capacitor move slow
 
(121 intermediate revisions by 10 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 GameData = require("Module:GameData")


-- returns the table of a specific item
-- Translate internal attribute names to plain text for module calls
function get_json_item(name)
local friendly_to_internal = {}
for i,v in pairs(data) do
friendly_to_internal["ammo"] = { "BonusClipSizePercent", "BonusClipSize", "ActiveReloadPercent" }
if (v["Name"] == name) then
friendly_to_internal["weapon damage"] = { "BaseAttackDamagePercent", "CloseRangeBonusWeaponPower", "LongRangeBonusWeaponPower", "AttackDamageWhenShielded" }
return v
friendly_to_internal["spirit damage"] = { "ProcBonusMagicDamage" }
end
friendly_to_internal["bullet shield health"] = { "BulletShieldMaxHealth", "BulletShieldOnCast", "SaviorBulletShieldHealth", "FlyingBulletShield", "VexBarrierBulletMaxHealth" }
end
friendly_to_internal["spirit shield health"] = { "TechShieldMaxHealth", "TechShieldOnCast", "SaviorMagicShieldHealth", "FlyingTechShield", "VexBarrierTechMaxHealth" }
return nil
friendly_to_internal["barrier"] = { "CombatBarrier", "GuardianWardCombatBarrier", "VexBarrierCombatBarrier" }
end
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" }


-- Adds commas delimiter to the thousands place
-- Override dead/hidden stats that exist in data but don't actually work in‑game.
function Format(amount)
-- Keys are item Names, values are tables of { [internalStatName] = replacementValue }
    local formatted = amount
local deadStatOverrides = {
     while true do
     ["Witchmail"] = { CooldownReduction = "0" },
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
    ["Refresher"] = { BulletResist = "0", TechResist = "0" },
        if (k == 0) then
    ["Glass Cannon"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
            break
    ["Majestic Leap"] = { SlowPercent = "0", MovementSpeedSlow = "0" },
        end
     ["Improved Spirit"] = { BonusSprintSpeed = "0"},
     end
}
    return formatted
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 44: Line 83:
end
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


local link_patterns = {}
-- Sort the order of items in a preset as it appears when no filtered ordering is applied.
link_patterns["BonusClipSizePercent"] = { "[Aa]mmo" }
-- Ordered first by slot and cost increasing, then alphabetical within same slot and cost.
link_patterns["BaseAttackDamagePercent"] = { "[Ww]eapon [Dd]amage" }
function defaultSort()
link_patterns["CloseRangeBonusWeaponPower"] = { "[Cc]lose [Ww]eapon [Dd]amage" }
    return function(a, b)
link_patterns["BulletLifestealPercent"] = { "[Bb]ullet [Ll]ifesteal" }
    if (get_cost(a["Name"]) == get_cost(b["Name"]) and sortByWeapon[a["Slot"]] == sortByWeapon[b["Slot"]]) then return a["Name"] < b["Name"] end
link_patterns["BonusHealth"] = { "[Hh]ealth" }
        return get_cost(a["Name"]) * sortByWeapon[a["Slot"]] < get_cost(b["Name"]) * sortByWeapon[b["Slot"]]
link_patterns["BonusHealthRegen"] = { "[Hh]ealth [Rr]egen" }
    end
link_patterns["BonusFireRate"] = { "[Ff]ire [Rr]ate" }
end


function FriendlyNames(desc)
-- Formats a stat value with a leading + or - sign and makes it bold.
for link, patterns in pairs(link_patterns) do
-- The function is robust and handles nil, number, string, or table inputs safely.
for i, v in ipairs(patterns) do
-- @function  signPrefix
    local a = desc:find(v)
-- @param      {any} The stat value to format.
if a then desc = link break
-- @return    {string} The formatted wikitext string.
    end
local function signPrefix(value)
    end
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
return desc
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)
p.itemPropTable = function(frame)
local TableValues = {}
local requirements = {}
local requirements = {}
local listofItems = ""
local listofKeysTable = {}
local property = frame.args[1] or mw.title.getCurrentTitle().text
local property = frame:getParent().args[1] or mw.title.getCurrentTitle().text
local friendlyPropertyName = property
local copyVar = property
property = FriendlyNames(property)
 
-- local item = get_json_item(item_name)
-- Get internal keys corresponding to the friendly property name
-- if(item == nil) then return "Item Not Found what" end
listofKeysTable = friendlyNames(property)  
 
local filteredData = GameData.get_entities(GameData.Dataset.ITEMS, listofKeysTable)
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 createTableRow = mw.html.create('tr')
local soulIcon = frame:expandTemplate{ title = 'Souls' }
createTableRow
 
local createTableHeader = mw.html.create('tr')
createTableHeader
:tag('th'):wikitext('Name'):done()
:tag('th'):wikitext('Name'):done()
:tag('th'):wikitext('Cost'):done()
:tag('th'):wikitext(soulIcon .. 'Cost'):done()
: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(createTableRow)


-- query all items
-- Track rowspans for items with multiple stats
for internalName, itemName in pairs(data) do --itemName is the key. go through whole table. in this case itemName can be upgrade_improved_stamina
local rowspanTracker = {}
 
for _, itemName in ipairs(filteredData) do  
local display = frame:expandTemplate{
local display = frame:expandTemplate{
title = 'ItemIcon',
title = 'ItemIcon',
args = {
args = { itemName["Name"] }
itemName["Name"]
}
}
}
 
if(itemName[property] ~= nil and itemName["Name"] ~= nil) then
local statCount = 0
listofItems = listofItems .. itemName["Name"] .. "<br/>"
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')
local tableData = mw.html.create('tr')
tableData
 
:tag('td'):wikitext(display):done()
if firstStat then
:tag('td'):wikitext(get_cost(itemName["Name"])):done()
tableData
:tag('td'):wikitext(get_type(itemName["Name"])):done()
:tag('td'):attr('rowspan', statCount):wikitext(display):done()
:tag('td'):wikitext("+<b>" .. itemName[property].. "</b> " .. friendlyPropertyName):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)
table.insert(requirements, tableData)
-- TableValues = table.insert(TableValues, itemName["Name"]) -- i may want to make a table first, so it includes weapon and cost to make it sortable.
end
end
end
end
for _, row in ipairs(requirements) do
createTableRow:node(row)
end
end
--createTable:node(createTableRow)
return tostring(createTable)
--return listofItems -- this is just a string list, same thing as createTable. delete this when line when finished
--return table.concat(TableValues, ", ")


    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
end


return p
return p