2020-05-15 12:42:39 -05:00
|
|
|
import EventEmitter from 'events';
|
|
|
|
|
|
|
|
import Gamedig from 'gamedig';
|
|
|
|
|
|
|
|
import LogParser from './log-parser/index.js';
|
|
|
|
import Rcon from './rcon/index.js';
|
|
|
|
|
|
|
|
import {
|
|
|
|
SERVER_LAYER_CHANGE,
|
|
|
|
SERVER_PLAYERS_UPDATED,
|
|
|
|
SERVER_LAYERS_UPDATED,
|
|
|
|
SERVER_A2S_UPDATED
|
|
|
|
} from './events/server.js';
|
|
|
|
|
|
|
|
import { LOG_PARSER_NEW_GAME } from './events/log-parser.js';
|
|
|
|
|
|
|
|
export default class Server extends EventEmitter {
|
|
|
|
constructor(options = {}) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
// store options
|
|
|
|
if (!('id' in options)) throw new Error('Server must have an ID.');
|
|
|
|
this.id = options.id;
|
|
|
|
|
|
|
|
if (!('host' in options)) throw new Error('Server must have a host.');
|
|
|
|
this.host = options.host;
|
|
|
|
|
2020-06-13 11:35:00 -05:00
|
|
|
if (!('queryPort' in options)) throw new Error('Server must have a queryPort.');
|
2020-05-15 12:42:39 -05:00
|
|
|
this.queryPort = options.queryPort;
|
|
|
|
|
|
|
|
this.updateInterval = options.updateInterval || 30 * 1000;
|
|
|
|
|
|
|
|
// setup additional classes
|
|
|
|
this.rcon = new Rcon(options, this);
|
|
|
|
this.logParser = new LogParser(options, this);
|
|
|
|
|
|
|
|
// setup internal data storage
|
|
|
|
this.layerHistory = options.layerHistory || [];
|
|
|
|
this.layerHistoryMaxLength = options.layerHistoryMaxLength || 20;
|
|
|
|
|
|
|
|
this.players = [];
|
|
|
|
|
|
|
|
// store additional information about players by SteamID
|
|
|
|
this.suffixStore = {};
|
|
|
|
|
|
|
|
// setup internal listeners
|
|
|
|
this.on(LOG_PARSER_NEW_GAME, this.onLayerChange.bind(this));
|
|
|
|
|
|
|
|
// setup period updaters
|
|
|
|
this.updatePlayers = this.updatePlayers.bind(this);
|
2020-06-13 11:35:00 -05:00
|
|
|
this.updatePlayerTimeout = setTimeout(this.updatePlayers, this.updateInterval);
|
2020-05-15 12:42:39 -05:00
|
|
|
|
2020-05-15 13:56:03 -05:00
|
|
|
setInterval(async () => {
|
2020-05-15 12:42:39 -05:00
|
|
|
const data = await this.rcon.getMapInfo();
|
|
|
|
this.currentLayer = data.currentLayer;
|
|
|
|
this.nextLayer = data.nextLayer;
|
|
|
|
this.emit(SERVER_LAYERS_UPDATED, data);
|
|
|
|
}, this.updateInterval);
|
|
|
|
|
2020-05-15 13:56:03 -05:00
|
|
|
setInterval(async () => {
|
2020-05-15 12:42:39 -05:00
|
|
|
const data = await Gamedig.query({
|
|
|
|
type: 'squad',
|
|
|
|
host: this.host,
|
|
|
|
port: this.queryPort
|
|
|
|
});
|
|
|
|
|
|
|
|
this.serverName = data.name;
|
|
|
|
|
|
|
|
this.maxPlayers = parseInt(data.maxplayers);
|
|
|
|
this.publicSlots = parseInt(data.raw.rules.NUMPUBCONN);
|
|
|
|
this.reserveSlots = parseInt(data.raw.rules.NUMPRIVCONN);
|
|
|
|
|
2020-05-18 10:34:43 -05:00
|
|
|
this.playerCount = parseInt(data.raw.rules.PlayerCount_i);
|
2020-05-15 12:42:39 -05:00
|
|
|
this.publicQueue = parseInt(data.raw.rules.PublicQueue_i);
|
|
|
|
this.reserveQueue = parseInt(data.raw.rules.ReservedQueue_i);
|
|
|
|
|
|
|
|
this.matchTimeout = parseFloat(data.raw.rules.MatchTimeout_f);
|
|
|
|
this.gameVersion = data.raw.version;
|
|
|
|
|
|
|
|
this.emit(SERVER_A2S_UPDATED, {
|
|
|
|
serverName: this.serverName,
|
|
|
|
maxPlayers: this.maxPlayers,
|
|
|
|
publicSlots: this.publicSlots,
|
|
|
|
reserveSlots: this.reserveSlots,
|
2020-05-18 10:34:43 -05:00
|
|
|
playerCount: this.playerCount,
|
2020-05-15 12:42:39 -05:00
|
|
|
publicQueue: this.publicQueue,
|
|
|
|
reserveQueue: this.reserveQueue,
|
|
|
|
matchTimeout: this.matchTimeout,
|
|
|
|
gameVersion: this.gameVersion
|
|
|
|
});
|
|
|
|
}, this.updateInterval);
|
|
|
|
}
|
|
|
|
|
|
|
|
async watch() {
|
|
|
|
console.log(`Watching server ${this.id}...`);
|
|
|
|
if (this.logParser) await this.logParser.watch();
|
|
|
|
if (this.rcon) await this.rcon.watch();
|
|
|
|
}
|
|
|
|
|
|
|
|
async unwatch() {
|
|
|
|
if (this.logParser) await this.logParser.unwatch();
|
|
|
|
if (this.rcon) await this.rcon.unwatch();
|
|
|
|
console.log('Stopped watching.');
|
|
|
|
}
|
|
|
|
|
|
|
|
async updatePlayers() {
|
|
|
|
clearTimeout(this.updatePlayerTimeout);
|
|
|
|
|
|
|
|
this.players = await this.rcon.listPlayers();
|
|
|
|
|
|
|
|
// readd additional information about the player we have collected
|
|
|
|
for (let i = 0; i < this.players.length; i++) {
|
|
|
|
this.players[i].suffix = this.suffixStore[this.players[i].steamID];
|
|
|
|
}
|
|
|
|
|
|
|
|
// delay another update
|
2020-06-13 11:35:00 -05:00
|
|
|
this.updatePlayerTimeout = setTimeout(this.updatePlayers, this.updateInterval);
|
2020-05-15 12:42:39 -05:00
|
|
|
|
|
|
|
this.emit(SERVER_PLAYERS_UPDATED, this.players);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getPlayerByName(name, suffix = false) {
|
|
|
|
let matchingPlayers;
|
|
|
|
|
|
|
|
matchingPlayers = [];
|
|
|
|
for (const player of this.players) {
|
|
|
|
if (player[suffix ? 'suffix' : 'name'] !== name) continue;
|
|
|
|
matchingPlayers.push(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matchingPlayers.length === 0) {
|
|
|
|
await this.updatePlayers();
|
|
|
|
|
|
|
|
matchingPlayers = [];
|
|
|
|
for (const player of this.players) {
|
|
|
|
if (player[suffix ? 'suffix' : 'name'] !== name) continue;
|
|
|
|
matchingPlayers.push(player);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matchingPlayers.length === 1) return matchingPlayers[0];
|
|
|
|
else return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
async getPlayerBySteamID(steamID) {
|
|
|
|
let matchingPlayers;
|
|
|
|
|
|
|
|
matchingPlayers = [];
|
|
|
|
for (const player of this.players) {
|
|
|
|
if (player.steamID !== steamID) continue;
|
|
|
|
matchingPlayers.push(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matchingPlayers.length === 0) {
|
|
|
|
await this.updatePlayers();
|
|
|
|
|
|
|
|
matchingPlayers = [];
|
|
|
|
for (const player of this.players) {
|
|
|
|
if (player.steamID !== steamID) continue;
|
|
|
|
matchingPlayers.push(player);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return matchingPlayers[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
onLayerChange(info) {
|
|
|
|
this.layerHistory.unshift(info);
|
|
|
|
this.layerHistory = this.layerHistory.slice(0, this.layerHistoryMaxLength);
|
|
|
|
this.emit(SERVER_LAYER_CHANGE, info);
|
|
|
|
}
|
|
|
|
}
|