Module:ItemTables: Difference between revisionsGive feedback
add fire rate and stamina, include inclusions of different stat names, cleanup. WORKS NOW BUT ALSO BREAKS, invocations need to be reversed from internal to friendly, to friendly to internal |
Gammaton32 (talk | contribs) capacitor move slow |
||
| (116 intermediate revisions by 10 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {}; | local p = {}; | ||
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") | |||
-- | -- 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) | --- 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 34: | 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 | 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"} | |||
} | |||
function | local function appendSuffix(internal) | ||
local | 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 | ||
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 requirements = {} | local requirements = {} | ||
local listofKeysTable = {} | local listofKeysTable = {} | ||
local property = frame:getParent().args[1] or mw.title.getCurrentTitle().text | |||
local property = frame.args[1] or mw.title.getCurrentTitle().text | |||
local copyVar = property | local copyVar = property | ||
listofKeysTable = | |||
-- 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') | local createTable = mw.html.create('table') | ||
:addClass('wikitable sortable item-stat-table') | |||
:tag('caption'):done() | |||
local | local soulIcon = frame:expandTemplate{ title = 'Souls' } | ||
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( | |||
-- Track rowspans for items with multiple stats | |||
local rowspanTracker = {} | |||
for _, itemName in ipairs(filteredData) do | |||
local display = frame:expandTemplate{ | 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 | for _, t in pairs(listofKeysTable) do | ||
local statValue = resolveStatValue(itemName, t) | |||
if( | 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 | |||
if firstStat then | |||
tableData | |||
:tag('td'):attr('rowspan', statCount):wikitext(display):done() | |||
:tag('td'):wikitext( | :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) | ||
end | end | ||
end | 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 | end | ||
return p | return p | ||