|
|
| (5 intermediate revisions by 2 users not shown) |
| Line 1: |
Line 1: |
| -- Builds tables of hero base stats that scale with a given scaling type,
| |
| -- read from [[Data:HeroData.json]].
| |
| --
| |
| -- Each public function below renders one table. The shared work lives in
| |
| -- build_scaling_table(), so adding a table for another scaling type (Boons,
| |
| -- for instance) only takes a new wrapper naming its scaling type.
| |
| --
| |
| -- {{#invoke:Sandbox/Vergir|write_spirit_scaling_table}}
| |
| local p = {} | | local p = {} |
|
| |
| 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 hero_data_module = require('Module:HeroData')
| |
|
| |
| -- Never listed: both are derived from the weapon stats that are already
| |
| -- listed in their own right, so they only ever duplicate a row.
| |
| local EXCLUDED_STATS = {
| |
| DPS = true,
| |
| SustainedDPS = true,
| |
| }
| |
|
| |
| -- Per stat handling of the base value shown before the scaling.
| |
| -- text: printed verbatim, for stats the data holds no base value for
| |
| -- sig_figs: rounds an over-precise base value
| |
| local BASE_DISPLAY = {
| |
| BulletResist = {text = "0%"},
| |
| TechResist = {text = "0%"},
| |
| RoundsPerSecond = {sig_figs = 3},
| |
| }
| |
|
| |
| --------------------------------------------------------------------------------
| |
| -- Helpers
| |
| --------------------------------------------------------------------------------
| |
|
| |
| -- next() ignores metamethods and mw.loadJsonData hands back proxy tables, so
| |
| -- keys always have to be collected through pairs() rather than tested with next().
| |
| 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
| |
|
| |
| -- 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
| |
|
| |
| local translated = dictionary_module.translate(stat)
| |
| if (translated ~= nil and translated:sub(1, 5) ~= "Key '") then return translated end
| |
|
| |
| return util_module.add_space_before_cap(stat) ..
| |
| mw.getCurrentFrame():expandTemplate{title = "MissingValveTranslationTooltip"}
| |
| 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)
| |
| return mw.getCurrentFrame():expandTemplate{
| |
| title = "HeroIcon",
| |
| args = {[1] = hero_data["Name"] or hero_key, l1 = lang_module.get_string(hero_key)},
| |
| }
| |
| end
| |
|
| |
| -- Base value as displayed, or nil when the stat has none to show.
| |
| local function base_value(hero_data, stat)
| |
| local display = BASE_DISPLAY[stat]
| |
| if (display ~= nil and display.text ~= nil) then return display.text end
| |
|
| |
| local base = hero_data_module.get_stat(hero_data, stat)
| |
| if (type(base) ~= "number") then return nil end
| |
| if (display ~= nil and display.sig_figs ~= nil) then
| |
| base = util_module.round_to_sig_fig(base, display.sig_figs)
| |
| end
| |
| return tostring(base)
| |
| end
| |
|
| |
| -- "6.3 + x0.0084", or the scaling alone when there is no base value to show.
| |
| local function scaling_cell(hero_data, stat, value, scaling_type)
| |
| local scaling_str = hero_data_module.write_scalar_str(value, scaling_type)
| |
| local base = base_value(hero_data, stat)
| |
| if (base == nil) then return scaling_str end
| |
| return base .. ' + ' .. scaling_str
| |
| end
| |
|
| |
| -- Every scaling a hero has of one type, minus the excluded stats and any
| |
| -- zeroes, ordered by localized stat name:
| |
| -- { { stat = , label = , value = }, ... }
| |
| local function hero_scalings(hero_data, scaling_key)
| |
| local scalings = {}
| |
| local hero_scaling_data = hero_data[scaling_key]
| |
| for _, stat in ipairs(sorted_keys(hero_scaling_data)) do
| |
| local value = hero_scaling_data[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
| |
| table.sort(scalings, function(a, b)
| |
| if (a.label == b.label) then return a.stat < b.stat end
| |
| return a.label < b.label
| |
| end)
| |
| return scalings
| |
| end
| |
|
| |
| --------------------------------------------------------------------------------
| |
| -- Shared table builder
| |
| --------------------------------------------------------------------------------
| |
|
| |
| -- scaling_type: "Spirit" or "Level", picking both the <type>Scaling data key
| |
| -- and the scaling template ({{Ss}} or {{PI}}).
| |
| local function build_scaling_table(scaling_type)
| |
| local scaling_key = scaling_type .. "Scaling"
| |
|
| |
| -- Collect the heroes that have anything to show
| |
| local rows = {}
| |
| for _, hero_key in ipairs(sorted_keys(heroes_data)) do
| |
| local hero_data = heroes_data[hero_key]
| |
| if (not hero_data["InDevelopment"] and not hero_data["IsDisabled"]) then
| |
| local scalings = hero_scalings(hero_data, scaling_key)
| |
| 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 sortable"',
| |
| '! ' .. 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 attributes = 'data-sort-value="' .. row.sort_name .. '"'
| |
| if (#row.scalings > 1) then
| |
| attributes = attributes .. ' rowspan="' .. #row.scalings .. '"'
| |
| end
| |
| table.insert(output, '| ' .. attributes .. ' | ' .. hero_icon(row.key, row.data))
| |
| end
| |
| table.insert(output, '| ' .. scaling.label)
| |
| table.insert(output, '| ' .. scaling_cell(row.data, scaling.stat, scaling.value, scaling_type))
| |
| end
| |
| end
| |
| table.insert(output, '|}')
| |
|
| |
| return table.concat(output, '\n')
| |
| end
| |
|
| |
| --------------------------------------------------------------------------------
| |
| -- Wikitext entry points
| |
| --------------------------------------------------------------------------------
| |
|
| |
| -- Hero base stats that scale with Spirit Power.
| |
| --{{#invoke:Sandbox/Vergir|write_spirit_scaling_table}}
| |
| function p.write_spirit_scaling_table(frame)
| |
| return build_scaling_table("Spirit")
| |
| end
| |
|
| |
| return p | | return p |