Module:AchievementSystem: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
Line 8: Line 8:


local Achievements = {}
local Achievements = {}
-- No debug logging in production code
local function debugLog(message)
    -- Debug logging disabled - do nothing
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 64: Line 59:
         if success and result then
         if success and result then
             return result
             return result
        else
            debugLog('ERROR: JSON decode failed: ' .. tostring(result or 'unknown error'))
         end
         end
     end
     end
      
      
    debugLog('CRITICAL ERROR: mw.text.jsonDecode not available!')
     return nil
     return nil
end
end
Line 132: Line 124:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.loadData(frame)
function Achievements.loadData(frame)
    debugLog("Starting to load achievement data")
     -- Use the request-level cache if we already loaded data once
     -- Use the request-level cache if we already loaded data once
     if dataCache then
     if dataCache then
        debugLog("Using request-level cached data")
         return dataCache
         return dataCache
     end
     end
Line 145: Line 134:
         if frame and type(frame) == "table" and frame.preprocess then
         if frame and type(frame) == "table" and frame.preprocess then
             -- Make sure frame is valid and has preprocess method
             -- Make sure frame is valid and has preprocess method
            debugLog("Using frame:preprocess to get JSON content")
             local preprocessSuccess, preprocessResult = pcall(function()
             local preprocessSuccess, preprocessResult = pcall(function()
                 return frame:preprocess('{{MediaWiki:AchievementData.json}}')
                 return frame:preprocess('{{MediaWiki:AchievementData.json}}')
Line 152: Line 140:
             if preprocessSuccess and preprocessResult then
             if preprocessSuccess and preprocessResult then
                 jsonText = preprocessResult
                 jsonText = preprocessResult
            else
                debugLog("frame:preprocess failed: " .. tostring(preprocessResult or 'unknown error'))
             end
             end
         end
         end
Line 159: Line 145:
         -- If we couldn't get JSON from frame:preprocess, fall back to direct content loading
         -- If we couldn't get JSON from frame:preprocess, fall back to direct content loading
         if not jsonText then
         if not jsonText then
            debugLog("No valid frame or preprocess failed, falling back to direct content loading")
           
             -- Try using mw.loadJsonData first (preferred method)
             -- Try using mw.loadJsonData first (preferred method)
             if mw.loadJsonData then
             if mw.loadJsonData then
                debugLog("Attempting to use mw.loadJsonData for " .. ACHIEVEMENT_DATA_PAGE)
               
                 local loadJsonSuccess, jsonData = pcall(function()
                 local loadJsonSuccess, jsonData = pcall(function()
                     return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
                     return mw.loadJsonData(ACHIEVEMENT_DATA_PAGE)
Line 170: Line 152:
                  
                  
                 if loadJsonSuccess and jsonData and type(jsonData) == 'table' then
                 if loadJsonSuccess and jsonData and type(jsonData) == 'table' then
                    debugLog("Successfully loaded data with mw.loadJsonData")
                     return jsonData
                     return jsonData
                else
                    debugLog("mw.loadJsonData failed: " .. tostring(jsonData or 'unknown error'))
                 end
                 end
             end
             end
Line 180: Line 159:
             local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
             local pageTitle = mw.title.new(ACHIEVEMENT_DATA_PAGE)
             if not pageTitle or not pageTitle.exists then
             if not pageTitle or not pageTitle.exists then
                debugLog(ACHIEVEMENT_DATA_PAGE .. " does not exist")
                 return DEFAULT_DATA
                 return DEFAULT_DATA
             end
             end
Line 190: Line 168:
              
              
             if contentSuccess and content and content ~= "" then
             if contentSuccess and content and content ~= "" then
                debugLog("Successfully retrieved raw content, length: " .. #content)
               
                 -- Remove any BOM or leading whitespace that might cause issues
                 -- Remove any BOM or leading whitespace that might cause issues
                 content = content:gsub("^%s+", "")
                 content = content:gsub("^%s+", "")
                 if content:byte(1) == 239 and content:byte(2) == 187 and content:byte(3) == 191 then
                 if content:byte(1) == 239 and content:byte(2) == 187 and content:byte(3) == 191 then
                    debugLog("Removing UTF-8 BOM from content")
                     content = content:sub(4)
                     content = content:sub(4)
                 end
                 end
Line 201: Line 176:
                 jsonText = content
                 jsonText = content
             else
             else
                debugLog("Failed to get content: " .. tostring(content or 'unknown error'))
                 return DEFAULT_DATA
                 return DEFAULT_DATA
             end
             end
Line 214: Line 188:
              
              
             if jsonDecodeSuccess and jsonData then
             if jsonDecodeSuccess and jsonData then
                debugLog("Successfully decoded content with standard jsonDecode")
                 return jsonData
                 return jsonData
             end
             end
Line 224: Line 197:
              
              
             if jsonDecodeSuccess and jsonData then
             if jsonDecodeSuccess and jsonData then
                debugLog("Successfully decoded content with JSON_TRY_FIXING")
                 return jsonData
                 return jsonData
             end
             end
           
            debugLog("All JSON decode approaches failed")
        else
            debugLog("No JSON text available or mw.text.jsonDecode not available")
         end
         end
          
          
         -- As absolute last resort, use local default data
         -- As absolute last resort, use local default data
        debugLog("All JSON loading approaches failed, using default data")
         return DEFAULT_DATA
         return DEFAULT_DATA
     end)
     end)


     if not success or not data then
     if not success or not data then
        debugLog("Critical error in load process: " .. tostring(data or 'unknown error'))
         data = DEFAULT_DATA
         data = DEFAULT_DATA
    end
    -- Show success source in log
    if data ~= DEFAULT_DATA then
        debugLog("Successfully loaded JSON data with " .. tostring(#(data.achievement_types or {})) .. " achievement types")
     end
     end


Line 257: Line 218:
function Achievements.getUserAchievements(pageId)
function Achievements.getUserAchievements(pageId)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
        debugLog("Empty page ID provided to getUserAchievements")
         return {}
         return {}
     end
     end
Line 263: Line 223:
     local data = Achievements.loadData()
     local data = Achievements.loadData()
     if not data or not data.user_achievements then
     if not data or not data.user_achievements then
        debugLog("No achievement data available in getUserAchievements")
         return {}
         return {}
     end
     end


     local key = tostring(pageId)
     local key = tostring(pageId)
    debugLog("Looking up achievements for ID: " .. key)
      
      
     -- Try string key first
     -- Try string key first
     local userAchievements = data.user_achievements[key] or {}
     local userAchievements = data.user_achievements[key] or {}
     if #userAchievements > 0 then
     if #userAchievements > 0 then
        debugLog("Found achievements using string key: " .. key)
         return ensureArray(userAchievements)
         return ensureArray(userAchievements)
     end
     end
Line 280: Line 237:
     local numKey = tonumber(key)
     local numKey = tonumber(key)
     if numKey and data.user_achievements[numKey] then
     if numKey and data.user_achievements[numKey] then
        debugLog("Found achievements using numeric key: " .. numKey)
         return ensureArray(data.user_achievements[numKey])
         return ensureArray(data.user_achievements[numKey])
     end
     end
Line 288: Line 244:
         local alt = "n" .. key
         local alt = "n" .. key
         if data.user_achievements[alt] and #data.user_achievements[alt] > 0 then
         if data.user_achievements[alt] and #data.user_achievements[alt] > 0 then
            debugLog("Found achievements using legacy key: " .. alt)
             return ensureArray(data.user_achievements[alt])
             return ensureArray(data.user_achievements[alt])
         end
         end
Line 296: Line 251:
     for userId, achievements in pairs(data.user_achievements) do
     for userId, achievements in pairs(data.user_achievements) do
         if tostring(userId) == key then
         if tostring(userId) == key then
            debugLog("Found achievements using string comparison with key type: " .. type(userId))
             return ensureArray(achievements)
             return ensureArray(achievements)
         end
         end
     end
     end
      
      
    debugLog("No achievements found for user " .. key)
     return {}
     return {}
end
end
Line 322: Line 275:
function Achievements.getAchievementName(achievementType)
function Achievements.getAchievementName(achievementType)
     if not achievementType or achievementType == '' then
     if not achievementType or achievementType == '' then
        debugLog("Empty achievement type provided to getAchievementName")
         return 'Unknown'
         return 'Unknown'
     end
     end
    debugLog("Looking up achievement name for type: '" .. tostring(achievementType) .. "'")


     local data = Achievements.loadData()
     local data = Achievements.loadData()
     if not data or not data.achievement_types then
     if not data or not data.achievement_types then
        debugLog("No achievement data or achievement_types missing")
         return achievementType
         return achievementType
     end
     end
Line 338: Line 287:
         if typeData.id == achievementType then
         if typeData.id == achievementType then
             if typeData.name and typeData.name ~= "" then
             if typeData.name and typeData.name ~= "" then
                debugLog("Found achievement: " .. typeData.id .. " with name: " .. typeData.name)
                 return typeData.name
                 return typeData.name
             else
             else
                debugLog("'" .. typeData.id .. "' has no name; using ID")
                 return achievementType
                 return achievementType
             end
             end
Line 347: Line 294:
     end
     end


    -- Special case for dev-role lookup
    if achievementType == "dev-role" then
        debugLog("Could not find dev-role in achievement_types!")
    end
    debugLog("No achievement found with type '" .. achievementType .. "'; using ID fallback")
     return achievementType
     return achievementType
end
end
Line 362: Line 303:
function Achievements.getTitleClass(pageId)
function Achievements.getTitleClass(pageId)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
        debugLog("Empty page ID provided to getTitleClass")
         return '', ''
         return '', ''
     end
     end
Line 368: Line 308:
     local userAchievements = Achievements.getUserAchievements(pageId)
     local userAchievements = Achievements.getUserAchievements(pageId)
     if #userAchievements == 0 then
     if #userAchievements == 0 then
        debugLog("No achievements found for user " .. tostring(pageId))
         return '', ''
         return '', ''
     end
     end
Line 378: Line 317:
     for _, achievement in ipairs(userAchievements) do
     for _, achievement in ipairs(userAchievements) do
         local achType = achievement.type
         local achType = achievement.type
        debugLog("Processing achievement type: " .. (achType or "nil"))
          
          
         for _, typeData in ipairs(data.achievement_types) do
         for _, typeData in ipairs(data.achievement_types) do
             if typeData.id == achType then
             if typeData.id == achType then
                 local tier = typeData.tier or 999
                 local tier = typeData.tier or 999
                debugLog("  Found type '" .. typeData.id .. "' with tier " .. tier .. " and name '" .. (typeData.name or "nil") .. "'")
                 if tier < highestTier then
                 if tier < highestTier then
                     highestTier = tier
                     highestTier = tier
                     highestAchievement = typeData
                     highestAchievement = typeData
                    debugLog("  New highest tier achievement: " .. typeData.id)
                 end
                 end
             end
             end
Line 394: Line 330:


     if not highestAchievement or not highestAchievement.id then
     if not highestAchievement or not highestAchievement.id then
        debugLog("No valid top-tier achievement found for user " .. tostring(pageId))
         return '', ''
         return '', ''
     end
     end
Line 400: Line 335:
     local cssClass = "achievement-" .. highestAchievement.id
     local cssClass = "achievement-" .. highestAchievement.id
     local displayName = highestAchievement.name or highestAchievement.id or "Award"
     local displayName = highestAchievement.name or highestAchievement.id or "Award"
   
    debugLog("Using top-tier achievement: " .. cssClass .. " with name: " .. displayName)
      
      
     return cssClass, displayName
     return cssClass, displayName
Line 471: Line 404:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getSpecificAchievement(pageId, achievementType)
function Achievements.getSpecificAchievement(pageId, achievementType)
    debugLog("Looking for achievement '" .. tostring(achievementType) ..
            "' in page ID: " .. tostring(pageId))
     if not pageId or not achievementType or pageId == '' then
     if not pageId or not achievementType or pageId == '' then
        debugLog("Invalid arguments for getSpecificAchievement")
         return nil
         return nil
     end
     end
Line 484: Line 413:
     for _, achievement in ipairs(userAchievements) do
     for _, achievement in ipairs(userAchievements) do
         if achievement.type == achievementType then
         if achievement.type == achievementType then
            debugLog("Found achievement: " .. achievementType .. " for user " .. tostring(pageId))
             return achievement
             return achievement
         end
         end
     end
     end


    debugLog("No match found for achievement type: " .. achievementType)
     return nil
     return nil
end
end
Line 498: Line 425:
function Achievements.getAchievementDefinition(achievementType)
function Achievements.getAchievementDefinition(achievementType)
     if not achievementType or achievementType == '' then
     if not achievementType or achievementType == '' then
        debugLog("ACHIEVEMENT-DEF: Empty achievement type")
         return nil
         return nil
     end
     end
Line 504: Line 430:
     local data = Achievements.loadData()
     local data = Achievements.loadData()
     if not data or not data.achievement_types then
     if not data or not data.achievement_types then
        debugLog("ACHIEVEMENT-DEF: No achievement data loaded")
         return nil
         return nil
     end
     end
Line 511: Line 436:
     for _, typeData in ipairs(data.achievement_types) do
     for _, typeData in ipairs(data.achievement_types) do
         if typeData.id == achievementType then
         if typeData.id == achievementType then
            debugLog("ACHIEVEMENT-DEF: Found definition for " .. achievementType)
             return typeData
             return typeData
         end
         end
     end
     end
      
      
    debugLog("ACHIEVEMENT-DEF: No definition found for " .. achievementType)
     return nil
     return nil
end
end
Line 562: Line 485:
function Achievements.getTitleAchievement(pageId)
function Achievements.getTitleAchievement(pageId)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
        debugLog("Empty page ID provided to getTitleAchievement")
         return '', '', ''
         return '', '', ''
     end
     end
Line 568: Line 490:
     local userAchievements = Achievements.getUserAchievements(pageId)
     local userAchievements = Achievements.getUserAchievements(pageId)
     if #userAchievements == 0 then
     if #userAchievements == 0 then
        debugLog("No achievements found for user " .. tostring(pageId))
         return '', '', ''
         return '', '', ''
     end
     end
Line 583: Line 504:
     local highestTier = 999
     local highestTier = 999
     local titleAchievement = nil
     local titleAchievement = nil
    -- Debug output for achievements
    debugLog("Processing " .. #userAchievements .. " achievements for page ID " .. tostring(pageId))
      
      
     for _, achievement in ipairs(userAchievements) do
     for _, achievement in ipairs(userAchievements) do
         local achType = achievement.type
         local achType = achievement.type
         if achType then
         if achType then
            debugLog("Processing achievement type: " .. achType)
             local typeData = typeDefinitions[achType]
             local typeData = typeDefinitions[achType]
             if typeData and typeData.type == "title" then
             if typeData and typeData.type == "title" then
                debugLog("Found title achievement: " .. achType .. " with tier " .. (typeData.tier or "nil"))
                 local tier = typeData.tier or 999
                 local tier = typeData.tier or 999
                 if tier < highestTier then
                 if tier < highestTier then
                     highestTier = tier
                     highestTier = tier
                     titleAchievement = typeData
                     titleAchievement = typeData
                    debugLog("New highest tier title achievement: " .. achType)
                 end
                 end
             end
             end
Line 605: Line 520:


     if not titleAchievement or not titleAchievement.id then
     if not titleAchievement or not titleAchievement.id then
        debugLog("No title achievement found for user " .. tostring(pageId))
         return '', '', ''
         return '', '', ''
     end
     end
Line 612: Line 526:
     local displayName = titleAchievement.name or achievementId
     local displayName = titleAchievement.name or achievementId
      
      
    debugLog("Found title achievement: " .. achievementId .. " with name: " .. displayName)
     return achievementId, displayName, achievementId
     return achievementId, displayName, achievementId
end
end


return Achievements
return Achievements