Module:Icon

From The Deadlock Wiki
Revision as of 13:32, 7 March 2026 by Saag (talk | contribs)
Jump to navigation Jump to search

Main module for rendering game icons.

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

Data sources

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.

Usage

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

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


local p = {}

-- Load language codes
local lang_codes = mw.loadJsonData("Data:LangCodes.json")

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

-- Cache for expensive existence checks (persists during page render)
local existenceCache = {}

-- Heroes/Items that don't have images uploaded yet
local missingImageOverrides = {
    -- Unreleased Heroes
    ["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 unified lookup table from bot-uploaded JSONs
local function buildIconData()
    if iconDataCache then
        return iconDataCache
    end
    
    local lookup = {}
    
    -- Load bot data
	mw.log('Loading hero/ability/item data')
    local heroData = mw.loadJsonData("Data:HeroData.json")
    local abilityData = mw.loadJsonData("Data:AbilityData.json")
    local itemData = mw.loadJsonData("Data:ItemData.json")
    
    -- Track which hero owns which ability (for linking)
    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()
            
            -- Check for missing image override
            local imageName = missingImageOverrides[lowerName] or (name .. ".png")
            
            lookup[lowerName] = {
                name = name,
                key = heroKey,
                link = 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
    
	-- Process Abilities
    for abilityKey, ability in pairs(abilityData) do
        if type(ability) == "table" and ability.Name then
            local name = ability.Name
            local lowerName = name:lower()
            local parentInfo = abilityToHero[abilityKey]
            
            -- 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
    
    -- Process Items
    for itemKey, item in pairs(itemData) do
        if type(item) == "table" and item.Name then
            local name = item.Name
            local lowerName = name:lower()
            
            lookup[lowerName] = {
                name = name,
                key = itemKey,
                link = name,
                image = name .. ".png",
                type = "item"
            }
        end
    end
    
    iconDataCache = lookup
    return lookup
end

-- Get icon data by name (with alias support)
local function getIconData(iconName)
    local data = buildIconData()
    local lowerName = iconName:lower()
    
    -- Check main lookup
    if data[lowerName] then
        return data[lowerName], nil
    end
    
    -- Check aliases (for edge cases like "doorman" -> "the doorman")
    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

-- Language handling
local function getLangData()
    if cachedLangData then
        return cachedLangCode, cachedLangData
    end

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

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

    cachedLangCode = langCode
    cachedLangData = langData

    return langCode, langData
end

-- Main render function
function p.render(frame)
    local args = frame:getParent().args
    local name = args[1] or ""
    local customText = args.l1 or ""
    
    -- Set Defaults
    local size = args.size or "20px"
    local noLink = args["no-link"] == "true"
    local iconOnly = args["icon-only"] == "true"

    -- Scan unnamed parameters
    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

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

    local langCode, langData = getLangData()
    
    -- Determine display name
    local displayName = ""
    if customText ~= "" then
        displayName = customText
    elseif iconData.key and langData[iconData.key] then
        displayName = langData[iconData.key]
    else
        displayName = iconData.name
    end

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

    -- Style logic (only abilities get theme class by default)
    local class = iconData.class or ""
    local style
    if class:find("theme", 1, true) then
        style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
    else
        style = 'style="position:relative; bottom:2px;"'
    end

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

    if iconOnly or displayName == "" then
        return iconHtml
    end

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

return p