From 2bd51c07250ac93807c9eccb5cf9b1e05c89b30f Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 5 Jan 2019 12:18:44 -0700 Subject: [PATCH] Achievements are now in 'achievements.hjson' + Config.general.achievementFile * Implement (re)caching (aka hot-reload) * Update values a bit --- config/achievements.hjson | 53 ++++++++++++++++++++++ core/achievement.js | 94 +++++++++++++++++++++++++++++++-------- core/config.js | 42 +---------------- core/config_util.js | 1 + 4 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 config/achievements.hjson diff --git a/config/achievements.hjson b/config/achievements.hjson new file mode 100644 index 00000000..cb58ddd1 --- /dev/null +++ b/config/achievements.hjson @@ -0,0 +1,53 @@ +{ + enabled : true, + + art : { + localHeader : 'achievement_local_header', + localFooter : 'achievement_local_footer', + globalHeader : 'achievement_global_header', + globalFooter : 'achievement_global_footer', + }, + + // :TODO: achievements should be a path/filename -> achievements.hjson & allow override/theming + + achievements : { + user_login_count : { + type : 'userStat', + statName : 'login_count', + retroactive : true, + + match : { + 2 : { + title : 'Return Caller', + globalText : '{userName} has returned to {boardName}!', + text : 'You\'ve returned to {boardName}!', + points : 5, + }, + 10 : { + title : '{achievedValue} Logins', + globalText : '{userName} has logged into {boardName} {achievedValue} times!', + text : 'You\'ve logged into {boardName} {achievedValue} times!', + points : 5, + }, + 25 : { + title : '{achievedValue} Logins', + globalText : '{userName} has logged into {boardName} {achievedValue} times!', + text : 'You\'ve logged into {boardName} {achievedValue} times!', + points : 10, + }, + 100 : { + title : '{boardName} Regular', + globalText : '{userName} has logged into {boardName} {achievedValue} times!', + text : 'You\'ve logged into {boardName} {achievedValue} times!', + points : 10, + }, + 500 : { + title : '{boardName} Addict', + globalText : '{userName} the BBS {boardName} addict has logged in {achievedValue} times!', + text : 'You\'re a {boardName} addict! You\'ve logged in {achievedValue} times!', + points : 25, + } + } + } + } +} \ No newline at end of file diff --git a/core/achievement.js b/core/achievement.js index 21cc7ae4..bcd6860f 100644 --- a/core/achievement.js +++ b/core/achievement.js @@ -4,6 +4,10 @@ // ENiGMA½ const Events = require('./events.js'); const Config = require('./config.js').get; +const { + getConfigPath, + getFullConfig, +} = require('./config_util.js'); const UserDb = require('./database.js').dbs.user; const { getISOTimestampString @@ -22,11 +26,13 @@ const { pipeToAnsi } = require('./color_codes.js'); const stringFormat = require('./string_format.js'); const StatLog = require('./stat_log.js'); const Log = require('./logger.js').log; +const ConfigCache = require('./config_cache.js'); // deps const _ = require('lodash'); const async = require('async'); const moment = require('moment'); +const paths = require('path'); class Achievement { constructor(data) { @@ -107,11 +113,56 @@ class Achievements { } init(cb) { + let achievementConfigPath = _.get(Config(), 'general.achievementFile'); + if(!achievementConfigPath) { + // :TODO: Log me + return cb(null); + } + achievementConfigPath = getConfigPath(achievementConfigPath); // qualify + + // :TODO: Log enabled + + const configLoaded = (achievementConfig) => { + if(true !== achievementConfig.enabled) { + this.stopMonitoringUserStatUpdateEvents(); + delete this.achievementConfig; + } else { + this.achievementConfig = achievementConfig; + this.monitorUserStatUpdateEvents(); + } + }; + + const changed = ( { fileName, fileRoot } ) => { + const reCachedPath = paths.join(fileRoot, fileName); + if(reCachedPath === achievementConfigPath) { + getFullConfig(achievementConfigPath, (err, achievementConfig) => { + if(err) { + return Log.error( { error : err.message }, 'Failed to reload achievement config from cache'); + } + configLoaded(achievementConfig); + }); + } + }; + + ConfigCache.getConfigWithOptions( + { + filePath : achievementConfigPath, + forceReCache : true, + callback : changed, + }, + (err, achievementConfig) => { + if(err) { + return cb(err); + } + + configLoaded(achievementConfig); + return cb(null); + } + ); + // :TODO: if enabled/etc., load achievements.hjson -> if theme achievements.hjson{}, merge @ display time? // merge for local vs global (per theme) clients - // ...only merge/override text - this.monitorUserStatUpdateEvents(); - return cb(null); + // ...only merge/override text } loadAchievementHitCount(user, achievementTag, field, cb) { @@ -139,7 +190,7 @@ class Achievements { return cb(err); } - Events.emit( + this.events.emit( Events.getSystemEvents().UserAchievementEarned, { user : info.client.user, @@ -172,7 +223,11 @@ class Achievements { } monitorUserStatUpdateEvents() { - this.events.on(Events.getSystemEvents().UserStatUpdate, userStatEvent => { + if(this.userStatEventListener) { + return; // already listening + } + + this.userStatEventListener = this.events.on(Events.getSystemEvents().UserStatUpdate, userStatEvent => { if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) { return; } @@ -182,10 +237,9 @@ class Achievements { return; } - const config = Config(); // :TODO: Make this code generic - find + return factory created object const achievementTag = _.findKey( - _.get(config, 'userAchievements.achievements', {}), + _.get(this.achievementConfig, 'achievements', {}), achievement => { if(false === achievement.enabled) { return false; @@ -199,7 +253,7 @@ class Achievements { return; } - const achievement = Achievement.factory(config.userAchievements.achievements[achievementTag]); + const achievement = Achievement.factory(this.achievementConfig.achievements[achievementTag]); if(!achievement) { return; } @@ -230,10 +284,11 @@ class Achievements { achievement, details, client, - matchField, - matchValue, - user : userStatEvent.user, - timestamp : moment(), + matchField, // match - may be in odd format + matchValue, // actual value + achievedValue : matchField, // achievement value met + user : userStatEvent.user, + timestamp : moment(), }; return callback(null, info); @@ -256,6 +311,13 @@ class Achievements { }); } + stopMonitoringUserStatUpdateEvents() { + if(this.userStatEventListener) { + this.events.removeListener(Events.getSystemEvents().UserStatUpdate, this.userStatEventListener); + delete this.userStatEventListener; + } + } + createAchievementInterruptItems(info, cb) { const dateTimeFormat = info.details.dateTimeFormat || @@ -291,7 +353,7 @@ class Achievements { const spec = _.get(info.details, `art.${name}`) || _.get(info.achievement, `art.${name}`) || - _.get(config, `userAchievements.art.${name}`); + _.get(this.achievementConfig, `art.${name}`); if(!spec) { return callback(null); } @@ -351,12 +413,6 @@ class Achievements { let achievements; exports.moduleInitialize = (initInfo, cb) => { - - if(false === _.get(Config(), 'userAchievements.enabled')) { - // :TODO: Log disabled - return cb(null); - } - achievements = new Achievements(initInfo.events); return achievements.init(cb); }; diff --git a/core/config.js b/core/config.js index a74b124c..fa99d5da 100644 --- a/core/config.js +++ b/core/config.js @@ -175,6 +175,7 @@ function getDefaultConfig() { menuFile : 'menu.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path promptFile : 'prompt.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path + achievementFile : 'achievements.hjson', }, users : { @@ -1004,46 +1005,5 @@ function getDefaultConfig() { loginHistoryMax: -1, // set to -1 for forever } }, - - userAchievements : { - enabled : true, - - art : { - localHeader : 'achievement_local_header', - localFooter : 'achievement_local_footer', - globalHeader : 'achievement_global_header', - globalFooter : 'achievement_global_footer', - }, - - // :TODO: achievements should be a path/filename -> achievements.hjson & allow override/theming - - achievements : { - user_login_count : { - type : 'userStat', - statName : 'login_count', - retroactive : true, - match : { - 10 : { - title : 'Return Caller', - globalText : '{userName} has logged in {matchValue} times!', - text : 'You\'ve logged in {matchValue} times!', - points : 5, - }, - 25 : { - title : 'Seems To Like It!', - globalText : '{userName} has logged in {matchValue} times!', - text : 'You\'ve logged in {matchValue} times!', - points : 10, - }, - 100 : { - title : '{boardName} Addict', - globalText : '{userName} the BBS {boardName} addict has logged in {matchValue} times!', - text : 'You\'re a {boardName} addict! You\'ve logged in {matchValue} times!', - points : 10, - } - } - } - } - } }; } diff --git a/core/config_util.js b/core/config_util.js index bda0e1bd..d64c7a24 100644 --- a/core/config_util.js +++ b/core/config_util.js @@ -10,6 +10,7 @@ const paths = require('path'); const async = require('async'); exports.init = init; +exports.getConfigPath = getConfigPath; exports.getFullConfig = getFullConfig; function getConfigPath(filePath) {