Module:Sandbox/LVL: Difference between revisions

Jump to navigation Jump to search
LVL (talk | contribs)
No edit summary
LVL (talk | contribs)
No edit summary
Line 1: Line 1:
local p = {}
local p = {}
local lang = require "Module:Lang"
local util_module = require "Module:Utilities"
local data = mw.loadJsonData("Data:Monster_Domosed/ItemData.json")


-- Load language codes
-- Get raw value, CSS class, and LocTokenOverride for a property
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local function get_raw_value(item_name, prop)
    local item = data[item_name] or nil
    if not item then
        for k,v in pairs(data) do
            if v.Name == item_name then item = v; break end
        end
    end
    if not item then return nil, nil, nil end
 
    local raw_prop = item[prop]
    if type(raw_prop) == "table" then
        local loc_override = raw_prop.LocTokenOverride or prop
        return raw_prop.Value, raw_prop.CSSClass, loc_override
    else
        return raw_prop, nil, prop
    end
end


-- Cache for compiled icon data (persists during page render)
-- Check if property exists in language data
local iconDataCache = nil
local function is_property_in_lang_data(prop)
local cachedLangCode, cachedLangData
    local keys = {
        prop .. "_label",
        prop .. "_Label",
        prop .. "_prefix",
        prop .. "_Prefix",
        prop .. "_postfix",
        prop .. "_Postfix",
        prop .. "_postvalue_label",
        prop .. "_Postvalue_Label"
    }


-- Build unified lookup table from bot-uploaded JSONs
    for _, key in ipairs(keys) do
local function buildIconData()
        if lang.get_string(key) then
    if iconDataCache then
            return true
         return iconDataCache
         end
     end
     end
      
 
    local lookup = {}
     return false
   
end
    -- Load bot data
 
    local heroData = mw.loadJsonData("Data:HeroData.json")
-- Get item by name, ignoring disabled if possible
    local abilityData = mw.loadJsonData("Data:AbilityData.json")
local function get_json_item(name)
    local itemData = mw.loadJsonData("Data:ItemData.json")
     for _, v in pairs(data) do
   
         if v.Name == name and (v.IsDisabled == false or v.IsDisabled == nil) then
    -- Track which hero owns which ability (for linking)
             return v
    local abilityToHero = {}
   
    -- Process Heroes
     for heroKey, hero in pairs(heroData) do
         if type(hero) == "table" and hero.Name then
            local name = hero.Name
            local lowerName = name:lower()
           
            lookup[lowerName] = {
                name = name,
                key = heroKey,
                link = name,
                image = name .. ".png",
                type = "hero"
            }
           
            -- Map abilities to parent hero
            if hero.BoundAbilities then
                for slot, ability in pairs(hero.BoundAbilities) do
                    if ability.Key then
                        abilityToHero[ability.Key] = {
                            heroName = name,
                            abilityName = ability.Name -- Use name from hero data if available
                        }
                    end
                end
             end
         end
         end
     end
     end
    for _, v in pairs(data) do
        if v.Name == name then return v end
    end
    return nil
end
-- Format stat value for Infobox, handling prefix/postfix, CSS, scaling, and conditional
local function format_stat(item_name, prop, skip_label, debug, allow_conditional)
    if allow_conditional == nil then allow_conditional = true end
    if prop == "AbilityCooldown" and not skip_label then
        return nil
    end
    local value, css, loc_key = get_raw_value(item_name, prop)
    if value == nil or value == 0 or value == "" then
    return nil
    end
    -- Convert string like "0m" or "0.0" to number for zero check
    local numeric_val = tonumber(tostring(value):match("^-?%d+%.?%d*")) or 0
    if numeric_val == 0 then
        return nil
    end
    -- Skip if property has no lang data
    local label_key = loc_key:gsub("^#", "")
if not (
    lang.get_string(label_key.."_label") or
    lang.get_string(label_key.."_Label") or
    lang.get_string(label_key.."_prefix") or
    lang.get_string(label_key.."_Prefix") or
    lang.get_string(label_key.."_postfix") or
    lang.get_string(label_key.."_Postfix") or
    lang.get_string(label_key.."_postvalue_label") or
    lang.get_string(label_key.."_Postvalue_Label")
) then
    return nil
end
if debug == 2 then
    local label_val, prefix_val, postfix_val
    -- determine which label key worked
    if lang.get_string(label_key.."_label") then
        label_val = label_key.."_label"
    elseif lang.get_string(label_key.."_Label") then
        label_val = label_key.."_Label"
    else
        label_val = "(none)"
    end
    -- determine which prefix key worked
    if lang.get_string(label_key.."_prefix") then
        prefix_val = label_key.."_prefix"
    elseif lang.get_string(label_key.."_Prefix") then
        prefix_val = label_key.."_Prefix"
    else
        prefix_val = "(none)"
    end
    -- determine which postfix key worked
    if lang.get_string(label_key.."_postfix") then
        postfix_val = label_key.."_postfix"
    elseif lang.get_string(label_key.."_Postfix") then
        postfix_val = label_key.."_Postfix"
    else
        postfix_val = "(none)"
    end
    return string.format(
        "raw invoke key: %s | raw value: %s | label key used: %s | prefix key used: %s | postfix key used: %s",
        loc_key,
        tostring(value),
        label_val,
        prefix_val,
        postfix_val
    )
end
    if prop == "BuildUpPerShot" then label_key = "BuildupPerShot"
    elseif prop == "DotDuration" then label_key = "DOTDuration"
    end
-- Helper function: get first non-nil string from variants
local function first_non_nil(...)
    for i = 1, select("#", ...) do
        local v = select(i, ...)
        if v and v ~= "" then return v end
    end
    return ""
end
-- Get actual label, prefix, postfix with fallbacks
local label = skip_label and "" or first_non_nil(
    lang.get_string(label_key.."_label"),
    lang.get_string(label_key.."_Label")
)
local prefix = first_non_nil(
    lang.get_string(label_key.."_prefix"),
    lang.get_string(label_key.."_Prefix")
)
local postfix = first_non_nil(
    lang.get_string(label_key.."_postfix"),
    lang.get_string(label_key.."_Postfix")
)
      
      
     -- Process Abilities
     if prefix == "" and postfix == "" and label == "" then
    for abilityKey, ability in pairs(abilityData) do
    return nil
        if type(ability) == "table" and ability.Name then
end
            local name = ability.Name
 
            local lowerName = name:lower()
    local val_str = tostring(value)
            local parentInfo = abilityToHero[abilityKey]
 
           
    ------------------------------------------------------------------
            -- Determine link (fallback to ability name if no parent found)
    -- CSS-specific formatting
            local link = parentInfo and parentInfo.heroName or name
    ------------------------------------------------------------------
           
    if css == "move_speed" or css == "sprint_speed" then
            lookup[lowerName] = {
        if postfix == "" or not postfix:find("%%") then
                name = name,
            val_str = val_str:gsub("/s", "") .. "/s"
                key = abilityKey,
        end
                link = link,
        local num = tonumber(val_str:match("^-?%d+%.?%d*"))
                image = name .. ".png",
        if num and num > 0 and not val_str:find("^%+") then
                type = "ability",
             val_str = "+" .. val_str
                class = "theme" -- Abilities get theme class by default
             }
         end
         end
     end
     end
      
 
     -- Process Items
     ------------------------------------------------------------------
     for itemKey, item in pairs(itemData) do
     -- Apply prefix
         if type(item) == "table" and item.Name then
     ------------------------------------------------------------------
             local name = item.Name
    if prefix ~= "" then
             local lowerName = name:lower()
         if prefix == "{s:sign}" then
           
             local num = tonumber(val_str:match("^-?%d+%.?%d*"))
            lookup[lowerName] = {
             if num and num >= 0 and not val_str:find("^%+") then
                 name = name,
                 val_str = "+"..val_str
                key = itemKey,
            end
                link = name,
        else
                image = name .. ".png",
            val_str = prefix..val_str
                type = "item"
            }
         end
         end
     end
     end
      
 
     iconDataCache = lookup
    ------------------------------------------------------------------
     return lookup
    -- Append postfix
    ------------------------------------------------------------------
-- Append postfix
if prop ~= "BuildUpPerShot" and postfix ~= "" and not val_str:find(postfix, 1, true) then
    if postfix == "HP/s" then
        val_str = val_str .. " " .. postfix
    else
        val_str = val_str .. postfix
    end
end
 
    ------------------------------------------------------------------
    -- Append scaling template {{Boon}} or {{Ss}}
    ------------------------------------------------------------------
    local item = get_json_item(item_name)
    local raw_prop = item[prop]
    if type(raw_prop) == "table" and raw_prop.Scale and raw_prop.Scale.Value ~= 0 then
        local scale_val = util_module.round_to_sig_fig(raw_prop.Scale.Value, 2)
        local template_name = (raw_prop.Scale.Type == "power_increase") and "Boon" or "Ss"
        val_str = val_str .. " " .. mw.getCurrentFrame():expandTemplate{ title = template_name, args = { scale_val } }
    end
 
    ------------------------------------------------------------------
    -- Append label
    ------------------------------------------------------------------
    if label ~= "" then
        val_str = val_str.." "..label
    end
 
    ------------------------------------------------------------------
     -- Append (Conditional) ONLY if allowed
     ------------------------------------------------------------------
if allow_conditional
    and type(raw_prop) == "table"
    and (raw_prop.UsageFlags == "ConditionallyApplied" or raw_prop.UsageFlags == "ConditionallyEnemyApplied")
then
    local conditional_text = lang.get_string("Citadel_Shop_ConditionalAttribute")
    val_str = val_str .. string.format(' <span style="color:#C0C0C0">(%s)</span>', conditional_text)
end
 
     return val_str
end
end


-- Get icon data by name (with alias support)
-- Special formatting for StatusEffect
local function getIconData(iconName)
local function format_property_for_infobox(item_name, prop, skip_label, debug)
     local data = buildIconData()
     if prop:match("^StatusEffect") then
    local lowerName = iconName:lower()
        return string.format(
   
            "[[{{#invoke:Lang|get_string|Citadel_%s}}]] [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]",
    -- Check main lookup
            prop
     if data[lowerName] then
        )
         return data[lowerName], nil
     else
         return format_stat(item_name, prop, skip_label, debug, true)
     end
     end
   
end
    -- Check aliases (for edge cases like "doorman" -> "the doorman")
 
     local aliases = {
function p.fill_tooltip(frame)
        ["doorman"] = "the doorman",
     local item_name = frame.args[1]
        ["mo and krill"] = "mo & krill",
    if not item_name then return "Item name missing" end
        ["mo"] = "mo & krill",
 
        ["krill"] = "mo & krill",
    local debug = tonumber(frame.args.debug) or 0
         ["curse"] = "cursed relic",
    local item = get_json_item(item_name)
         ["debuff remover"] = "dispel magic"
    if not item then return "Item not found" end
 
    local ItemData = require("Module:ItemData")
    local lines = {
         "{{Infobox item",
         "| item_name = " .. item_name
     }
     }
      
 
     if aliases[lowerName] then
     ----------------------------------------------------------------------
         return data[aliases[lowerName]], nil
     -- Check if AbilityCooldown or AbilityChargeUpTime exists in sections
    ----------------------------------------------------------------------
    local cooldown_found_in_sections = false
    local chargeup_found_in_sections = false
    for _, section in ipairs(item.TooltipSections or {}) do
        for _, entry in ipairs(section.Entries or {}) do
            for _, prop in ipairs(entry.Properties or {}) do
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
                if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
            end
            for _, prop in ipairs(entry.ImportantProperties or {}) do
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
                if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
            end
         end
     end
     end
   
    return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
end


-- Language handling (unchanged from your original)
    ----------------------------------------------------------------------
local function getLangData()
    -- Innate stats
    if cachedLangData then
    ----------------------------------------------------------------------
         return cachedLangCode, cachedLangData
    local innate_index = 1
    for _, section in ipairs(item.TooltipSections or {}) do
        if section.Type == "EArea_Innate" then
            for _, entry in ipairs(section.Entries or {}) do
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" then
                        local val = format_stat(item_name, prop, false, debug, false)
                        if val then
                            table.insert(lines, string.format("| item_stat%d = %s", innate_index, val))
                            innate_index = innate_index + 1
                        end
                    end
                end
            end
         end
     end
     end


     local title = mw.title.getCurrentTitle().fullText
    ----------------------------------------------------------------------
    local subpageLang = title:match("/([^/]+)$")
    -- Passive stats
    local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
    ----------------------------------------------------------------------
     local passive_counter = 1
    for _, section in ipairs(item.TooltipSections or {}) do
        if section.Type ~= "EArea_Innate" and section.Type ~= "EArea_Active" then
            for _, entry in ipairs(section.Entries or {}) do
                local loc = entry.LocString and entry.LocString:gsub("#","") or nil
                if loc then
                    table.insert(
                        lines,
                        string.format(
                            "| passive%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
                            passive_counter, loc, item_name
                        )
                    )
                end
 
                local stat_index = 1
                local seen_props = {}


    local langData = {}
                -- ImportantProperties
    if langCode ~= "en" then
                for _, prop in ipairs(entry.ImportantProperties or {}) do
        local success, data = pcall(function()
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" and is_property_in_lang_data(prop) and not seen_props[prop] then
            return mw.loadJsonData("Data:Lang " .. langCode .. ".json")
                        local val = format_property_for_infobox(item_name, prop, false, debug)
        end)
                        if val then
        if success and type(data) == "table" then
                            table.insert(lines, string.format("| passive%d_stat%d = %s", passive_counter, stat_index, val))
             langData = data
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end
 
                -- Properties
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_property_for_infobox(item_name, prop, false, debug)
                        if val then
                            table.insert(lines, string.format("| passive%d_stat%d = %s", passive_counter, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end
 
                -- AbilityCooldown
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop == "AbilityCooldown" then
                        local cd = format_stat(item_name, prop, true, debug)
                        if cd then
                            table.insert(lines, string.format("| passive%d_cooldown = %s", passive_counter, cd))
                        end
                    end
                end
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop == "AbilityCooldown" then
                        local cd = format_stat(item_name, prop, true, debug)
                        if cd then
                            table.insert(lines, string.format("| passive%d_cooldown = %s", passive_counter, cd))
                        end
                    end
                end
 
                -- AbilityChargeUpTime
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop == "AbilityChargeUpTime" then
                        local cu = format_stat(item_name, prop, true, debug)
                        if cu then
                            table.insert(lines, string.format("| passive%d_chargeup = %s", passive_counter, cu))
                        end
                    end
                end
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop == "AbilityChargeUpTime" then
                        local cu = format_stat(item_name, prop, true, debug)
                        if cu then
                            table.insert(lines, string.format("| passive%d_chargeup = %s", passive_counter, cu))
                        end
                    end
                end
 
                passive_counter = passive_counter + 1
             end
         end
         end
     end
     end


     cachedLangCode = langCode
     ----------------------------------------------------------------------
     cachedLangData = langData
    -- Active stats
    ----------------------------------------------------------------------
    local active_index = 1
     for _, section in ipairs(item.TooltipSections or {}) do
        if section.Type == "EArea_Active" then
            for _, entry in ipairs(section.Entries or {}) do
                if entry.LocString then
                    table.insert(
                        lines,
                        string.format(
                            "| active%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
                            active_index,
                            entry.LocString:gsub("#",""),
                            item_name
                        )
                    )
                end
 
                local stat_index = 1
                local seen_props = {}
 
                -- ImportantProperties
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_property_for_infobox(item_name, prop, false, debug)
                        if val then
                            table.insert(lines, string.format("| active%d_stat%d = %s", active_index, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end
 
                -- Properties
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_property_for_infobox(item_name, prop, false, debug)
                        if val then
                            table.insert(lines, string.format("| active%d_stat%d = %s", active_index, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end


    return langCode, langData
                -- AbilityCooldown
end
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop == "AbilityCooldown" then
                        local cd = format_stat(item_name, prop, true, debug)
                        if cd then
                            table.insert(lines, string.format("| active%d_cooldown = %s", active_index, cd))
                        end
                    end
                end
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop == "AbilityCooldown" then
                        local cd = format_stat(item_name, prop, true, debug)
                        if cd then
                            table.insert(lines, string.format("| active%d_cooldown = %s", active_index, cd))
                        end
                    end
                end


-- Main render function (updated to handle missing keys gracefully)
                -- AbilityChargeUpTime
function p.render(frame)
                for _, prop in ipairs(entry.Properties or {}) do
    local args = frame:getParent().args
                    if prop == "AbilityChargeUpTime" then
    local name = args[1] or ""
                        local cu = format_stat(item_name, prop, true, debug)
    local customText = args.l1 or ""
                        if cu then
   
                            table.insert(lines, string.format("| active%d_chargeup = %s", active_index, cu))
    -- Set Defaults
                        end
    local size = args.size or "20px"
                    end
    local noLink = args["no-link"] == "true"
                end
    local iconOnly = args["icon-only"] == "true"
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop == "AbilityChargeUpTime" then
                        local cu = format_stat(item_name, prop, true, debug)
                        if cu then
                            table.insert(lines, string.format("| active%d_chargeup = %s", active_index, cu))
                        end
                    end
                end


    -- Scan unnamed parameters
                 active_index = active_index + 1
    for k, v in pairs(args) do
        if type(k) == "number" and k > 1 then
            local val = mw.text.trim(v)
           
            if val == "icon-only" then
                 iconOnly = true
            elseif val == "no-link" then
                noLink = true
            elseif val:match("^%d+px$") then
                size = val
             end
             end
         end
         end
     end
     end


     -- Get icon data automatically from JSON
     ----------------------------------------------------------------------
     local iconData, err = getIconData(name)
     -- Global fallback for AbilityCooldown and AbilityChargeUpTime
     if not iconData then
    ----------------------------------------------------------------------
         return "Error: " .. err
    if not cooldown_found_in_sections and item.AbilityCooldown then
        local cd = format_stat(item_name, "AbilityCooldown", true, debug)
        if cd then
            table.insert(lines, "| item_stat_cooldown = " .. cd)
        end
    end
 
     if not chargeup_found_in_sections and item.AbilityChargeUpTime then
         local cu = format_stat(item_name, "AbilityChargeUpTime", true, debug)
        if cu then
            table.insert(lines, "| item_stat_chargeup = " .. cu)
        end
     end
     end


     local langCode, langData = getLangData()
     ----------------------------------------------------------------------
      
     -- Components
     -- Determine display name
     ----------------------------------------------------------------------
     local displayName = ""
     for i = 1, 2 do
    if customText ~= "" then
        local comp = ItemData.get_component_name {
        displayName = customText
            args = { item_name, i }
    elseif iconData.key and langData[iconData.key] then
        }
        displayName = langData[iconData.key]
        if comp and comp ~= "" then
    else
            table.insert(lines, string.format("| component%d_name = %s", i, comp))
         displayName = iconData.name
         end
     end
     end


     -- Localize link if on translated subpage
     ----------------------------------------------------------------------
     local baseLink = iconData.link or ""
    -- Is Component Of
    local link = (langCode ~= "en" and baseLink ~= "") and (baseLink .. "/" .. langCode) or baseLink
    ----------------------------------------------------------------------
     for i = 1, 3 do
        local parent = ItemData.get_builds_into_name {
            args = { item_name, i }
        }
        if parent and parent ~= "" then
            table.insert(lines, string.format("| iscomponentof%d_name = %s", i, parent))
        end
    end


     -- Style logic (only abilities get theme class by default)
     ----------------------------------------------------------------------
     local class = iconData.class or ""
    -- Sounds (sound1, sound2, sound3, ...)
    local extra = (class:find("invert", 1, true) and " filter:invert(1);" or "")
     ----------------------------------------------------------------------
    local style
    for key, value in pairs(frame.args) do
    if class:find("theme", 1, true) then
        local index = tostring(key):match("^sound(%d+)$")
        style = 'class="module-icon-ability" style="position:relative; bottom:2px;' .. extra .. '"'
        if index and value ~= "" then
    else
            table.insert(lines, string.format("| sound%s = %s", index, value))
         style = 'style="position:relative; bottom:2px;' .. extra .. '"'
         end
     end
     end


     -- Generate icon HTML
     ----------------------------------------------------------------------
     local imageLink = noLink and "" or link
     table.insert(lines, "}}")
     local iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, iconData.image, size, imageLink)
     local output = table.concat(lines, "\n")


     if iconOnly or displayName == "" then
     if debug == 1 or debug == 2 then
         return iconHtml
         return output
     end
     end


     local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)
     return mw.getCurrentFrame():preprocess(output)
    return '<span style="white-space:nowrap;">' .. iconHtml .. " " .. textHtml .. '</span>'
end
end


return p
return p