Editing Module:Sandbox/VergirGive feedback
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
| Latest revision | Your text | ||
| 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 | ||