2020-10-22 16:06:28 -05:00
|
|
|
import BasePlugin from './base-plugin.js';
|
2020-10-25 18:31:46 -05:00
|
|
|
import Logger from 'core/logger';
|
2020-10-22 16:06:28 -05:00
|
|
|
|
|
|
|
export default class AutoKickAFK extends BasePlugin {
|
|
|
|
static get description() {
|
|
|
|
return 'The <code>AutoKickAFK</code> plugin will automatically kick players that are not in a squad after a specified ammount of time.';
|
|
|
|
}
|
|
|
|
|
|
|
|
static get defaultEnabled() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static get optionsSpecification() {
|
|
|
|
return {
|
2020-10-25 18:18:24 -05:00
|
|
|
warningMessage: {
|
2020-10-22 16:06:28 -05:00
|
|
|
required: false,
|
2020-10-25 18:18:24 -05:00
|
|
|
description: 'Message SquadJS will send to players warning them they will be kicked',
|
|
|
|
default: 'Join a squad, you are are unassigned and will be kicked'
|
2020-10-22 16:06:28 -05:00
|
|
|
},
|
2020-10-25 18:18:24 -05:00
|
|
|
kickMessage: {
|
2020-10-22 19:22:34 -05:00
|
|
|
required: false,
|
2020-10-25 18:18:24 -05:00
|
|
|
description: 'Message to send to players when they are kicked',
|
|
|
|
default: 'Unassigned - automatically removed'
|
|
|
|
},
|
2020-10-27 16:31:05 -05:00
|
|
|
frequencyOfWarnings: {
|
2020-10-25 18:18:24 -05:00
|
|
|
required: false,
|
|
|
|
description: 'How often in seconds should we warn the player about being AFK?',
|
|
|
|
default: 30
|
|
|
|
},
|
|
|
|
afkTimer: {
|
|
|
|
required: false,
|
|
|
|
description: 'How long in minutes to wait before a player that is AFK is kicked',
|
|
|
|
default: 6
|
2020-10-22 19:22:34 -05:00
|
|
|
},
|
2020-10-27 16:31:05 -05:00
|
|
|
playerThreshold: {
|
2020-10-22 19:22:34 -05:00
|
|
|
required: false,
|
2020-10-27 16:31:05 -05:00
|
|
|
description:
|
|
|
|
'Player count required for Auto Kick to start kicking players to disable set to -1 to disable',
|
2020-10-22 19:22:34 -05:00
|
|
|
default: 93
|
|
|
|
},
|
2020-10-27 16:31:05 -05:00
|
|
|
queueThreshold: {
|
2020-10-22 19:22:34 -05:00
|
|
|
required: false,
|
2020-10-27 16:31:05 -05:00
|
|
|
description:
|
|
|
|
'The number of players in the queue before Auto Kick starts kicking players set to -1 to disable',
|
2020-10-22 19:22:34 -05:00
|
|
|
default: -1
|
2020-10-25 19:24:28 -05:00
|
|
|
},
|
2020-10-27 16:31:05 -05:00
|
|
|
roundStartDelay: {
|
2020-10-25 19:24:28 -05:00
|
|
|
required: false,
|
2020-10-27 16:31:05 -05:00
|
|
|
description:
|
|
|
|
'Time delay in minutes from start of the round before auto AFK starts kicking again',
|
2020-10-25 19:24:28 -05:00
|
|
|
default: 15
|
2020-10-27 16:48:09 -05:00
|
|
|
},
|
|
|
|
ignoreAdmins: {
|
2020-10-22 19:22:34 -05:00
|
|
|
required: false,
|
|
|
|
description: 'Whether or not admins will be auto kicked for being unassigned',
|
|
|
|
default: false
|
2020-10-27 16:48:09 -05:00
|
|
|
}
|
2020-10-22 16:06:28 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
/**
|
|
|
|
* trackedPlayers[<steam64ID>] = <tracker>
|
|
|
|
*
|
|
|
|
* <tracker> = {
|
|
|
|
* player: <playerObj>
|
|
|
|
* warnings: <int>
|
|
|
|
* startTime: <Epoch Date>
|
|
|
|
* warnTimerID: <intervalID>
|
|
|
|
* kickTimerID: <timeoutID>
|
|
|
|
* }
|
|
|
|
*/
|
2020-10-22 16:06:28 -05:00
|
|
|
constructor(server, options) {
|
|
|
|
super();
|
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
this.server = server;
|
|
|
|
this.options = options;
|
|
|
|
|
2020-10-27 16:31:05 -05:00
|
|
|
this.kickTimeout = options.afkTimer * 60 * 1000;
|
2020-10-25 18:18:24 -05:00
|
|
|
this.warningInterval = options.frequencyOfWarnings * 1000;
|
2020-10-25 19:24:28 -05:00
|
|
|
this.gracePeriod = options.roundStartDelay * 60 * 1000;
|
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
this.trackingListUpdateFrequency = 1 * 60 * 1000; // 1min
|
|
|
|
this.cleanUpFrequency = 20 * 60 * 1000; // 20min
|
|
|
|
|
2020-10-25 19:24:28 -05:00
|
|
|
this.betweenRounds = false;
|
2020-10-25 18:18:24 -05:00
|
|
|
|
|
|
|
this.trackedPlayers = {};
|
2020-10-25 19:24:28 -05:00
|
|
|
|
|
|
|
server.on('NEW_GAME', async (info) => {
|
|
|
|
this.betweenRounds = true;
|
2020-10-28 13:43:22 -05:00
|
|
|
this.updateTrackingList();
|
2020-10-27 16:31:05 -05:00
|
|
|
setTimeout(async () => {
|
2020-10-25 19:24:28 -05:00
|
|
|
this.betweenRounds = false;
|
|
|
|
}, this.gracePeriod);
|
|
|
|
});
|
|
|
|
|
2020-10-27 16:31:05 -05:00
|
|
|
server.on('PLAYER_SQUAD_CHANGE', async (player) => {
|
|
|
|
if (player.steamID in this.trackedPlayers && player.squadID !== null) {
|
2020-10-28 13:43:22 -05:00
|
|
|
this.untrackPlayer(player.steamID);
|
2020-10-25 19:24:28 -05:00
|
|
|
}
|
|
|
|
});
|
2020-10-22 16:06:28 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
// tracking list update loop
|
|
|
|
setInterval(this.updateTrackingList.bind(this), this.trackingListUpdateFrequency);
|
2020-10-25 19:24:28 -05:00
|
|
|
|
2020-10-28 14:33:21 -05:00
|
|
|
// removes players no longer on the server that may be in trackedPlayers
|
2020-10-28 13:43:22 -05:00
|
|
|
setInterval(() => {
|
|
|
|
for (const steamID of Object.keys(this.trackedPlayers))
|
|
|
|
if (!(steamID in server.players.map((p) => p.steamID))) this.untrackPlayer(steamID);
|
2020-10-28 14:33:21 -05:00
|
|
|
}, this.cleanUpFrequency);
|
2020-10-28 13:43:22 -05:00
|
|
|
}
|
2020-10-25 19:24:28 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
runConditions() {
|
|
|
|
// return true; // force run for testing
|
2020-10-28 14:33:21 -05:00
|
|
|
const totalQueue = this.server.publicQueue + this.server.reserveQueue;
|
|
|
|
const queueMet = this.options.queueThreshold > 0 && this.options.queueThreshold < totalQueue;
|
|
|
|
const countMet =
|
|
|
|
this.options.playerCountThreshold > 0 &&
|
|
|
|
this.options.playerCountThreshold < this.server.players.count;
|
|
|
|
|
|
|
|
const run = !this.betweenRounds && (queueMet || countMet);
|
|
|
|
|
|
|
|
Logger.verbose(
|
|
|
|
'AutoAFK',
|
|
|
|
2,
|
|
|
|
`RUN?: ${run} = ${!this.betweenRounds} && (${queueMet} || ${countMet})`
|
|
|
|
);
|
|
|
|
return run;
|
2020-10-28 13:43:22 -05:00
|
|
|
}
|
2020-10-25 18:18:24 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
async updateTrackingList(forceUpdate = false) {
|
|
|
|
if (!this.runConditions()) {
|
|
|
|
// clear all tracked players if run conditions are not met.
|
|
|
|
for (const steamID of Object.keys(this.trackedPlayers)) this.untrackPlayer(steamID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (forceUpdate) await this.server.updatePlayerList();
|
|
|
|
|
|
|
|
// loop through players on server and start tracking players not in a squad
|
|
|
|
for (const player of this.server.players) {
|
|
|
|
const isTracked = player.steamID in this.trackedPlayers;
|
|
|
|
const isUnassigned = player.squadID === null;
|
2020-10-28 14:33:21 -05:00
|
|
|
const isAdmin = player.steamID in this.server.admins.map((a) => a.steamID);
|
|
|
|
|
|
|
|
if (isUnassigned && isAdmin) Logger.verbose('AutoAFK', 2, `Admin is AFK: ${player.name}`);
|
2020-10-28 13:43:22 -05:00
|
|
|
|
|
|
|
// start tracking player
|
2020-10-28 14:33:21 -05:00
|
|
|
if (isUnassigned && !isTracked && !(isAdmin && this.options.ignoreAdmins))
|
2020-10-28 13:43:22 -05:00
|
|
|
this.trackedPlayers[player.steamID] = this.trackPlayer(player);
|
|
|
|
|
|
|
|
// tracked player joined a squad remove them (redundant afer adding PLAYER_SQUAD_CHANGE, keeping for now)
|
|
|
|
if (!isUnassigned && isTracked) this.untrackPlayer(player.steamID);
|
|
|
|
}
|
|
|
|
}
|
2020-10-27 16:31:05 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
msFormat(ms) {
|
|
|
|
// take in generic # of ms and return formatted MM:SS
|
|
|
|
const min = Math.floor((ms / 1000 / 60) << 0);
|
|
|
|
const sec = Math.floor((ms / 1000) % 60);
|
|
|
|
return `${min}:${sec}`;
|
|
|
|
}
|
2020-10-25 18:18:24 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
trackPlayer(player) {
|
|
|
|
Logger.verbose('AutoAFK', 1, `Tracking: ${player.name}`);
|
2020-10-25 18:18:24 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
const tracker = {
|
|
|
|
player: player,
|
|
|
|
warnings: 0,
|
|
|
|
startTime: Date.now()
|
2020-10-27 16:31:05 -05:00
|
|
|
};
|
2020-10-25 18:18:24 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
// continuously warn player at rate set in options
|
|
|
|
tracker.warnTimerID = setInterval(async () => {
|
|
|
|
const timeLeft = this.msFormat(this.kickTimeout - (Date.now() - tracker.startTime));
|
|
|
|
|
|
|
|
this.server.rcon.warn(player.steamID, `${this.options.warningMessage} - ${timeLeft}`);
|
|
|
|
Logger.verbose('AutoAFK', 1, `Warning: ${player.name} (${timeLeft})`);
|
|
|
|
tracker.warnings++;
|
|
|
|
}, this.warningInterval);
|
|
|
|
|
|
|
|
// set timeout to kick player
|
|
|
|
tracker.kickTimerID = setTimeout(async () => {
|
|
|
|
// ensures player is still afk
|
|
|
|
await this.updateTrackingList(true);
|
|
|
|
|
|
|
|
this.server.rcon.kick(player.steamID, this.options.kickMessage);
|
|
|
|
this.server.emit('PLAYER_AFK_KICKED', tracker);
|
|
|
|
Logger.verbose('AutoAFK', 1, `Kicked: ${player.name}`);
|
|
|
|
this.untrackPlayer(player.steamID);
|
|
|
|
}, this.kickTimeout);
|
|
|
|
return tracker;
|
|
|
|
}
|
2020-10-22 19:22:34 -05:00
|
|
|
|
2020-10-28 13:43:22 -05:00
|
|
|
untrackPlayer(steamID) {
|
|
|
|
const tracker = this.trackedPlayers[steamID];
|
|
|
|
// clear player warning interval
|
|
|
|
clearInterval(tracker.warnTimerID);
|
|
|
|
// clear player kick timeout
|
|
|
|
clearTimeout(tracker.kickTimerID);
|
|
|
|
// remove player tracker
|
|
|
|
delete this.trackedPlayers[steamID];
|
|
|
|
Logger.verbose('AutoAFK', 1, `[AutoAFK] unTrack: ${tracker.player.name}`);
|
2020-10-22 16:06:28 -05:00
|
|
|
}
|
|
|
|
}
|