Module:AchievementSystem: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:AchievementSystem
--[[
-- Loads data from MediaWiki:AchievementData.json
* Name: AchievementSystem
-- All achievement styling is defined in CSS/Templates.css, not in the JSON. This module only assigns CSS classes based on achievement IDs in the format:
* Author: Mark W. Datysgeld
-- .person-template .template-title.achievement-{id}::after {}
* Description: Comprehensive achievement system that manages user badges and titles throughout ICANNWiki, loading data from MediaWiki JSON files and providing rendering functions for Person templates
* Notes: Loads from MediaWiki:AchievementData.json (user assignments) and MediaWiki:AchievementList.json (type definitions). CSS styling defined in Templates.css using achievement-{id} format. Includes caching and fallback mechanisms for robust JSON handling
]]
 
---@class UserAchievement
---@field type string
---@field date? string


local Achievements = {}
local Achievements = {}
Line 84: Line 90:


local DEFAULT_DATA = {
local DEFAULT_DATA = {
     schema_version = 1,
     schema_version = 2,
     last_updated = os.date('!%Y-%m-%dT%H:%M:%SZ'),
     last_updated = os.date('!%Y-%m-%dT%H:%M:%SZ'),
     achievement_types = {},
     achievement_types = {},
Line 92: Line 98:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Load achievement types from the JSON page
-- Load achievement types from the JSON page
-- @param frame - The Scribunto frame object for preprocessing
-- @return Array of achievement type definitions
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.loadTypes(frame)
function Achievements.loadTypes(frame)
Line 220: Line 228:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Load achievement data from the JSON page
-- Load achievement data from the JSON page
-- @param frame - The Scribunto frame object for preprocessing
-- @return Table containing the full achievement data
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.loadData(frame)
function Achievements.loadData(frame)
Line 312: Line 322:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Get user achievements
-- Get user achievements
-- @param pageId - The page ID to get achievements for
-- @return Array of achievement objects for the specified page
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local userAchievementsCache = {}
---@return UserAchievement[]
function Achievements.getUserAchievements(pageId)
function Achievements.getUserAchievements(pageId)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
         return {}
         return {}
    end
   
    -- Check cache first
    local cacheKey = tostring(pageId)
    if userAchievementsCache[cacheKey] then
        return userAchievementsCache[cacheKey]
     end
     end


Line 323: Line 344:
     end
     end


     local key = tostring(pageId)
     local key = cacheKey
     local userEntry = data.user_achievements[key]
     local userEntry = data.user_achievements[key]
      
      
     -- If found with string key, return achievements
     -- If found with string key, return achievements
     if userEntry and userEntry.achievements then
     if userEntry and userEntry.achievements then
         return ensureArray(userEntry.achievements)
         local achievements = ensureArray(userEntry.achievements)
        userAchievementsCache[cacheKey] = achievements
        return achievements
     end
     end
      
      
Line 336: Line 359:
         userEntry = data.user_achievements[numKey]
         userEntry = data.user_achievements[numKey]
         if userEntry and userEntry.achievements then
         if userEntry and userEntry.achievements then
             return ensureArray(userEntry.achievements)
             local achievements = ensureArray(userEntry.achievements)
            userAchievementsCache[cacheKey] = achievements
            return achievements
         end
         end
     end
     end
      
      
    -- Cache empty result to avoid repeated lookups
    userAchievementsCache[cacheKey] = {}
     return {}
     return {}
end
end
Line 345: Line 372:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Check if a page/user has any achievements
-- Check if a page/user has any achievements
-- @param pageId - The page ID to check
-- @return Boolean indicating if the page has any achievements
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.hasAchievements(pageId)
function Achievements.hasAchievements(pageId)
Line 353: Line 382:
     local userAchievements = Achievements.getUserAchievements(pageId)
     local userAchievements = Achievements.getUserAchievements(pageId)
     return #userAchievements > 0
     return #userAchievements > 0
end
--------------------------------------------------------------------------------
-- Get all badge-type achievements for a user
-- @param pageId - The page ID to check
-- @param frame - The Scribunto frame object for preprocessing
-- @return Array of badge achievement objects
--------------------------------------------------------------------------------
function Achievements.getBadgeAchievements(pageId, frame)
    if not pageId or pageId == '' then
        return {}
    end
    local userAchievements = Achievements.getUserAchievements(pageId)
    if #userAchievements == 0 then
        return {}
    end
    local types = Achievements.loadTypes(frame)
   
    -- Build a lookup table for achievement types for efficient access
    local typeDefinitions = {}
    for _, typeData in ipairs(types) do
        if typeData.id and typeData.type then
            typeDefinitions[typeData.id] = typeData
        end
    end
   
    local badgeAchievements = {}
    -- Filter user achievements to only include badge types
    for _, achievementTbl in ipairs(userAchievements) do
        local achType = achievementTbl['type']
        if achType and typeDefinitions[achType] and typeDefinitions[achType]['type'] == "badge" then
                local newAchievement = {
                    type = achType,
                    date = achievementTbl['date'] or '',
                    name = typeDefinitions[achType].name or achType,
                    category = typeDefinitions[achType].category
                }
            table.insert(badgeAchievements, newAchievement)
        end
    end
    return badgeAchievements
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Get a user-friendly name for a given achievement type
-- Get a user-friendly name for a given achievement type
-- @param achievementType - The achievement type ID
-- @param frame - The Scribunto frame object for preprocessing
-- @return String containing the user-friendly name
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getAchievementName(achievementType, frame)
function Achievements.getAchievementName(achievementType, frame)
Line 382: Line 458:
-- Find the top-tier Title achievement for the user (lowest tier number)
-- Find the top-tier Title achievement for the user (lowest tier number)
-- Return the CSS class and the readable achievement name
-- Return the CSS class and the readable achievement name
-- @param pageId - The page ID to get the title achievement for
-- @param frame - The Scribunto frame object for preprocessing
-- @return CSS class, display name
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getTitleClass(pageId, frame)
function Achievements.getTitleClass(pageId, frame)
Line 398: Line 477:


     for _, achievement in ipairs(userAchievements) do
     for _, achievement in ipairs(userAchievements) do
         local achType = achievement.type
         local achType = achievement["type"]
          
          
         for _, typeData in ipairs(types) do
         for _, typeData in ipairs(types) do
Line 423: Line 502:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Renders a box with the top-tier achievement for the user
-- Renders a box with the top-tier achievement for the user
-- @param pageId - The page ID to render the achievement box for
-- @param frame - The Scribunto frame object for preprocessing
-- @return HTML string containing the achievement box
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.renderAchievementBox(pageId, frame)
function Achievements.renderAchievementBox(pageId, frame)
Line 475: Line 557:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Get page name for a given page ID
-- Get page name for a given page ID
-- @param pageId - The page ID to get the name for
-- @return String containing the page name
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getPageName(pageId)
function Achievements.getPageName(pageId)
Line 504: Line 588:
      
      
     return ''
     return ''
end
--------------------------------------------------------------------------------
-- Simple pass-through to track pages (for future expansions)
--------------------------------------------------------------------------------
function Achievements.trackPage(pageId, pageName) -- REVIEW
    -- In the future, this could update the page_name in the JSON data
    return true
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Retrieve a specific achievement if present, by type
-- Retrieve a specific achievement if present, by type
-- @param pageId - The page ID to get the achievement for
-- @param achievementType - The achievement type ID to look for
-- @return Achievement object or nil if not found
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getSpecificAchievement(pageId, achievementType)
function Achievements.getSpecificAchievement(pageId, achievementType)
Line 525: Line 604:
      
      
     -- Direct lookup for the requested achievement type
     -- Direct lookup for the requested achievement type
     for _, achievement in ipairs(userAchievements) do
     for _, achievementTbl in ipairs(userAchievements) do
         if achievement.type == achievementType then
         if achievementTbl["type"] == achievementType then
             return achievement
local def = Achievements.getAchievementDefinition(achievementType)
             return {
                type    = achievementTbl.type,
                date    = achievementTbl.date or '',
                name    = def and def.name or achievementType,
                category = def and def.category
            }
         end
         end
     end
     end
Line 536: Line 621:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Get achievement definition directly from JSON data
-- Get achievement definition directly from JSON data
-- @param achievementType - The achievement type ID to get the definition for
-- @param frame - The Scribunto frame object for preprocessing
-- @return Achievement type definition or nil if not found
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getAchievementDefinition(achievementType, frame)
function Achievements.getAchievementDefinition(achievementType, frame)
Line 558: Line 646:
-- This specifically looks for achievements with type="title"
-- This specifically looks for achievements with type="title"
-- Return the CSS class, readable achievement name, and achievement ID (or empty strings if none found)
-- Return the CSS class, readable achievement name, and achievement ID (or empty strings if none found)
-- @param pageId - The page ID to get the title achievement for
-- @param frame - The Scribunto frame object for preprocessing
-- @return achievementId, displayName, achievementId
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function Achievements.getTitleAchievement(pageId, frame)
function Achievements.getTitleAchievement(pageId, frame)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
         return '', '', ''
         return nil
     end
     end


     local userAchievements = Achievements.getUserAchievements(pageId)
     local userAchievements = Achievements.getUserAchievements(pageId)
     if #userAchievements == 0 then
     if #userAchievements == 0 then
         return '', '', ''
         return nil
     end
     end


Line 582: Line 673:
      
      
     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
             local typeData = typeDefinitions[achType]
             local typeData = typeDefinitions[achType]
             if typeData and typeData.type == "title" then
             if typeData and typeData["type"] == "title" then
                 local tier = typeData.tier or 999
                 local tier = typeData.tier or 999
                 if tier < highestTier then
                 if tier < highestTier then
Line 595: Line 686:
     end
     end


     if not titleAchievement or not titleAchievement.id then
     return titleAchievement
         return '', '', ''
end
 
-- Renders a title block with achievement integration
function Achievements.renderTitleBlockWithAchievement(args, titleClass, titleText, achievementClass, achievementId, achievementName)
    titleClass = titleClass or "template-title"
   
    -- Only add achievement attributes if they exist
    if achievementClass and achievementClass ~= "" and achievementId and achievementId ~= "" then
         return string.format(
            '|-\n! colspan="2" class="%s %s" data-achievement-id="%s" data-achievement-name="%s" | %s',
            titleClass, achievementClass, achievementId, achievementName, titleText
        )
    else
        -- Clean row with no achievement data
        return string.format('|-\n! colspan="2" class="%s" | %s', titleClass, titleText)
    end
end
 
--------------------------------------------------------------------------------
-- Generate wikitext category links for a given list of achievements
-- @param achievements - An array of user achievement objects
-- @param frame - The Scribunto frame object
-- @return A string of wikitext category links, e.g., "[[Category:Cat1]][[Category:Cat2]]"
--------------------------------------------------------------------------------
function Achievements.getCategoryLinks(achievements, frame)
    if not achievements or #achievements == 0 then
        return ""
     end
     end


     local achievementId = titleAchievement.id
     local types = Achievements.loadTypes(frame)
     local displayName = titleAchievement.name or achievementId
    local typeDefinitions = {}
      
    for _, typeData in ipairs(types) do
     return achievementId, displayName, achievementId
        typeDefinitions[typeData.id] = typeData
    end
 
    local categoryLinks = {}
    local foundCategories = {} -- To prevent duplicate categories
 
     for _, ach in ipairs(achievements) do
        local achType = ach['type']
        local definition = typeDefinitions[achType]
       
        if definition and definition.category and definition.category ~= "" and not foundCategories[definition.category] then
            table.insert(categoryLinks, "[[Category:" .. definition.category .. "]]")
            foundCategories[definition.category] = true
        end
     end
 
     return table.concat(categoryLinks)
end
end


return Achievements
return Achievements