+ ACS: AC for achievement count check
+ ACS: AP for achievement point check + User minutes used on the system are now tracked + MCI: TO for total time spent online system (friendly format) * Fix up a couple ACS bugs with |value| * Fix formatting of achievement text + Add more achievements * Fix achievement duration formatting
This commit is contained in:
parent
091a9ae2c7
commit
2788c37492
|
@ -26,6 +26,7 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
|
|||
* `oputil.js user rm` and `oputil.js user info` are in! See [oputil CLI](/docs/admin/oputil.md).
|
||||
* Performing a file scan/import using `oputil.js fb scan` now recognizes various `FILES.BBS` formats.
|
||||
* Usernames found in the `config.users.badUserNames` are now not only disallowed from applying, but disconnected at any login attempt.
|
||||
* Total minutes online is now tracked for users. Of course, it only starts after you get the update :)
|
||||
|
||||
|
||||
## 0.0.8-alpha
|
||||
|
|
|
@ -989,9 +989,9 @@
|
|||
pointsSGR: "|12"
|
||||
textSGR: "|00|03"
|
||||
globalTextSGR: "|03"
|
||||
boardName: "|10"
|
||||
userName: "|11"
|
||||
achievedValue: "|15"
|
||||
boardNameSGR: "|10"
|
||||
userNameSGR: "|11"
|
||||
achievedValueSGR: "|15"
|
||||
}
|
||||
|
||||
overrides: {
|
||||
|
|
|
@ -307,29 +307,60 @@
|
|||
match: {
|
||||
1: {
|
||||
title: "Nevermind!"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationSeconds}. Guess it's not their thing!"
|
||||
text: "You ran a door for only {achievedValue!durationSeconds}. Not your thing?"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationMinutes}. Guess it's not their thing!"
|
||||
text: "You ran a door for only {achievedValue!durationMinutes}. Not your thing?"
|
||||
points: 5
|
||||
}
|
||||
10: {
|
||||
title: "It's OK I Guess"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationSeconds}!"
|
||||
text: "You ran a door for {achievedValue!durationSeconds}!"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationMinutes}!"
|
||||
text: "You ran a door for {achievedValue!durationMinutes}!"
|
||||
points: 10
|
||||
}
|
||||
30: {
|
||||
title: "Good Game"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationSeconds}!"
|
||||
text: "You ran a door for {achievedValue!durationSeconds}!"
|
||||
globalText: "{userName} ran a door for {achievedValue!durationMinutes}!"
|
||||
text: "You ran a door for {achievedValue!durationMinutes}!"
|
||||
points: 20
|
||||
}
|
||||
60: {
|
||||
title: "Textmode Dragon Slayer"
|
||||
globalText: "{userName} has spent {achievedValue!durationSeconds} in a door!"
|
||||
text: "You've spent {achievedValue!durationSeconds} in a door!"
|
||||
globalText: "{userName} has spent {achievedValue!durationMinutes} in a door!"
|
||||
text: "You've spent {achievedValue!durationMinutes} in a door!"
|
||||
points: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user_total_system_online_minutes: {
|
||||
type: userStatSet
|
||||
statName: minutes_online_total_count
|
||||
match: {
|
||||
30: {
|
||||
title: "Just Poking Around"
|
||||
globalText: "{userName} has spent {achievedValue!durationMinutes} on {boardName}!"
|
||||
text: "You've been on {boardName} for a total of {achievedValue!durationMinutes}!"
|
||||
points: 5
|
||||
}
|
||||
60: {
|
||||
title: "Mildly Interesting"
|
||||
globalText: "{userName} has spent {achievedValue!durationMinutes} on {boardName}!"
|
||||
text: "You've been on {boardName} for a total of {achievedValue!durationMinutes}!"
|
||||
points: 15
|
||||
}
|
||||
120: {
|
||||
title: "Nothing Better to Do"
|
||||
globalText: "{userName} has spent {achievedValue!durationMinutes} on {boardName}!"
|
||||
text: "You've been on {boardName} for a total of {achievedValue!durationMinutes}!"
|
||||
points: 25
|
||||
}
|
||||
1440: {
|
||||
title: "Idle Bot"
|
||||
globalText: "{userName} is probably a bot. They've spent {achievedValue!durationMinutes} on {boardName}!"
|
||||
text: "You're a bot, aren't you? You've been on {boardName} for a total of {achievedValue!durationMinutes}!"
|
||||
points: 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -406,19 +406,23 @@ class Achievements {
|
|||
|
||||
getFormattedTextFor(info, textType, defaultSgr = '|07') {
|
||||
const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {});
|
||||
const defSgr = themeDefaults[`${textType}SGR`] || defaultSgr;
|
||||
const textTypeSgr = themeDefaults[`${textType}SGR`] || defaultSgr;
|
||||
|
||||
const wrap = (fieldName, value) => {
|
||||
return `${themeDefaults[fieldName] || defSgr}${value}${defSgr}`;
|
||||
const formatObj = this.getFormatObject(info);
|
||||
|
||||
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) {
|
||||
r += formatOpts;
|
||||
}
|
||||
return `${r}}${textTypeSgr}`;
|
||||
});
|
||||
};
|
||||
|
||||
let formatObj = this.getFormatObject(info);
|
||||
formatObj = _.reduce(formatObj, (out, v, k) => {
|
||||
out[k] = wrap(k, v);
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
return stringFormat(`${defSgr}${info.details[textType]}`, formatObj);
|
||||
return stringFormat(`${textTypeSgr}${wrap(info.details[textType])}`, formatObj);
|
||||
}
|
||||
|
||||
createAchievementInterruptItems(info, cb) {
|
||||
|
|
|
@ -1004,7 +1004,7 @@ function peg$parse(input, options) {
|
|||
TW : function termWidth() {
|
||||
return !isNaN(value) && _.get(client, 'term.termWidth', 0) >= value;
|
||||
},
|
||||
ID : function isUserId(value) {
|
||||
ID : function isUserId() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1024,6 +1024,20 @@ function peg$parse(input, options) {
|
|||
const midnight = now.clone().startOf('day')
|
||||
const minutesPastMidnight = now.diff(midnight, 'minutes');
|
||||
return !isNaN(value) && minutesPastMidnight >= value;
|
||||
},
|
||||
AC : function achievementCount() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
const count = user.getPropertyAsNumber(UserProps.AchievementTotalCount) || 0;
|
||||
return !isNan(value) && points >= value;
|
||||
},
|
||||
AP : function achievementPoints() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
const points = user.getPropertyAsNumber(UserProps.AchievementTotalPoints) || 0;
|
||||
return !isNan(value) && points >= value;
|
||||
}
|
||||
}[acsCode](value);
|
||||
} catch (e) {
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
// General
|
||||
// * http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
// * http://www.inwap.com/pdp10/ansicode.txt
|
||||
// * Excellent information with many standards covered (for hterm):
|
||||
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/doc/ControlSequences.md
|
||||
//
|
||||
// Other Implementations
|
||||
// * https://github.com/chjj/term.js/blob/master/src/term.js
|
||||
|
|
|
@ -40,6 +40,7 @@ const MenuStack = require('./menu_stack.js');
|
|||
const ACS = require('./acs.js');
|
||||
const Events = require('./events.js');
|
||||
const UserInterruptQueue = require('./user_interrupt_queue.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const stream = require('stream');
|
||||
|
@ -442,13 +443,36 @@ Client.prototype.startIdleMonitor = function() {
|
|||
|
||||
//
|
||||
// Every 1m, check for idle.
|
||||
// We also update minutes spent online the system here,
|
||||
// if we have a authenticated user.
|
||||
//
|
||||
this.idleCheck = setInterval( () => {
|
||||
const nowMs = Date.now();
|
||||
|
||||
const idleLogoutSeconds = this.user.isAuthenticated() ?
|
||||
Config().users.idleLogoutSeconds :
|
||||
Config().users.preAuthIdleLogoutSeconds;
|
||||
let idleLogoutSeconds;
|
||||
if(this.user.isAuthenticated()) {
|
||||
idleLogoutSeconds = Config().users.idleLogoutSeconds;
|
||||
|
||||
//
|
||||
// We don't really want to be firing off an event every 1m for
|
||||
// every user, but want at least some updates for various things
|
||||
// such as achievements. Send off every 5m.
|
||||
//
|
||||
const minOnline = this.user.incrementProperty(UserProps.MinutesOnlineTotalCount, 1);
|
||||
if(0 === (minOnline % 5)) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserStatIncrement,
|
||||
{
|
||||
user : this.user,
|
||||
statName : UserProps.MinutesOnlineTotalCount,
|
||||
statIncrementBy : 1,
|
||||
statValue : minOnline
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
idleLogoutSeconds = Config().users.preAuthIdleLogoutSeconds;
|
||||
}
|
||||
|
||||
if(nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000)) {
|
||||
this.emit('idle timeout');
|
||||
|
@ -473,6 +497,14 @@ Client.prototype.end = function () {
|
|||
currentModule.leave();
|
||||
}
|
||||
|
||||
// persist time online for authenticated users
|
||||
if(this.user.isAuthenticated()) {
|
||||
this.user.persistProperty(
|
||||
UserProps.MinutesOnlineTotalCount,
|
||||
this.user.getProperty(UserProps.MinutesOnlineTotalCount)
|
||||
);
|
||||
}
|
||||
|
||||
this.stopIdleMonitor();
|
||||
|
||||
try {
|
||||
|
|
|
@ -160,6 +160,10 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
const minutes = client.user.properties[UserProps.DoorRunTotalMinutes] || 0;
|
||||
return moment.duration(minutes, 'minutes').humanize();
|
||||
},
|
||||
TO : function friendlyTotalTimeOnSystem(client) {
|
||||
const minutes = client.user.properties[UserProps.MinutesOnlineTotalCount] || 0;
|
||||
return moment.duration(minutes, 'minutes').humanize();
|
||||
},
|
||||
|
||||
//
|
||||
// Date/Time
|
||||
|
|
16
core/user.js
16
core/user.js
|
@ -443,6 +443,22 @@ module.exports = class User {
|
|||
);
|
||||
}
|
||||
|
||||
setProperty(propName, propValue) {
|
||||
this.properties[propName] = propValue;
|
||||
}
|
||||
|
||||
incrementProperty(propName, incrementBy) {
|
||||
incrementBy = incrementBy || 1;
|
||||
let newValue = parseInt(this.getProperty(propName));
|
||||
if(newValue) {
|
||||
newValue += incrementBy;
|
||||
} else {
|
||||
newValue = incrementBy;
|
||||
}
|
||||
this.setProperty(propName, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
getProperty(propName) {
|
||||
return this.properties[propName];
|
||||
}
|
||||
|
|
|
@ -55,5 +55,7 @@ module.exports = {
|
|||
|
||||
AchievementTotalCount : 'achievement_total_count',
|
||||
AchievementTotalPoints : 'achievement_total_points',
|
||||
|
||||
MinutesOnlineTotalCount : 'minutes_online_total_count',
|
||||
};
|
||||
|
||||
|
|
|
@ -34,7 +34,9 @@ The following are ACS codes available as of this writing:
|
|||
| NR<i>ratio</i> | User has upload/download count ratio >= _ratio_ |
|
||||
| KR<i>ratio</i> | User has a upload/download byte ratio >= _ratio_ |
|
||||
| PC<i>ratio</i> | User has a post/call ratio >= _ratio_ |
|
||||
| MM<i>minutes</i> | It is currently >= _minutes_ past midnight (system time)
|
||||
| MM<i>minutes</i> | It is currently >= _minutes_ past midnight (system time) |
|
||||
| AC<i>achievementCount</i> | User has >= _achievementCount_ achievements |
|
||||
| AP<i>achievementPoints</i> | User has >= _achievementPoints_ achievement points |
|
||||
|
||||
\* Many more ACS codes are planned for the near future.
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
TW : function termWidth() {
|
||||
return !isNaN(value) && _.get(client, 'term.termWidth', 0) >= value;
|
||||
},
|
||||
ID : function isUserId(value) {
|
||||
ID : function isUserId() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
|
@ -180,6 +180,20 @@
|
|||
const midnight = now.clone().startOf('day')
|
||||
const minutesPastMidnight = now.diff(midnight, 'minutes');
|
||||
return !isNaN(value) && minutesPastMidnight >= value;
|
||||
},
|
||||
AC : function achievementCount() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
const count = user.getPropertyAsNumber(UserProps.AchievementTotalCount) || 0;
|
||||
return !isNan(value) && points >= value;
|
||||
},
|
||||
AP : function achievementPoints() {
|
||||
if(!user) {
|
||||
return false;
|
||||
}
|
||||
const points = user.getPropertyAsNumber(UserProps.AchievementTotalPoints) || 0;
|
||||
return !isNan(value) && points >= value;
|
||||
}
|
||||
}[acsCode](value);
|
||||
} catch (e) {
|
||||
|
|
Loading…
Reference in New Issue