Module:Icon: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
mNo edit summary
Added resourcelookup2.json, table_data, dictionary/rename support, localized anchor, and 2 additional parameters.
Line 1: Line 1:
local p = {}
local p = {}


local iconData = mw.loadJsonData("Data:ResourceLookup.json")
local iconDataRaw = mw.loadJsonData("Data:ResourceLookup.json")
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
local iconData2 = mw.loadJsonData("Data:ResourceLookup2.json")
local lang_module = require('Module:Lang')
local dictionary = mw.loadJsonData("Data:Dictionary")
 
local iconData = {}


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


local linkOverrides = {
for k, v in pairs(iconData2) do
    ["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
     iconData[k] = v
     ["Spirit Lifesteal"] = "Spirit Lifesteal (item)"
end
}


local legacyItems = {
local lang_codes = mw.loadJsonData("Data:LangCodes.json")
    ["ammo scavenger"] = { name = "Ammo Scavenger", type = "item" },
local lang_module = require("Module:Lang")
    ["enduring spirit"] = { name = "Enduring Spirit", type = "item" },
local table_data = require("Module:IconSandbox/data")
    ["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 = {
local cachedLangCode
    ["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 = {
local linkOverrides = table_data.linkOverrides
    ["doorman"] = "the doorman",
local legacyItems = table_data.legacyItems
    ["mo and krill"] = "mo & krill",
local missingImageOverrides = table_data.missingImageOverrides
    ["mo"] = "mo & krill",
local aliases = table_data.aliases
    ["krill"] = "mo & krill",
local renames = table_data.renames
    ["curse"] = "cursed relic",
    ["improved bullet armor"] = "bullet resilience",
    ["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",
    ["shadow explosion"] = "shadow transformation",
    ["watcher's covenant"] = "kudzu connection"
}


local function getLangCode()
local function getLangCode()
Line 66: Line 41:


-- Infer image path from name, with override support for missing images
-- Infer image path from name, with override support for missing images
local function getImage(iconInfo)
local function getImage(iconInfo, args)
     local lowerName = iconInfo.name:lower()
     local lowerName = iconInfo.name:lower()
    if args.image then
        return args.image
    end
     return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
     return missingImageOverrides[lowerName] or (iconInfo.name .. ".png")
end
-- Build localized anchor (#Abilities)
local function getLocalizedAnchor(key, fallback, langCode)
    local anchor = fallback
    if langCode ~= "en" and key then
        local translated = lang_module.get_string(key, langCode, fallback)
        if type(translated) == "string" then
            anchor = translated:gsub("<[^>]+>", "")
        end
    end
    anchor = anchor:gsub(" ", "_")
    return anchor
end
end


-- Infer link from type and available fields
-- Infer link from type and available fields
local function getLink(iconInfo)
local function getLink(iconInfo, langCode)
     if iconInfo.type == "ability" then
     if iconInfo.type == "ability" then
         local hero = iconInfo.hero_name or iconInfo.name
         local hero = iconInfo.hero_name or iconInfo.name
         return hero .. "#Abilities"
        -- Data:Lang en.json
        local anchor = getLocalizedAnchor("guide_basics_abilities_header", "Abilities", langCode)
         return hero .. "#" .. anchor
     end
     end
     return linkOverrides[iconInfo.name] or iconInfo.name
     return linkOverrides[iconInfo.name] or iconInfo.name
end
end


local function getTranslatedName(iconInfo, langCode)
local function getTranslatedName(iconInfo, langCode)
     if langCode == "en" or not iconInfo.key then
     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
         return iconInfo.name
     end
     end
      
 
     local translatedName = lang_module.get_string(iconInfo.key, langCode, iconInfo.name)
     -- 2. Dictionary lookup
   
     if iconInfo.key_dictionary and iconInfo.key_dictionary ~= "" then
    if type(translatedName) == "string" then
        local entry = dictionary[iconInfo.key_dictionary]
        translatedName = translatedName:gsub("<[^>]+>", "")
 
        if type(entry) == "table" then
            return entry[langCode]
                or entry.en
                or iconInfo.name
        end
     end
     end
      
 
     return translatedName or iconInfo.name
     -- No valid key > fallback
     return iconInfo.name
end
end


-- Returns the canonical display name for a given user input.
-- Returns the canonical display name for a given user input
-- Used by templates to store the correct casing in data attributes
-- Used by templates to store the correct casing in data attributes
-- so that Lua modules which do exact string matching receive the right value.
-- Normalizes casing so exact string matches work reliably in Lua modules
function p.getCanonicalName(frame)
function p.getCanonicalName(frame)
     local name = frame.args[1] or ""
     local name = frame.args[1] or ""
     local lowerName = name:lower()
     local lowerName = name:lower()


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


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


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


Line 118: Line 134:


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


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


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


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


     local customText = args.l1 or name
     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"


Line 146: Line 162:
         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
Line 154: Line 174:
     local langCode = getLangCode()
     local langCode = getLangCode()


     local displayName = customText
     local displayName
     if displayName == name then
    if customText then
        displayName = customText
     elseif renames[name:lower()] then
        -- Preserve original if renamed
        displayName = originalName
    else
         displayName = getTranslatedName(iconInfo, langCode)
         displayName = getTranslatedName(iconInfo, langCode)
     end
     end


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


Line 170: Line 195:


     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


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


     -- Abilities get the module-icon-ability class for light/dark theme filter support
     -- Add CSS class for abilities
     local style
     local style
     if iconInfo.type == "ability" then
     if iconInfo.type == "ability" then
Line 185: Line 211:
     end
     end


    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, 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 194: Line 224:


     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
end


Line 207: Line 242:
-- Module entrypoint
-- Module entrypoint
function p._render(name, args)
function p._render(name, args)
return render(name, args)
    return render(name, args)
end
end


return p
return p

Revision as of 21:54, 17 May 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 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:IconSandbox/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

-- Build localized anchor (#Abilities)
local function getLocalizedAnchor(key, fallback, langCode)
    local anchor = fallback

    if langCode ~= "en" and key then
        local translated = lang_module.get_string(key, langCode, fallback)
        if type(translated) == "string" then
            anchor = translated:gsub("<[^>]+>", "")
        end
    end

    anchor = anchor:gsub(" ", "_")
    return anchor
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
        -- Data:Lang en.json
        local anchor = getLocalizedAnchor("guide_basics_abilities_header", "Abilities", langCode)
        return hero .. "#" .. anchor
    end

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

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

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

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