Module:Icon

From The Deadlock Wiki
Revision as of 04:06, 29 April 2026 by Monster Domosed (talk | contribs) (Added localized icon names based on page language)
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 = {}

local iconData = mw.loadJsonData("Data:ResourceLookup.json")
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local lang_module = require('Module:Lang')

local cachedLangCode

local linkOverrides = {
    ["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
    ["Spirit Lifesteal"] = "Spirit Lifesteal (item)"
}

local legacyItems = {
    ["ammo scavenger"] = { name = "Ammo Scavenger", type = "item" },
    ["enduring spirit"] = { name = "Enduring Spirit", type = "item" },
    ["bullet armor"]    = { name = "Bullet Armor", type = "item" },
    ["spirit armor"]    = { name = "Spirit Armor", type = "item" },
    ["soul explosion"]  = { name = "Soul Explosion", type = "item" },
    ["soul rebirth"]    = { name = "Soul Rebirth", type = "item" },
}

local missingImageOverrides = {
    ["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",
    ["bullet armor"] = "Bullet Armor (item).png",
    ["spirit armor"] = "Spirit Armor (item).png",
}

local aliases = {
    ["doorman"] = "the doorman",
    ["mo and krill"] = "mo & krill",
    ["mo"] = "mo & krill",
    ["krill"] = "mo & krill",
    ["curse"] = "cursed relic",
    ["debuff remover"] = "dispel magic",
    ["defend and fight!"] = "plot armor",
    ["conjure dragon"] = "bookwyrm",
    ["backstabber"] = "stalker",
    ["flying strike"] = "flying slash",
    ["tornado"] = "dust devil",
    ["kudzu bomb"] = "entangling thorns",
    ["watcher's covenant"] = "kudzu connection"
}

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)
    local lowerName = iconInfo.name:lower()
    return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
end

-- Infer link from type and available fields
local function getLink(iconInfo)
    if iconInfo.type == "ability" then
        local hero = iconInfo.hero_name or iconInfo.name
        return hero .. "#Abilities"
    end
    return linkOverrides[iconInfo.name] or iconInfo.name
end

local function getTranslatedName(iconInfo, langCode)
    if langCode == "en" or not iconInfo.key then
        return iconInfo.name
    end
    
    local translatedName = lang_module.get_string(iconInfo.key, langCode, iconInfo.name)
    
    if type(translatedName) == "string" then
        translatedName = translatedName:gsub("<[^>]+>", "")
    end
    
    return translatedName 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
-- so that Lua modules which do exact string matching receive the right value.
function p.getCanonicalName(frame)
    local name = frame.args[1] or ""
    local lowerName = name:lower()

    if aliases[lowerName] then
        lowerName = aliases[lowerName]
    end

    local iconInfo = iconData[lowerName]
    if not iconInfo then
        iconInfo = legacyItems[lowerName]
    end

    if not iconInfo then
        return name -- fall back to original input if not found
    end

    return iconInfo.name
end

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

    if aliases[lowerName] then
        lowerName = aliases[lowerName]
    end

    -- 1. Check live game data
    local iconInfo = iconData[lowerName]
    
    -- 2. If not found, check legacy/unused items
    if not iconInfo then
        iconInfo = legacyItems[lowerName]
    end

    -- 3. If still not found, throw error
    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 or name
    local size = args.size or "20px"
    local noLink = args["no-link"] == "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 = customText
    if displayName == name then
        displayName = getTranslatedName(iconInfo, langCode)
    end

    local baseLink = getLink(iconInfo)
    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 langCode ~= "en" and baseLink ~= "" then
        link = baseLink .. "/" .. langCode
    end

    -- Re-attach the fragment at the very end
    link = link .. fragment

    -- Abilities get the module-icon-ability class for light/dark theme filter support
    local style
    if iconInfo.type == "ability" then
        style = 'class="module-icon-ability" style="position:relative; bottom:2px;"'
    else
        style = 'style="position:relative; bottom:2px;"'
    end

    local image = getImage(iconInfo)
    local imageLink = noLink and "" or link
    local iconHtml = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, 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

-- 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