Module:Icon: Difference between revisions

LVL (talk | contribs)
Fix ability link priority: prefer abilities linked to heroes over duplicate/orphan entries with same name
LVL (talk | contribs)
mNo edit summary
 
(33 intermediate revisions by 6 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


-- Load language codes
local iconDataRaw = mw.loadJsonData("Data:ResourceLookup.json")
local iconData2 = mw.loadJsonData("Data:ResourceLookup2.json")
local dictionary = mw.loadJsonData("Data:Dictionary")
 
local iconData = {}
 
for k, v in pairs(iconDataRaw) do
    iconData[k] = v
end
 
for k, v in pairs(iconData2) do
    iconData[k] = v
end
 
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local lang_module = require("Module:Lang")
local table_data = require("Module:Icon/data")


-- Cache for compiled icon data (persists during page render)
local cachedLangCode
local iconDataCache = nil
local cachedLangCode, cachedLangData


-- Heroes/Items that don't have images uploaded yet
local linkOverrides = table_data.linkOverrides
local missingImageOverrides = {
local legacyItems = table_data.legacyItems
    -- Unreleased Heroes
local missingImageOverrides = table_data.missingImageOverrides
    ["boho"] = "Module-Icon missing.png",
local aliases = table_data.aliases
    ["druid"] = "Module-Icon missing.png",
local renames = table_data.renames
    ["fortuna"] = "Module-Icon missing.png",
    ["graf"] = "Module-Icon missing.png",
    ["gunslinger"] = "Module-Icon missing.png",
    ["skyrunner"] = "Module-Icon missing.png",
    ["swan"] = "Module-Icon missing.png",
    ["thumper"] = "Module-Icon missing.png",
    -- Add any others that show red links
}


-- Build unified lookup table from bot-uploaded JSONs
local function getLangCode()
local function buildIconData()
     if cachedLangCode then
     if iconDataCache then
         return cachedLangCode
         return iconDataCache
     end
     end
   
 
    local lookup = {}
     local title = mw.title.getCurrentTitle().fullText
   
     local subpageLang = title:match("/([^/]+)$")
    -- Load bot data
     local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
     local heroData = mw.loadJsonData("Data:HeroData.json")
 
     local abilityData = mw.loadJsonData("Data:AbilityData.json")
     cachedLangCode = langCode
     local itemData = mw.loadJsonData("Data:ItemData.json")
     return langCode
      
end
     -- Track which hero owns which ability (for linking)
 
    local abilityToHero = {}
-- Infer image path from name, with override support for missing images
   
local function getImage(iconInfo, args)
    -- Process Heroes
    local lowerName = iconInfo.name:lower()
    for heroKey, hero in pairs(heroData) do
 
        if type(hero) == "table" and hero.Name then
    if args.image then
            local name = hero.Name
        return args.image
            local lowerName = name:lower()
    end
           
 
            -- Check for missing image override
    return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
            local imageName = missingImageOverrides[lowerName] or (name .. ".png")
end
           
 
            lookup[lowerName] = {
-- Get the translated name
                name = name,
local function getTranslatedName(iconInfo, langCode)
                key = heroKey,
    if langCode == "en" then
                link = name,
        return iconInfo.name
                image = imageName,
                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
                        }
                    end
                end
            end
        end
     end
     end
      
 
-- Process Abilities
     -- 1. Direct key
     for abilityKey, ability in pairs(abilityData) do
     if iconInfo.key and iconInfo.key ~= "" then
        if type(ability) == "table" and ability.Name then
        local translated = lang_module.get_string(iconInfo.key, langCode, iconInfo.name
            local name = ability.Name
        )
            local lowerName = name:lower()
 
            local parentInfo = abilityToHero[abilityKey]
        if type(translated) == "string" then
           
            return (translated:gsub("<[^>]+>", ""))
            -- If we already have this ability name, prefer the one linked to a hero
            if lookup[lowerName] and lookup[lowerName].type == "ability" then
                local existingHasParent = lookup[lowerName].link ~= lookup[lowerName].name
                local newHasParent = parentInfo ~= nil
               
                -- Skip if existing is linked to hero but this one isn't (prevents orphans from overwriting real ones)
                if existingHasParent and not newHasParent then
                    -- Skip this orphan ability
                else
                    -- Overwrite: new has parent and existing doesn't, or both/neither have parents
                    local link = parentInfo and parentInfo.heroName or name
                    lookup[lowerName] = {
                        name = name,
                        key = abilityKey,
                        link = link,
                        image = name .. ".png",
                        type = "ability",
                        class = "theme"
                    }
                end
            else
                -- No existing entry, add normally
                local link = parentInfo and parentInfo.heroName or name
                lookup[lowerName] = {
                    name = name,
                    key = abilityKey,
                    link = link,
                    image = name .. ".png",
                    type = "ability",
                    class = "theme"
                }
            end
         end
         end
        return iconInfo.name
     end
     end
   
 
     -- Process Items
     -- 2. Dictionary lookup
     for itemKey, item in pairs(itemData) do
     if iconInfo.key_dictionary and iconInfo.key_dictionary ~= "" then
         if type(item) == "table" and item.Name then
        local entry = dictionary[iconInfo.key_dictionary]
             local name = item.Name
 
            local lowerName = name:lower()
         if type(entry) == "table" then
           
             return entry[langCode]
            lookup[lowerName] = {
                 or entry.en
                 name = name,
                 or iconInfo.name
                 key = itemKey,
                link = name,
                image = name .. ".png",
                type = "item"
            }
         end
         end
     end
     end
   
 
     iconDataCache = lookup
     -- No valid key > fallback
     return lookup
     return iconInfo.name
end
end


-- Get icon data by name (with alias support)
-- Infer link from type and available fields
local function getIconData(iconName)
local function getLink(iconInfo, langCode)
     local data = buildIconData()
     if iconInfo.type == "ability" then
    local lowerName = iconName:lower()
        local hero = iconInfo.hero_name or iconInfo.name
   
       
    -- Check main lookup
        -- Get the translated name for the specific ability
    if data[lowerName] then
        local abilityName = getTranslatedName(iconInfo, langCode)
         return data[lowerName], nil
       
        -- Format for URL: replace spaces with underscores
        local anchor = abilityName:gsub(" ", "_")
       
         return hero .. "#" .. anchor
     end
     end
   
 
    -- Check aliases (for edge cases like "doorman" -> "the doorman")
     return linkOverrides[iconInfo.name] or iconInfo.name
    local aliases = {
        ["doorman"] = "the doorman",
        ["mo and krill"] = "mo & krill",
        ["mo"] = "mo & krill",
        ["krill"] = "mo & krill",
        ["curse"] = "cursed relic",
        ["debuff remover"] = "dispel magic"
     }
   
    if aliases[lowerName] then
        return data[aliases[lowerName]], nil
    end
   
    return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
end
end


-- Language handling
-- Returns the canonical display name for a given user input
local function getLangData()
-- Used by templates to store the correct casing in data attributes
     if cachedLangData then
-- Normalizes casing so exact string matches work reliably in Lua modules
         return cachedLangCode, cachedLangData
function p.getCanonicalName(frame)
    local name = frame.args[1] or ""
    local lowerName = name:lower()
 
     if renames[lowerName] then
        lowerName = renames[lowerName]
    elseif aliases[lowerName] then
         lowerName = aliases[lowerName]
     end
     end


     local title = mw.title.getCurrentTitle().fullText
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]
    local subpageLang = title:match("/([^/]+)$")
    local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"


     local langData = {}
     -- If unknown, return original input
     if langCode ~= "en" then
     if not iconInfo then
         local success, data = pcall(function()
         return name
            return mw.loadJsonData("Data:Lang " .. langCode .. ".json")
        end)
        if success and type(data) == "table" then
            langData = data
        end
     end
     end


     cachedLangCode = langCode
-- Quick hack: ability names are used by MediaWiki:Gadget-infobox-tooltip.js and expected to be localized
     cachedLangData = langData
     local langCode = getLangCode()
 
    if iconInfo.type == "ability" then
        return getTranslatedName(iconInfo, langCode)
     end


     return langCode, langData
     return iconInfo.name
end
end


-- Main render function
local function render(name, args)
function p.render(frame)
     local originalName = name
     local args = frame:getParent().args
    local lowerName = name:lower()
     local name = args[1] or ""
    args = args or {}
     local customText = args.l1 or ""
 
      
    if renames[lowerName] then
    -- Set Defaults
        lowerName = renames[lowerName]
    elseif aliases[lowerName] then
        lowerName = aliases[lowerName]
    end
 
    -- Lookup live data > legacy fallback
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]
 
    -- Throw error if not found
    if not iconInfo then
        return string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", name)
    end
 
     local customText = args.l1 ~= "" and args.l1 or nil
     local customLink = args.link ~= "" and args.link or nil
     local size = args.size or "20px"
     local size = args.size or "20px"
     local noLink = args["no-link"] == "true"
     local noLink = args["no-link"] == "true"
    local noFile = args["no-file"] == "true"
     local iconOnly = args["icon-only"] == "true"
     local iconOnly = args["icon-only"] == "true"


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


    -- Get icon data automatically from JSON
     local langCode = getLangCode()
     local iconData, err = getIconData(name)
    if not iconData then
        return "Error: " .. err
    end


    local langCode, langData = getLangData()
     local displayName
   
     if customText then
    -- Determine display name
     local displayName = ""
     if customText ~= "" then
         displayName = customText
         displayName = customText
     elseif iconData.key and langData[iconData.key] then
     elseif renames[name:lower()] then
         displayName = langData[iconData.key]
        -- Preserve original if renamed
         displayName = originalName
     else
     else
         displayName = iconData.name
         displayName = getTranslatedName(iconInfo, langCode)
    end
 
    local baseLink = customLink or getLink(iconInfo, langCode)
    local fragment = ""
 
    -- Split the fragment (#Abilities) from the base link
    if baseLink:find("#") then
        local parts = mw.text.split(baseLink, "#", true)
        baseLink = parts[1]
        fragment = "#" .. (parts[2] or "")
     end
     end


    -- Localize link if on translated subpage
     local link = baseLink
    local baseLink = iconData.link or ""
     local link = (langCode ~= "en" and baseLink ~= "") and (baseLink .. "/" .. langCode) or baseLink


     -- Style logic (only abilities get theme class by default)
    if not customLink and langCode ~= "en" and baseLink ~= "" then
     local class = iconData.class or ""
        link = baseLink .. "/" .. langCode
     local extra = (class:find("invert", 1, true) and " filter:invert(1);" or "")
    end
 
     -- Reattach fragment at the end
     link = link .. fragment
 
     -- Add CSS class for abilities
     local style
     local style
     if class:find("theme", 1, true) then
     if iconInfo.type == "ability" then
         style = 'class="module-icon-ability" style="position:relative; bottom:2px;' .. extra .. '"'
         style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
     else
     else
         style = 'style="position:relative; bottom:2px;' .. extra .. '"'
         style = 'class="c-icon" style="position:relative; bottom:2px;"'
     end
     end


    -- Generate icon HTML
     local imageLink = noLink and "" or link
     local imageLink = noLink and "" or link
     local iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, iconData.image, size, imageLink)
     local iconHtml = ""
 
    if not noFile then
        local image = getImage(iconInfo, args)
        iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, image, size, imageLink)
    end


     if iconOnly or displayName == "" then
     if iconOnly or displayName == "" then
Line 251: Line 222:


     local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)
     local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)
     return '<span style="white-space:nowrap;">' .. iconHtml .. " " .. textHtml .. '</span>'
 
     if iconHtml ~= "" then
        return '<span style="white-space:nowrap;">' .. iconHtml .. " " .. textHtml .. '</span>'
    else
        return textHtml
    end
end
 
-- Template entrypoint
function p.render(frame)
    local args = frame:getParent().args
    local name = args[1] or ""
 
    return render(name, args)
end
 
-- Module entrypoint
function p._render(name, args)
    return render(name, args)
end
end


return p
return p