WIP on user achievements
* Hook up events for testing * Start to plug in experimental interrupt
This commit is contained in:
parent
c5a72c7356
commit
a34dab6a73
|
@ -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);
|
||||||
|
};
|
||||||
|
|
|
@ -1003,6 +1003,41 @@ function getDefaultConfig() {
|
||||||
systemEvents : {
|
systemEvents : {
|
||||||
loginHistoryMax: -1, // set to -1 for forever
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -120,11 +120,14 @@ class StatLog {
|
||||||
|
|
||||||
//
|
//
|
||||||
// User specific stats
|
// 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) {
|
setUserStat(user, statName, statValue, cb) {
|
||||||
// note: cb is optional in PersistUserProperty
|
// 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) {
|
getUserStat(user, statName) {
|
||||||
|
|
|
@ -21,4 +21,5 @@ module.exports = {
|
||||||
UserSendMail : 'codes.l33t.enigma.system.user_send_mail',
|
UserSendMail : 'codes.l33t.enigma.system.user_send_mail',
|
||||||
UserRunDoor : 'codes.l33t.enigma.system.user_run_door',
|
UserRunDoor : 'codes.l33t.enigma.system.user_run_door',
|
||||||
UserSendNodeMsg : 'codes.l33t.enigma.system.user_send_node_msg',
|
UserSendNodeMsg : 'codes.l33t.enigma.system.user_send_node_msg',
|
||||||
|
UserStatUpdate : 'codes.l33t.enigma.system.user_stat_set', // { ..., statName, statValue }
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue