Achievements are now in 'achievements.hjson'

+ Config.general.achievementFile
* Implement (re)caching (aka hot-reload)
* Update values a bit
This commit is contained in:
Bryan Ashby 2019-01-05 12:18:44 -07:00
parent c332b0f3ec
commit 2bd51c0725
4 changed files with 130 additions and 60 deletions

53
config/achievements.hjson Normal file
View File

@ -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,
}
}
}
}
}

View File

@ -4,6 +4,10 @@
// ENiGMA½ // ENiGMA½
const Events = require('./events.js'); const Events = require('./events.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
const {
getConfigPath,
getFullConfig,
} = require('./config_util.js');
const UserDb = require('./database.js').dbs.user; const UserDb = require('./database.js').dbs.user;
const { const {
getISOTimestampString getISOTimestampString
@ -22,11 +26,13 @@ const { pipeToAnsi } = require('./color_codes.js');
const stringFormat = require('./string_format.js'); const stringFormat = require('./string_format.js');
const StatLog = require('./stat_log.js'); const StatLog = require('./stat_log.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const ConfigCache = require('./config_cache.js');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
const async = require('async'); const async = require('async');
const moment = require('moment'); const moment = require('moment');
const paths = require('path');
class Achievement { class Achievement {
constructor(data) { constructor(data) {
@ -107,11 +113,56 @@ class Achievements {
} }
init(cb) { 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? // :TODO: if enabled/etc., load achievements.hjson -> if theme achievements.hjson{}, merge @ display time?
// merge for local vs global (per theme) clients // merge for local vs global (per theme) clients
// ...only merge/override text // ...only merge/override text
this.monitorUserStatUpdateEvents();
return cb(null);
} }
loadAchievementHitCount(user, achievementTag, field, cb) { loadAchievementHitCount(user, achievementTag, field, cb) {
@ -139,7 +190,7 @@ class Achievements {
return cb(err); return cb(err);
} }
Events.emit( this.events.emit(
Events.getSystemEvents().UserAchievementEarned, Events.getSystemEvents().UserAchievementEarned,
{ {
user : info.client.user, user : info.client.user,
@ -172,7 +223,11 @@ class Achievements {
} }
monitorUserStatUpdateEvents() { 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)) { if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
return; return;
} }
@ -182,10 +237,9 @@ class Achievements {
return; return;
} }
const config = Config();
// :TODO: Make this code generic - find + return factory created object // :TODO: Make this code generic - find + return factory created object
const achievementTag = _.findKey( const achievementTag = _.findKey(
_.get(config, 'userAchievements.achievements', {}), _.get(this.achievementConfig, 'achievements', {}),
achievement => { achievement => {
if(false === achievement.enabled) { if(false === achievement.enabled) {
return false; return false;
@ -199,7 +253,7 @@ class Achievements {
return; return;
} }
const achievement = Achievement.factory(config.userAchievements.achievements[achievementTag]); const achievement = Achievement.factory(this.achievementConfig.achievements[achievementTag]);
if(!achievement) { if(!achievement) {
return; return;
} }
@ -230,10 +284,11 @@ class Achievements {
achievement, achievement,
details, details,
client, client,
matchField, matchField, // match - may be in odd format
matchValue, matchValue, // actual value
user : userStatEvent.user, achievedValue : matchField, // achievement value met
timestamp : moment(), user : userStatEvent.user,
timestamp : moment(),
}; };
return callback(null, info); 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) { createAchievementInterruptItems(info, cb) {
const dateTimeFormat = const dateTimeFormat =
info.details.dateTimeFormat || info.details.dateTimeFormat ||
@ -291,7 +353,7 @@ class Achievements {
const spec = const spec =
_.get(info.details, `art.${name}`) || _.get(info.details, `art.${name}`) ||
_.get(info.achievement, `art.${name}`) || _.get(info.achievement, `art.${name}`) ||
_.get(config, `userAchievements.art.${name}`); _.get(this.achievementConfig, `art.${name}`);
if(!spec) { if(!spec) {
return callback(null); return callback(null);
} }
@ -351,12 +413,6 @@ class Achievements {
let achievements; let achievements;
exports.moduleInitialize = (initInfo, cb) => { exports.moduleInitialize = (initInfo, cb) => {
if(false === _.get(Config(), 'userAchievements.enabled')) {
// :TODO: Log disabled
return cb(null);
}
achievements = new Achievements(initInfo.events); achievements = new Achievements(initInfo.events);
return achievements.init(cb); return achievements.init(cb);
}; };

View File

@ -175,6 +175,7 @@ function getDefaultConfig() {
menuFile : 'menu.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path 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 promptFile : 'prompt.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path
achievementFile : 'achievements.hjson',
}, },
users : { users : {
@ -1004,46 +1005,5 @@ function getDefaultConfig() {
loginHistoryMax: -1, // set to -1 for forever 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,
}
}
}
}
}
}; };
} }

View File

@ -10,6 +10,7 @@ const paths = require('path');
const async = require('async'); const async = require('async');
exports.init = init; exports.init = init;
exports.getConfigPath = getConfigPath;
exports.getFullConfig = getFullConfig; exports.getFullConfig = getFullConfig;
function getConfigPath(filePath) { function getConfigPath(filePath) {