Module:AchievementSystem: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:AchievementSystem
--[[
-- Loads data from MediaWiki:AchievementData.json and MediaWiki:AchievementList.json
* Name: AchievementSystem
-- AchievementList.json contains achievement type definitions
* Author: Mark W. Datysgeld
-- AchievementData.json contains user achievement assignments
* 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
-- 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:
* 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
-- .person-template .template-title.achievement-{id}::after {}
]]
 
---@class UserAchievement
---@field type string
---@field date? string


local Achievements = {}
local Achievements = {}
Line 323: Line 327:
local userAchievementsCache = {}
local userAchievementsCache = {}


---@return UserAchievement[]
function Achievements.getUserAchievements(pageId)
function Achievements.getUserAchievements(pageId)
     if not pageId or pageId == '' then
     if not pageId or pageId == '' then
Line 377: 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


Line 428: 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 539: Line 588:
      
      
     return ''
     return ''
end
--------------------------------------------------------------------------------
-- Simple pass-through to track pages (for future expansions)
-- @param pageId - The page ID to track
-- @param pageName - The page name
-- @return Boolean indicating success
--------------------------------------------------------------------------------
function Achievements.trackPage(pageId, pageName) -- REVIEW
    -- In the future, this could update the page_name in the JSON data
    return true
end
end


Line 566: 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 608: Line 652:
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 629: 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 642: 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
end


     local achievementId = titleAchievement.id
--------------------------------------------------------------------------------
     local displayName = titleAchievement.name or achievementId
-- Generate wikitext category links for a given list of achievements
      
-- @param achievements - An array of user achievement objects
     return achievementId, displayName, achievementId
-- @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
 
    local types = Achievements.loadTypes(frame)
     local typeDefinitions = {}
    for _, typeData in ipairs(types) do
        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