WIP on user achievements

* Hook up events for testing
* Start to plug in experimental interrupt
This commit is contained in:
Bryan Ashby 2019-01-02 22:13:42 -07:00
parent c5a72c7356
commit a34dab6a73
5 changed files with 156 additions and 2 deletions

103
core/achievement.js Normal file
View File

@ -0,0 +1,103 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const Events = require('./events.js');
const Config = require('./config.js').get;
const UserDb = require('./database.js').dbs.user;
const UserInterruptQueue = require('./user_interrupt_queue.js');
const {
getConnectionByUserId
} = require('./client_connections.js');
// deps
const _ = require('lodash');
class Achievements {
constructor(events) {
this.events = events;
}
init(cb) {
this.monitorUserStatUpdateEvents();
return cb(null);
}
loadAchievementHitCount(user, achievementTag, field, value, cb) {
UserDb.get(
`SELECT COUNT() AS count
FROM user_achievement
WHERE user_id = ? AND achievement_tag = ? AND match_field = ? AND match_value >= ?;`,
[ user.userId, achievementTag, field, value ],
(err, row) => {
return cb(err, row && row.count || 0);
}
);
}
monitorUserStatUpdateEvents() {
this.events.on(Events.getSystemEvents().UserStatUpdate, userStatEvent => {
const statValue = parseInt(userStatEvent.statValue, 10);
if(isNaN(statValue)) {
return;
}
const config = Config();
const achievementTag = _.findKey(
_.get(config, 'userAchievements.achievements', {}),
achievement => {
if(false === achievement.enabled) {
return false;
}
return 'userStat' === achievement.type &&
achievement.statName === userStatEvent.statName;
}
);
if(!achievementTag) {
return;
}
const achievement = config.userAchievements.achievements[achievementTag];
let matchValue = Object.keys(achievement.match || {}).sort( (a, b) => b - a).find(v => statValue >= v);
if(matchValue) {
const match = achievement.match[matchValue];
//
// Check if we've triggered this event before
//
this.loadAchievementHitCount(userStatEvent.user, achievementTag, null, matchValue, (err, count) => {
if(count > 0) {
return;
}
const conn = getConnectionByUserId(userStatEvent.user.userId);
if(!conn) {
return;
}
const interruptItem = {
text : match.text,
pause : true,
};
UserInterruptQueue.queue(interruptItem, { omit : conn} );
});
}
});
}
}
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);
};

View File

@ -1003,6 +1003,41 @@ function getDefaultConfig() {
systemEvents : {
loginHistoryMax: -1, // set to -1 for forever
}
},
userAchievements : {
enabled : true,
artHeader : 'achievement_header',
artFooter : 'achievement_footer',
achievements : {
user_login_count : {
type : 'userStat',
statName : 'login_count',
retroactive : true,
match : {
10 : {
title : 'Return Caller',
globalText : '{userName} has logged in {statValue} times!',
text : 'You\'ve logged in {statValue} times!',
points : 5,
},
25 : {
title : 'Seems To Like It!',
globalText : '{userName} has logged in {statValue} times!',
text : 'You\'ve logged in {statValue} times!',
points : 10,
},
100 : {
title : '{boardName} Addict',
globalText : '{userName} the BBS {boardName} addict has logged in {statValue} times!',
text : 'You\'re a {boardName} addict! You\'ve logged in {statValue} times!',
points : 10,
}
}
}
}
}
};
}

View File

@ -189,6 +189,18 @@ const DB_INIT_TABLE = {
);`
);
dbs.user.run(
`CREATE TABLE IF NOT EXISTS user_achievement (
user_id INTEGER NOT NULL,
achievement_tag VARCHAR NOT NULL,
timestamp DATETIME NOT NULL,
match_field VARCHAR NOT NULL,
match_value VARCHAR NOT NULL,
UNIQUE(user_id, achievement_tag, match_field, match_value),
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
);`
);
return cb(null);
},

View File

@ -120,11 +120,14 @@ class StatLog {
//
// User specific stats
// These are simply convience methods to the user's properties
// These are simply convenience methods to the user's properties
//
setUserStat(user, statName, statValue, cb) {
// note: cb is optional in PersistUserProperty
return user.persistProperty(statName, statValue, cb);
user.persistProperty(statName, statValue, cb);
const Events = require('./events.js'); // we need to late load currently
return Events.emit(Events.getSystemEvents().UserStatUpdate, { user, statName, statValue } );
}
getUserStat(user, statName) {

View File

@ -21,4 +21,5 @@ module.exports = {
UserSendMail : 'codes.l33t.enigma.system.user_send_mail',
UserRunDoor : 'codes.l33t.enigma.system.user_run_door',
UserSendNodeMsg : 'codes.l33t.enigma.system.user_send_node_msg',
UserStatUpdate : 'codes.l33t.enigma.system.user_stat_set', // { ..., statName, statValue }
};