279 lines
No EOL
9.7 KiB
JavaScript
279 lines
No EOL
9.7 KiB
JavaScript
const ePlayer = require('./Player.js')
|
|
const path = require('path')
|
|
const Commands = require(path.join(__dirname, `../Commands.js`))
|
|
const EventEmitter = require('events')
|
|
const ip = require('public-ip')
|
|
const Game = require(path.join(__dirname, `../../Configuration/DefaultGameSettings.json`))
|
|
const Maps = Game.Maps
|
|
const Gametypes = Game.Gametypes
|
|
const Permissions = require(path.join(__dirname, `../../Configuration/NSMConfiguration.json`)).Permissions
|
|
const wait = require('delay')
|
|
const Utils = new (require(path.join(__dirname, `../../Utils/Utils.js`)))()
|
|
|
|
var wasRunning = true
|
|
class Server extends EventEmitter {
|
|
constructor(IP, Port, Rcon, Database, sessionStore, clientData, Managers, Id, Manager, config) {
|
|
super()
|
|
this.Clients = []
|
|
this.Rcon = Rcon
|
|
this.IP = IP
|
|
this.Id = Id
|
|
this.PORT = Port
|
|
this.clientHistory = []
|
|
this.clientActivity = []
|
|
this.DB = Database
|
|
this.MaxClients = 18
|
|
this.Mapname = ''
|
|
this.clientData = clientData
|
|
this.Gametype = 'UNKNOWN'
|
|
this.HostnameRaw = `[${this.IP}:${this.PORT}]`
|
|
this.uptime = 0
|
|
this.configGamename = config.Gamename
|
|
this.Gamename = 'UNKNOWN'
|
|
this.Managers = Managers
|
|
this.Manager = Manager
|
|
this.previousUptime = 0
|
|
this.previousStatus = null
|
|
this.heartbeatRetry = this.Rcon.commandPrefixes.Rcon.retries ? this.Rcon.commandPrefixes.Rcon.retries : 1
|
|
this.sessionStore = sessionStore
|
|
this.lastInit = new Date()
|
|
this.on('init', this.onInitGame.bind(this))
|
|
this.config = config
|
|
this.reservedSlots = config.reservedSlots
|
|
Manager.Commands = new Commands()
|
|
this.setMaxListeners(50)
|
|
}
|
|
getMap(name) {
|
|
return this.Maps.find(Map => Map.Name.toLocaleLowerCase().startsWith(name) || Map.Alias.toLocaleLowerCase().startsWith(name) )
|
|
}
|
|
getGametype() {
|
|
return Gametypes[this.Gametype] ? { Name: this.Gametype, Alias: Gametypes[this.Gametype] } : { Name: this.Gametype, Alias: this.Gametype }
|
|
}
|
|
getClients() {
|
|
return this.Clients.filter(c => c)
|
|
}
|
|
getMapname() {
|
|
var map = this.getMap(this.Mapname)
|
|
return map ? map : { Name: this.Mapname, Alias: this.Mapname }
|
|
}
|
|
onInitGame() {
|
|
if (new Date() - this.lastInit < 500) {
|
|
return
|
|
}
|
|
|
|
this.lastInit = new Date()
|
|
|
|
var loadMap = async () => {
|
|
this.removeListener('line', loadMap)
|
|
this.Mapname = await this.Rcon.getDvar('mapname')
|
|
this.Gametype = await this.Rcon.getDvar('g_gametype')
|
|
this.emit('map_loaded', this.Mapname, this.Gametype)
|
|
}
|
|
|
|
this.on('line', loadMap)
|
|
}
|
|
findLocalClient(name) {
|
|
var clientIdRegex = /\@([0-9]+)/g
|
|
var found = false
|
|
|
|
name = name.match(clientIdRegex) ? clientIdRegex.exec(name)[1] : name
|
|
|
|
this.Clients.forEach(Client => {
|
|
if (!Client) return
|
|
|
|
if (Client.Name.toLocaleLowerCase().startsWith(name.toLocaleLowerCase()) || Client.ClientId == name) {
|
|
found = Client
|
|
}
|
|
})
|
|
return found
|
|
}
|
|
getStaffMembers() {
|
|
var staff = []
|
|
this.Clients.forEach(Client => {
|
|
if (!Client) return
|
|
Client.PermissionLevel >= Permissions.Levels['ROLE_MODERATOR'] && staff.push(Client)
|
|
})
|
|
staff.sort((a, b) => {
|
|
return b.PermissionLevel - a.PermissionLevel
|
|
})
|
|
return staff
|
|
}
|
|
async setDvarsAsync() {
|
|
try {
|
|
this.Gametype = await this.Rcon.getDvar(this.Rcon.commandPrefixes.Dvars.gametype)
|
|
|
|
this.Gamename = !this.configGamename
|
|
? await this.Rcon.getDvar(this.Rcon.commandPrefixes.Dvars.gamename)
|
|
: this.configGamename
|
|
|
|
this.Maps = this.Gamename != 'UNKNOWN' ? Maps.find(x => x.Game == this.Gamename) ? Maps.find(x => x.Game == this.Gamename).Maps : [] : []
|
|
|
|
this.mapRotation = (await this.Rcon.getDvar(this.Rcon.commandPrefixes.Dvars.maprotation))
|
|
this.mapRotation = this.mapRotation.match(/map +([a-z|_|\d]+)/gi)
|
|
? this.mapRotation.match(/map +([a-z|_|\d]+)/gi).map(x => x.trim().split(/\s+/g)[1])
|
|
: []
|
|
|
|
this.Hostname = await this.Rcon.getDvarRaw(this.Rcon.commandPrefixes.Dvars.hostname)
|
|
this.HostnameRaw = this.Hostname
|
|
|
|
this.Mapname = await this.Rcon.getDvar(this.Rcon.commandPrefixes.Dvars.mapname)
|
|
|
|
this.MaxClients = parseInt(this.config.maxClientsOverride
|
|
? this.config.maxClientsOverride
|
|
: await this.Rcon.getDvar(this.Rcon.commandPrefixes.Dvars.maxclients))
|
|
|
|
this.Clients = new Array(this.MaxClients).fill(null)
|
|
|
|
this.Rcon.isRunning = true
|
|
|
|
this.externalIP = !this.IP.match(/(^127\.)|(localhost)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/g) ? this.IP : await ip.v4()
|
|
this.emit('dvars_loaded', this)
|
|
this.dvarsLoaded = true
|
|
|
|
this.HeartbeatInt = setInterval(this.Heartbeat.bind(this), 15000)
|
|
}
|
|
catch (e) { }
|
|
}
|
|
tellStaffGlobal(Message) {
|
|
this.Managers.forEach(Manager => {
|
|
Manager.Server.tellStaff(Message)
|
|
})
|
|
}
|
|
tellStaff(Message) {
|
|
this.Clients.filter(x => x && x.PermissionLevel >= Permissions.Levels['ROLE_MODERATOR'] && x.Tell(Message))
|
|
}
|
|
async getClient(name) {
|
|
var clientIdRegex = /\@([0-9]+)/g
|
|
|
|
if (!name.match(/\@([0-9]+)/g) && this.findClientByName(name)) {
|
|
return this.findClientByName(name)
|
|
}
|
|
|
|
var Clients = name.match(clientIdRegex)
|
|
? [await this.DB.getClient(clientIdRegex.exec(name)[1])]
|
|
: ((name.length >= 3 && !name.match('%')) ? (await this.DB.getClientByName(name, 20)) : false)
|
|
|
|
var Client = Clients ? Clients.reverse()[0] : false
|
|
return Client
|
|
}
|
|
toString() {
|
|
return `${this.IP}:${this.PORT}`
|
|
}
|
|
getAddress() {
|
|
return `${this.externalIP}:${this.PORT}`
|
|
}
|
|
getPlayerByName(Name) {
|
|
var Client = this.Clients.find(x => x && x.Name.startsWith(Name))
|
|
return Client
|
|
}
|
|
findClientByName(Name) {
|
|
var Client = null
|
|
|
|
this.Managers.forEach(Manager => {
|
|
if (Client) return
|
|
Client = Manager.Server.Clients.find(x => x && x.Name.toLocaleLowerCase().startsWith(Name.toLocaleLowerCase()))
|
|
})
|
|
|
|
return Client
|
|
}
|
|
findClient(ClientId) {
|
|
var Client = null
|
|
|
|
this.Managers.forEach(Manager => {
|
|
if (Client) return
|
|
Client = Manager.Server.Clients.find(x => x && x.ClientId == ClientId)
|
|
})
|
|
|
|
return Client
|
|
}
|
|
async Heartbeat() {
|
|
try {
|
|
var status = await this.Rcon.executeCommandAsync('status')
|
|
|
|
if (!status) {
|
|
if (this.heartbeatRetry <= 0) {
|
|
this.Rcon.isRunning = false
|
|
wasRunning && this.Manager.log(`^1Connection lost with ^6[${this.toString()}]^7`)
|
|
wasRunning = false
|
|
}
|
|
this.heartbeatRetry > 0 && this.heartbeatRetry--
|
|
} else {
|
|
this.heartbeatRetry = 2
|
|
this.Rcon.isRunning = true
|
|
}
|
|
|
|
if (!this.Rcon.isRunning && status != false) {
|
|
this.heartbeatRetry = 1
|
|
this.Rcon.isRunning = true
|
|
wasRunning = true
|
|
this.Manager.log(`^1Connection re-established with ^6[${this.toString()}]^7`)
|
|
setTimeout( async () => {
|
|
await this.loadClientsAsync()
|
|
this.emit('reload')
|
|
}, 10000)
|
|
}
|
|
}
|
|
catch (e) {}
|
|
}
|
|
async loadClientsAsync(retry = 1) {
|
|
var status = await this.Rcon.getStatus()
|
|
|
|
if (!status) {
|
|
await wait(1000 * retry)
|
|
this.loadClientsAsync(++retry)
|
|
return
|
|
}
|
|
|
|
for (var i = 0; i < this.Clients.length; i++) {
|
|
if (!this.Clients[i]) continue
|
|
this.Clients[i].removeAllListeners()
|
|
this.Clients[i] = null
|
|
}
|
|
|
|
status.data.clients.forEach(async c => {
|
|
if (this.Clients[c.num]) this.Clients[c.num].removeAllListeners()
|
|
this.Clients[c.num] = new ePlayer(c.guid, c.name, c.num, c.address, this)
|
|
|
|
await this.Clients[c.num].build()
|
|
|
|
if (!this.Clients[c.num]) return
|
|
|
|
this.emit('connect', this.Clients[c.num])
|
|
this.emit('any_event', {type: 'join', Origin: this.Clients[c.num]})
|
|
})
|
|
}
|
|
globalBroadcast(Message) {
|
|
this.Managers.forEach(Manager => {
|
|
Manager.Server.Broadcast(Message)
|
|
})
|
|
}
|
|
async Broadcast (string) {
|
|
string = string.toString()
|
|
|
|
if (!string) {
|
|
return
|
|
}
|
|
|
|
string = string.replace(new RegExp(/\s+/g), ' ')
|
|
|
|
var chunks = Utils.breakString(string, this.Rcon.commandPrefixes.Dvars.maxSayLength, ' ')
|
|
|
|
for (var i = 0; i < chunks.length; i++) {
|
|
await this.Rcon.executeCommandAsync(this.Rcon.commandPrefixes.Rcon.Say.replace('%MESSAGE%', chunks[i]))
|
|
}
|
|
}
|
|
isZM() {
|
|
return ['zclassic', 'zstandard'].includes(this.Gametype)
|
|
}
|
|
isZMAlt() {
|
|
return ['zgrief', 'zcleansed'].includes(this.Gametype)
|
|
}
|
|
isAliens() {
|
|
return ['aliens'].includes(this.Gametypes)
|
|
}
|
|
isMP() {
|
|
return !this.isZM() && !this.isZMAlt() && !this.isAliens()
|
|
}
|
|
}
|
|
|
|
module.exports = Server |