Module:Icon: Difference between revisions

LVL (talk | contribs)
Fix expensive parser function limit errors by removing title.exists check, now uses direct linking for localized pages
LVL (talk | contribs)
Testing fix for many icons on one page
Line 4: Line 4:
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)
-- Cache for compiled icon data, split by type (persists during page render)
local iconDataCache = nil
local heroCacheBuilt = false
local abilityCacheBuilt = false
local itemCacheBuilt = false
local iconDataCache = {}
 
local cachedLangCode, cachedLangData
local cachedLangCode, cachedLangData


Line 25: Line 29:
}
}


-- Build unified lookup table from bot-uploaded JSONs
-- Build lookup table from bot-uploaded JSONs, only loading the relevant JSON per type
local function buildIconData()
-- to reduce CPU cost on pages with many icon calls
    if iconDataCache then
local function buildIconData(iconType)
        return iconDataCache
     -- Only load hero data if not already built
    end
     if iconType == "hero" and not heroCacheBuilt then
   
        local heroData = mw.loadJsonData("Data:HeroData.json")
    local lookup = {}
 
   
        -- Process Heroes
     -- Load bot data
        for heroKey, hero in pairs(heroData) do
     local heroData = mw.loadJsonData("Data:HeroData.json")
            if type(hero) == "table" and hero.Name then
    local abilityData = mw.loadJsonData("Data:AbilityData.json")
                local name = hero.Name
    local itemData = mw.loadJsonData("Data:ItemData.json")
                local lowerName = name:lower()
   
 
    -- Track which hero owns which ability (for linking)
                -- Check for missing image override
    local abilityToHero = {}
                local imageName = missingImageOverrides[lowerName] or (name .. ".png")
   
 
    -- Process Heroes
                iconDataCache[lowerName] = {
    for heroKey, hero in pairs(heroData) do
                    name = name,
        if type(hero) == "table" and hero.Name then
                    key = heroKey,
            local name = hero.Name
                    link = name,
            local lowerName = name:lower()
                    image = imageName,
           
                    type = "hero"
            -- Check for missing image override
                }
            local imageName = missingImageOverrides[lowerName] or (name .. ".png")
             end
           
        end
            lookup[lowerName] = {
 
                name = name,
        heroCacheBuilt = true
                key = heroKey,
 
                link = name,
    -- Only load ability data (and hero data for parent linking) if not already built
                image = imageName,
    elseif iconType == "ability" and not abilityCacheBuilt then
                type = "hero"
        local heroData = mw.loadJsonData("Data:HeroData.json")
            }
        local abilityData = mw.loadJsonData("Data:AbilityData.json")
              
 
            -- Map abilities to parent hero
        -- Track which hero owns which ability (for linking)
             if hero.BoundAbilities then
        local abilityToHero = {}
 
        for heroKey, hero in pairs(heroData) do
             if type(hero) == "table" and hero.BoundAbilities then
                 for slot, ability in pairs(hero.BoundAbilities) do
                 for slot, ability in pairs(hero.BoundAbilities) do
                     if ability.Key then
                     if ability.Key then
                         abilityToHero[ability.Key] = {
                         abilityToHero[ability.Key] = {
                             heroName = name,
                             heroName = hero.Name,
                             abilityName = ability.Name
                             abilityName = ability.Name
                         }
                         }
Line 70: Line 77:
             end
             end
         end
         end
    end
 
   
        -- Process Abilities
-- Process Abilities
        for abilityKey, ability in pairs(abilityData) do
    for abilityKey, ability in pairs(abilityData) do
            if type(ability) == "table" and ability.Name then
        if type(ability) == "table" and ability.Name then
                local name = ability.Name
            local name = ability.Name
                local lowerName = name:lower()
            local lowerName = name:lower()
                local parentInfo = abilityToHero[abilityKey]
            local parentInfo = abilityToHero[abilityKey]
 
           
                -- If we already have this ability name, prefer the one linked to a hero
            -- If we already have this ability name, prefer the one linked to a hero
                if iconDataCache[lowerName] and iconDataCache[lowerName].type == "ability" then
            if lookup[lowerName] and lookup[lowerName].type == "ability" then
                    local existingHasParent = iconDataCache[lowerName].link ~= iconDataCache[lowerName].name
                local existingHasParent = lookup[lowerName].link ~= lookup[lowerName].name
                    local newHasParent = parentInfo ~= nil
                local newHasParent = parentInfo ~= nil
 
               
                    -- Skip if existing is linked to hero but this one isn't (prevents orphans from overwriting real ones)
                -- Skip if existing is linked to hero but this one isn't (prevents orphans from overwriting real ones)
                    if existingHasParent and not newHasParent then
                if existingHasParent and not newHasParent then
                        -- Skip this orphan ability
                    -- 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
                 else
                     -- Overwrite: new has parent and existing doesn't, or both/neither have parents
                     -- No existing entry, add normally
                     local link = parentInfo and parentInfo.heroName or name
                     local link = parentInfo and parentInfo.heroName or name
                     lookup[lowerName] = {
                     iconDataCache[lowerName] = {
                         name = name,
                         name = name,
                         key = abilityKey,
                         key = abilityKey,
Line 99: Line 117:
                     }
                     }
                 end
                 end
             else
             end
                -- No existing entry, add normally
        end
                 local link = parentInfo and parentInfo.heroName or name
 
                 lookup[lowerName] = {
        abilityCacheBuilt = true
 
    -- Only load item data if not already built
    elseif iconType == "item" and not itemCacheBuilt then
        local itemData = mw.loadJsonData("Data:ItemData.json")
 
        -- 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()
 
                 iconDataCache[lowerName] = {
                     name = name,
                     name = name,
                     key = abilityKey,
                     key = itemKey,
                     link = link,
                     link = name,
                     image = name .. ".png",
                     image = name .. ".png",
                     type = "ability",
                     type = "item"
                    class = "theme"
                 }
                 }
             end
             end
         end
         end
        itemCacheBuilt = true
     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
end


-- Get icon data by name (with alias support)
-- Get icon data by name and type (with alias support)
local function getIconData(iconName)
local function getIconData(iconName, iconType)
     local data = buildIconData()
     buildIconData(iconType)
     local lowerName = iconName:lower()
     local lowerName = iconName:lower()
   
 
     -- Check main lookup
     -- Check main lookup
     if data[lowerName] then
     if iconDataCache[lowerName] then
         return data[lowerName], nil
         return iconDataCache[lowerName], nil
     end
     end
   
 
     -- Check aliases (for edge cases like "doorman" -> "the doorman")
     -- Check aliases (for edge cases like "doorman" -> "the doorman")
     local aliases = {
     local aliases = {
Line 153: Line 165:
         ["debuff remover"] = "dispel magic"
         ["debuff remover"] = "dispel magic"
     }
     }
   
 
     if aliases[lowerName] then
     if aliases[lowerName] then
         return data[aliases[lowerName]], nil
         return iconDataCache[aliases[lowerName]], nil
     end
     end
   
 
     return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
     return nil, string.format("[[:Category:Module:Icon ERROR|Icon not found]] ('%s'). [[Category:Module:Icon ERROR]]", iconName)
end
end
Line 192: Line 204:
     local name = args[1] or ""
     local name = args[1] or ""
     local customText = args.l1 or ""
     local customText = args.l1 or ""
      
     local iconType = args.type or "item"
 
     -- Set Defaults
     -- Set Defaults
     local size = args.size or "20px"
     local size = args.size or "20px"
Line 202: Line 215:
         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
             elseif val == "no-link" then
             elseif val == "no-link" then
                 noLink = true
                 noLink = true
             elseif val:match("^%d+px$") then  
             elseif val:match("^%d+px$") then
                 size = val
                 size = val
             end
             end
Line 213: Line 226:
     end
     end


     -- Get icon data automatically from JSON
     -- Get icon data automatically from JSON, using type to limit which JSON is loaded
     local iconData, err = getIconData(name)
     local iconData, err = getIconData(name, iconType)
     if not iconData then
     if not iconData then
         return "Error: " .. err
         return "Error: " .. err
Line 220: Line 233:


     local langCode, langData = getLangData()
     local langCode, langData = getLangData()
   
 
     -- Determine display name
     -- Determine display name
     local displayName = ""
     local displayName = ""
Line 234: Line 247:
     local baseLink = iconData.link or ""
     local baseLink = 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