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
|
||||
|
||||
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 be shy to ask for help:
|
||||
|
@ -53,7 +53,7 @@
|
|||
|
||||
achievements: {
|
||||
user_login_count: {
|
||||
type: userStat
|
||||
type: userStatSet
|
||||
statName: login_count
|
||||
match: {
|
||||
2: {
|
||||
|
@ -90,7 +90,7 @@
|
|||
}
|
||||
|
||||
user_post_count: {
|
||||
type: userStat
|
||||
type: userStatSet
|
||||
statName: post_count
|
||||
match: {
|
||||
5: {
|
||||
|
@ -121,7 +121,7 @@
|
|||
}
|
||||
|
||||
user_upload_count: {
|
||||
type: userStat
|
||||
type: userStatSet
|
||||
statName: ul_total_count
|
||||
match: {
|
||||
1: {
|
||||
|
@ -159,7 +159,7 @@
|
|||
}
|
||||
|
||||
user_download_count: {
|
||||
type: userStat
|
||||
type: userStatSet
|
||||
statName: dl_total_count
|
||||
match: {
|
||||
1: {
|
||||
|
@ -196,7 +196,7 @@
|
|||
}
|
||||
|
||||
user_door_runs: {
|
||||
type: userStat
|
||||
type: userStatSet
|
||||
statName: door_run_total_count
|
||||
match: {
|
||||
1: {
|
||||
|
@ -233,7 +233,7 @@
|
|||
}
|
||||
|
||||
user_door_total_minutes: {
|
||||
type: userStat
|
||||
type: userStatInc
|
||||
statName: door_run_total_minutes
|
||||
match: {
|
||||
1: {
|
||||
|
|
|
@ -45,7 +45,11 @@ class Achievement {
|
|||
static factory(data) {
|
||||
let achievement;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -56,13 +60,15 @@ class Achievement {
|
|||
|
||||
static get Types() {
|
||||
return {
|
||||
UserStat : 'userStat',
|
||||
UserStatSet : 'userStatSet',
|
||||
UserStatInc : 'userStatInc',
|
||||
};
|
||||
}
|
||||
|
||||
isValid() {
|
||||
switch(this.data.type) {
|
||||
case Achievement.Types.UserStat :
|
||||
case Achievement.Types.UserStatSet :
|
||||
case Achievement.Types.UserStatInc :
|
||||
if(!_.isString(this.data.statName)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -129,12 +135,12 @@ class Achievements {
|
|||
const configLoaded = (achievementConfig) => {
|
||||
if(true !== achievementConfig.enabled) {
|
||||
Log.info('Achievements are not enabled');
|
||||
this.stopMonitoringUserStatUpdateEvents();
|
||||
this.stopMonitoringUserStatEvents();
|
||||
delete this.achievementConfig;
|
||||
} else {
|
||||
Log.info('Achievements are enabled');
|
||||
this.achievementConfig = achievementConfig;
|
||||
this.monitorUserStatUpdateEvents();
|
||||
this.monitorUserStatEvents();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -240,18 +246,22 @@ class Achievements {
|
|||
);
|
||||
}
|
||||
|
||||
monitorUserStatUpdateEvents() {
|
||||
if(this.userStatEventListener) {
|
||||
monitorUserStatEvents() {
|
||||
if(this.userStatEventListeners) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statValue = parseInt(userStatEvent.statValue, 10);
|
||||
if(isNaN(statValue)) {
|
||||
if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -262,7 +272,7 @@ class Achievements {
|
|||
if(false === achievement.enabled) {
|
||||
return false;
|
||||
}
|
||||
return Achievement.Types.UserStat === achievement.type &&
|
||||
return [ Achievement.Types.UserStatSet, Achievement.Types.UserStatInc ].includes(achievement.type) &&
|
||||
achievement.statName === userStatEvent.statName;
|
||||
}
|
||||
);
|
||||
|
@ -276,6 +286,14 @@ class Achievements {
|
|||
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);
|
||||
if(!details || _.isUndefined(matchField) || _.isUndefined(matchValue)) {
|
||||
return;
|
||||
|
@ -345,10 +363,10 @@ class Achievements {
|
|||
});
|
||||
}
|
||||
|
||||
stopMonitoringUserStatUpdateEvents() {
|
||||
if(this.userStatEventListener) {
|
||||
this.events.removeListener(Events.getSystemEvents().UserStatUpdate, this.userStatEventListener);
|
||||
delete this.userStatEventListener;
|
||||
stopMonitoringUserStatEvents() {
|
||||
if(this.userStatEventListeners) {
|
||||
this.events.removeMultipleEventListener(this.userStatEventListeners);
|
||||
delete this.userStatEventListeners;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
const { MenuModule } = require('./menu_module.js');
|
||||
const { resetScreen } = require('./ansi_term.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
|
||||
const async = require('async');
|
||||
const SSHClient = require('ssh2').Client;
|
||||
const moment = require('moment');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'DoorParty',
|
||||
|
@ -54,10 +58,18 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
|||
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
const startTime = moment();
|
||||
|
||||
const restorePipe = function() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
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`;
|
||||
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...
|
||||
self.client.term.output.pipe(stream);
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ const events = require('events');
|
|||
const Log = require('./logger.js').log;
|
||||
const SystemEvents = require('./system_events.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = new class Events extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -35,12 +38,30 @@ module.exports = new class Events extends events.EventEmitter {
|
|||
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 => {
|
||||
this.on(eventName, event => {
|
||||
listener(eventName, event);
|
||||
});
|
||||
const listenWrapper = _.partial(listener, _, eventName);
|
||||
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
|
||||
// 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
|
||||
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 } );
|
||||
if(!options.noEvent) {
|
||||
const Events = require('./events.js'); // we need to late load currently
|
||||
Events.emit(Events.getSystemEvents().UserStatSet, { user, statName, statValue } );
|
||||
}
|
||||
}
|
||||
|
||||
setUserStat(user, statName, statValue, cb) {
|
||||
return this.setUserStatWithOptions(user, statName, statValue, {}, cb);
|
||||
}
|
||||
|
||||
getUserStat(user, statName) {
|
||||
|
@ -143,16 +149,27 @@ class StatLog {
|
|||
|
||||
let newValue = parseInt(user.properties[statName]);
|
||||
if(newValue) {
|
||||
if(!_.isNumber(newValue)) {
|
||||
if(!_.isNumber(newValue) && cb) {
|
||||
return cb(new Error(`Value for ${statName} is not a number!`));
|
||||
}
|
||||
|
||||
newValue += incrementBy;
|
||||
} else {
|
||||
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 :)
|
||||
|
@ -362,7 +379,7 @@ class StatLog {
|
|||
systemEvents.UserAchievementEarned,
|
||||
];
|
||||
|
||||
Events.addListenerMultipleEvents(interestedEvents, (eventName, event) => {
|
||||
Events.addMultipleEventListener(interestedEvents, (event, eventName) => {
|
||||
this.appendUserLogEntry(
|
||||
event.user,
|
||||
'system_event',
|
||||
|
|
|
@ -21,6 +21,7 @@ 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 }
|
||||
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 }
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue