Module:Sandbox/Vergir: Difference between revisionsGive feedback
Jump to navigation
Jump to search
Probe: candidate localization keys for table headers (with help from vergir-bot LLM) |
Test draft of the Spirit scaling table module (with help from vergir-bot LLM) |
||
| Line 1: | Line 1: | ||
-- | -- Renders a table of hero base stats that scale with Spirit Power, built from | ||
-- {{#invoke:Sandbox/Vergir| | -- the SpiritScaling entries in [[Data:HeroData.json]]. | ||
-- {{#invoke:Sandbox/Vergir|write_spirit_scaling_table}} | |||
local p = {} | local p = {} | ||
local lang_module = require('Module:Lang') | local heroes_data = mw.loadJsonData("Data:HeroData.json") | ||
local attributes_data = mw.loadJsonData("Data:AttributeData.json") | |||
local util_module = require('Module:Utilities') | |||
local lang_module = require('Module:Lang') | |||
local dictionary_module = require('Module:Dictionary') | local dictionary_module = require('Module:Dictionary') | ||
local hero_data_module = require('Module:HeroData') | |||
-- Stats left out by default. Both are derived from stats that are already | |||
-- listed in their own right, so including them would duplicate every row. | |||
local DEFAULT_EXCLUDED_STATS = "DPS,SustainedDPS" | |||
-------------------------------------------------------------------------------- | |||
-- Helpers | |||
-------------------------------------------------------------------------------- | |||
function | -- next() ignores metamethods and mw.loadJsonData hands back proxy tables, so | ||
local | -- keys always have to be collected through pairs() rather than tested with next(). | ||
for | local function sorted_keys(tbl) | ||
local keys = {} | |||
if (tbl == nil) then return keys end | |||
for key in pairs(tbl) do table.insert(keys, key) end | |||
table.sort(keys) | |||
return keys | |||
end | |||
-- Comma or whitespace separated list to a set. | |||
local function to_set(list) | |||
local set = {} | |||
if (list == nil) then return set end | |||
for entry in string.gmatch(list, "[^,%s]+") do set[entry] = true end | |||
return set | |||
end | |||
local function missing_translation_tooltip() | |||
return mw.getCurrentFrame():expandTemplate{title = "MissingValveTranslationTooltip"} | |||
end | |||
-- Localized name of a stat, resolved in three steps: | |||
-- 1. [[Data:AttributeData.json]] label -> Valve's own translation | |||
-- 2. [[Data:Dictionary]] entry, for stats Valve has no label for | |||
-- 3. the raw key, spaced out, with a missing-translation tooltip | |||
local function stat_label(stat) | |||
for _, category in pairs(attributes_data) do | |||
local attribute = category[stat] | |||
if (attribute ~= nil and attribute["label"] ~= nil) then | |||
local localized = lang_module.get_string(attribute["label"]) | |||
if (localized ~= nil and localized ~= '') then return localized end | |||
end | |||
end | end | ||
local translated = dictionary_module.translate(stat) | |||
for _, | if (translated ~= nil and translated:sub(1, 5) ~= "Key '") then return translated end | ||
table.insert( | |||
return util_module.add_space_before_cap(stat) .. missing_translation_tooltip() | |||
end | |||
-- Heroes are listed as they are read, so "The Doorman" sorts under D. | |||
local function sort_name(hero_data, hero_key) | |||
return (hero_data["Name"] or hero_key):gsub("^The ", "") | |||
end | |||
local function hero_icon(hero_key, hero_data) | |||
local template_args = { | |||
[1] = hero_data["Name"] or hero_key, | |||
l1 = lang_module.get_string(hero_key), | |||
} | |||
return mw.getCurrentFrame():expandTemplate{title = "HeroIcon", args = template_args} | |||
end | |||
-- Every Spirit scaling a hero has, minus the excluded stats and any zeroes, | |||
-- ordered by localized stat name: { { stat = , label = , value = }, ... } | |||
local function hero_scalings(hero_data, excluded_stats) | |||
local scalings = {} | |||
for _, stat in ipairs(sorted_keys(hero_data["SpiritScaling"])) do | |||
local value = hero_data["SpiritScaling"][stat] | |||
if (not excluded_stats[stat] and type(value) == "number" and value ~= 0) then | |||
table.insert(scalings, {stat = stat, label = stat_label(stat), value = value}) | |||
end | |||
end | end | ||
table.insert( | table.sort(scalings, function(a, b) | ||
table. | if (a.label == b.label) then return a.stat < b.stat end | ||
return a.label < b.label | |||
return table.concat( | end) | ||
return scalings | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Wikitext entry point | |||
-------------------------------------------------------------------------------- | |||
--{{#invoke:Sandbox/Vergir|write_spirit_scaling_table|exclude=|include_unreleased=}} | |||
-- exclude: comma separated stat keys to leave out, defaults to | |||
-- "DPS,SustainedDPS". Pass an empty value to list every stat. | |||
-- include_unreleased: "yes" to also list heroes flagged InDevelopment or IsDisabled. | |||
function p.write_spirit_scaling_table(frame) | |||
local args = frame.args or {} | |||
local excluded_stats = to_set(args["exclude"] or DEFAULT_EXCLUDED_STATS) | |||
local include_unreleased = (args["include_unreleased"] == "yes" or args["include_unreleased"] == "true") | |||
-- Collect the heroes that have anything left to show | |||
local rows = {} | |||
for _, hero_key in ipairs(sorted_keys(heroes_data)) do | |||
local hero_data = heroes_data[hero_key] | |||
local released = not hero_data["InDevelopment"] and not hero_data["IsDisabled"] | |||
if (released or include_unreleased) then | |||
local scalings = hero_scalings(hero_data, excluded_stats) | |||
if (#scalings > 0) then | |||
table.insert(rows, { | |||
key = hero_key, | |||
data = hero_data, | |||
sort_name = sort_name(hero_data, hero_key), | |||
scalings = scalings, | |||
}) | |||
end | |||
end | |||
end | |||
if (#rows == 0) then return '' end | |||
table.sort(rows, function(a, b) return a.sort_name < b.sort_name end) | |||
-- Build the table | |||
local output = { | |||
'{| class="wikitable"', | |||
'|+ ' .. dictionary_module.translate("SpiritScalingTableCaption"), | |||
'! ' .. dictionary_module.translate("Hero"), | |||
'! ' .. dictionary_module.translate("Stats"), | |||
'! ' .. dictionary_module.translate("Scaling"), | |||
} | |||
for _, row in ipairs(rows) do | |||
for index, scaling in ipairs(row.scalings) do | |||
table.insert(output, '|-') | |||
if (index == 1) then | |||
local rowspan = '' | |||
if (#row.scalings > 1) then rowspan = ' rowspan="' .. #row.scalings .. '" |' end | |||
table.insert(output, '|' .. rowspan .. ' ' .. hero_icon(row.key, row.data)) | |||
end | |||
table.insert(output, '| ' .. scaling.label) | |||
table.insert(output, '| ' .. hero_data_module.write_scalar_str(scaling.value, "Spirit")) | |||
end | |||
end | |||
table.insert(output, '|}') | |||
return table.concat(output, '\n') | |||
end | end | ||
return p | return p | ||