Module:Sandbox/LVL

From The Deadlock Wiki
Revision as of 19:07, 15 September 2025 by LVL (talk | contribs)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Sandbox/LVL/doc

local p = {}
local util_module = require("Module:Utilities")
local lang_codes_set = mw.loadJsonData("Data:LangCodes.json")
local dictionary_module = require("Module:Dictionary")

-- Overrides applied to searches by key. Designed to handle edge cases where
-- the expected key does not have a localization entry
local KEY_OVERRIDES = {
    MoveSlowPercent_label = 'MovementSlow_label',
    BonusHealthRegen_label = 'HealthRegen_label',
    BarbedWireRadius_label = 'Radius_label',
    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)
    -- For testing purposes, always load the sandbox data regardless of language code.
    return mw.loadJsonData("Data:Sandbox.json")
end

local function process_newlines(text)
    if not text or type(text) ~= "string" then return text end
    -- Replace "n with newline character
    return text:gsub('"n', '<br/>')
end

local function process_html_tags(text, frame)
    if not text or type(text) ~= "string" then return text end
    
    local replacements = {
	    SpiritIcon = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
	    tech_damage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
	    SpiritDamage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
	    StatDesc_TechPower = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
	    SpiritResist = {icon='{{Icon/Purple|[[File:Spirit_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#bc8ee8', link='Damage_Resistance'},
	    FireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
	    fire_rate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
	    ReducedFireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
	    MeleeDamage = {icon='{{Icon/Brown|[[File:Melee damage.png|link=Melee Damage|20px]]}}', label_color='#ec981a', link='Melee Damage'},
	    WeaponDamage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
	    damage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
	    bullet_damage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
	    BulletResist = {icon='{{Icon/Brown|[[File:Bullet_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#ec981a', link='Damage_Resistance'},
	    CombatBarrier = {icon='{{Icon/White|[[File:Bullet_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#ffefd7', link='Barrier'},
	    BonusWeaponDamage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
	    MoveSpeed = {icon='{{Icon/White|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
	    BonusSpiritDamage = {icon='{{Icon/Purple|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
	    BonusFireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
	    Heal = {icon='{{Icon/Green|[[File:Health_regen.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
	    BonusMoveSpeed = {icon='{{Icon/White|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
	    move_speed = {icon='{{Icon/White|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
	    BonusSprintSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Sprint speed|20px]]}}', label_color='#ffefd7', link='Sprint speed'},
	    Spirit = {icon='{{Icon/NoColor|[[File:Spirit_icon.png|link=Spirit Power|20px]]}}', label_color='#bc8ee8', link='Spirit Power'},
	    Stun = {icon='{{Icon/White|[[File:Status_Stun.png|link=Stun|20px]]}}', link='Stun'},
	    SpiritDPS = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
	    Healing = {icon='{{Icon/Green|[[File:Health_regen.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
	    Regen = {icon='{{Icon/Green|[[File:Health_regen.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
	    MaxHealth = {icon='{{Icon/Green|[[File:Healing.png|link=Health|20px]]}}', label_color='#13f278', link='Health'},
	    Slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
	    slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
	    SlowResistance = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow#Movement_Slow_Resist|20px]]}}', label_color='#ffefd7', link='Movement_Slow#Movement_Slow_Resist'},
        Immobilize = {icon='{{Icon/White|[[File:Immobilize.png|link=Status Effects#Immobilized|20px]]}}', label_color='#ffefd7', link='Status Effects#Immobilized'},
        cooldown = {icon='{{Icon/White|[[File:AttributeIconTechDuration.png|link=Ability Cooldown|20px]]}}', label_color='#ffefd7', link='Ability Cooldown'},
        stun = {icon='{{Icon/White|[[File:Stun.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        invisible = {icon='{{Icon/White|[[File:Invisible.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        cast = {icon='{{Icon/White|[[File:Cast.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        silence = {icon='{{Icon/White|[[File:Silence.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        Silence = {icon='{{Icon/Purple|[[File:Silence.png|link=Silence|20px]]}}', label_color='#bc8ee8', link='Silence'},
        revealed = {icon='{{Icon/White|[[File:Revealed.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        bullet_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
        tech_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
        Pulling =   {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
	    Pull =      {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
	    Pulls =     {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
	    KnockBack = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
	    KnockUp =   {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
	}
   
    -- Process HTML spans
    text = text:gsub('<span class="highlight">(.-)</span>', "<span style= font-weight:bold>%1</span>")
    text = text:gsub('<span class="highlight_spirit">(.-)</span>', '<span style="font-weight:bold; color:#bc8ee8">%1</span>')
    text = text:gsub('<span class="diminish">(.-)</span>', '<span style="font-style: italic;color:#C0C0C0">%1</span>')
    text = text:gsub('<span class=".-">(.-)</span>', '%1')
    text = text:gsub('<Panel class=\"AbilityPropertyIcon StatDesc_TechPower\">', '{{Icon/Purple|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}')
    text = text:gsub('</Panel>', '')
    
    -- Process panel tags
	text = text:gsub('<Panel class=\"AbilityPropertyIcon prop_(.-)\">', function(key)
	    local replacement = replacements[key]
	    if replacement then
	        local label_color = replacement.label_color or 'inherit'
	        local icon = replacement.icon or ''
	        local link = replacement.link or ''
	        -- pre-process string, then check to omit link if the string is empty
	        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
	        
	        if translated == '' then
	            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
	        else
	            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>', 
	                label_color, 
	                icon, 
	                link, 
	                translated)
	        end
	    else
	        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
	    end
	end)
	    
    -- Process citadel_inline_attribute tags
	text = text:gsub("{g:citadel_inline_attribute:'(.-)'}", function(key)
	    local replacement = replacements[key]
	    if replacement then
	        local label_color = replacement.label_color or 'inherit'
	        local icon = replacement.icon or ''
	        local link = replacement.link or ''
	        -- pre-process string, then check to omit link if the string is empty
	        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
	        
	        if translated == '' then
	            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
	        else
	            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>', 
	                label_color, 
	                icon, 
	                link, 
	                translated)
	        end
	    else
	        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
	    end
	end)

    KEYBINDS = {
	    Attack = '{{Mouse|1}}',
	    ADS = '{{Mouse|2}}',
	    AltCast = '{{Mouse|3}}',
	    Reload = 'R',
	    Roll = 'Shift',
	    Mantle = 'Space',
	    Crouch = 'Ctrl',
	    AbilityMelee = 'Melee',
	    Ability1 = '1',
	    Ability2 = '2',
	    Ability3 = '3',
	    Ability4 = '4',
	    MoveDown = 'Down',
	    MoveForward = 'Forward',
    }
    
    -- Process citadel_binding/citadel_keybind tags to replace with keybind strings/templates
	text = text:gsub("{g:citadel_binding:'(.-)'}", function(key)
	    local replacement = KEYBINDS[key]
	    if replacement then
	        return replacement
	    else
	        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing keybind - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
	    end
	end)
	
	text = text:gsub("{g:citadel_keybind:'(.-)'}", function(key)
		local replacement = KEYBINDS[key]
	    if replacement then
	        return replacement
	    else
	        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing keybind - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
	    end
	end)

	return frame:preprocess(text)
end


function p.get_string(key, lang_code_override, fallback_str, remove_var_index, item_name)
    -- Get frame object (either passed directly or via first argument)
    local frame
    if type(key) == "table" and key.args then
        frame = key
        key = frame.args[1]
        lang_code_override = frame.args["lang_code_override"]
        fallback_str = frame.args["fallback_str"]
        remove_var_index = frame.args["remove_var_index"]
        item_name = frame.args["item_name"]
    else
        frame = mw.getCurrentFrame()
    end

    -- Determine lang_code if not overridden
    local lang_code = lang_code_override
    if (lang_code == '' or lang_code == nil) then
        lang_code = p.get_lang_code()
    end

    -- Retrieve lang data
    local data = get_lang_file(lang_code)
    if (data == nil) then
        return string.format("Lang code '%s' does not have a json file", lang_code)    
    end
    
    -- Localize
    local label = data[KEY_OVERRIDES[key] or key]

	-- Some labels use key with "_postvalue_label" instead, so check this if previous search failed
	if label == nil and key ~= nil and key:sub(-6) == '_label' then
	    local postvalue_key = key:sub(1, -7) .. '_postvalue_label'
	    label = data[postvalue_key]
	end
	
    if (label == nil) then
        -- Apply fallback (without HTML processing for fallback)
        local fallback_tooltip = frame:expandTemplate{title = "MissingValveTranslationTooltip"}
        local fallback
        if (fallback_str == 'en') then
            fallback = p.get_string(key, 'en', key .. fallback_tooltip, remove_var_index, item_name)
        elseif fallback_str == 'dictionary' then
            return dictionary_module.translate(key, lang_code_override)
        elseif fallback_str ~= nil then
            fallback = fallback_str
        else
            return ''
        end
        return fallback .. fallback_tooltip
    end
    
    -- Always retrieve the last parameter if string contains "|"
    if type(label) == "string" and label:find("|") then
        local parts = mw.text.split(label, "|")
        label = parts[#parts]
    end
    
   -- Remove hashtags
   if type(label) == "string" and label:find("#") then
        local parts = mw.text.split(label, "#")
        label = parts[#parts]
    end

    -- Apply remove_var
    if (remove_var_index ~= nil) then 
        label = util_module.remove_var(label, remove_var_index)
    end
    
    -- Process variables if item_name is provided
    if item_name and item_name ~= '' then
        local item_data_module = require('Module:ItemData')
        label = label:gsub("{s:([^}]+)}", function(variable_name)
            -- Get the value from ItemData
            local value = item_data_module.get_prop({args = {item_name, variable_name}})
            
            -- If value exists, remove non-number symbols (but keep + and -)
            if value then
                value = value:gsub("[^0-9+-]", "")
                -- Return empty string if nothing left, otherwise return the filtered value
                return value ~= "" and value or variable_name
            else
                return variable_name -- Fallback to variable name if value not found
            end
        end)
    end
    
    -- Process HTML and return as "raw" HTML
    label = process_newlines(label)
    label = process_html_tags(label, frame)
    -- Create HTML object for safe output
    local html = mw.html.create()
    html:wikitext(label)
    return tostring(html)
end

-- Search for a localized string using its English label
p.search_string = function(frame)
    local label = frame.args[1]
    local lang_code_override = frame.args[2]

    local result = p._search_string(label, lang_code_override)
    result = process_newlines(result)
    return result
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 = p.get_lang_code()
    end

    -- Load the language files
    local data_en = get_lang_file('en')  -- English data
    local data_lang = get_lang_file(lang_code)  -- Target language data

    if (data_lang == nil) then
        error("Lang code '%s' does not have a json file", lang_code)    
    end
    
    -- Search for the key in the English data
    local key = nil
    for k, v in pairs(data_en) do
        if v == label then
            key = k  -- Find the key corresponding to the label
            break
        end
    end

    -- Default to input label if localized string is not found
    if (key == nil) then
        return label
    end
    if (data_lang[key] == nil) then
        return label
    end

    return data_lang[key]
end

p.get_lang_code = function()
    local title = mw.title.getCurrentTitle()
    local lang_code = title.fullText:match(".*/(.*)$")
    
    if lang_code == nil or lang_codes_set[lang_code] == nil then
        return 'en'    
    end
        
    return lang_code
end

return p