diff --git a/core/achievement.js b/core/achievement.js index bb11e3c4..46f97e03 100644 --- a/core/achievement.js +++ b/core/achievement.js @@ -9,9 +9,16 @@ const UserInterruptQueue = require('./user_interrupt_queue.js'); const { getConnectionByUserId } = require('./client_connections.js'); +const UserProps = require('./user_property.js'); +const { Errors } = require('./enig_error.js'); +const { getThemeArt } = require('./theme.js'); +const { pipeToAnsi } = require('./color_codes.js'); +const stringFormat = require('./string_format.js'); // deps const _ = require('lodash'); +const async = require('async'); +const moment = require('moment'); class Achievements { constructor(events) { @@ -61,31 +68,143 @@ class Achievements { 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]; + const details = achievement.match[matchValue]; + matchValue = parseInt(matchValue); - // - // Check if we've triggered this event before - // - this.loadAchievementHitCount(userStatEvent.user, achievementTag, null, matchValue, (err, count) => { - if(count > 0) { - return; - } + async.series( + [ + (callback) => { + this.loadAchievementHitCount(userStatEvent.user, achievementTag, null, matchValue, (err, count) => { + if(err) { + return callback(err); + } + return callback(count > 0 ? Errors.General('Achievement already acquired') : null); + }); + }, + (callback) => { + const client = getConnectionByUserId(userStatEvent.user.userId); + if(!client) { + return callback(Errors.UnexpectedState('Failed to get client for user ID')); + } - const conn = getConnectionByUserId(userStatEvent.user.userId); - if(!conn) { - return; - } + const info = { + achievement, + details, + client, + value : matchValue, + user : userStatEvent.user, + timestamp : moment(), + }; - const interruptItem = { - text : match.text, - pause : true, - }; + this.createAchievementInterruptItems(info, (err, interruptItems) => { + if(err) { + return callback(err); + } - UserInterruptQueue.queue(interruptItem, { omit : conn} ); - }); + if(interruptItems.local) { + UserInterruptQueue.queue(interruptItems.local, { clients : client } ); + } + + if(interruptItems.global) { + UserInterruptQueue.queue(interruptItems.global, { omit : client } ); + } + }); + } + ] + ); } }); } + + createAchievementInterruptItems(info, cb) { + const dateTimeFormat = + info.details.dateTimeFormat || + info.achievement.dateTimeFormat || + info.client.currentTheme.helpers.getDateTimeFormat(); + + const config = Config(); + + const formatObj = { + 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, + value : info.value, + timestamp : moment(info.timestamp).format(dateTimeFormat), + boardName : config.general.boardName, + }; + + const title = stringFormat(info.details.title, formatObj); + const text = stringFormat(info.details.text, formatObj); + + let globalText; + if(info.details.globalText) { + globalText = stringFormat(info.details.globalText, formatObj); + } + + const getArt = (name, callback) => { + const spec = + _.get(info.details, `art.${name}`) || + _.get(info.achievement, `art.${name}`) || + _.get(config, `userAchievements.art.${name}`); + if(!spec) { + return callback(null); + } + const getArtOpts = { + name : spec, + client : this.client, + random : false, + }; + getThemeArt(getArtOpts, (err, artInfo) => { + // ignore errors + return callback(artInfo ? artInfo.data : null); + }); + }; + + const interruptItems = {}; + let itemTypes = [ 'local' ]; + if(globalText) { + itemTypes.push('global'); + } + + async.each(itemTypes, (itemType, nextItemType) => { + async.waterfall( + [ + (callback) => { + getArt('header', headerArt => { + return callback(null, headerArt); + }); + }, + (headerArt, callback) => { + getArt('footer', footerArt => { + return callback(null, headerArt, footerArt); + }); + }, + (headerArt, footerArt, callback) => { + const itemText = 'global' === itemType ? globalText : text; + interruptItems[itemType] = { + text : `${title}\r\n${itemText}`, + pause : true, + }; + if(headerArt || footerArt) { + interruptItems[itemType].contents = `${headerArt || ''}\r\n${pipeToAnsi(itemText)}\r\n${footerArt || ''}`; + } + return callback(null); + } + ], + err => { + return nextItemType(err); + } + ); + }, + err => { + return cb(err, interruptItems); + }); + } } let achievements; diff --git a/core/config.js b/core/config.js index f854e2f5..b73490cc 100644 --- a/core/config.js +++ b/core/config.js @@ -1008,8 +1008,12 @@ function getDefaultConfig() { userAchievements : { enabled : true, - artHeader : 'achievement_header', - artFooter : 'achievement_footer', + art : { + header : 'achievement_header', + footer : 'achievement_footer', + }, + + // :TODO: achievements should be a path/filename -> achievements.hjson & allow override/theming achievements : { user_login_count : { @@ -1019,20 +1023,20 @@ function getDefaultConfig() { match : { 10 : { title : 'Return Caller', - globalText : '{userName} has logged in {statValue} times!', - text : 'You\'ve logged in {statValue} times!', + globalText : '{userName} has logged in {value} times!', + text : 'You\'ve logged in {value} times!', points : 5, }, 25 : { title : 'Seems To Like It!', - globalText : '{userName} has logged in {statValue} times!', - text : 'You\'ve logged in {statValue} times!', + globalText : '{userName} has logged in {value} times!', + text : 'You\'ve logged in {value} 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!', + globalText : '{userName} the BBS {boardName} addict has logged in {value} times!', + text : 'You\'re a {boardName} addict! You\'ve logged in {value} times!', points : 10, } }