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: | ||
-- | --[[ | ||
* Name: AchievementSystem | |||
* Author: Mark W. Datysgeld | |||
-- | * 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 = | 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 = | 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 | ||
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 | ||
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 | 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 | 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 _, | for _, achievementTbl in ipairs(userAchievements) do | ||
if | if achievementTbl["type"] == achievementType then | ||
return | 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 | local achType = achievement["type"] | ||
if achType then | if achType then | ||
local typeData = typeDefinitions[achType] | local typeData = typeDefinitions[achType] | ||
if typeData and typeData | 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 | ||
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 | local types = Achievements.loadTypes(frame) | ||
local | local typeDefinitions = {} | ||
for _, typeData in ipairs(types) do | |||
return | 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 | ||