|
|
| (14 intermediate revisions by 2 users not shown) |
| Line 1: |
Line 1: |
| local p = {}; | | local p = {} |
| local heroes_data = mw.loadJsonData("Data:HeroData.json")
| |
| local util_module = require('Module:Utilities')
| |
| local lang_module = require('Module:Lang')
| |
| | |
| --------------------------------------------------------------------------------
| |
| -- Internal helpers
| |
| --------------------------------------------------------------------------------
| |
| | |
| -- Localized string for a key, falling back to a readable version of `fallback`
| |
| -- plus a "missing translation" tooltip.
| |
| local function localize(key, fallback)
| |
| local result = lang_module.get_string(key)
| |
| if (result == "") or (result == nil) then
| |
| result = util_module.add_space_before_cap(fallback) .. mw.getCurrentFrame():expandTemplate{title="MissingValveTranslationTooltip"}
| |
| end
| |
| return result
| |
| end
| |
| | |
| -- Raw scaling value of a stat under LevelScaling / SpiritScaling, or nil when
| |
| -- the hero has no scaling of that type for that stat.
| |
| local function raw_scaling(hero_data, scaling_type, stat)
| |
| local scaling = hero_data and hero_data[scaling_type .. "Scaling"]
| |
| if (scaling == nil or stat == nil) then return nil end
| |
| return scaling[stat]
| |
| end
| |
| | |
| -- Mapping from hero name to internal tag name
| |
| local TAG_NAME_MAP = {
| |
| ["Abrams"] = "Abrams",
| |
| ["Bebop"] = "Bebop",
| |
| ["Billy"] = "Punkgoat",
| |
| ["Calico"] = "Nano",
| |
| ["The Doorman"] = "Doorman",
| |
| ["Drifter"] = "Drifter",
| |
| ["Dynamo"] = "Dynamo",
| |
| ["Apollo"] = "Fencer",
| |
| ["Graves"] = "Necro",
| |
| ["Grey Talon"] = "Orion",
| |
| ["Haze"] = "Haze",
| |
| ["Holliday"] = "Astro",
| |
| ["Infernus"] = "Inferno",
| |
| ["Ivy"] = "Tengu",
| |
| ["Kelvin"] = "Kelvin",
| |
| ["Lady Geist"] = "Geist",
| |
| ["Lash"] = "Lash",
| |
| ["McGinnis"] = "Engineer",
| |
| ["Mina"] = "VampireBat",
| |
| ["Mirage"] = "Mirage",
| |
| ["Mo & Krill"] = "Digger",
| |
| ["Paige"] = "Bookworm",
| |
| ["Paradox"] = "Chrono",
| |
| ["Celeste"] = "Unicorn",
| |
| ["Pocket"] = "Synth",
| |
| ["Rem"] = "Familiar",
| |
| ["Seven"] = "Gigawatt",
| |
| ["Shiv"] = "Shiv",
| |
| ["Silver"] = "Werewolf",
| |
| ["Silver (Transformed)"] = "Werewolf",
| |
| ["Sinclair"] = "Magician",
| |
| ["Venator"] = "Priest",
| |
| ["Victor"] = "Frank",
| |
| ["Vindicta"] = "Vindicta",
| |
| ["Viscous"] = "Viscous",
| |
| ["Vyper"] = "Viper",
| |
| ["Warden"] = "Warden",
| |
| ["Wraith"] = "Wraith",
| |
| ["Yamato"] = "Yamato",
| |
| }
| |
| | |
| --------------------------------------------------------------------------------
| |
| -- Data access, for use by other modules
| |
| --------------------------------------------------------------------------------
| |
| | |
| -- returns the table of a specific item, used by external modules
| |
| function p.get_json_item(name)
| |
| for i,v in pairs(heroes_data) do
| |
| if (v["Name"] == name) then
| |
| return v
| |
| end
| |
| end
| |
| return nil
| |
| end
| |
| | |
| -- Hero table for a hero_* key or an exact English name, or nil.
| |
| local function hero_by_key_or_name(input)
| |
| local hero = heroes_data[input]
| |
| if (hero == nil) then hero = p.get_json_item(input) end
| |
| return hero
| |
| end
| |
| | |
| -- Value of a stat, checking the hero's top level, then Weapon, then AltFire.
| |
| -- Returns nil when the hero has no such stat.
| |
| function p.get_stat(hero_data, stat)
| |
| if (hero_data == nil or stat == nil) then return nil end
| |
| local value = hero_data[stat]
| |
| if (value == nil and hero_data.Weapon) then
| |
| value = hero_data.Weapon[stat]
| |
| end
| |
| if (value == nil and hero_data.Weapon and hero_data.Weapon.AltFire) then
| |
| value = hero_data.Weapon.AltFire[stat]
| |
| end
| |
| return value
| |
| end
| |
| | |
| -- Ordered list of a hero's scalings for a stat, Level first then Spirit:
| |
| -- { { type = "Level", value = 1.5 }, { type = "Spirit", value = 0.04 } }
| |
| -- Absent and zero scalings are omitted, so an empty list means "no scaling".
| |
| function p.get_scalings(hero_data, stat)
| |
| local scalings = {}
| |
| for _, scaling_type in ipairs({"Level", "Spirit"}) do
| |
| local value = raw_scaling(hero_data, scaling_type, stat)
| |
| if (value ~= nil and value ~= 0) then
| |
| table.insert(scalings, { type = scaling_type, value = value })
| |
| end
| |
| end
| |
| return scalings
| |
| end
| |
| | |
| -- DEPRECATED, kept for [[Module:HeroComparisonTable]]. Use p.get_scalings.
| |
| -- Keyed by scaling value, so it silently drops an entry when Level and Spirit
| |
| -- scale by the same amount, and it keeps zero scalings.
| |
| function p.get_hero_scaling_data(hero_data, hero_stat_key)
| |
| local scaling_data_returned = {}
| |
| for _, scaling_type in ipairs({"Spirit", "Level"}) do
| |
| local scaling_value = raw_scaling(hero_data, scaling_type, hero_stat_key)
| |
| if (scaling_value ~= nil) then
| |
| scaling_data_returned[scaling_value] = scaling_type
| |
| end
| |
| end
| |
| return scaling_data_returned
| |
| end
| |
| | |
| -- Whether a hero has a stat at all: a non-zero base value, or any scaling.
| |
| function p.has_stat(hero_data, stat)
| |
| local base = p.get_stat(hero_data, stat)
| |
| if (base ~= nil and base ~= 0) then return true end
| |
| return #p.get_scalings(hero_data, stat) > 0
| |
| end
| |
| | |
| -- Resolves a hero_* key or an English hero name to a hero_* key.
| |
| -- Returns nil when the input matches no hero. Name matching is case-insensitive.
| |
| function p.find_hero_key(input)
| |
| if (input == nil or input == '') then return nil end
| |
| if (heroes_data[input] ~= nil) then return input end
| |
| local wanted = mw.ustring.lower(input)
| |
| for hero_key, hero_data in pairs(heroes_data) do
| |
| if (hero_data["Name"] and mw.ustring.lower(hero_data["Name"]) == wanted) then
| |
| return hero_key
| |
| end
| |
| end
| |
| return nil
| |
| end
| |
| | |
| -- A hero's Nth roster tag (1-3) by English name, or '' when there is no such tag.
| |
| function p.hero_tag(hero_en, index)
| |
| local tagname = hero_en and TAG_NAME_MAP[hero_en]
| |
| if (tagname == nil) then return '' end
| |
| if (index ~= 1 and index ~= 2 and index ~= 3) then return '' end
| |
| return lang_module.get_string(string.format("Citadel_%s_HeroTag_%d", tagname, index)) or ''
| |
| end
| |
| | |
| --------------------------------------------------------------------------------
| |
| -- Wikitext entry points
| |
| --------------------------------------------------------------------------------
| |
| | |
| -- returns the key of the specified hero's english name
| |
| function p.get_hero_key(frame)
| |
| local hero_name_input = frame.args[1]
| |
| if not hero_name_input then
| |
| return "Error: Hero name not specified."
| |
| end
| |
| return p.find_hero_key(hero_name_input) or "Hero not found."
| |
| end
| |
| | |
| --{{#invoke:HeroData|resolve_key|HERO_KEY_OR_NAME}}--
| |
| -- The matching hero_* key, or '' when the input matches no hero.
| |
| function p.resolve_key(frame)
| |
| return p.find_hero_key(frame.args[1]) or ''
| |
| end
| |
| | |
| --{{#invoke:HeroData|get_hero_var|HERO_NAME|STAT_NAME|sig_figs_or_localize}}--
| |
| --sig_figs optional for rounding floats
| |
| p.get_hero_var = function(frame)
| |
| local hero_name = frame.args[1]
| |
| local hero_stat_key = frame.args[2]
| |
| local sig_figs_or_localize = frame.args[3]
| |
|
| |
| local hero = hero_by_key_or_name(hero_name)
| |
| if(hero == nil) then return "Hero Not Found" end
| |
|
| |
| local var_value = p.get_stat(hero, hero_stat_key)
| |
| if(var_value == nil) then return 0 end
| |
|
| |
| --round
| |
| if (sig_figs_or_localize ~= nil and tonumber(sig_figs_or_localize) ~= nil) then
| |
| var_value = util_module.round_to_sig_fig(var_value, sig_figs_or_localize)
| |
| if (var_value == nil) then return "get_hero_var() error with rounding" end
| |
| end
| |
|
| |
| --localize
| |
| if (sig_figs_or_localize == "true") then
| |
| return lang_module.get_string(var_value)
| |
| end
| |
| | |
| return var_value
| |
| end
| |
| | |
| --{{#invoke:HeroData|get_alt_fire_var|HERO_NAME|STAT_NAME|sig_figs_or_localize}}--
| |
| p.get_alt_fire_var = function(frame)
| |
| local hero_name = frame.args[1]
| |
| local stat_key = frame.args[2]
| |
| local sig_figs_or_localize = frame.args[3]
| |
|
| |
| local hero = hero_by_key_or_name(hero_name)
| |
| if(hero == nil) then return "Hero Not Found" end
| |
|
| |
| if not (hero.Weapon and hero.Weapon.AltFire) then
| |
| return "No Alt Fire"
| |
| end
| |
|
| |
| local var_value = hero.Weapon.AltFire[stat_key]
| |
| if(var_value == nil) then return 0 end
| |
|
| |
| --round
| |
| if (sig_figs_or_localize ~= nil and tonumber(sig_figs_or_localize) ~= nil) then
| |
| var_value = util_module.round_to_sig_fig(var_value, sig_figs_or_localize)
| |
| end
| |
|
| |
| --localize
| |
| if (sig_figs_or_localize == "true") then
| |
| return lang_module.get_string(var_value)
| |
| end
| |
| | |
| return var_value
| |
| end
| |
| | |
| --{{#invoke:HeroData|get_list_elem|HERO_NAME|VAR|NUMBER|LOCALIZE}}
| |
| p.get_list_elem = function(frame)
| |
| local hero_name = frame.args[1]
| |
| local var = frame.args[2]
| |
| local number_int = tonumber(frame.args[3])
| |
| local localize_arg = frame.args[4]
| |
|
| |
| local hero = hero_by_key_or_name(hero_name)
| |
| if(hero == nil) then return "Hero Not Found" end
| |
|
| |
| local list = p.get_stat(hero, var)
| |
| if (list == nil) then return "" end
| |
| local element = list[number_int]
| |
| if (element == nil) then return "" end
| |
|
| |
| if localize_arg=="true" then
| |
| element = lang_module.get_string(element)
| |
| end
| |
|
| |
| return element
| |
| end
| |
| | |
| p.get_ability_key = function(frame)
| |
| local hero_key = frame.args[1]
| |
| local bound_slot_number = frame.args[2]
| |
| local hero_data = heroes_data[hero_key]
| |
| if (hero_data == nil) then return "Hero key "..hero_key.. " not found" end
| |
| local bound_abilities_data = hero_data["BoundAbilities"]
| |
| if (bound_abilities_data == nil) then return "Hero key " .. hero_key.. " has no BoundAbilities" end
| |
| return bound_abilities_data[tonumber(bound_slot_number)]["Key"]
| |
| end
| |
| | |
| p.write_role_playstyle_quote = function(frame)
| |
| local hero_key = frame.args[1]
| |
| local hero_data = heroes_data[hero_key]
| |
| if (hero_data == nil) then return hero_key.." not found" end
| |
| local role_key = hero_data["Role"]
| |
| local role_localized = lang_module.get_string(role_key, nil, 'en')
| |
| local playstyle_key = hero_data["Playstyle"]
| |
| local playstyle_localized = lang_module.get_string(playstyle_key, nil, 'en')
| |
|
| |
| local str = "<b>" .. role_localized .. '</b><br>' .. playstyle_localized
| |
| local template_args = {}
| |
| template_args[1] = ""
| |
| template_args[2] = str
| |
| return frame:expandTemplate{title = 'Quotation', args = template_args}
| |
| end
| |
| | |
| p.write_default_items = function(frame)
| |
| local hero_key = frame.args[1] --unlocalized
| |
| if (hero_key == nil) then return "No hero key provided" end
| |
| local str = ""
| |
| local hero = heroes_data[hero_key]
| |
| if (hero == nil) then return "Hero not found, must be unlocalized" end
| |
| local template_title = 'PageRef'
| |
| for i, item_key in ipairs(hero["RecommendedItems"] or {}) do
| |
| local template_args = {}
| |
| template_args[1] = lang_module.get_string(item_key, 'en')
| |
| template_args['alt_name'] = localize(item_key, item_key)
| |
| local expanded_template = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
| str = str .. "* " .. expanded_template .. "\n"
| |
| end
| |
| return str
| |
| end
| |
| | |
| --If the hero scales with the stat, it returns {{Ss|value}} or {{Ls|value}}, else blank string
| |
| --{{#invoke:HeroData|get_hero_scalar|HERO_NAME|SCALING_TYPE|STAT_NAME|sig_figs_or_localize}}--
| |
| p.get_hero_scalar = function(frame)
| |
| local hero_key = frame.args[1]
| |
| local scaling_type = frame.args[2]
| |
| local hero_stat_key = frame.args[3]
| |
| local sig_figs_or_localize = frame.args[4]
| |
| local no_template = frame.args["no_template"]
| |
|
| |
| local hero_data = hero_by_key_or_name(hero_key)
| |
| if(hero_data == nil) then return "Hero not found." end
| |
|
| |
| local scaling_value = raw_scaling(hero_data, scaling_type, hero_stat_key)
| |
| if scaling_value == nil then return "" end
| |
| | |
| --round
| |
| if (sig_figs_or_localize ~= nil and tonumber(sig_figs_or_localize) ~= nil) then
| |
| scaling_value = util_module.round_to_sig_fig(scaling_value, sig_figs_or_localize)
| |
| if (scaling_value == nil) then return "get_hero_scalar() error with rounding" end
| |
| end
| |
|
| |
| --localize
| |
| if (sig_figs_or_localize == "true") then
| |
| return lang_module.get_string(scaling_value)
| |
| end
| |
|
| |
| if no_template == "true" then
| |
| return scaling_value
| |
| end
| |
|
| |
| return p.write_scalar_str(scaling_value, scaling_type)
| |
| end
| |
| | |
| -- Check if a hero has a stat (Base, Boons, or Spirit Scaling)
| |
| -- Returns "true" if found, nil if not (for #if templates)
| |
| p.hero_has_stat = function(frame)
| |
| local hero_key = frame.args[1]
| |
| local stat_key = frame.args[2]
| |
| if (hero_key == nil) then return nil end
| |
|
| |
| local hero_data = hero_by_key_or_name(hero_key)
| |
| if (hero_data == nil) then return nil end
| |
|
| |
| if p.has_stat(hero_data, stat_key) then return "true" end
| |
| return nil
| |
| end
| |
| | |
| -- Function to call hero tags based on name
| |
| p.get_hero_tag = function (frame)
| |
| local name = frame.args[1]
| |
| local number = tonumber(frame.args[2])
| |
| | |
| -- Validate input
| |
| if not name then
| |
| return "Error: Missing name input."
| |
| end
| |
| | |
| if number ~= 1 and number ~= 2 and number ~= 3 then
| |
| return string.format("Error: Invalid number '%s'. Must be 1, 2, or 3.", tostring(frame.args[2]))
| |
| end
| |
| | |
| if TAG_NAME_MAP[name] == nil then
| |
| return string.format("Error: Unknown name '%s'.", name)
| |
| end
| |
| | |
| return p.hero_tag(name, number)
| |
| end
| |
| | |
| --{{#invoke:HeroData|has_tags|HERO_ENGLISH_NAME}}--
| |
| -- 'true' when the hero has roster tags, '' otherwise.
| |
| function p.has_tags(frame)
| |
| if p.hero_tag(frame.args[1], 1) ~= '' then return 'true' end
| |
| return ''
| |
| end
| |
| | |
| -- Retrieve scaling string of a hero's given stat, if it has scaling, else return blank
| |
| -- Scaling string meaning the expanded template {{Ss|scalar}} or {{Ls|scalar}}
| |
| function p.write_scalar_str(scaling_value, scaling_type, compact)
| |
| local scaling_abbrevs = {Spirit = "Ss", Level = "PI"}
| |
|
| |
| -- Return blank if it doesnt scale
| |
| if (scaling_value == 0) then return "" end
| |
|
| |
| -- Round it
| |
| scaling_value = util_module.round_to_sig_fig(scaling_value, 3)
| |
|
| |
| --The hero has a scaling value with this stat
| |
| local template_title = "Template:" .. scaling_abbrevs[scaling_type]
| |
| local template_args = {}
| |
| template_args["1"] = scaling_value
| |
| if compact then
| |
| template_args["compact"] = "yes"
| |
| template_args["show_value"] = "yes"
| |
| end
| |
|
| |
| local template_call = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
|
| |
| return template_call:gsub("\n", ""):gsub("\r", "")
| |
| end
| |
| | |
| -- Outputs a wikitable of heroes grouped by GroundDashDuration using {{HeroIcon|hero_name}}
| |
| -- {{#invoke:HeroData|write_ground_dash_buckets}}
| |
| p.write_ground_dash_buckets = function(frame)
| |
| local buckets = {
| |
| [0.62] = {},
| |
| [0.68] = {},
| |
| [0.72] = {},
| |
| }
| |
| | |
| -- Collect heroes
| |
| for hero_key, hero_data in pairs(heroes_data) do
| |
| if not hero_data.InDevelopment and not hero_data.IsDisabled then
| |
| local dash = p.get_stat(hero_data, "GroundDashDuration")
| |
| if dash and buckets[dash] then
| |
| table.insert(buckets[dash], hero_data.Name or hero_key)
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Sort alphabetically
| |
| for _, list in pairs(buckets) do
| |
| table.sort(list)
| |
| end
| |
| | |
| -- Determine max rows dynamically
| |
| local max_rows = 0
| |
| for _, list in pairs(buckets) do
| |
| if #list > max_rows then max_rows = #list end
| |
| end
| |
| | |
| local output = {"{| class='wikitable mw-collapsible'",
| |
| "|+Hero Dash Buckets", "! Bucket 1 !! Bucket 2 !! Bucket 3"}
| |
| local current_frame = frame or mw.getCurrentFrame()
| |
| | |
| for i = 1, max_rows do
| |
| local row = {}
| |
| for _, bucket in ipairs({0.62, 0.68, 0.72}) do
| |
| local hero_name = buckets[bucket][i]
| |
| table.insert(row, hero_name and current_frame:expandTemplate{title = "HeroIcon", args = {hero_name}} or "")
| |
| end
| |
| table.insert(output, "|-\n| " .. table.concat(row, " || "))
| |
| end
| |
| | |
| table.insert(output, "|}")
| |
| return table.concat(output, "\n")
| |
| end
| |
| --------------------------------------------------------------------------------
| |
| -- TEMPORARY parity harness - NOT part of the live module.
| |
| -- {{#invoke:Sandbox/Vergir|test|SUITE}} where SUITE is one of:
| |
| -- stats | list | scalars | scalings | has_stat | keys | tags
| |
| --------------------------------------------------------------------------------
| |
| | |
| local old_module = require('Module:HeroData')
| |
| | |
| local function repr(v, depth)
| |
| depth = depth or 0
| |
| if type(v) ~= 'table' then return tostring(v) end
| |
| if depth > 3 then return '<deep>' end
| |
| local entries = {}
| |
| for k, val in pairs(v) do
| |
| table.insert(entries, { k = tostring(k), v = val })
| |
| end
| |
| table.sort(entries, function(a, b) return a.k < b.k end)
| |
| local parts = {}
| |
| for _, e in ipairs(entries) do
| |
| table.insert(parts, e.k .. '=' .. repr(e.v, depth + 1))
| |
| end
| |
| return '{' .. table.concat(parts, ',') .. '}'
| |
| end
| |
| | |
| -- Calls fn(...) and returns a normalised string, so that a raised error on one
| |
| -- side compares equal to the same error on the other (line numbers stripped).
| |
| local function attempt(fn, ...)
| |
| local ok, result = pcall(fn, ...)
| |
| if ok then return repr(result) end
| |
| return 'ERR:' .. tostring(result):gsub("^.-:%d+: ", "")
| |
| end
| |
| | |
| local function sorted_hero_keys()
| |
| local list = {}
| |
| for hero_key in pairs(heroes_data) do table.insert(list, hero_key) end
| |
| table.sort(list)
| |
| return list
| |
| end
| |
| | |
| -- Every key appearing anywhere in the hero data, so sweeps are exhaustive.
| |
| local function all_stat_keys()
| |
| local seen = {}
| |
| for _, hero in pairs(heroes_data) do
| |
| local sources = {
| |
| hero,
| |
| hero.Weapon or {},
| |
| (hero.Weapon and hero.Weapon.AltFire) or {},
| |
| hero.LevelScaling or {},
| |
| hero.SpiritScaling or {},
| |
| }
| |
| for _, source in ipairs(sources) do
| |
| for stat in pairs(source) do
| |
| if type(stat) == 'string' then seen[stat] = true end
| |
| end
| |
| end
| |
| end
| |
| local list = {}
| |
| for stat in pairs(seen) do table.insert(list, stat) end
| |
| table.sort(list)
| |
| return list
| |
| end
| |
| | |
| local MAX_ROWS = 50
| |
| | |
| local function report(title, checked, mismatches)
| |
| local out = {
| |
| "'''" .. title .. "'''",
| |
| '',
| |
| 'Comparisons: ' .. checked,
| |
| 'Mismatches: ' .. #mismatches,
| |
| '',
| |
| }
| |
| if #mismatches == 0 then
| |
| table.insert(out, ':<span style="color:#13f278">No differences.</span>')
| |
| return table.concat(out, '\n')
| |
| end
| |
| table.insert(out, '{| class="wikitable"')
| |
| table.insert(out, '! Case !! old !! new')
| |
| for i, row in ipairs(mismatches) do
| |
| if i > MAX_ROWS then
| |
| table.insert(out, '|-\n| colspan="3" | ... and ' .. (#mismatches - MAX_ROWS) .. ' more')
| |
| break
| |
| end
| |
| table.insert(out, '|-\n| <code>' .. row[1] .. '</code> || <code>' ..
| |
| mw.text.nowiki(row[2]) .. '</code> || <code>' .. mw.text.nowiki(row[3]) .. '</code>')
| |
| end
| |
| table.insert(out, '|}')
| |
| return table.concat(out, '\n')
| |
| end
| |
| | |
| local suites = {}
| |
| | |
| -- p.get_stat, via get_hero_var
| |
| suites.stats = function()
| |
| local checked, mismatches = 0, {}
| |
| local stat_keys = all_stat_keys()
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| for _, stat in ipairs(stat_keys) do
| |
| local frame = { args = { hero_key, stat } }
| |
| local a = attempt(old_module.get_hero_var, frame)
| |
| local b = attempt(p.get_hero_var, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b })
| |
| end
| |
| end
| |
| end
| |
| return report('get_hero_var (p.get_stat)', checked, mismatches)
| |
| end
| |
| | |
| -- p.get_stat, via get_list_elem
| |
| suites.list = function()
| |
| local checked, mismatches = 0, {}
| |
| local stat_keys = all_stat_keys()
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| for _, stat in ipairs(stat_keys) do
| |
| for index = 1, 2 do
| |
| local frame = { args = { hero_key, stat, tostring(index) } }
| |
| local a = attempt(old_module.get_list_elem, frame)
| |
| local b = attempt(p.get_list_elem, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { hero_key .. ' / ' .. stat .. ' [' .. index .. ']', a, b })
| |
| end
| |
| end
| |
| end
| |
| end
| |
| return report('get_list_elem (p.get_stat)', checked, mismatches)
| |
| end
| |
| | |
| -- raw_scaling, via get_hero_scalar (both template and no_template modes)
| |
| suites.scalars = function()
| |
| local checked, mismatches = 0, {}
| |
| local stat_keys = all_stat_keys()
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| for _, stat in ipairs(stat_keys) do
| |
| for _, scaling_type in ipairs({ 'Level', 'Spirit' }) do
| |
| for _, no_template in ipairs({ 'true', '' }) do
| |
| local frame = { args = { hero_key, scaling_type, stat, no_template = no_template } }
| |
| local a = attempt(old_module.get_hero_scalar, frame)
| |
| local b = attempt(p.get_hero_scalar, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, {
| |
| hero_key .. ' / ' .. scaling_type .. ' / ' .. stat ..
| |
| (no_template == 'true' and ' (raw)' or ''), a, b })
| |
| end
| |
| end
| |
| end
| |
| end
| |
| end
| |
| return report('get_hero_scalar (raw_scaling)', checked, mismatches)
| |
| end
| |
| | |
| -- The deprecated shim must stay byte-identical for Module:HeroComparisonTable
| |
| suites.scalings = function()
| |
| local checked, mismatches = 0, {}
| |
| local stat_keys = all_stat_keys()
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| local hero_data = heroes_data[hero_key]
| |
| for _, stat in ipairs(stat_keys) do
| |
| local a = attempt(old_module.get_hero_scaling_data, hero_data, stat)
| |
| local b = attempt(p.get_hero_scaling_data, hero_data, stat)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b })
| |
| end
| |
| end
| |
| end
| |
| return report('get_hero_scaling_data (deprecated shim)', checked, mismatches)
| |
| end
| |
| | |
| -- EXPECTED to differ: new version is Weapon/AltFire aware
| |
| suites.has_stat = function()
| |
| local checked, mismatches = 0, {}
| |
| local stat_keys = all_stat_keys()
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| for _, stat in ipairs(stat_keys) do
| |
| local frame = { args = { hero_key, stat } }
| |
| local a = attempt(old_module.hero_has_stat, frame)
| |
| local b = attempt(p.hero_has_stat, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { hero_key .. ' / ' .. stat, a, b })
| |
| end
| |
| end
| |
| end
| |
| return report('hero_has_stat (EXPECTED to widen)', checked, mismatches)
| |
| end
| |
| | |
| -- EXPECTED to differ: new version also accepts a hero_* key
| |
| suites.keys = function()
| |
| local checked, mismatches = 0, {}
| |
| local inputs = {}
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| table.insert(inputs, hero_key)
| |
| local name = heroes_data[hero_key]['Name']
| |
| if name then
| |
| table.insert(inputs, name)
| |
| table.insert(inputs, mw.ustring.upper(name))
| |
| table.insert(inputs, mw.ustring.lower(name))
| |
| end
| |
| end
| |
| for _, extra in ipairs({ '', ' ', 'Not A Hero', 'hero_nope' }) do
| |
| table.insert(inputs, extra)
| |
| end
| |
| for _, input in ipairs(inputs) do
| |
| local frame = { args = { input } }
| |
| local a = attempt(old_module.get_hero_key, frame)
| |
| local b = attempt(p.get_hero_key, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { input, a, b })
| |
| end
| |
| end
| |
| return report('get_hero_key (EXPECTED to widen)', checked, mismatches)
| |
| end
| |
| | |
| -- get_hero_tag must be unchanged; the has_tags predicate may narrow
| |
| suites.tags = function()
| |
| local checked, mismatches = 0, {}
| |
| local names = {}
| |
| for _, hero_key in ipairs(sorted_hero_keys()) do
| |
| local name = heroes_data[hero_key]['Name']
| |
| if name then table.insert(names, name) end
| |
| end
| |
| for _, extra in ipairs({ 'Not A Hero', '' }) do table.insert(names, extra) end
| |
| | |
| for _, name in ipairs(names) do
| |
| for _, index in ipairs({ '0', '1', '2', '3', '4', 'x' }) do
| |
| local frame = { args = { name, index } }
| |
| local a = attempt(old_module.get_hero_tag, frame)
| |
| local b = attempt(p.get_hero_tag, frame)
| |
| checked = checked + 1
| |
| if a ~= b then
| |
| table.insert(mismatches, { name .. ' [' .. index .. ']', a, b })
| |
| end
| |
| end
| |
| -- Predicate: old Infobox_hero.hero_has_tags vs new p.has_tags
| |
| local old_tag = old_module.get_hero_tag({ args = { name, '1' } })
| |
| local old_pred = tostring(string.find(old_tag, 'Error:') == nil)
| |
| local new_pred = tostring(p.has_tags({ args = { name } }) == 'true')
| |
| checked = checked + 1
| |
| if old_pred ~= new_pred then
| |
| table.insert(mismatches, { 'PREDICATE ' .. name, old_pred, new_pred })
| |
| end
| |
| end
| |
| return report('get_hero_tag + has_tags predicate', checked, mismatches)
| |
| end
| |
| | |
| function p.test(frame)
| |
| local suite = frame.args[1]
| |
| if suite == nil or suites[suite] == nil then
| |
| local names = {}
| |
| for name in pairs(suites) do table.insert(names, name) end
| |
| table.sort(names)
| |
| return 'Pick a suite: ' .. table.concat(names, ', ')
| |
| end
| |
| return suites[suite]()
| |
| end
| |
| | |
| return p | | return p |