|
|
| Line 1: |
Line 1: |
| local p = {}; | | local p = {}; |
| local heroes_data = mw.loadJsonData("Data:Sandbox.json") | | local lang = require "Module:Lang" |
| local attributes_data = mw.loadJsonData("Data:AttributeData.json") | | local util_module = require "Module:Utilities" |
| local attribute_orders = mw.loadJsonData("Data:StatInfoboxOrder.json") | | local data = mw.loadJsonData("Data:Sandbox.json") |
| local util_module = require('Module:Utilities')
| |
| local lang_module = require('Module:Lang')
| |
| local dictionary_module = require('Module:Dictionary')
| |
| local attribute_module = require('Module:AttributeData')
| |
|
| |
|
| -- returns the table of a specific item, used by external modules | | -- A list of item properties that should have a '%' sign appended to their base value. |
| function p.get_json_item(name)
| | local percentage_properties = { |
| for i,v in pairs(heroes_data) do | | AbilityLifestealPercentHero = true, |
| | AbilityLifestealPercentHeroPassive = true, |
| | ActiveReloadPercent = true, |
| | AirControlPercent = true, |
| | AirMoveIncreasePercent = true, |
| | AmmoReloadPercent = true, |
| | BaseAttackDamagePercent = true, |
| | BaseAttackDamagePercentAtMaxDuration = true, |
| | BaseAttackDamagePercentBonus = true, |
| | BonusAbilityDurationPercent = true, |
| | BonusAttackRangePercent = true, |
| | BonusBulletSpeedPercent = true, |
| | BonusClipSizePercent = true, |
| | BonusDamagePercent = true, |
| | BonusFireRate = true, |
| | BonusHeavyMeleeDamage = true, |
| | BonusMeleeDamagePercent = true, |
| | BonusSoulsPct = true, |
| | BonusZoomPercent = true, |
| | BuffMeleeDamageBoost = true, |
| | BulletArmorReduction = true, |
| | BulletDamageReflectedPct = true, |
| | BulletLifestealPercent = true, |
| | BulletProcDeflectionPercent = true, |
| | BulletResist = true, |
| | BulletResistReduction = true, |
| | CooldownBetweenChargeReduction = true, |
| | CooldownReduction = true, |
| | CooldownReductionPctOnNonHeroes = true, |
| | CritDamagePercent = true, |
| | DamagePctVsNonHeroes = true, |
| | DeathImmunityDamageReduction = true, |
| | DeflectionPercent = true, |
| | DotHealthPercent = true, |
| | FireRateSlow = true, |
| | GroundDashReductionPercent = true, |
| | HealAmpCastPercent = true, |
| | HealAmpReceivePenaltyPercent = true, |
| | HealAmpRegenPenaltyPercent = true, |
| | HealAmpRegenPercent = true, |
| | HealLifePercentOutOfCombat = true, |
| | HealPercentAmount = true, |
| | HealPercentPerHeadshot = true, |
| | ItemCooldownReduction = true, |
| | LifestealHealPercent = true, |
| | MagicIncreasePerStack = true, |
| | MagicResistReduction = true, |
| | MaxHealthDamage = true, |
| | MaxHealthLossPercent = true, |
| | MaxHealthPercentAsDPS = true, |
| | MaxSlowPercent = true, |
| | MeleeDistanceScale = true, |
| | MeleeResistPercent = true, |
| | MoveWhileShootingSpeedPenaltyReductionPercent = true, |
| | MoveWhileZoomedSpeedPenaltyReductionPercent = true, |
| | MovementSpeedSlow = true, |
| | NonHeroHealPct = true, |
| | NonHeroReductionPercent = true, |
| | NonPlayerBonusWeaponPower = true, |
| | NonPlayerBulletResist = true, |
| | OutgoingDamagePenaltyPercent = true, |
| | ParrySuccessHealPercentage = true, |
| | PercentDamage = true, |
| | ProcBaseAttackDamagePercent = true, |
| | ProcBaseAttackDamagePercentAltFire = true, |
| | ProcChance = true, |
| | ReduceFootstepSound = true, |
| | ReloadSpeedMultipler = true, |
| | RicochetDamagePercent = true, |
| | SelfModifier = true, |
| | SlideScale = true, |
| | SlowPercent = true, |
| | SlowResistancePercent = true, |
| | SpiritDamageReductionProc = true, |
| | SpiritDamageReflectedPct = true, |
| | StaminaCooldownReduction = true, |
| | StatusResistancePercent = true, |
| | TechArmorDamageReduction = true, |
| | TechDamagePercent = true, |
| | TechDamageReduction = true, |
| | TechPowerPercent = true, |
| | TechPowerReduction = true, |
| | TechRadiusMultiplier = true, |
| | TechRadiusMultiplierBuff = true, |
| | TechRangeMultiplier = true, |
| | TechRangeMultiplierBuff = true, |
| | TechResist = true, |
| | TechResistBelowThreshold = true, |
| | } |
| | |
| | ---------------------------------------- Utility functions |
| | |
| | -- Returns the table of a specific item. |
| | -- Prioritizes active (non-disabled) items if multiple entries share the same name. |
| | -- @function get_json_item |
| | -- @param {string} |
| | -- @return {table} |
| | local function get_json_item(name) |
| | -- First, try to find an active item. This handles cases where a disabled item |
| | -- with the same name exists in the data (e.g., old versions). |
| | for _, v in pairs(data) do |
| | if (v["Name"] == name and (v["IsDisabled"] == false or v["IsDisabled"] == nil)) then |
| | return v -- Return the active item immediately |
| | end |
| | end |
| | |
| | -- If no active item was found, fall back to finding any item with the name. |
| | -- This preserves functionality for items that are intentionally disabled. |
| | for _, v in pairs(data) do |
| if (v["Name"] == name) then | | if (v["Name"] == name) then |
| return v | | return v |
| end | | end |
| end | | end |
| return nil | | |
| | return nil -- Return nil if no item was found at all |
| end | | end |
|
| |
|
| Line 24: |
Line 129: |
| local function get_similar_items(property) | | local function get_similar_items(property) |
| local similarItems = {} | | local similarItems = {} |
| for _, v in pairs(heroes_data) do | | for _, v in pairs(data) do |
| if (v[property] ~= nil) then | | if (v[property] ~= nil) then |
| table.insert(similarItems, v) | | table.insert(similarItems, v) |
| Line 32: |
Line 137: |
| end | | end |
|
| |
|
| -- returns the key of the specified hero's english name | | -- Adds commas delimiter to the thousands place |
| function p.get_hero_key(name) | | local function Format(amount) |
| for i,v in pairs(heroes_data) do | | local contentLanguage = mw.language.getContentLanguage() |
| if (v["Name"] == name) then
| | local formatted = amount |
| return i
| | if(type(formatted) ~= "number") then return "<span style=\"color:red;\">Cannot format non-number value.</span>" end |
| end
| | return contentLanguage:formatNum(tonumber(formatted)) |
| end
| |
| return nil
| |
| end | | end |
|
| |
|
| | --- Get cost of an item |
| | -- @function get_cost |
| | -- @param {string} |
| | -- @return {number} |
| | local function get_cost(item_name) |
| | local item = get_json_item(item_name) |
| | if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end |
| | return tonumber(item["Cost"]) |
| | end |
|
| |
|
| --{{#invoke:HeroData|get_hero_var|HERO_NAME|STAT_NAME|sig_figs_or_localize}}-- | | --- Get type/slot/category of an item |
| --sig_figs optional for rounding floats | | -- @function get_type |
| p.get_hero_var = function(frame)
| | -- @param {string} item_name |
| local hero_name = frame.args[1]
| | -- @param {boolean} raw (optional) |
| local hero_stat_key = frame.args[2]
| | -- @return {string} |
| local sig_figs_or_localize = frame.args[3]
| | local function get_type(item_name, raw) |
|
| | local item = get_json_item(item_name) |
| local hero = heroes_data[hero_name] --check if hero key is passed instead
| | if not item then |
| if(hero == nil) then hero = p.get_json_item(hero_name) end --check if hero name is passed
| | return "<span style=\"color:red;\">Item Not Found.</span>" |
| if(hero == nil) then return "Hero Not Found" end --both invalid, error
| |
|
| |
| local var_value = 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 | | end |
|
| |
|
| return var_value | | local slot = item["Slot"] |
| end | | if not slot then return nil end |
| | |
| | local key_map = { |
| | Weapon = 'CitadelCategoryWeapon', |
| | Armor = 'CitadelCategoryArmor', |
| | Tech = 'CitadelCategoryTech' |
| | } |
| | |
| | local key = key_map[slot] |
| | if not key then return nil end |
|
| |
|
| --{{#invoke:HeroData|get_list_elem|HERO_NAME|VAR|NUMBER|LOCALIZE}}
| | if raw then |
| p.get_list_elem = function(frame)
| | return lang.get_string(key, "en") |
| local hero_name = frame.args[1]
| | else |
| local var = frame.args[2]
| | return lang.get_string(key) |
| local number_str = frame.args[3]
| |
| local number_int = tonumber(number_str)
| |
| local localize = frame.args[4]
| |
| local hero = heroes_data[hero_name] --check if hero key is passed instead
| |
| if(hero == nil) then hero = p.get_json_item(hero_name) end --check if hero name is passed | |
| if(hero == nil) then return "Hero Not Found" end --both invalid, error
| |
| if (hero == nil) then return "Hero " .. hero_name .. " not found" end
| |
| local list = hero[var]
| |
| if (list == nil) then return "" end
| |
| local element = list[number_int]
| |
| if (element == nil) then return "" end
| |
| | |
| if localize=="true" then
| |
| element = lang_module.get_string(element) | |
| end | | end |
|
| |
| return element
| |
| end | | 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) | | ---------------------------------------- Invocable functions |
| local hero_key = frame.args[1] | | |
| local hero_data = heroes_data[hero_key] | | --- {{#invoke:ItemData|get_cost|ITEM_NAME}} access point |
| if (hero_data == nil) then return hero_key.." not found" end
| | p.get_cost = function(frame) |
| local role_key = hero_data["Role"]
| | local item_name = frame.args[1] |
| local role_localized = lang_module.get_string(role_key, nil, 'en')
| | return Format(get_cost(item_name)) |
| 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 | | end |
|
| |
|
| p.write_default_items = function(frame)
| | -- Module access point |
| local hero_key = frame.args[1] --unlocalized
| | function p._get_cost(a) |
| if (hero_key == nil) then return "No hero key provided" end
| | a = tostring(a) or '0' |
| local str = ""
| | return get_cost(a) |
| 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"]) do | |
| 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 | | 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 hero_data = heroes_data[hero_key]
| |
| if(hero_data == nil) then hero_data = p.get_json_item(hero_key) end
| |
| if(hero_data == nil) then return "Hero not found." end
| |
|
| |
| local scaling_data = hero_data[scaling_type .. "Scaling"]
| |
| if scaling_data == nil then return scaling_type .. "Scaling data not found for hero" .. hero_key end
| |
|
| |
| local scaling_value = scaling_data[hero_stat_key]
| |
| if scaling_value == nil then return scaling_type .. "Scaling data does not have key " .. hero_stat_key .. " for hero " .. hero_key end
| |
|
| |
|
| --round
| | -- {{#invoke:ItemData|get_type|ITEM_NAME}} access point |
| if (sig_figs_or_localize ~= nil and tonumber(sig_figs_or_localize) ~= nil) then
| | p.get_type = function(frame) |
| scaling_value = util_module.round_to_sig_fig(scaling_value, sig_figs_or_localize)
| | local item_name = frame.args[1] |
| if (scaling_value == nil) then return "get_hero_scalar() error with rounding" end
| | -- Accept 'raw' as string "true" (case sensitive) |
| end
| | local raw = frame.args['raw'] == 'true' |
|
| | return get_type(item_name, raw) |
| --localize | |
| if (sig_figs_or_localize == "true") then
| |
| return lang_module.get_string(scaling_value)
| |
| end
| |
|
| |
| local scaling_str = p.write_scalar_str(scaling_value, scaling_type) | |
| if (scaling_str ~= "") then scaling_str = " " .. scaling_str end
| |
|
| |
| return scaling_str | |
| end | | end |
|
| |
|
| -- Retrieve scaling string of a hero's given stat, if it has scaling, else return blank | | -- Module access point |
| -- Scaling string meaning the expanded template {{Ss|scalar}} or {{Ls|scalar}}
| | function p._get_type(a) |
| function p.write_scalar_str(scaling_value, scaling_type) | | a = tostring(a) or "" |
| local scaling_abbrevs = {Spirit = "Ss", Level = "PI"} | | return get_type(a) |
|
| |
| -- 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] --scaling type's abbreviation
| |
|
| |
| local template_args = {}
| |
| template_args["1"] = scaling_value --store in 1st arg for {{{1}}} to grab it from {{SS}} or {{LS}} template
| |
|
| |
| local template_call = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
| return template_call
| |
| end | | end |
|
| |
|
| -- Retrieve scaling value and type of a hero's given stat, if it has scaling, else return 0 | | --{{#invoke:ItemData|is_active|ITEM_NAME}}-- |
| function p.get_hero_scaling_data(hero_data, hero_stat_key)
| | p.is_active = function(frame) |
| local scaling_type_full | | local item_name = frame.args[1] |
| local scaling_data | | local item = get_json_item(item_name) |
| local scaling_value | | if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end |
| local scaling_types = {"Spirit", "Level"}
| |
| local scaling_data_returned = {}
| |
| | | |
| for index, scaling_type in ipairs(scaling_types) do | | if (item["Activation"] == "Passive") then |
| scaling_type_full = scaling_type .. "Scaling"
| | return false |
| scaling_data = hero_data[scaling_type_full]
| | else |
|
| | return true |
| --If the scaling data exists
| |
| if (scaling_data ~= nil) then
| |
| scaling_value = scaling_data[hero_stat_key]
| |
|
| |
| --If the stat scales
| |
| if (scaling_value ~= nil) then
| |
| scaling_data_returned[scaling_value] = scaling_type
| |
| end
| |
| end | |
| end | | end |
|
| |
| return scaling_data_returned
| |
| end | | end |
|
| |
|
| --HERO_NAME in english
| | --{{#invoke:ItemData|get_prop|ITEM_NAME|PROPERTY}}-- |
| --{{#invoke:HeroData|write_infobox|HERO_NAME}}-- | | --check Data:ItemData.json for properties |
| p.write_infobox = function(frame) | | p.get_prop = function(frame) |
| -- Get hero data | | local item_name = frame.args[1] |
| hero_key = frame.args[1]
| | local property = frame.args[2] |
| hero_data = heroes_data[hero_key]
| | local item = get_json_item(item_name) |
| if(hero_data == nil) then return "Hero " .. hero_key .. " Not Found" end
| | if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end |
|
| | |
| local infobox_attributes = {
| | local prop_value = item[property] |
| Weapon = {'DPS','ClipSize','RoundsPerSecond','ReloadTime'},
| | if (prop_value == nil) then return nil end |
| Vitality = {'MaxHealth','BulletResist','TechResist','MaxMoveSpeed'}
| |
| }
| |
|
| |
| -- Declarations and initializations
| |
| local category_data = attribute_module.get_category_data()
| |
| local stats
| |
| local stat_data
| |
| local stat_value
| |
| local stat_text
| |
| local label
| |
| local postfix
| |
| local should_display
| |
| local image_file_name
| |
| local image_file
| |
| local icon_and_value_str
| |
| local template_args = {}
| |
| local stat_values = {Weapon = "", Vitality = ""}
| |
|
| |
| -- Add the main parameters
| |
| template_args["name_english"] = lang_module.get_string(hero_key, 'en')
| |
| template_args["name_localized"] = localize(hero_key, hero_key)
| |
|
| |
| -- Iterate attribute categories
| |
| for category, stats in pairs(infobox_attributes) do
| |
| -- Iterate stats
| |
| for _, stat_name in ipairs(stats) do
| |
| -- Confirm its in attribute data and retrieve it
| |
| attribute_data = attributes_data[category]
| |
| if (attribute_data == nil) then return "Category " .. category .. " in infobox_attributes is not in Data:AttributeData" end
| |
| stat_data = attribute_data[stat_name]
| |
| if (stat_data == nil) then return "Attribute " .. stat_name .. " in infobox_attributes is not in Data:AttributeData" end
| |
|
| |
| -- gets the stat's value
| |
| stat_value = hero_data[stat_name]
| |
| if (stat_value == nil) then
| |
| stat_value = 0 --default to 0 if not present
| |
| end
| |
|
| |
| --Round value to 3 significant figures
| |
| stat_value = util_module.round_to_sig_fig(stat_value, 3)
| |
|
| |
| -- get label and postfix
| |
| label = localize(stat_data["label"], stat_name)
| |
| postfix = stat_data["postfix"]
| |
| if (postfix == nil) then
| |
| postfix = ""
| |
| else
| |
| -- light grey for postfixes
| |
| postfix = lang_module.get_string(postfix)
| |
| if (postfix == nil) then
| |
| postfix = ""
| |
| end
| |
| postfix = '<span style="color: #666666;">' .. postfix .. "</span>"
| |
| end
| |
| -- if a language is missing the postfix, use no postfix
| |
|
| |
|
| |
| -- Check if icon file exists, and if not, don't use any image
| |
| image_file_name = 'File:AttributeIcon' .. stat_name .. '.png'
| |
| image_file = util_module.get_image_file(image_file_name, 15, stat_name)
| |
|
| |
| -- Create the template'd icon
| |
| local icon_color = attribute_module.get_attr_icon_color(stat_name)
| |
| icon_template_title = mw.title.new("Template:Icon/" .. icon_color)
| |
| icon_template_args = {}
| |
| icon_template_args[1] = image_file
| |
| icon_template_args[2] = stat_value .. postfix
| |
| icon_and_value_str = mw.getCurrentFrame():expandTemplate{ title = icon_template_title, args = icon_template_args }
| |
|
| |
| label = ',<span style="color: #999999; background-color: #2F2F2F; display: inline-flex; width: 100%; height: 100%; justify-content: center; align-items: center;">' .. label .. '</span>'
| |
|
| |
| -- First add localized stat name for left column
| |
| stat_values[category] = stat_values[category] .. label
| |
|
| |
| -- Then add icon and stat value for right column
| |
| stat_values[category] = stat_values[category] .. ',' .. icon_and_value_str
| |
| end
| |
|
| |
| -- Add the stat values in the category to i.e weapon_values and vitality_values parameters
| |
| template_args[string.lower(category) .. "_values"] = stat_values[category]
| |
| end
| |
|
| |
| -- Use expandTemplate to evaluate the Infobox_hero template
| |
| local template_title = mw.title.new("User:Sur")
| |
| local expanded_template = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
|
| |
| return expanded_template
| |
| end | |
|
| |
|
| | local display_value |
| | local scaling_str = "" -- Default to an empty string for non-scaled values |
|
| |
|
| --{{#invoke:HeroData|write_stat_infoboxes|HERO_KEY}} | | -- Determine the base value and scaling string (if any) |
| --Creates {{Infobox_stat}}'s' for the 3 categories Weapon, Vitality, Spirit
| | if(type(prop_value) == "table") then |
| p.write_stat_infoboxes = function(frame)
| | if prop_value["Value"] ~= nil and prop_value["Scale"] ~= nil and prop_value["Scale"]["Value"] ~= nil and prop_value["Scale"]["Type"] ~= nil then |
| local hero_key = frame.args[1]
| | -- This is a structured attribute with scaling data |
| if(hero_key == nil) then return "Hero parameter missing" end | | display_value = tostring(prop_value["Value"]) |
|
| | local scale_val = tonumber(prop_value["Scale"]["Value"]) |
| -- Use expandTemplate to evaluate the Infobox_hero template
| |
| local template_title = mw.title.new("Template:Infobox_stat") --name of the template
| |
| local template_calls = '<div style="display: flex; flex-direction: column;">' --all template calls concatenated
| |
| local template_call --current template call
| |
| local template_args = {} --current template arguments
| |
| local cell_values --current cell values
| |
| local cell_value --current cell value
| |
| local label --current stat's label
| |
| local postfix --current stat's postfix
| |
| local stat_value --current stat's numerical value in the hero data
| |
| local hero_data = heroes_data[hero_key]
| |
| local stats --stats of the current category
| |
| local image_file_name --name of the image_file to check
| |
| local image_file --mw returned image file
| |
| local extra_blank_cell --add a blank cell after certain stats
| |
| local stats_to_add_blank_after = {CritDamageReceivedScale = true}
| |
| if (hero_data == nil) then return "Hero Not Found" end
| |
| local category_data = attribute_module.get_category_data()
| |
| local should_display
| |
|
| |
| for _, category in ipairs(attribute_orders["category_order"]) do
| |
| if (category ~= "Spirit") then --hide Spirit section for now
| |
| stats = attributes_data[category] | |
| local category_values = category_data[category] | |
| template_args["box_name"] = category_values.unlocalized_name
| |
| template_args["box_rgb"] = category_values.rgb
| |
| template_args["num_cols"] = 2
| |
| cell_values = ""
| |
| | | |
| -- Determine cell values | | if scale_val ~= 0 then |
| for _, stat_name in ipairs(attribute_orders[category]["attribute_order"]) do
| | local scale_type = prop_value["Scale"]["Type"] |
| local stat_data = stats[stat_name] | | local rounded_scale_val = util_module.round_to_sig_fig(scale_val, 2) |
| if stat_data == nil then return "Stat " .. stat_name .. " from StatInfoboxOrder has no data in AttributeData" end
| |
| | | |
| -- gets the stat's value if it has that stat, default to 0 if not | | local template_title |
| stat_value = hero_data[stat_name] | | if scale_type == "power_increase" then |
| if (stat_value == nil) then
| | template_title = "Template:Boon" |
| stat_value = 0 --default to 0 if not present
| |
| end
| |
|
| |
| --Round value to 3 significant figures
| |
| stat_value = util_module.round_to_sig_fig(stat_value, 3)
| |
|
| |
| -- get label and postfix
| |
| label = localize(stat_data["label"], stat_name)
| |
| postfix = stat_data["postfix"]
| |
| if (postfix == nil) then
| |
| postfix = "" | |
| else | | else |
| -- light grey for postfixes | | template_title = "Template:Ss" |
| postfix = '<span style="color: #666666;">' .. lang_module.get_string(postfix) .. "</span>"
| |
| end
| |
| -- if a language is missing the postfix, use no postfix
| |
| if (postfix == nil) then
| |
| postfix = ""
| |
| end | | end |
| | | |
| -- Check if icon file exists, and if not, don't use any image | | local template_args = { [1] = rounded_scale_val } |
| image_file_name = 'File:AttributeIcon' .. stat_name .. '.png'
| | -- Add a space before the scaling template for proper separation |
| -- 15px and link to stat name (page name might not match perfectly yet)
| | scaling_str = " " .. mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args } |
| image_file = util_module.get_image_file(image_file_name, 15, stat_name)
| | end |
|
| | else |
| -- Create the template'd icon | | -- This is a simple list (e.g., TargetTypes), handle and return it directly |
| local icon_color = attribute_module.get_attr_icon_color(stat_name) | | local tableValues = {} |
| if (icon_color == "Brown") then
| | for _, v in pairs(prop_value) do |
| image_file = '<span style="position: relative; bottom: 2px; filter: invert(42%) sepia(10%) saturate(2912%) hue-rotate(351deg) brightness(90%) contrast(87%);">' .. image_file .. '</span>'
| | table.insert(tableValues, tostring(v)) |
| end
| |
| -- use white instead of grey if grey is returned
| |
|
| |
| -- Add an empty cell following some stats to align them correctly as seen in game
| |
| if (stats_to_add_blank_after[stat_name]) then
| |
| extra_blank_cell = " ," --prefixed space is needed
| |
| else
| |
| extra_blank_cell = ""
| |
| end
| |
|
| |
| -- slightly lighter color for stat name
| |
| label = '<span style="color: #3d3d3d;">' .. label .. '</span>'
| |
|
| |
| -- Add the scaling str if it scales
| |
| local scaling_data = p.get_hero_scaling_data(hero_data, stat_name)
| |
| local scaling_strs = ""
| |
| if (scaling_data ~= nil) then
| |
| for scaling_value, scaling_type in pairs(scaling_data) do
| |
| local scaling_str = p.write_scalar_str(scaling_value, scaling_type)
| |
| if (scaling_str ~= "") then scaling_str = " " .. scaling_str end
| |
| scaling_strs = scaling_strs .. scaling_str
| |
| end
| |
| end
| |
|
| |
| -- Set cell value as "Icon StatvaluePostfix Statname Scaling,"
| |
| -- If icon file already exists, set it to #REDIRECT to the duplicate file page
| |
| cell_value = image_file .. " " .. stat_value .. postfix .. " " .. label .. scaling_strs .. "," .. extra_blank_cell
| |
|
| |
| cell_values = cell_values .. cell_value --add value to values list
| |
| end | | end |
| template_args["cell_values"] = cell_values | | return table.concat(tableValues, ", ") |
|
| |
|
| |
| -- Write current call
| |
| template_call = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
|
| |
| -- Add call to the set of calls
| |
| template_calls = template_calls .. "\n" .. template_call
| |
| end | | end |
| | else |
| | -- This is a simple, non-table property |
| | display_value = tostring(prop_value) |
| end | | end |
| | | |
| return template_calls .. "</div>" | | -- Assemble the final string, adding the '%' sign if necessary |
| | if percentage_properties[property] == true then |
| | return display_value .. "%" .. scaling_str |
| | else |
| | return display_value .. scaling_str |
| | end |
| end | | end |
|
| |
|
| --writes the massive hero comparison table for a specific PI and SP
| | p.get_component_name = function(frame) |
| -- scaling icons are shown only if PI and SP are both 0 or both not provided
| | local item_name = frame.args[1] |
| --{{#invoke:HeroData|write_hero_comparison_table|POWER_INCREASES|SPIRIT_POWER}}
| | local item = get_json_item(item_name) |
| p.write_hero_comparison_table = function(frame) | | if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end |
| -- process inputs
| |
| local power_increases = frame.args[1] | |
| if (power_increases == nil) then power_increases = 0 end | |
| local spirit_power = frame.args[2]
| |
| if (spirit_power == nil) then spirit_power = 0 end | |
| | | |
| -- Show scaling icons if power/increases/spirit power are 0/not specified | | local components = item['Components'] |
| local display_scaling_icons = false
| | --return empty string if no component found |
| if (power_increases == 0 and spirit_power == 0) then | | if(components == nil) then return nil end |
| display_scaling_icons = true
| |
| end
| |
| | | |
| -- Initializations and declarations
| | local component_key = components[1] |
| local row_str = "" | | local component = data[component_key] |
| local body_str = "" | | if (component == nil) then return nil end |
| local in_development
| |
| local is_disabled | |
| local template_title = ""
| |
| local template_args = {}
| |
| local hero_icon
| |
| local hero_td_style
| |
| local scalar_str
| |
| | | |
| -- Add hero comp stats to | | return component['Name'] |
| local stats_to_include = { | | end |
| Weapon = {
| | |
| "DPS",
| | -- {{Items count}} access point |
| "SustainedDPS",
| | function p.get_headcount() |
| "BulletDamage",
| | local iter = 0 |
| "RoundsPerSecond",
| | local tbl = get_similar_items("IsDisabled") |
| "FireRate",
| | for _, v in ipairs(tbl) do |
| "ClipSize",
| | if (v["IsDisabled"] == false and v["Name"] ~= nil) then |
| "ReloadTime",
| | iter = iter + 1 |
| "ReloadDelay",
| | end |
| "BulletsPerShot",
| | end |
| "BulletsPerBurst",
| | return iter |
| "BurstInterShotInterval",
| | end |
| "LightMeleeDamage",
| | |
| "HeavyMeleeDamage",
| | |
| "ReloadSingle",
| | -- Gets the codename (the key in the JSON data) for a given item's display name. |
| "BulletSpeed",
| | -- Prioritizes active items to handle cases where disabled duplicates exist. |
| "BulletGravityScale",
| | p.get_codename = function(frame) |
| "FalloffStartRange",
| | local name = frame.args[1] |
| "FalloffEndRange",
| |
| "BonusAttackRange",
| |
| "RoundsPerSecondAtMaxSpin",
| |
| "SpinAcceleration",
| |
| "SpinDeceleration"
| |
| },
| |
| Vitality = {
| |
| "MaxHealth", | |
| "BaseHealthRegen",
| |
| "BulletResist",
| |
| "TechResist",
| |
| "CritDamageReceivedScale",
| |
| "MaxMoveSpeed",
| |
| "SprintSpeed",
| |
| "StaminaCooldown",
| |
| "Stamina"
| |
| }
| |
| } | |
| | | |
| -- Iterate heroes | | -- First, try to find an active item. |
| for hero_key, hero_data in pairs(heroes_data) do | | for k, v in pairs(data) do |
| -- Ensure they are not in development and they are an active hero
| | if (v["Name"] == name and (v["IsDisabled"] == false or v["IsDisabled"] == nil)) then |
| in_development = hero_data["InDevelopment"]
| | return k -- Return the codename of the active item |
| is_disabled = hero_data["IsDisabled"]
| |
| if (not is_disabled) then | |
|
| |
| --Add the row's stats
| |
| row_str = ""
| |
|
| |
| -- Retrieve hero's localized name
| |
| hero_name = localize(hero_key, hero_key)
| |
|
| |
| -- Retrieve hero's english name, used for hero icon
| |
| hero_name_en = hero_data["Name"]
| |
|
| |
| -- Expand hero icon
| |
| template_title = "Template:HeroIcon"
| |
| template_args[1] = hero_name_en
| |
| template_args[2] = hero_name
| |
| hero_icon = mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
| |
|
| |
| -- First column in each row is hero name
| |
| row_str = row_str .. "<td>" .. hero_icon .. "</td>"
| |
|
| |
| -- Consecutive columns are then stats
| |
| -- Iterate categories
| |
| for _, category in ipairs(attribute_orders["category_order"]) do
| |
| category_attrs = attributes_data[category]
| |
|
| |
| -- Iterate attributes within the category
| |
| if (stats_to_include[category] ~= nil) then
| |
|
| |
| for _, attr_key in ipairs(stats_to_include[category]) do
| |
|
| |
| --Retrieve the stats value from hero data
| |
| stat_value = hero_data[attr_key]
| |
| if (stat_value == nil) then stat_value = 0 end
| |
|
| |
| -- Retrieve scaling val and str if it scales
| |
| local scaling_data = p.get_hero_scaling_data(hero_data, attr_key)
| |
| local scaling_strs = ""
| |
| if (scaling_data ~= nil) then
| |
| for scaling_value, scaling_type in pairs(scaling_data) do
| |
| local scaling_str = p.write_scalar_str(scaling_value, scaling_type)
| |
| if (scaling_str ~= "") then scaling_str = " " .. scaling_str end
| |
| if (scaling_str ~= nil) then
| |
| scaling_strs = scaling_strs .. scaling_str
| |
| end
| |
| -- Scale the stat value
| |
| if (scaling_type == "Spirit") then
| |
| stat_value = stat_value + (spirit_power * scaling_value)
| |
| elseif (scaling_type == "Level") then
| |
| if attr_key == 'TechResist' or attr_key == 'BulletResist' then
| |
| -- each PI for resists is multiplicative
| |
| stat_value = (stat_value/100 + 1 - (1-scaling_value/100)^power_increases)*100
| |
| else
| |
| stat_value = stat_value + (power_increases * scaling_value)
| |
| end
| |
| end
| |
| end
| |
| end
| |
| if (not display_scaling_icons) then scaling_strs = "" end
| |
|
| |
|
| |
|
| |
| -- Convert from boolean to string, or round it
| |
| if (type(stat_value)) == 'boolean' then
| |
| stat_value = tostring(stat_value)
| |
| else
| |
| stat_value = util_module.round_to_sig_fig(stat_value, 3)
| |
| end
| |
|
| |
| -- Add it to the row
| |
| row_str = row_str .. "<td>" .. stat_value .. scaling_strs .. "</td>"
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- Add row to the body
| |
| row_str = "<tr>" .. row_str .. "</tr>"
| |
| body_str = body_str .. row_str
| |
|
| |
| first_hero = false
| |
| end | | end |
| end | | end |
| | | |
|
| | -- If no active item was found, fall back to finding any item with the name. |
| -- First header is Hero | | for k, v in pairs(data) do |
| local headers_str = "<th>" .. "Hero" .. "</th>"
| | if (v["Name"] == name) then |
| local header_style = ""
| | return k |
| local category_data = attribute_module.get_category_data()
| |
| local postfix
| |
| -- Used for keys that are not localized by Valve
| |
| local postfix_key_map = {
| |
| ["ReloadDelay"] = "StatDesc_ReloadTime_postfix",
| |
| ["BulletsPerShot"] = "",
| |
| ["BulletsPerBurst"] = "",
| |
| ["BurstInterShotInterval"] = "StatDesc_ReloadTime_postfix",
| |
| ["ReloadSingle"] = "",
| |
| ["BonusAttackRange"] = "StatDesc_WeaponRangeFalloffMax_postfix",
| |
| ["SustainedDPS"] = "DPS_postfix",
| |
| ["RoundsPerSecondAtMaxSpin"] = "",
| |
| ["SpinAcceleration"] = "BonusFireRate_postfix",
| |
| ["SpinDeceleration"] = "BonusFireRate_postfix"
| |
| }
| |
|
| |
| -- Iterate stats again for displaying headers with their respective color
| |
| for _, category in ipairs(attribute_orders["category_order"]) do | |
| category_attrs = attributes_data[category] | |
| category_rgb = category_data[category]["rgb"]
| |
|
| |
| -- Iterate attributes within the category
| |
| if (stats_to_include[category] ~= nil) then
| |
| for _, attr_key in ipairs(stats_to_include[category]) do
| |
| attr_data = category_attrs[attr_key]
| |
|
| |
| if (attr_data ~= nil) then
| |
| -- Localize, fallback to attr_key with proper spaces
| |
| attr_localized = lang_module.get_string(attr_data["label"])
| |
| if (attr_localized == nil or attr_localized == "") then attr_localized = util_module.add_space_before_cap(attr_key) end
| |
|
| |
| -- Get postfix
| |
| postfix = lang_module.get_string(attr_data["postfix"])
| |
| if (postfix == nil or postfix == "") then
| |
| postfix = ""
| |
| else
| |
| postfix = " (" .. postfix .. ")"
| |
| end
| |
| else -- If not in attributes data, use dictionary translate and postfix
| |
| attr_localized = dictionary_module.translate(attr_key)
| |
| postfix = lang_module.get_string(postfix_key_map[attr_key])
| |
| if postfix == nil then
| |
| return "attr_key " .. attr_key .. " must be added to postfix_key_map"
| |
| end
| |
| if postfix ~= "" then
| |
| postfix = " (" .. postfix .. ")"
| |
| end
| |
| end
| |
|
| |
| header_style = ' style="background-color: ' .. "rgb(" .. category_rgb .. ') ;"'
| |
|
| |
| headers_str = headers_str .. "<th" .. header_style .. ">" .. attr_localized .. postfix .. "</th>"
| |
| end
| |
| end | | end |
| end | | end |
| headers_str = "<tr>" .. headers_str .. "</tr>" | | |
| | | return nil -- Return nil if no item was found at all |
| local table_str = '<table class="wikitable sortable" style="table-layout: auto; width: 100%;">' .. headers_str .. body_str .. "</table>" | |
| local output_str = '<div style="overflow-x: auto; width: 100%;">' .. table_str .. '</div>'
| |
| return output_str
| |
| end
| |
| | |
| 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 | | 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",
| |
| ["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",
| |
| ["Pocket"] = "Synth",
| |
| ["Seven"] = "Gigawatt",
| |
| ["Shiv"] = "Shiv",
| |
| ["Sinclair"] = "Magician",
| |
| ["Victor"] = "Frank",
| |
| ["Vindicta"] = "Vindicta",
| |
| ["Viscous"] = "Viscous",
| |
| ["Vyper"] = "Viper",
| |
| ["Warden"] = "Warden",
| |
| ["Wraith"] = "Wraith",
| |
| ["Yamato"] = "Yamato",
| |
| }
| |
| | |
| -- 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
| |
| | |
| local tagname = TAG_NAME_MAP[name]
| |
| if not tagname then
| |
| return string.format("Error: Unknown name '%s'.", name)
| |
| end
| |
| | |
| -- Build localization key
| |
| local key = string.format("Citadel_%s_HeroTag_%d", tagname, number)
| |
| | |
| -- Call Lang.get_string
| |
| local result = lang_module.get_string(key)
| |
| | |
| return result or ("[Missing string for key: " .. key .. "]")
| |
| end
| |
|
| |
|
| return p | | return p |