Achievement & Event improvements
* User stat set vs user stat increment system events * Proper addMultipleEventListener() and removeMultipleEventListener() Events APIs * userStatSet vs userStatInc user stat achievement types. userStatInc for example can be used for door minutes used
This commit is contained in:
parent
925ca134c6
commit
34c9178099
|
@ -31,7 +31,7 @@
|
||||||
- Applying customizations via the achievements section in theme.hjson
|
- Applying customizations via the achievements section in theme.hjson
|
||||||
|
|
||||||
Some tips:
|
Some tips:
|
||||||
- For 'userStat' types, see user_property.js
|
- For 'userStatSet' types, see user_property.js
|
||||||
|
|
||||||
Don"t forget to RTFM ...er uh... see the documentation for more information and
|
Don"t forget to RTFM ...er uh... see the documentation for more information and
|
||||||
don"t be shy to ask for help:
|
don"t be shy to ask for help:
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
achievements: {
|
achievements: {
|
||||||
user_login_count: {
|
user_login_count: {
|
||||||
type: userStat
|
type: userStatSet
|
||||||
statName: login_count
|
statName: login_count
|
||||||
match: {
|
match: {
|
||||||
2: {
|
2: {
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
user_post_count: {
|
user_post_count: {
|
||||||
type: userStat
|
type: userStatSet
|
||||||
statName: post_count
|
statName: post_count
|
||||||
match: {
|
match: {
|
||||||
5: {
|
5: {
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
user_upload_count: {
|
user_upload_count: {
|
||||||
type: userStat
|
type: userStatSet
|
||||||
statName: ul_total_count
|
statName: ul_total_count
|
||||||
match: {
|
match: {
|
||||||
1: {
|
1: {
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
user_download_count: {
|
user_download_count: {
|
||||||
type: userStat
|
type: userStatSet
|
||||||
statName: dl_total_count
|
statName: dl_total_count
|
||||||
match: {
|
match: {
|
||||||
1: {
|
1: {
|
||||||
|
@ -196,7 +196,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
user_door_runs: {
|
user_door_runs: {
|
||||||
type: userStat
|
type: userStatSet
|
||||||
statName: door_run_total_count
|
statName: door_run_total_count
|
||||||
match: {
|
match: {
|
||||||
1: {
|
1: {
|
||||||
|
@ -233,7 +233,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
user_door_total_minutes: {
|
user_door_total_minutes: {
|
||||||
type: userStat
|
type: userStatInc
|
||||||
statName: door_run_total_minutes
|
statName: door_run_total_minutes
|
||||||
match: {
|
match: {
|
||||||
1: {
|
1: {
|
||||||
|
|
|
@ -45,7 +45,11 @@ class Achievement {
|
||||||
static factory(data) {
|
static factory(data) {
|
||||||
let achievement;
|
let achievement;
|
||||||
switch(data.type) {
|
switch(data.type) {
|
||||||
case Achievement.Types.UserStat : achievement = new UserStatAchievement(data); break;
|
case Achievement.Types.UserStatSet :
|
||||||
|
case Achievement.Types.UserStatInc :
|
||||||
|
achievement = new UserStatAchievement(data);
|
||||||
|
break;
|
||||||
|
|
||||||
default : return;
|
default : return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +60,15 @@ class Achievement {
|
||||||
|
|
||||||
static get Types() {
|
static get Types() {
|
||||||
return {
|
return {
|
||||||
UserStat : 'userStat',
|
UserStatSet : 'userStatSet',
|
||||||
|
UserStatInc : 'userStatInc',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
switch(this.data.type) {
|
switch(this.data.type) {
|
||||||
case Achievement.Types.UserStat :
|
case Achievement.Types.UserStatSet :
|
||||||
|
case Achievement.Types.UserStatInc :
|
||||||
if(!_.isString(this.data.statName)) {
|
if(!_.isString(this.data.statName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -129,12 +135,12 @@ class Achievements {
|
||||||
const configLoaded = (achievementConfig) => {
|
const configLoaded = (achievementConfig) => {
|
||||||
if(true !== achievementConfig.enabled) {
|
if(true !== achievementConfig.enabled) {
|
||||||
Log.info('Achievements are not enabled');
|
Log.info('Achievements are not enabled');
|
||||||
this.stopMonitoringUserStatUpdateEvents();
|
this.stopMonitoringUserStatEvents();
|
||||||
delete this.achievementConfig;
|
delete this.achievementConfig;
|
||||||
} else {
|
} else {
|
||||||
Log.info('Achievements are enabled');
|
Log.info('Achievements are enabled');
|
||||||
this.achievementConfig = achievementConfig;
|
this.achievementConfig = achievementConfig;
|
||||||
this.monitorUserStatUpdateEvents();
|
this.monitorUserStatEvents();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -240,18 +246,22 @@ class Achievements {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
monitorUserStatUpdateEvents() {
|
monitorUserStatEvents() {
|
||||||
if(this.userStatEventListener) {
|
if(this.userStatEventListeners) {
|
||||||
return; // already listening
|
return; // already listening
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStatEventListener = this.events.on(Events.getSystemEvents().UserStatUpdate, userStatEvent => {
|
const listenEvents = [
|
||||||
|
Events.getSystemEvents().UserStatSet,
|
||||||
|
Events.getSystemEvents().UserStatIncrement
|
||||||
|
];
|
||||||
|
|
||||||
|
this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => {
|
||||||
if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
|
if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const statValue = parseInt(userStatEvent.statValue, 10);
|
if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
|
||||||
if(isNaN(statValue)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +272,7 @@ class Achievements {
|
||||||
if(false === achievement.enabled) {
|
if(false === achievement.enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return Achievement.Types.UserStat === achievement.type &&
|
return [ Achievement.Types.UserStatSet, Achievement.Types.UserStatInc ].includes(achievement.type) &&
|
||||||
achievement.statName === userStatEvent.statName;
|
achievement.statName === userStatEvent.statName;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -276,6 +286,14 @@ class Achievements {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statValue = parseInt(
|
||||||
|
Achievement.Types.UserStatSet === achievement.data.type ? userStatEvent.statValue : userStatEvent.statIncrementBy,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
if(isNaN(statValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
|
const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
|
||||||
if(!details || _.isUndefined(matchField) || _.isUndefined(matchValue)) {
|
if(!details || _.isUndefined(matchField) || _.isUndefined(matchValue)) {
|
||||||
return;
|
return;
|
||||||
|
@ -345,10 +363,10 @@ class Achievements {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stopMonitoringUserStatUpdateEvents() {
|
stopMonitoringUserStatEvents() {
|
||||||
if(this.userStatEventListener) {
|
if(this.userStatEventListeners) {
|
||||||
this.events.removeListener(Events.getSystemEvents().UserStatUpdate, this.userStatEventListener);
|
this.events.removeMultipleEventListener(this.userStatEventListeners);
|
||||||
delete this.userStatEventListener;
|
delete this.userStatEventListeners;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
const { MenuModule } = require('./menu_module.js');
|
const { MenuModule } = require('./menu_module.js');
|
||||||
const { resetScreen } = require('./ansi_term.js');
|
const { resetScreen } = require('./ansi_term.js');
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
|
const Events = require('./events.js');
|
||||||
|
const StatLog = require('./stat_log.js');
|
||||||
|
const UserProps = require('./user_property.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const SSHClient = require('ssh2').Client;
|
const SSHClient = require('ssh2').Client;
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'DoorParty',
|
name : 'DoorParty',
|
||||||
|
@ -54,10 +58,18 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
||||||
|
|
||||||
let pipeRestored = false;
|
let pipeRestored = false;
|
||||||
let pipedStream;
|
let pipedStream;
|
||||||
|
const startTime = moment();
|
||||||
|
|
||||||
const restorePipe = function() {
|
const restorePipe = function() {
|
||||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||||
self.client.term.output.unpipe(pipedStream);
|
self.client.term.output.unpipe(pipedStream);
|
||||||
self.client.term.output.resume();
|
self.client.term.output.resume();
|
||||||
|
|
||||||
|
const endTime = moment();
|
||||||
|
const runTimeMinutes = Math.floor(moment.duration(endTime.diff(startTime)).asMinutes());
|
||||||
|
if(runTimeMinutes > 0) {
|
||||||
|
StatLog.incrementUserStat(self.client.user, UserProps.DoorRunTotalMinutes, runTimeMinutes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,6 +95,9 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
||||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||||
stream.write(rlogin);
|
stream.write(rlogin);
|
||||||
|
|
||||||
|
StatLog.incrementUserStat(self.client.user, UserProps.DoorRunTotalCount, 1);
|
||||||
|
Events.emit(Events.getSystemEvents().UserRunDoor, { user : self.client.user } );
|
||||||
|
|
||||||
pipedStream = stream; // :TODO: this is hacky...
|
pipedStream = stream; // :TODO: this is hacky...
|
||||||
self.client.term.output.pipe(stream);
|
self.client.term.output.pipe(stream);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ const events = require('events');
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger.js').log;
|
||||||
const SystemEvents = require('./system_events.js');
|
const SystemEvents = require('./system_events.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = new class Events extends events.EventEmitter {
|
module.exports = new class Events extends events.EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -35,12 +38,30 @@ module.exports = new class Events extends events.EventEmitter {
|
||||||
return super.once(event, listener);
|
return super.once(event, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
addListenerMultipleEvents(events, listener) {
|
//
|
||||||
Log.trace( { events }, 'Registring event listeners');
|
// Listen to multiple events for a single listener.
|
||||||
|
// Called with: listener(event, eventName)
|
||||||
|
//
|
||||||
|
// The returned object must be used with removeMultipleEventListener()
|
||||||
|
//
|
||||||
|
addMultipleEventListener(events, listener) {
|
||||||
|
Log.trace( { events }, 'Registering event listeners');
|
||||||
|
|
||||||
|
const listeners = [];
|
||||||
|
|
||||||
events.forEach(eventName => {
|
events.forEach(eventName => {
|
||||||
this.on(eventName, event => {
|
const listenWrapper = _.partial(listener, _, eventName);
|
||||||
listener(eventName, event);
|
this.on(eventName, listenWrapper);
|
||||||
|
listeners.push( { eventName, listenWrapper } );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMultipleEventListener(listeners) {
|
||||||
|
Log.trace( { events }, 'Removing listeners');
|
||||||
|
listeners.forEach(listener => {
|
||||||
|
this.removeListener(listener.eventName, listener.listenWrapper);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,12 +122,18 @@ class StatLog {
|
||||||
// User specific stats
|
// User specific stats
|
||||||
// These are simply convenience methods to the user's properties
|
// These are simply convenience methods to the user's properties
|
||||||
//
|
//
|
||||||
setUserStat(user, statName, statValue, cb) {
|
setUserStatWithOptions(user, statName, statValue, options, cb) {
|
||||||
// note: cb is optional in PersistUserProperty
|
// note: cb is optional in PersistUserProperty
|
||||||
user.persistProperty(statName, statValue, cb);
|
user.persistProperty(statName, statValue, cb);
|
||||||
|
|
||||||
|
if(!options.noEvent) {
|
||||||
const Events = require('./events.js'); // we need to late load currently
|
const Events = require('./events.js'); // we need to late load currently
|
||||||
return Events.emit(Events.getSystemEvents().UserStatUpdate, { user, statName, statValue } );
|
Events.emit(Events.getSystemEvents().UserStatSet, { user, statName, statValue } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserStat(user, statName, statValue, cb) {
|
||||||
|
return this.setUserStatWithOptions(user, statName, statValue, {}, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserStat(user, statName) {
|
getUserStat(user, statName) {
|
||||||
|
@ -143,16 +149,27 @@ class StatLog {
|
||||||
|
|
||||||
let newValue = parseInt(user.properties[statName]);
|
let newValue = parseInt(user.properties[statName]);
|
||||||
if(newValue) {
|
if(newValue) {
|
||||||
if(!_.isNumber(newValue)) {
|
if(!_.isNumber(newValue) && cb) {
|
||||||
return cb(new Error(`Value for ${statName} is not a number!`));
|
return cb(new Error(`Value for ${statName} is not a number!`));
|
||||||
}
|
}
|
||||||
|
|
||||||
newValue += incrementBy;
|
newValue += incrementBy;
|
||||||
} else {
|
} else {
|
||||||
newValue = incrementBy;
|
newValue = incrementBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setUserStat(user, statName, newValue, cb);
|
this.setUserStatWithOptions(user, statName, newValue, { noEvent : true }, err => {
|
||||||
|
if(!err) {
|
||||||
|
const Events = require('./events.js'); // we need to late load currently
|
||||||
|
Events.emit(
|
||||||
|
Events.getSystemEvents().UserStatIncrement,
|
||||||
|
{ user, statName, statIncrementBy: incrementBy, statValue : newValue }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cb) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// the time "now" in the ISO format we use and love :)
|
// the time "now" in the ISO format we use and love :)
|
||||||
|
@ -362,7 +379,7 @@ class StatLog {
|
||||||
systemEvents.UserAchievementEarned,
|
systemEvents.UserAchievementEarned,
|
||||||
];
|
];
|
||||||
|
|
||||||
Events.addListenerMultipleEvents(interestedEvents, (eventName, event) => {
|
Events.addMultipleEventListener(interestedEvents, (event, eventName) => {
|
||||||
this.appendUserLogEntry(
|
this.appendUserLogEntry(
|
||||||
event.user,
|
event.user,
|
||||||
'system_event',
|
'system_event',
|
||||||
|
|
|
@ -21,6 +21,7 @@ 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 }
|
UserStatSet : 'codes.l33t.enigma.system.user_stat_set', // { ..., statName, statValue }
|
||||||
|
UserStatIncrement : 'codes.l33t.enigma.system.user_stat_increment', // {..., statName, statIncrementBy, statValue }
|
||||||
UserAchievementEarned : 'codes.l33t.enigma.system.user_achievement_earned', // {..., achievementTag, points }
|
UserAchievementEarned : 'codes.l33t.enigma.system.user_achievement_earned', // {..., achievementTag, points }
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue