First pass formatting with Prettier
* Added .prettierrc.json * Added .prettierignore * Formatted
This commit is contained in:
@@ -2,38 +2,28 @@
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Events = require('./events.js');
|
||||
const Config = require('./config.js').get;
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const { getConfigPath } = require('./config_util');
|
||||
const UserDb = require('./database.js').dbs.user;
|
||||
const {
|
||||
getISOTimestampString
|
||||
} = require('./database.js');
|
||||
const UserInterruptQueue = require('./user_interrupt_queue.js');
|
||||
const {
|
||||
getConnectionByUserId
|
||||
} = require('./client_connections.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const {
|
||||
Errors,
|
||||
ErrorReasons
|
||||
} = require('./enig_error.js');
|
||||
const { getThemeArt } = require('./theme.js');
|
||||
const {
|
||||
pipeToAnsi,
|
||||
stripMciColorCodes
|
||||
} = require('./color_codes.js');
|
||||
const stringFormat = require('./string_format.js');
|
||||
const StatLog = require('./stat_log.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const Events = require('./events.js');
|
||||
const Config = require('./config.js').get;
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const { getConfigPath } = require('./config_util');
|
||||
const UserDb = require('./database.js').dbs.user;
|
||||
const { getISOTimestampString } = require('./database.js');
|
||||
const UserInterruptQueue = require('./user_interrupt_queue.js');
|
||||
const { getConnectionByUserId } = require('./client_connections.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const { Errors, ErrorReasons } = require('./enig_error.js');
|
||||
const { getThemeArt } = require('./theme.js');
|
||||
const { pipeToAnsi, stripMciColorCodes } = require('./color_codes.js');
|
||||
const stringFormat = require('./string_format.js');
|
||||
const StatLog = require('./stat_log.js');
|
||||
const Log = require('./logger.js').log;
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const moment = require('moment');
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const moment = require('moment');
|
||||
|
||||
exports.getAchievementsEarnedByUser = getAchievementsEarnedByUser;
|
||||
exports.getAchievementsEarnedByUser = getAchievementsEarnedByUser;
|
||||
|
||||
class Achievement {
|
||||
constructor(data) {
|
||||
@@ -44,59 +34,65 @@ class Achievement {
|
||||
}
|
||||
|
||||
static factory(data) {
|
||||
if(!data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let achievement;
|
||||
switch(data.type) {
|
||||
case Achievement.Types.UserStatSet :
|
||||
case Achievement.Types.UserStatInc :
|
||||
case Achievement.Types.UserStatIncNewVal :
|
||||
switch (data.type) {
|
||||
case Achievement.Types.UserStatSet:
|
||||
case Achievement.Types.UserStatInc:
|
||||
case Achievement.Types.UserStatIncNewVal:
|
||||
achievement = new UserStatAchievement(data);
|
||||
break;
|
||||
|
||||
default : return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if(achievement.isValid()) {
|
||||
if (achievement.isValid()) {
|
||||
return achievement;
|
||||
}
|
||||
}
|
||||
|
||||
static get Types() {
|
||||
return {
|
||||
UserStatSet : 'userStatSet',
|
||||
UserStatInc : 'userStatInc',
|
||||
UserStatIncNewVal : 'userStatIncNewVal',
|
||||
UserStatSet: 'userStatSet',
|
||||
UserStatInc: 'userStatInc',
|
||||
UserStatIncNewVal: 'userStatIncNewVal',
|
||||
};
|
||||
}
|
||||
|
||||
isValid() {
|
||||
switch(this.data.type) {
|
||||
case Achievement.Types.UserStatSet :
|
||||
case Achievement.Types.UserStatInc :
|
||||
case Achievement.Types.UserStatIncNewVal :
|
||||
if(!_.isString(this.data.statName)) {
|
||||
switch (this.data.type) {
|
||||
case Achievement.Types.UserStatSet:
|
||||
case Achievement.Types.UserStatInc:
|
||||
case Achievement.Types.UserStatIncNewVal:
|
||||
if (!_.isString(this.data.statName)) {
|
||||
return false;
|
||||
}
|
||||
if(!_.isObject(this.data.match)) {
|
||||
if (!_.isObject(this.data.match)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default : return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getMatchDetails(/*matchAgainst*/) {
|
||||
}
|
||||
getMatchDetails(/*matchAgainst*/) {}
|
||||
|
||||
isValidMatchDetails(details) {
|
||||
if(!details || !_.isString(details.title) || !_.isString(details.text) || !_.isNumber(details.points)) {
|
||||
if (
|
||||
!details ||
|
||||
!_.isString(details.title) ||
|
||||
!_.isString(details.text) ||
|
||||
!_.isNumber(details.points)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (_.isString(details.globalText) || !details.globalText);
|
||||
return _.isString(details.globalText) || !details.globalText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +101,13 @@ class UserStatAchievement extends Achievement {
|
||||
super(data);
|
||||
|
||||
// sort match keys for quick match lookup
|
||||
this.matchKeys = Object.keys(this.data.match || {}).map(k => parseInt(k)).sort( (a, b) => b - a);
|
||||
this.matchKeys = Object.keys(this.data.match || {})
|
||||
.map(k => parseInt(k))
|
||||
.sort((a, b) => b - a);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
if(!super.isValid()) {
|
||||
if (!super.isValid()) {
|
||||
return false;
|
||||
}
|
||||
return !Object.keys(this.data.match).some(k => !parseInt(k));
|
||||
@@ -118,11 +116,11 @@ class UserStatAchievement extends Achievement {
|
||||
getMatchDetails(matchValue) {
|
||||
let ret = [];
|
||||
let matchField = this.matchKeys.find(v => matchValue >= v);
|
||||
if(matchField) {
|
||||
if (matchField) {
|
||||
const match = this.data.match[matchField];
|
||||
matchField = parseInt(matchField);
|
||||
if(this.isValidMatchDetails(match) && !isNaN(matchField)) {
|
||||
ret = [ match, matchField, matchValue ];
|
||||
if (this.isValidMatchDetails(match) && !isNaN(matchField)) {
|
||||
ret = [match, matchField, matchValue];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -151,7 +149,7 @@ class Achievements {
|
||||
}
|
||||
|
||||
const configLoaded = () => {
|
||||
if(true !== this.config.get().enabled) {
|
||||
if (true !== this.config.get().enabled) {
|
||||
Log.info('Achievements are not enabled');
|
||||
this.enabled = false;
|
||||
this.stopMonitoringUserStatEvents();
|
||||
@@ -163,11 +161,11 @@ class Achievements {
|
||||
};
|
||||
|
||||
this.config = new ConfigLoader({
|
||||
onReload : err => {
|
||||
onReload: err => {
|
||||
if (!err) {
|
||||
configLoaded();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.config.init(configPath, err => {
|
||||
@@ -182,10 +180,10 @@ class Achievements {
|
||||
|
||||
_getConfigPath() {
|
||||
const path = _.get(Config(), 'general.achievementFile');
|
||||
if(!path) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
return getConfigPath(path); // qualify
|
||||
return getConfigPath(path); // qualify
|
||||
}
|
||||
|
||||
loadAchievementHitCount(user, achievementTag, field, cb) {
|
||||
@@ -193,7 +191,7 @@ class Achievements {
|
||||
`SELECT COUNT() AS count
|
||||
FROM user_achievement
|
||||
WHERE user_id = ? AND achievement_tag = ? AND match = ?;`,
|
||||
[ user.userId, achievementTag, field],
|
||||
[user.userId, achievementTag, field],
|
||||
(err, row) => {
|
||||
return cb(err, row ? row.count : 0);
|
||||
}
|
||||
@@ -202,14 +200,23 @@ class Achievements {
|
||||
|
||||
record(info, localInterruptItem, cb) {
|
||||
StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalCount, 1);
|
||||
StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalPoints, info.details.points);
|
||||
StatLog.incrementUserStat(
|
||||
info.client.user,
|
||||
UserProps.AchievementTotalPoints,
|
||||
info.details.points
|
||||
);
|
||||
|
||||
const cleanTitle = stripMciColorCodes(localInterruptItem.title);
|
||||
const cleanText = stripMciColorCodes(localInterruptItem.achievText);
|
||||
const cleanTitle = stripMciColorCodes(localInterruptItem.title);
|
||||
const cleanText = stripMciColorCodes(localInterruptItem.achievText);
|
||||
|
||||
const recordData = [
|
||||
info.client.user.userId, info.achievementTag, getISOTimestampString(info.timestamp), info.matchField,
|
||||
cleanTitle, cleanText, info.details.points,
|
||||
info.client.user.userId,
|
||||
info.achievementTag,
|
||||
getISOTimestampString(info.timestamp),
|
||||
info.matchField,
|
||||
cleanTitle,
|
||||
cleanText,
|
||||
info.details.points,
|
||||
];
|
||||
|
||||
UserDb.run(
|
||||
@@ -217,20 +224,17 @@ class Achievements {
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);`,
|
||||
recordData,
|
||||
err => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.events.emit(
|
||||
Events.getSystemEvents().UserAchievementEarned,
|
||||
{
|
||||
user : info.client.user,
|
||||
achievementTag : info.achievementTag,
|
||||
points : info.details.points,
|
||||
title : cleanTitle,
|
||||
text : cleanText,
|
||||
}
|
||||
);
|
||||
this.events.emit(Events.getSystemEvents().UserAchievementEarned, {
|
||||
user: info.client.user,
|
||||
achievementTag: info.achievementTag,
|
||||
points: info.details.points,
|
||||
title: cleanTitle,
|
||||
text: cleanText,
|
||||
});
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
@@ -238,12 +242,12 @@ class Achievements {
|
||||
}
|
||||
|
||||
display(info, interruptItems, cb) {
|
||||
if(interruptItems.local) {
|
||||
UserInterruptQueue.queue(interruptItems.local, { clients : info.client } );
|
||||
if (interruptItems.local) {
|
||||
UserInterruptQueue.queue(interruptItems.local, { clients: info.client });
|
||||
}
|
||||
|
||||
if(interruptItems.global) {
|
||||
UserInterruptQueue.queue(interruptItems.global, { omit : info.client } );
|
||||
if (interruptItems.global) {
|
||||
UserInterruptQueue.queue(interruptItems.global, { omit: info.client });
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
@@ -252,7 +256,7 @@ class Achievements {
|
||||
recordAndDisplayAchievement(info, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
callback => {
|
||||
return this.createAchievementInterruptItems(info, callback);
|
||||
},
|
||||
(interruptItems, callback) => {
|
||||
@@ -262,7 +266,7 @@ class Achievements {
|
||||
},
|
||||
(interruptItems, callback) => {
|
||||
return this.display(info, interruptItems, callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
@@ -271,164 +275,228 @@ class Achievements {
|
||||
}
|
||||
|
||||
monitorUserStatEvents() {
|
||||
if(this.userStatEventListeners) {
|
||||
if (this.userStatEventListeners) {
|
||||
return; // already listening
|
||||
}
|
||||
|
||||
const listenEvents = [
|
||||
Events.getSystemEvents().UserStatSet,
|
||||
Events.getSystemEvents().UserStatIncrement
|
||||
Events.getSystemEvents().UserStatIncrement,
|
||||
];
|
||||
|
||||
this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => {
|
||||
if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// :TODO: Make this code generic - find + return factory created object
|
||||
const achievementTags = Object.keys(_.pickBy(
|
||||
_.get(this.config.get(), 'achievements', {}),
|
||||
achievement => {
|
||||
if(false === achievement.enabled) {
|
||||
return false;
|
||||
}
|
||||
const acceptedTypes = [
|
||||
Achievement.Types.UserStatSet,
|
||||
Achievement.Types.UserStatInc,
|
||||
Achievement.Types.UserStatIncNewVal,
|
||||
];
|
||||
return acceptedTypes.includes(achievement.type) && achievement.statName === userStatEvent.statName;
|
||||
}
|
||||
));
|
||||
|
||||
if(0 === achievementTags.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
async.eachSeries(achievementTags, (achievementTag, nextAchievementTag) => {
|
||||
const achievement = Achievement.factory(this.getAchievementByTag(achievementTag));
|
||||
if(!achievement) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
const statValue = parseInt(
|
||||
[ Achievement.Types.UserStatSet, Achievement.Types.UserStatIncNewVal ].includes(achievement.data.type) ?
|
||||
userStatEvent.statValue :
|
||||
userStatEvent.statIncrementBy
|
||||
);
|
||||
if(isNaN(statValue)) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
|
||||
if(!details) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
async.waterfall(
|
||||
this.userStatEventListeners = this.events.addMultipleEventListener(
|
||||
listenEvents,
|
||||
userStatEvent => {
|
||||
if (
|
||||
[
|
||||
(callback) => {
|
||||
this.loadAchievementHitCount(userStatEvent.user, achievementTag, matchField, (err, count) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(count > 0 ? Errors.General('Achievement already acquired', ErrorReasons.TooMany) : null);
|
||||
});
|
||||
},
|
||||
(callback) => {
|
||||
const client = getConnectionByUserId(userStatEvent.user.userId);
|
||||
if(!client) {
|
||||
return callback(Errors.UnexpectedState('Failed to get client for user ID'));
|
||||
UserProps.AchievementTotalCount,
|
||||
UserProps.AchievementTotalPoints,
|
||||
].includes(userStatEvent.statName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!_.isNumber(userStatEvent.statValue) &&
|
||||
!_.isNumber(userStatEvent.statIncrementBy)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// :TODO: Make this code generic - find + return factory created object
|
||||
const achievementTags = Object.keys(
|
||||
_.pickBy(
|
||||
_.get(this.config.get(), 'achievements', {}),
|
||||
achievement => {
|
||||
if (false === achievement.enabled) {
|
||||
return false;
|
||||
}
|
||||
const acceptedTypes = [
|
||||
Achievement.Types.UserStatSet,
|
||||
Achievement.Types.UserStatInc,
|
||||
Achievement.Types.UserStatIncNewVal,
|
||||
];
|
||||
return (
|
||||
acceptedTypes.includes(achievement.type) &&
|
||||
achievement.statName === userStatEvent.statName
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const info = {
|
||||
achievementTag,
|
||||
achievement,
|
||||
details,
|
||||
client,
|
||||
matchField, // match - may be in odd format
|
||||
matchValue, // actual value
|
||||
achievedValue : matchField, // achievement value met
|
||||
user : userStatEvent.user,
|
||||
timestamp : moment(),
|
||||
};
|
||||
if (0 === achievementTags.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const achievementsInfo = [ info ];
|
||||
return callback(null, achievementsInfo, info);
|
||||
},
|
||||
(achievementsInfo, basicInfo, callback) => {
|
||||
if(true !== achievement.data.retroactive) {
|
||||
return callback(null, achievementsInfo);
|
||||
}
|
||||
async.eachSeries(
|
||||
achievementTags,
|
||||
(achievementTag, nextAchievementTag) => {
|
||||
const achievement = Achievement.factory(
|
||||
this.getAchievementByTag(achievementTag)
|
||||
);
|
||||
if (!achievement) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
const index = achievement.matchKeys.findIndex(v => v < matchField);
|
||||
if(-1 === index || !Array.isArray(achievement.matchKeys)) {
|
||||
return callback(null, achievementsInfo);
|
||||
}
|
||||
const statValue = parseInt(
|
||||
[
|
||||
Achievement.Types.UserStatSet,
|
||||
Achievement.Types.UserStatIncNewVal,
|
||||
].includes(achievement.data.type)
|
||||
? userStatEvent.statValue
|
||||
: userStatEvent.statIncrementBy
|
||||
);
|
||||
if (isNaN(statValue)) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
// For userStat, any lesser match keys(values) are also met. Example:
|
||||
// matchKeys: [ 500, 200, 100, 20, 10, 2 ]
|
||||
// ^---- we met here
|
||||
// ^------------^ retroactive range
|
||||
//
|
||||
async.eachSeries(achievement.matchKeys.slice(index), (k, nextKey) => {
|
||||
const [ det, fld, val ] = achievement.getMatchDetails(k);
|
||||
if(!det) {
|
||||
return nextKey(null);
|
||||
}
|
||||
const [details, matchField, matchValue] =
|
||||
achievement.getMatchDetails(statValue);
|
||||
if (!details) {
|
||||
return nextAchievementTag(null);
|
||||
}
|
||||
|
||||
this.loadAchievementHitCount(userStatEvent.user, achievementTag, fld, (err, count) => {
|
||||
if(!err || count && 0 === count) {
|
||||
achievementsInfo.push(Object.assign(
|
||||
{},
|
||||
basicInfo,
|
||||
{
|
||||
details : det,
|
||||
matchField : fld,
|
||||
achievedValue : fld,
|
||||
matchValue : val,
|
||||
async.waterfall(
|
||||
[
|
||||
callback => {
|
||||
this.loadAchievementHitCount(
|
||||
userStatEvent.user,
|
||||
achievementTag,
|
||||
matchField,
|
||||
(err, count) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
));
|
||||
return callback(
|
||||
count > 0
|
||||
? Errors.General(
|
||||
'Achievement already acquired',
|
||||
ErrorReasons.TooMany
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
callback => {
|
||||
const client = getConnectionByUserId(
|
||||
userStatEvent.user.userId
|
||||
);
|
||||
if (!client) {
|
||||
return callback(
|
||||
Errors.UnexpectedState(
|
||||
'Failed to get client for user ID'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return nextKey(null);
|
||||
});
|
||||
},
|
||||
() => {
|
||||
return callback(null, achievementsInfo);
|
||||
});
|
||||
},
|
||||
(achievementsInfo, callback) => {
|
||||
// reverse achievementsInfo so we display smallest > largest
|
||||
achievementsInfo.reverse();
|
||||
const info = {
|
||||
achievementTag,
|
||||
achievement,
|
||||
details,
|
||||
client,
|
||||
matchField, // match - may be in odd format
|
||||
matchValue, // actual value
|
||||
achievedValue: matchField, // achievement value met
|
||||
user: userStatEvent.user,
|
||||
timestamp: moment(),
|
||||
};
|
||||
|
||||
async.eachSeries(achievementsInfo, (achInfo, nextAchInfo) => {
|
||||
return this.recordAndDisplayAchievement(achInfo, err => {
|
||||
return nextAchInfo(err);
|
||||
});
|
||||
},
|
||||
const achievementsInfo = [info];
|
||||
return callback(null, achievementsInfo, info);
|
||||
},
|
||||
(achievementsInfo, basicInfo, callback) => {
|
||||
if (true !== achievement.data.retroactive) {
|
||||
return callback(null, achievementsInfo);
|
||||
}
|
||||
|
||||
const index = achievement.matchKeys.findIndex(
|
||||
v => v < matchField
|
||||
);
|
||||
if (
|
||||
-1 === index ||
|
||||
!Array.isArray(achievement.matchKeys)
|
||||
) {
|
||||
return callback(null, achievementsInfo);
|
||||
}
|
||||
|
||||
// For userStat, any lesser match keys(values) are also met. Example:
|
||||
// matchKeys: [ 500, 200, 100, 20, 10, 2 ]
|
||||
// ^---- we met here
|
||||
// ^------------^ retroactive range
|
||||
//
|
||||
async.eachSeries(
|
||||
achievement.matchKeys.slice(index),
|
||||
(k, nextKey) => {
|
||||
const [det, fld, val] =
|
||||
achievement.getMatchDetails(k);
|
||||
if (!det) {
|
||||
return nextKey(null);
|
||||
}
|
||||
|
||||
this.loadAchievementHitCount(
|
||||
userStatEvent.user,
|
||||
achievementTag,
|
||||
fld,
|
||||
(err, count) => {
|
||||
if (!err || (count && 0 === count)) {
|
||||
achievementsInfo.push(
|
||||
Object.assign({}, basicInfo, {
|
||||
details: det,
|
||||
matchField: fld,
|
||||
achievedValue: fld,
|
||||
matchValue: val,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return nextKey(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
() => {
|
||||
return callback(null, achievementsInfo);
|
||||
}
|
||||
);
|
||||
},
|
||||
(achievementsInfo, callback) => {
|
||||
// reverse achievementsInfo so we display smallest > largest
|
||||
achievementsInfo.reverse();
|
||||
|
||||
async.eachSeries(
|
||||
achievementsInfo,
|
||||
(achInfo, nextAchInfo) => {
|
||||
return this.recordAndDisplayAchievement(
|
||||
achInfo,
|
||||
err => {
|
||||
return nextAchInfo(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err && ErrorReasons.TooMany !== err.reasonCode) {
|
||||
Log.warn( { error : err.message, userStatEvent }, 'Error handling achievement for user stat event');
|
||||
}
|
||||
return nextAchievementTag(null); // always try the next, regardless
|
||||
if (err && ErrorReasons.TooMany !== err.reasonCode) {
|
||||
Log.warn(
|
||||
{ error: err.message, userStatEvent },
|
||||
'Error handling achievement for user stat event'
|
||||
);
|
||||
}
|
||||
return nextAchievementTag(null); // always try the next, regardless
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
stopMonitoringUserStatEvents() {
|
||||
if(this.userStatEventListeners) {
|
||||
if (this.userStatEventListeners) {
|
||||
this.events.removeMultipleEventListener(this.userStatEventListeners);
|
||||
delete this.userStatEventListeners;
|
||||
}
|
||||
@@ -436,34 +504,38 @@ class Achievements {
|
||||
|
||||
getFormatObject(info) {
|
||||
return {
|
||||
userName : info.user.username,
|
||||
userRealName : info.user.properties[UserProps.RealName],
|
||||
userLocation : info.user.properties[UserProps.Location],
|
||||
userAffils : info.user.properties[UserProps.Affiliations],
|
||||
nodeId : info.client.node,
|
||||
title : info.details.title,
|
||||
userName: info.user.username,
|
||||
userRealName: info.user.properties[UserProps.RealName],
|
||||
userLocation: info.user.properties[UserProps.Location],
|
||||
userAffils: info.user.properties[UserProps.Affiliations],
|
||||
nodeId: info.client.node,
|
||||
title: info.details.title,
|
||||
//text : info.global ? info.details.globalText : info.details.text,
|
||||
points : info.details.points,
|
||||
achievedValue : info.achievedValue,
|
||||
matchField : info.matchField,
|
||||
matchValue : info.matchValue,
|
||||
timestamp : moment(info.timestamp).format(info.dateTimeFormat),
|
||||
boardName : Config().general.boardName,
|
||||
points: info.details.points,
|
||||
achievedValue: info.achievedValue,
|
||||
matchField: info.matchField,
|
||||
matchValue: info.matchValue,
|
||||
timestamp: moment(info.timestamp).format(info.dateTimeFormat),
|
||||
boardName: Config().general.boardName,
|
||||
};
|
||||
}
|
||||
|
||||
getFormattedTextFor(info, textType, defaultSgr = '|07') {
|
||||
const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
|
||||
const textTypeSgr = themeDefaults[`${textType}SGR`] || defaultSgr;
|
||||
const themeDefaults = _.get(
|
||||
info.client.currentTheme,
|
||||
'achievements.defaults',
|
||||
{}
|
||||
);
|
||||
const textTypeSgr = themeDefaults[`${textType}SGR`] || defaultSgr;
|
||||
|
||||
const formatObj = this.getFormatObject(info);
|
||||
|
||||
const wrap = (input) => {
|
||||
const wrap = input => {
|
||||
const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g');
|
||||
return input.replace(re, (m, formatVar, formatOpts) => {
|
||||
const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr;
|
||||
let r = `${varSgr}{${formatVar}`;
|
||||
if(formatOpts) {
|
||||
if (formatOpts) {
|
||||
r += formatOpts;
|
||||
}
|
||||
return `${r}}${textTypeSgr}`;
|
||||
@@ -480,10 +552,10 @@ class Achievements {
|
||||
info.client.currentTheme.helpers.getDateTimeFormat();
|
||||
|
||||
const title = this.getFormattedTextFor(info, 'title');
|
||||
const text = this.getFormattedTextFor(info, 'text');
|
||||
const text = this.getFormattedTextFor(info, 'text');
|
||||
|
||||
let globalText;
|
||||
if(info.details.globalText) {
|
||||
if (info.details.globalText) {
|
||||
globalText = this.getFormattedTextFor(info, 'globalText');
|
||||
}
|
||||
|
||||
@@ -492,13 +564,13 @@ class Achievements {
|
||||
_.get(info.details, `art.${name}`) ||
|
||||
_.get(info.achievement, `art.${name}`) ||
|
||||
_.get(this.config.get(), `art.${name}`);
|
||||
if(!spec) {
|
||||
if (!spec) {
|
||||
return callback(null);
|
||||
}
|
||||
const getArtOpts = {
|
||||
name : spec,
|
||||
client : this.client,
|
||||
random : false,
|
||||
name: spec,
|
||||
client: this.client,
|
||||
random: false,
|
||||
};
|
||||
getThemeArt(getArtOpts, (err, artInfo) => {
|
||||
// ignore errors
|
||||
@@ -507,67 +579,86 @@ class Achievements {
|
||||
};
|
||||
|
||||
const interruptItems = {};
|
||||
let itemTypes = [ 'local' ];
|
||||
if(globalText) {
|
||||
let itemTypes = ['local'];
|
||||
if (globalText) {
|
||||
itemTypes.push('global');
|
||||
}
|
||||
|
||||
async.each(itemTypes, (itemType, nextItemType) => {
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
getArt(`${itemType}Header`, headerArt => {
|
||||
return callback(null, headerArt);
|
||||
});
|
||||
},
|
||||
(headerArt, callback) => {
|
||||
getArt(`${itemType}Footer`, footerArt => {
|
||||
return callback(null, headerArt, footerArt);
|
||||
});
|
||||
},
|
||||
(headerArt, footerArt, callback) => {
|
||||
const itemText = 'global' === itemType ? globalText : text;
|
||||
interruptItems[itemType] = {
|
||||
title,
|
||||
achievText : itemText,
|
||||
text : `${title}\r\n${itemText}`,
|
||||
pause : true,
|
||||
};
|
||||
if(headerArt || footerArt) {
|
||||
const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
|
||||
const defaultContentsFormat = '{title}\r\n{message}';
|
||||
const contentsFormat = 'global' === itemType ?
|
||||
themeDefaults.globalFormat || defaultContentsFormat :
|
||||
themeDefaults.format || defaultContentsFormat;
|
||||
|
||||
const formatObj = Object.assign(this.getFormatObject(info), {
|
||||
title : this.getFormattedTextFor(info, 'title', ''), // ''=defaultSgr
|
||||
message : itemText,
|
||||
async.each(
|
||||
itemTypes,
|
||||
(itemType, nextItemType) => {
|
||||
async.waterfall(
|
||||
[
|
||||
callback => {
|
||||
getArt(`${itemType}Header`, headerArt => {
|
||||
return callback(null, headerArt);
|
||||
});
|
||||
},
|
||||
(headerArt, callback) => {
|
||||
getArt(`${itemType}Footer`, footerArt => {
|
||||
return callback(null, headerArt, footerArt);
|
||||
});
|
||||
},
|
||||
(headerArt, footerArt, callback) => {
|
||||
const itemText = 'global' === itemType ? globalText : text;
|
||||
interruptItems[itemType] = {
|
||||
title,
|
||||
achievText: itemText,
|
||||
text: `${title}\r\n${itemText}`,
|
||||
pause: true,
|
||||
};
|
||||
if (headerArt || footerArt) {
|
||||
const themeDefaults = _.get(
|
||||
info.client.currentTheme,
|
||||
'achievements.defaults',
|
||||
{}
|
||||
);
|
||||
const defaultContentsFormat = '{title}\r\n{message}';
|
||||
const contentsFormat =
|
||||
'global' === itemType
|
||||
? themeDefaults.globalFormat ||
|
||||
defaultContentsFormat
|
||||
: themeDefaults.format || defaultContentsFormat;
|
||||
|
||||
const contents = pipeToAnsi(stringFormat(contentsFormat, formatObj));
|
||||
const formatObj = Object.assign(
|
||||
this.getFormatObject(info),
|
||||
{
|
||||
title: this.getFormattedTextFor(
|
||||
info,
|
||||
'title',
|
||||
''
|
||||
), // ''=defaultSgr
|
||||
message: itemText,
|
||||
}
|
||||
);
|
||||
|
||||
interruptItems[itemType].contents =
|
||||
`${headerArt || ''}\r\n${contents}\r\n${footerArt || ''}`;
|
||||
}
|
||||
return callback(null);
|
||||
const contents = pipeToAnsi(
|
||||
stringFormat(contentsFormat, formatObj)
|
||||
);
|
||||
|
||||
interruptItems[itemType].contents = `${
|
||||
headerArt || ''
|
||||
}\r\n${contents}\r\n${footerArt || ''}`;
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return nextItemType(err);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return nextItemType(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
err => {
|
||||
return cb(err, interruptItems);
|
||||
});
|
||||
);
|
||||
},
|
||||
err => {
|
||||
return cb(err, interruptItems);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let achievementsInstance;
|
||||
|
||||
function getAchievementsEarnedByUser(userId, cb) {
|
||||
if(!achievementsInstance) {
|
||||
if (!achievementsInstance) {
|
||||
return cb(Errors.UnexpectedState('Achievements not initialized'));
|
||||
}
|
||||
|
||||
@@ -576,39 +667,42 @@ function getAchievementsEarnedByUser(userId, cb) {
|
||||
FROM user_achievement
|
||||
WHERE user_id = ?
|
||||
ORDER BY DATETIME(timestamp);`,
|
||||
[ userId ],
|
||||
[userId],
|
||||
(err, rows) => {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const earned = rows.map(row => {
|
||||
const earned = rows
|
||||
.map(row => {
|
||||
const achievement = Achievement.factory(
|
||||
achievementsInstance.getAchievementByTag(row.achievement_tag)
|
||||
);
|
||||
if (!achievement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const achievement = Achievement.factory(achievementsInstance.getAchievementByTag(row.achievement_tag));
|
||||
if(!achievement) {
|
||||
return;
|
||||
}
|
||||
const earnedInfo = {
|
||||
achievementTag: row.achievement_tag,
|
||||
type: achievement.data.type,
|
||||
retroactive: achievement.data.retroactive,
|
||||
title: row.title,
|
||||
text: row.text,
|
||||
points: row.points,
|
||||
timestamp: moment(row.timestamp),
|
||||
};
|
||||
|
||||
const earnedInfo = {
|
||||
achievementTag : row.achievement_tag,
|
||||
type : achievement.data.type,
|
||||
retroactive : achievement.data.retroactive,
|
||||
title : row.title,
|
||||
text : row.text,
|
||||
points : row.points,
|
||||
timestamp : moment(row.timestamp),
|
||||
};
|
||||
switch (earnedInfo.type) {
|
||||
case [Achievement.Types.UserStatSet]:
|
||||
case [Achievement.Types.UserStatInc]:
|
||||
case [Achievement.Types.UserStatIncNewVal]:
|
||||
earnedInfo.statName = achievement.data.statName;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(earnedInfo.type) {
|
||||
case [ Achievement.Types.UserStatSet ] :
|
||||
case [ Achievement.Types.UserStatInc ] :
|
||||
case [ Achievement.Types.UserStatIncNewVal ] :
|
||||
earnedInfo.statName = achievement.data.statName;
|
||||
break;
|
||||
}
|
||||
|
||||
return earnedInfo;
|
||||
}).filter(a => a); // remove any empty records (ie: no achievement.hjson entry exists anymore).
|
||||
return earnedInfo;
|
||||
})
|
||||
.filter(a => a); // remove any empty records (ie: no achievement.hjson entry exists anymore).
|
||||
|
||||
return cb(null, earned);
|
||||
}
|
||||
@@ -617,8 +711,8 @@ function getAchievementsEarnedByUser(userId, cb) {
|
||||
|
||||
exports.moduleInitialize = (initInfo, cb) => {
|
||||
achievementsInstance = new Achievements(initInfo.events);
|
||||
achievementsInstance.init( err => {
|
||||
if(err) {
|
||||
achievementsInstance.init(err => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user