|
|
| Line 1: |
Line 1: |
| local p = {}; | | local p = {} |
| local data = mw.loadJsonData("Data:ItemData.json") | | local util_module = require("Module:Utilities") |
| | local lang_codes_set = mw.loadJsonData("Data:LangCodes.json") |
| | local dictionary_module = require("Module:Dictionary") |
|
| |
|
| -- Utility function to safely convert to number | | -- Overrides applied to searches by key. Designed to handle edge cases where |
| local function toNumber(value) | | -- the expected key does not have a localization entry |
| if type(value) == "number" then | | local KEY_OVERRIDES = { |
| return value
| | MoveSlowPercent_label = 'MovementSlow_label', |
| elseif type(value) == "string" then | | BonusHealthRegen_label = 'HealthRegen_label', |
| local cleaned = value:gsub("[^%d.-]", "")
| | BarbedWireRadius_label = 'Radius_label', |
| return tonumber(cleaned) or 0 | | BarbedWireDamagePerMeter_label = 'DamagePerMeter_label', |
| | BuildUpDuration_label = 'BuildupDuration_label', |
| | TechArmorDamageReduction_label = 'TechArmorDamageReduction_Label', |
| | DamageAbsorb_label = 'DamageAbsorb_Label', |
| | InvisRegen_label = 'InvisRegen_Label', |
| | EvasionChance_label = 'EvasionChance_Label', |
| | DelayBetweenShots_label = 'DelayBetweenShots_Label', |
| | } |
| | |
| | function get_lang_file(lang_code) |
| | local file_name = string.format("Data:Lang_%s.json", lang_code) |
| | local success, data = pcall(mw.loadJsonData, file_name) |
| | if success then |
| | return data |
| | else |
| | return nil |
| end | | end |
| return 0
| |
| end | | end |
|
| |
|
| -- Normalize strings for comparison | | -- Helper function to replace \"n with newlines |
| local function normalize(str) | | local function process_newlines(str) |
| return mw.ustring.lower(mw.text.trim(str or "")) | | if str then |
| | return str:gsub('\\"n', '\n') |
| | end |
| | return str |
| end | | end |
|
| |
|
| -- Safe item access | | -- Get a localized string by the raw key |
| local function get_json_item(name)
| | p.get_string = function(key, lang_code_override, fallback_str, remove_var_index) |
| if not name or type(name) ~= 'string' then return nil end | | -- If called internally (direct Lua call), args will be passed directly. |
| local targetName = normalize(name) | | -- If called from wikitext, `key` will be the `frame` object, and we get args from `frame.args`. |
|
| | |
| for _, item in pairs(data) do | | -- Handle the case where it's called via #invoke (i.e., from wikitext) |
| if item.Name and normalize(item.Name) == targetName then
| | if type(key) == "table" and key.args then |
| if item.IsDisabled ~= true then
| | local frame = key |
| return item
| | key = frame.args[1] |
| end
| | lang_code_override = frame.args["lang_code_override"] |
| end | | fallback_str = frame.args["fallback_str"] |
| | remove_var_index = frame.args["remove_var_index"] |
| end | | end |
| return nil
| |
| end
| |
|
| |
|
| -- Find items that use this item as a component | | -- Determine lang_code if not overridden |
| local function get_parent_items(item_name)
| | local lang_code = lang_code_override |
| local parents = {} | | if (lang_code == '' or lang_code == nil) then |
| for _, item in pairs(data) do | | lang_code = get_lang_code() |
| if item.Components and type(item.Components) == "table" then
| |
| for _, comp_id in ipairs(item.Components) do
| |
| local component = data[comp_id]
| |
| if component and component.Name == item_name then
| |
| table.insert(parents, item.Name)
| |
| end
| |
| end
| |
| end
| |
| end | | end |
| return parents
| |
| end
| |
|
| |
| -- Format numbers with localization
| |
| local lang = mw.language.getContentLanguage()
| |
| local function formatNum(amount)
| |
| return lang:formatNum(toNumber(amount))
| |
| end
| |
|
| |
|
| -- Smart stat display formatter | | -- Retrieve lang data |
| local function formatStat(stat, value) | | local data = get_lang_file(lang_code) |
| local icon_map = {
| | if (data == nil) then |
| TechPower = "Spirit icon.png",
| | return string.format("Lang code '%s' does not have a json file", lang_code) |
| WeaponPower = "Weapon Icon.png",
| |
| TechResist = "Spirit Resist Icon.png",
| |
| BulletResist = "Bullet Resist Icon.png",
| |
| AbilityCooldown = "Cooldown Icon.png"
| |
| }
| |
|
| |
| local suffix_map = {
| |
| Percent = "%",
| |
| Speed = "m/s",
| |
| Range = "m",
| |
| Duration = "s",
| |
| Cooldown = "s",
| |
| Health = " HP",
| |
| Stacks = " stacks",
| |
| Damage = " Damage",
| |
| FireRate = " Fire Rate",
| |
| Lifesteal = "% Lifesteal"
| |
| }
| |
|
| |
| -- Special cases with custom formatting
| |
| if stat == "AmmoPerSoul" then | |
| return string.format("• %d ammo per Soul", value)
| |
| elseif stat == "SpiritPowerPerSoul" then
| |
| return string.format("• +%d Spirit Power per Soul", value)
| |
| elseif stat == "MaxStacks" then
| |
| return string.format("• Max %d stacks", value) | |
| end | | end |
| | | |
| -- Automatic formatting based on stat name | | -- Localize |
| local display = "• " | | local label = data[KEY_OVERRIDES[key] or key] |
| local icon = icon_map[stat] | | if (label == nil) then |
| local base_stat = stat:gsub("Bonus", ""):gsub("Percent", "")
| | -- Apply fallback |
|
| | local fallback_tooltip = mw.getCurrentFrame():expandTemplate{title = "MissingValveTranslationTooltip"} |
| -- Add value
| | local fallback |
| display = display .. tostring(value)
| | if (fallback_str == 'en') then |
|
| | fallback = p.get_string(key, 'en', key .. fallback_tooltip, remove_var_index, upper_lower) |
| -- Add icon if available
| | elseif fallback_str == 'dictionary' then |
| if icon then
| | return dictionary_module.translate(key, lang_code_override) |
| display = display .. " [[File:" .. icon .. "|20px]]" | | elseif fallback_str ~= nil then |
| | fallback = fallback_str |
| | else |
| | return '' |
| | end |
| | return process_newlines(fallback) .. fallback_tooltip |
| end | | end |
| | | |
| -- Add appropriate suffix | | -- Apply remove_var |
| for pattern, suffix in pairs(suffix_map) do | | if (remove_var_index ~= nil) then |
| if stat:find(pattern) then | | label = util_module.remove_var(label, remove_var_index) |
| return display .. suffix
| |
| end
| |
| end | | end |
| | | |
| -- Default case - show stat name if no special formatting
| | return process_newlines(label) |
| return display .. " " .. base_stat | |
| end | | end |
|
| |
|
| -- Main function with improved error handling | | -- Search for a localized string using its English label |
| p.generate_infobox = function(frame) | | p.search_string = function(frame) |
| local item_name = frame.args[1] | | local label = frame.args[1] |
| local manual_component = frame.args.component | | local lang_code_override = frame.args[2] |
| | | |
| if not item_name or item_name == "" then | | return p._search_string(label, lang_code_override) |
| return frame:preprocess("{{Error|No item name specified}}") | | end |
| | |
| | -- search_string, but for internal use by other modules |
| | p._search_string = function(label, lang_code_override) |
| | lang_code = lang_code_override |
| | if (lang_code == '' or lang_code == nil) then |
| | lang_code = get_lang_code() |
| end | | end |
|
| |
| local item = get_json_item(item_name)
| |
| if not item then
| |
| return frame:preprocess("{{Error|Item not found: " .. item_name .. "}}")
| |
| end
| |
|
| |
| -- Determine item type
| |
| local item_type = "Weapon" -- default
| |
| if item.Slot then
| |
| local slot = normalize(item.Slot)
| |
| if slot == "armor" then
| |
| item_type = "Vitality"
| |
| elseif slot == "tech" then
| |
| item_type = "Spirit"
| |
| end
| |
| end
| |
|
| |
| -- Color definitions
| |
| local colors = {
| |
| Weapon = {bg = "#80550F", header = "#C97A03", border = "#C97A03"},
| |
| Vitality = {bg = "#4D7214", header = "#659818", border = "#659818"},
| |
| Spirit = {bg = "#623585", header = "#8B56B4", border = "#8B56B4"}
| |
| }
| |
| local color = colors[item_type] or colors.Weapon
| |
|
| |
|
| -- Process components | | -- Load the language files |
| local components_text = "" | | local data_en = get_lang_file('en') -- English data |
| if item.Components and type(item.Components) == "table" then
| | local data_lang = get_lang_file(lang_code) -- Target language data |
| local valid_components = {}
| |
| for _, comp_id in ipairs(item.Components) do
| |
| local component = data[comp_id]
| |
| if component and component.Name then
| |
| table.insert(valid_components, "{{ItemIcon|" .. component.Name .. "}}")
| |
| end
| |
| end
| |
|
| |
| if #valid_components > 0 then
| |
| components_text = string.format(
| |
| '|-\n! colspan="4" style="text-align:left; padding:0 12px; font-weight:bold; font-size:16px; font-family:\'Retail Demo Bold\',serif; background-color: %s; color: %s" | COMPONENTS:<br/>%s',
| |
| (item_type == "Weapon" and "#9E630C" or item_type == "Vitality" and "#203500" or "#372248"),
| |
| (item_type == "Weapon" and "#DCCFC6" or item_type == "Vitality" and "#C7C9C6" or "#C9C7CB"),
| |
| table.concat(valid_components, "<br/>")
| |
| )
| |
| end
| |
| end
| |
|
| |
|
| -- Process parent items | | if (data_lang == nil) then |
| local parent_items_text = ""
| | error("Lang code '%s' does not have a json file", lang_code) |
| local parent_items = {}
| |
|
| |
| if manual_component and manual_component ~= "" then
| |
| table.insert(parent_items, manual_component) | |
| else
| |
| parent_items = get_parent_items(item.Name)
| |
| end | | end |
| | | |
| if #parent_items > 0 then | | -- Search for the key in the English data |
| local parent_icons = {}
| | local key = nil |
| for _, parent in ipairs(parent_items) do
| | for k, v in pairs(data_en) do |
| table.insert(parent_icons, "{{ItemIcon|" .. parent .. "}}") | | if v == label then |
| | key = k -- Find the key corresponding to the label |
| | break |
| end | | end |
|
| |
| parent_items_text = string.format(
| |
| '|-\n! colspan="4" style="text-align:left; padding:0 12px; font-weight:bold; font-size:16px; font-family:\'Retail Demo Bold\',serif; background-color: %s; color: %s" | IS COMPONENT OF:<br/>%s',
| |
| (item_type == "Weapon" and "#704A0C" or item_type == "Vitality" and "#436310" or "#552D74"),
| |
| (item_type == "Weapon" and "#D1CBC6" or item_type == "Vitality" and "#CACFC7" or "#CCC8D2"),
| |
| table.concat(parent_icons, "<br/>")
| |
| )
| |
| end | | end |
|
| |
|
| -- Process ALL stats dynamically | | -- Default to input label if localized string is not found |
| local stats = {} | | if (key == nil) then |
| local skip_stats = {
| | return process_newlines(label) |
| Name = true, Description = true, Cost = true, Tier = true,
| |
| Activation = true, Slot = true, Components = true,
| |
| TargetTypes = true, ShopFilters = true, IsDisabled = true,
| |
| AbilityCastDelay = true, AbilityChannelTime = true,
| |
| AbilityPostCastDuration = true, AbilityCharges = true,
| |
| AbilityCooldownBetweenCharge = true, ChannelMoveSpeed = true,
| |
| AbilityResourceCost = true
| |
| }
| |
|
| |
| for stat, value in pairs(item) do
| |
| if not skip_stats[stat] and value ~= nil and value ~= "0" and value ~= 0 then
| |
| local num_value = toNumber(value)
| |
| local formatted = formatStat(stat, num_value)
| |
| if formatted then
| |
| table.insert(stats, formatted)
| |
| end
| |
| end
| |
| end | | end |
| | | if (data_lang[key] == nil) then |
| -- Sort stats alphabetically for consistent display
| | return process_newlines(label) |
| table.sort(stats)
| |
| | |
| -- Calculate shop bonus
| |
| local tier = toNumber(item.Tier or 1)
| |
| local shop_bonus = "+0"
| |
| if item_type == "Weapon" then
| |
| shop_bonus = string.format("+%d%% Weapon Damage", 6 + (tier-1)*4)
| |
| elseif item_type == "Vitality" then
| |
| shop_bonus = string.format("+%d%% Base Health", 11 + (tier-1)*3) | |
| else -- Spirit
| |
| shop_bonus = string.format("+%d Spirit Power", 4 * tier)
| |
| end | | end |
|
| |
|
| -- Build infobox parts | | return process_newlines(data_lang[key]) |
| local parts = {
| | end |
| -- Header
| |
| string.format(
| |
| '<div class="infobox_item" style="float:right; display:inline-block; vertical-align:top;">\n' ..
| |
| '{| class="wikitable" style="text-align:left; border-collapse:collapse; width:312px; max-width:100%%; color: #FFEFD7; padding:12px; font-family:\'Retail Demo Regular\',serif; background-color: %s"\n' ..
| |
| '! colspan="4" style="width:280px; padding:5px 12px; text-align:center; font-size:24px; font-family:\'Retail Demo Bold\',serif; background-color: %s" | %s',
| |
| color.bg, color.header, item.Name
| |
| ),
| |
|
| |
| -- Image
| |
| string.format(
| |
| '|-\n| colspan=4 class="infobox-image" style="padding:5px; text-align:center;" | [[File:%s.png|144px]]',
| |
| item.Name
| |
| ),
| |
|
| |
| -- Cost
| |
| string.format(
| |
| '|-\n| colspan=2 style="text-align:right; width:50%%; border-right:1px solid %s; padding-right:0.5em;" | Cost\n' ..
| |
| '| colspan=2 style="padding-left:0.5em;" | {{Souls|%s}}',
| |
| color.border, formatNum(item.Cost)
| |
| ),
| |
|
| |
| -- Tier
| |
| string.format(
| |
| '|-\n| colspan=2 style="text-align:right; width:50%%; border-right:1px solid %s; padding-right:0.5em;" | Tier\n' ..
| |
| '| colspan=2 style="padding-left:0.5em;" | %s',
| |
| color.border, tier
| |
| ),
| |
|
| |
| -- Shop Bonus
| |
| string.format(
| |
| '|-\n| colspan=2 style="text-align:right; width:50%%; border-right:1px solid %s; padding-right:0.5em;" | Shop Bonus\n' ..
| |
| '| colspan=2 style="padding-left:0.5em;" | %s',
| |
| color.border, shop_bonus
| |
| )
| |
| }
| |
|
| |
|
| -- Add components if they exist | | function get_lang_code() |
| if components_text ~= "" then | | local title = mw.title.getCurrentTitle() |
| table.insert(parts, components_text) | | local lang_code = title.fullText:match(".*/(.*)$") |
| | |
| | if lang_code == nil or lang_codes_set[lang_code] == nil then |
| | return 'en' |
| end | | end |
| | | |
| -- Add parent items if they exist
| | return lang_code |
| if parent_items_text ~= "" then
| |
| table.insert(parts, parent_items_text)
| |
| end
| |
| | |
| -- Add description if it exists
| |
| if item.Description then
| |
| table.insert(parts, | |
| string.format(
| |
| '|-\n| colspan="4" style="text-align:left; padding:0 12px; color:#FFEFD7; background-color: %s" | %s',
| |
| color.bg, item.Description
| |
| )
| |
| )
| |
| end
| |
| | |
| -- Add stats if they exist
| |
| if #stats > 0 then
| |
| table.insert(parts,
| |
| '|-\n| ' .. table.concat(stats, "\n|-\n| ")
| |
| )
| |
| end
| |
| | |
| -- Close table
| |
| table.insert(parts, "|}\n</div>")
| |
| | |
| -- Process and return
| |
| return frame:preprocess(table.concat(parts, "\n")) | |
| end | | end |
|
| |
|
| return p | | return p |