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 = {}; | ||
return 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 | |||