Module:Icon: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
LVL (talk | contribs)
Testing fix for many icons on one page
LVL (talk | contribs)
mNo edit summary
 
(29 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 lang_codes = mw.loadJsonData("Data:LangCodes.json")
local iconData2 = mw.loadJsonData("Data:ResourceLookup2.json")
local dictionary = mw.loadJsonData("Data:Dictionary")


-- Cache for compiled icon data, split by type (persists during page render)
local iconData = {}
local heroCacheBuilt = false
local abilityCacheBuilt = false
local itemCacheBuilt = false
local iconDataCache = {}


local cachedLangCode, cachedLangData
for k, v in pairs(iconDataRaw) do
    iconData[k] = v
end


-- Cache for expensive existence checks (persists during page render)
for k, v in pairs(iconData2) do
local existenceCache = {}
    iconData[k] = v
end


-- Heroes/Items that don't have images uploaded yet
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local missingImageOverrides = {
local lang_module = require("Module:Lang")
    -- Unreleased Heroes
local table_data = require("Module:Icon/data")
    ["boho"] = "Module-Icon missing.png",
    ["druid"] = "Module-Icon missing.png",
    ["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 lookup table from bot-uploaded JSONs, only loading the relevant JSON per type
local cachedLangCode
-- to reduce CPU cost on pages with many icon calls
local function buildIconData(iconType)
    -- Only load hero data if not already built
    if iconType == "hero" and not heroCacheBuilt then
        local heroData = mw.loadJsonData("Data:HeroData.json")


        -- Process Heroes
local linkOverrides = table_data.linkOverrides
        for heroKey, hero in pairs(heroData) do
local legacyItems = table_data.legacyItems
            if type(hero) == "table" and hero.Name then
local missingImageOverrides = table_data.missingImageOverrides
                local name = hero.Name
local aliases = table_data.aliases
                local lowerName = name:lower()
local renames = table_data.renames


                -- Check for missing image override
local function getLangCode()
                local imageName = missingImageOverrides[lowerName] or (name .. ".png")
    if cachedLangCode then
        return cachedLangCode
    end


                iconDataCache[lowerName] = {
    local title = mw.title.getCurrentTitle().fullText
                    name = name,
    local subpageLang = title:match("/([^/]+)$")
                    key = heroKey,
    local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
                    link = name,
                    image = imageName,
                    type = "hero"
                }
            end
        end


        heroCacheBuilt = true
    cachedLangCode = langCode
    return langCode
end


    -- Only load ability data (and hero data for parent linking) if not already built
-- Infer image path from name, with override support for missing images
    elseif iconType == "ability" and not abilityCacheBuilt then
local function getImage(iconInfo, args)
        local heroData = mw.loadJsonData("Data:HeroData.json")
    local lowerName = iconInfo.name:lower()
        local abilityData = mw.loadJsonData("Data:AbilityData.json")


         -- Track which hero owns which ability (for linking)
    if args.image then
        local abilityToHero = {}
         return args.image
    end


        for heroKey, hero in pairs(heroData) do
    return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
            if type(hero) == "table" and hero.BoundAbilities then
end
                for slot, ability in pairs(hero.BoundAbilities) do
                    if ability.Key then
                        abilityToHero[ability.Key] = {
                            heroName = hero.Name,
                            abilityName = ability.Name
                        }
                    end
                end
            end
        end


        -- Process Abilities
-- Get the translated name
        for abilityKey, ability in pairs(abilityData) do
local function getTranslatedName(iconInfo, langCode)
            if type(ability) == "table" and ability.Name then
    if langCode == "en" then
                local name = ability.Name
        return iconInfo.name
                local lowerName = name:lower()
    end
                local parentInfo = abilityToHero[abilityKey]


                -- If we already have this ability name, prefer the one linked to a hero
    -- 1. Direct key
                if iconDataCache[lowerName] and iconDataCache[lowerName].type == "ability" then
    if iconInfo.key and iconInfo.key ~= "" then
                    local existingHasParent = iconDataCache[lowerName].link ~= iconDataCache[lowerName].name
        local translated = lang_module.get_string(iconInfo.key, langCode, iconInfo.name
                    local newHasParent = parentInfo ~= nil
        )


                    -- Skip if existing is linked to hero but this one isn't (prevents orphans from overwriting real ones)
        if type(translated) == "string" then
                    if existingHasParent and not newHasParent then
            return (translated:gsub("<[^>]+>", ""))
                        -- 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
                        iconDataCache[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
                    iconDataCache[lowerName] = {
                        name = name,
                        key = abilityKey,
                        link = link,
                        image = name .. ".png",
                        type = "ability",
                        class = "theme"
                    }
                end
            end
         end
         end


         abilityCacheBuilt = true
         return iconInfo.name
    end


     -- Only load item data if not already built
     -- 2. Dictionary lookup
     elseif iconType == "item" and not itemCacheBuilt then
     if iconInfo.key_dictionary and iconInfo.key_dictionary ~= "" then
         local itemData = mw.loadJsonData("Data:ItemData.json")
         local entry = dictionary[iconInfo.key_dictionary]


         -- Process Items
         if type(entry) == "table" then
        for itemKey, item in pairs(itemData) do
            return entry[langCode]
            if type(item) == "table" and item.Name then
                 or entry.en
                 local name = item.Name
                 or iconInfo.name
                 local lowerName = name:lower()
        end
    end


                iconDataCache[lowerName] = {
    -- No valid key > fallback
                    name = name,
    return iconInfo.name
                    key = itemKey,
end
                    link = name,
                    image = name .. ".png",
                    type = "item"
                }
            end
        end


         itemCacheBuilt = true
-- Infer link from type and available fields
local function getLink(iconInfo, langCode)
    if iconInfo.type == "ability" then
        local hero = iconInfo.hero_name or iconInfo.name
       
        -- Get the translated name for the specific ability
        local abilityName = getTranslatedName(iconInfo, langCode)
       
        -- Format for URL: replace spaces with underscores
         local anchor = abilityName:gsub(" ", "_")
       
        return hero .. "#" .. anchor
     end
     end
    return linkOverrides[iconInfo.name] or iconInfo.name
end
end


-- Get icon data by name and type (with alias support)
-- Returns the canonical display name for a given user input
local function getIconData(iconName, iconType)
-- Used by templates to store the correct casing in data attributes
     buildIconData(iconType)
-- Normalizes casing so exact string matches work reliably in Lua modules
     local lowerName = iconName:lower()
function p.getCanonicalName(frame)
     local name = frame.args[1] or ""
     local lowerName = name:lower()


     -- Check main lookup
     if renames[lowerName] then
     if iconDataCache[lowerName] then
        lowerName = renames[lowerName]
         return iconDataCache[lowerName], nil
     elseif aliases[lowerName] then
         lowerName = aliases[lowerName]
     end
     end


    -- Check aliases (for edge cases like "doorman" -> "the doorman")
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]
     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
    -- If unknown, return original input
         return iconDataCache[aliases[lowerName]], nil
     if not iconInfo then
         return name
     end
     end


    return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
-- Quick hack: ability names are used by MediaWiki:Gadget-infobox-tooltip.js and expected to be localized
end
    local langCode = getLangCode()


-- Language handling
     if iconInfo.type == "ability" then
local function getLangData()
         return getTranslatedName(iconInfo, langCode)
     if cachedLangData then
         return cachedLangCode, cachedLangData
     end
     end


     local title = mw.title.getCurrentTitle().fullText
     return iconInfo.name
     local subpageLang = title:match("/([^/]+)$")
end
     local langCode = (subpageLang and lang_codes[subpageLang]) and subpageLang or "en"
 
local function render(name, args)
     local originalName = name
    local lowerName = name:lower()
     args = args or {}


    local langData = {}
     if renames[lowerName] then
     if langCode ~= "en" then
         lowerName = renames[lowerName]
         local success, data = pcall(function()
    elseif aliases[lowerName] then
            return mw.loadJsonData("Data:Lang " .. langCode .. ".json")
         lowerName = aliases[lowerName]
        end)
         if success and type(data) == "table" then
            langData = data
        end
     end
     end


     cachedLangCode = langCode
     -- Lookup live data > legacy fallback
     cachedLangData = langData
     local iconInfo = iconData[lowerName] or legacyItems[lowerName]


     return langCode, langData
     -- Throw error if not found
end
    if not iconInfo then
        return string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", name)
    end


-- Main render function
     local customText = args.l1 ~= "" and args.l1 or nil
function p.render(frame)
     local customLink = args.link ~= "" and args.link or nil
     local args = frame:getParent().args
    local name = args[1] or ""
    local customText = args.l1 or ""
     local iconType = args.type or "item"
 
    -- Set Defaults
     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
Line 226: Line 170:
     end
     end


    -- Get icon data automatically from JSON, using type to limit which JSON is loaded
     local langCode = getLangCode()
     local iconData, err = getIconData(name, iconType)
 
     if not iconData then
    local displayName
         return "Error: " .. err
     if customText then
         displayName = customText
    elseif renames[name:lower()] then
        -- Preserve original if renamed
        displayName = originalName
    else
        displayName = getTranslatedName(iconInfo, langCode)
     end
     end


     local langCode, langData = getLangData()
     local baseLink = customLink or getLink(iconInfo, langCode)
    local fragment = ""


     -- Determine display name
     -- Split the fragment (#Abilities) from the base link
    local displayName = ""
     if baseLink:find("#") then
     if customText ~= "" then
         local parts = mw.text.split(baseLink, "#", true)
         displayName = customText
         baseLink = parts[1]
    elseif iconData.key and langData[iconData.key] then
         fragment = "#" .. (parts[2] or "")
         displayName = langData[iconData.key]
    else
         displayName = iconData.name
     end
     end


    -- Localize link if on translated subpage
    local baseLink = iconData.link or ""
     local link = baseLink
     local link = baseLink


     if langCode ~= "en" and baseLink ~= "" then
     if not customLink and langCode ~= "en" and baseLink ~= "" then
         link = baseLink .. "/" .. langCode
         link = baseLink .. "/" .. langCode
     end
     end


     -- Style logic (only abilities get theme class by default)
     -- Reattach fragment at the end
     local class = iconData.class or ""
     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;"'
         style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
     else
     else
         style = 'style="position:relative; bottom:2px;"'
         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 270: 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

Latest revision as of 14:07, 29 July 2026

Main module for rendering game icons.

Note: This module is not intended for direct use. Use the templates below.

Data sources

[edit source]

The module reads from Data:ResourceLookup.json, a pre-indexed lookup table generated by Deadbot at deploy time. When the bot uploads new game files, hero, ability, and item icons update automatically.

An additional resource for other icons can be found at Data:ResourceLookup2.json, a manually maintained table; some entries may rely on a dictionary key.

See Module:Icon/data for manual overrides, legacy items, aliases, and renames used by this module.

Any of the following templates can be used. The module automatically detects the icon type:

Note: While all four templates work identically, it is recommended to use the appropriate one for clarity (e.g., use {{AbilityIcon|Siphon Life}} even though {{HeroIcon|Siphon Life}} will also work).

Error tracking

[edit source]

Icons that fail to render are added to Category:Module:Icon ERROR.


local p = {}

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_module = require("Module:Lang")
local table_data = require("Module:Icon/data")

local cachedLangCode

local linkOverrides = table_data.linkOverrides
local legacyItems = table_data.legacyItems
local missingImageOverrides = table_data.missingImageOverrides
local aliases = table_data.aliases
local renames = table_data.renames

local function getLangCode()
    if cachedLangCode then
        return cachedLangCode
    end

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

    cachedLangCode = langCode
    return langCode
end

-- Infer image path from name, with override support for missing images
local function getImage(iconInfo, args)
    local lowerName = iconInfo.name:lower()

    if args.image then
        return args.image
    end

    return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
end

-- Get the translated name
local function getTranslatedName(iconInfo, langCode)
    if langCode == "en" then
        return iconInfo.name
    end

    -- 1. Direct key
    if iconInfo.key and iconInfo.key ~= "" then
        local translated = lang_module.get_string(iconInfo.key, langCode, iconInfo.name
        )

        if type(translated) == "string" then
            return (translated:gsub("<[^>]+>", ""))
        end

        return iconInfo.name
    end

    -- 2. Dictionary lookup
    if iconInfo.key_dictionary and iconInfo.key_dictionary ~= "" then
        local entry = dictionary[iconInfo.key_dictionary]

        if type(entry) == "table" then
            return entry[langCode]
                or entry.en
                or iconInfo.name
        end
    end

    -- No valid key > fallback
    return iconInfo.name
end

-- Infer link from type and available fields
local function getLink(iconInfo, langCode)
    if iconInfo.type == "ability" then
        local hero = iconInfo.hero_name or iconInfo.name
        
        -- Get the translated name for the specific ability
        local abilityName = getTranslatedName(iconInfo, langCode)
        
        -- Format for URL: replace spaces with underscores
        local anchor = abilityName:gsub(" ", "_")
        
        return hero .. "#" .. anchor
    end

    return linkOverrides[iconInfo.name] or iconInfo.name
end

-- Returns the canonical display name for a given user input
-- Used by templates to store the correct casing in data attributes
-- Normalizes casing so exact string matches work reliably in Lua modules
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

    local iconInfo = iconData[lowerName] or legacyItems[lowerName]

    -- If unknown, return original input
    if not iconInfo then
        return name
    end

	-- Quick hack: ability names are used by MediaWiki:Gadget-infobox-tooltip.js and expected to be localized
    local langCode = getLangCode()

    if iconInfo.type == "ability" then
        return getTranslatedName(iconInfo, langCode)
    end

    return iconInfo.name
end

local function render(name, args)
    local originalName = name
    local lowerName = name:lower()
    args = args or {}

    if renames[lowerName] then
        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 noLink = args["no-link"] == "true"
    local noFile = args["no-file"] == "true"
    local iconOnly = args["icon-only"] == "true"

    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

    local langCode = getLangCode()

    local displayName
    if customText then
        displayName = customText
    elseif renames[name:lower()] then
        -- Preserve original if renamed
        displayName = originalName
    else
        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

    local link = baseLink

    if not customLink and langCode ~= "en" and baseLink ~= "" then
        link = baseLink .. "/" .. langCode
    end

    -- Reattach fragment at the end
    link = link .. fragment

    -- Add CSS class for abilities
    local style
    if iconInfo.type == "ability" then
        style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
    else
        style = 'class="c-icon" style="position:relative; bottom:2px;"'
    end

    local imageLink = noLink and "" or link
    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
        return iconHtml
    end

    local textHtml = (noLink or link == "") and displayName or string.format('[[%s|%s]]', link, displayName)

    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

return p