Module:Icon: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
updated aliases
LVL (talk | contribs)
Switch to pre-built lookup table for faster page rendering
Line 1: Line 1:
local p = {}
local p = {}


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


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


-- Cache for expensive existence checks (persists during page render)
local linkOverrides = {
local existenceCache = {}
    ["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
    ["Spirit Lifesteal"] = "Spirit Lifesteal (item)"
}


-- Heroes/Items that don't have images uploaded yet
local missingImageOverrides = {
local missingImageOverrides = {
    -- Unreleased Heroes
     ["boho"] = "Module-Icon missing.png",
     ["boho"] = "Module-Icon missing.png",
     ["druid"] = "Module-Icon missing.png",  
     ["druid"] = "Module-Icon missing.png",
     ["fortuna"] = "Module-Icon missing.png",
     ["fortuna"] = "Module-Icon missing.png",
     ["graf"] = "Module-Icon missing.png",
     ["graf"] = "Module-Icon missing.png",
Line 22: Line 20:
     ["swan"] = "Module-Icon missing.png",
     ["swan"] = "Module-Icon missing.png",
     ["thumper"] = "Module-Icon missing.png",
     ["thumper"] = "Module-Icon missing.png",
    -- Add any others that show red links
}
}


-- Items that link to disambiguation pages
local aliases = {
local linkOverrides = {
     ["doorman"] = "the doorman",
     ["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
    ["mo and krill"] = "mo & krill",
     ["Spirit Lifesteal"] = "Spirit Lifesteal (item)"
    ["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",
}
}


-- Build unified lookup table from bot-uploaded JSONs
local function buildIconData()
    if iconDataCache then
        return iconDataCache
    end
   
    local lookup = {}
   
    -- Load bot 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",
        ["defend and fight!"] = "plot armor",
        ["conjure dragon"] = "bookwyrm",
        ["backstabber"] = "stalker",
        ["flying strike"] = "flying slash",
        ["tornado"] = "dust devil",
        ["kudzu bomb"] = "entangling thorns",
        ["kudzu connection"] = "watcher's covenant"
    }
   
    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()
local function getLangData()
     if cachedLangData then
     if cachedLangData then
Line 196: Line 58:
     cachedLangCode = langCode
     cachedLangCode = langCode
     cachedLangData = langData
     cachedLangData = langData
    return langCode, langData
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


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


-- Main render function
function p.render(frame)
function p.render(frame)
     local args = frame:getParent().args
     local args = frame:getParent().args
     local name = args[1] or ""
     local name = args[1] or ""
    local lowerName = name:lower()
    if aliases[lowerName] then
        lowerName = aliases[lowerName]
    end
    local iconInfo = iconData[lowerName]
    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 ""
     local customText = args.l1 or ""
   
    -- 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 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 iconOnly = true
             if val == "icon-only" then
             elseif val == "no-link" then noLink = true
                iconOnly = true
             elseif val:match("^%d+px$") then size = val end
             elseif val == "no-link" then
                noLink = true
             elseif val:match("^%d+px$") then  
                size = val
            end
         end
         end
     end
     end


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


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


    -- Localize link if on translated subpage
     local baseLink = getLink(iconInfo)
     local baseLink = linkOverrides[iconData.name] or iconData.link or ""
     local link = baseLink
     local link = baseLink
   
     if langCode ~= "en" and baseLink ~= "" then
     if langCode ~= "en" and baseLink ~= "" then
         link = baseLink .. "/" .. langCode
         link = baseLink .. "/" .. langCode
     end
     end


     -- Style logic (only abilities get theme class by default)
     -- Abilities get the module-icon-ability class for light/dark theme filter support
    local class = iconData.class or ""
     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
Line 261: Line 129:
     end
     end


     -- Generate icon HTML
     local image = getImage(iconInfo)
     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 = string.format('<span %s>[[File:%s|%s|link=%s]]</span>', style, image, size, imageLink)


     if iconOnly or displayName == "" then
     if iconOnly or displayName == "" then

Revision as of 14:16, 11 March 2026

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 cachedLangCode, cachedLangData

local linkOverrides = {
    ["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
    ["Spirit Lifesteal"] = "Spirit Lifesteal (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",
}

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",
}

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

-- 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
        return iconInfo.hero_name or iconInfo.name
    end
    return linkOverrides[iconInfo.name] or iconInfo.name
end

function p.render(frame)
    local args = frame:getParent().args
    local name = args[1] or ""
    local lowerName = name:lower()

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

    local iconInfo = iconData[lowerName]

    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 ""
    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, langData = getLangData()

    local displayName = customText
    if displayName == "" then
        if iconInfo.key and langData[iconInfo.key] then
            displayName = langData[iconInfo.key]
        else
            displayName = iconInfo.name
        end
    end

    local baseLink = getLink(iconInfo)
    local link = baseLink
    if langCode ~= "en" and baseLink ~= "" then
        link = baseLink .. "/" .. langCode
    end

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

return p