diff --git a/core/log-parser/index.js b/core/log-parser/index.js index 2a91ebbc..d5047366 100644 --- a/core/log-parser/index.js +++ b/core/log-parser/index.js @@ -16,9 +16,13 @@ export default class LogParser extends EventEmitter { this.eventStore = { disconnected: {}, // holding area, cleared on map change. - players: {}, // persistent data, steamid, controller, suffix. + players: [], // persistent data, steamid, controller, suffix. + playersEOS: [], // proxies from EOSID to persistent data, steamid, controller, suffix. + connectionIdToSteamID: new Map(), session: {}, // old eventstore, nonpersistent data - clients: {} // used in the connection chain before we resolve a player. + clients: {}, // used in the connection chain before we resolve a player. + lastConnection: {}, // used to store the last client connection data to then associate a steamid + joinRequests: [] }; this.linesPerMinute = 0; diff --git a/core/rcon.js b/core/rcon.js index 604cbf22..770aa800 100644 --- a/core/rcon.js +++ b/core/rcon.js @@ -337,8 +337,7 @@ export default class Rcon extends EventEmitter { if (type === SERVERDATA_AUTH) { this.callbackIds.push({ id: this.count, cmd: body }); - Logger.verbose('RCON', 2, `Writing Auth Packet`); - Logger.verbose('RCON', 4, `Writing packet with type "${type}" and body "${body}".`); + this.responseCallbackQueue.push(() => {}); this.responseCallbackQueue.push((decodedPacket) => { this.client.removeListener('error', onError); @@ -352,7 +351,6 @@ export default class Rcon extends EventEmitter { } }); } else { - Logger.verbose('RCON', 2, `Writing packet with type "${type}" and body "${body}".`); this.callbackIds.push({ id: this.count, cmd: body }); this.responseCallbackQueue.push((response) => { this.client.removeListener('error', onError); diff --git a/package.json b/package.json index 29459d4e..5fd1e1bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SquadJS", - "version": "3.8.2", + "version": "4.0.0", "repository": "https://github.com/Team-Silver-Sphere/SquadJS.git", "author": "Thomas Smyth ", "license": "BSL-1.0", diff --git a/squad-server/index.js b/squad-server/index.js index 490f0a7d..840df26e 100644 --- a/squad-server/index.js +++ b/squad-server/index.js @@ -1,7 +1,6 @@ import EventEmitter from 'events'; import axios from 'axios'; -import Gamedig from 'gamedig'; import Logger from 'core/logger'; import { SQUADJS_API_DOMAIN } from 'core/constants'; @@ -19,7 +18,7 @@ export default class SquadServer extends EventEmitter { constructor(options = {}) { super(); - for (const option of ['host', 'queryPort']) + for (const option of ['host']) if (!(option in options)) throw new Error(`${option} must be specified.`); this.id = options.id; @@ -73,13 +72,13 @@ export default class SquadServer extends EventEmitter { this.admins = await fetchAdminLists(this.options.adminLists); await this.rcon.connect(); - await this.logParser.watch(); - await this.updateSquadList(); - await this.updatePlayerList(); + await this.updatePlayerList(this); await this.updateLayerInformation(); await this.updateA2SInformation(); + await this.logParser.watch(); + Logger.verbose('SquadServer', 1, `Watching ${this.serverName}...`); await this.pingSquadJSAPI(); @@ -154,9 +153,12 @@ export default class SquadServer extends EventEmitter { }); this.rcon.on('SQUAD_CREATED', async (data) => { - data.player = await this.getPlayerBySteamID(data.playerSteamID, true); + data.player = await this.getPlayerByEOSID(data.playerEOSID, true); + data.player.squadID = data.squadID; + delete data.playerName; delete data.playerSteamID; + delete data.playerEOSID; this.emit('SQUAD_CREATED', data); }); @@ -207,7 +209,13 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_CONNECTED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + Logger.verbose( + 'SquadServer', + 1, + `Player connected ${data.playerSuffix} - SteamID: ${data.steamID} - EOSID: ${data.eosID} - IP: ${data.ip}` + ); + + data.player = await this.getPlayerByEOSID(data.eosID); if (data.player) data.player.suffix = data.playerSuffix; delete data.steamID; @@ -217,7 +225,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_DISCONNECTED', async (data) => { - data.player = await this.getPlayerBySteamID(data.steamID); + data.player = await this.getPlayerByEOSID(data.eosID); delete data.steamID; @@ -226,12 +234,16 @@ export default class SquadServer extends EventEmitter { this.logParser.on('PLAYER_DAMAGED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); - if (data.victim && data.attacker) + if (data.attacker && !data.attacker.playercontroller && data.attackerController) + data.attacker.playercontroller = data.attackerController; + + if (data.victim && data.attacker) { data.teamkill = data.victim.teamID === data.attacker.teamID && data.victim.steamID !== data.attacker.steamID; + } delete data.victimName; delete data.attackerName; @@ -241,7 +253,7 @@ export default class SquadServer extends EventEmitter { this.logParser.on('PLAYER_WOUNDED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -259,7 +271,7 @@ export default class SquadServer extends EventEmitter { this.logParser.on('PLAYER_DIED', async (data) => { data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); if (!data.attacker) data.attacker = await this.getPlayerByController(data.attackerPlayerController); @@ -275,9 +287,9 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_REVIVED', async (data) => { - data.victim = await this.getPlayerByName(data.victimName); - data.attacker = await this.getPlayerByName(data.attackerName); - data.reviver = await this.getPlayerByName(data.reviverName); + data.victim = await this.getPlayerByEOSID(data.victimEOSID); + data.attacker = await this.getPlayerByEOSID(data.attackerEOSID); + data.reviver = await this.getPlayerByEOSID(data.reviverEOSID); delete data.victimName; delete data.attackerName; @@ -287,7 +299,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_POSSESS', async (data) => { - data.player = await this.getPlayerByNameSuffix(data.playerSuffix); + data.player = await this.getPlayerByEOSID(data.playerEOSID); if (data.player) data.player.possessClassname = data.possessClassname; delete data.playerSuffix; @@ -296,7 +308,7 @@ export default class SquadServer extends EventEmitter { }); this.logParser.on('PLAYER_UNPOSSESS', async (data) => { - data.player = await this.getPlayerByNameSuffix(data.playerSuffix); + data.player = await this.getPlayerByEOSID(data.playerEOSID); delete data.playerSuffix; @@ -310,6 +322,23 @@ export default class SquadServer extends EventEmitter { this.logParser.on('TICK_RATE', (data) => { this.emit('TICK_RATE', data); }); + + this.logParser.on('CLIENT_EXTERNAL_ACCOUNT_INFO', (data) => { + this.rcon.addIds(data.steamID, data.eosID); + }); + // this.logParser.on('CLIENT_CONNECTED', (data) => { + // Logger.verbose("SquadServer", 1, `Client connected. Connection: ${data.connection} - SteamID: ${data.steamID}`) + // }) + // this.logParser.on('CLIENT_LOGIN_REQUEST', (data) => { + // Logger.verbose("SquadServer", 1, `Login request. ChainID: ${data.chainID} - Suffix: ${data.suffix} - EOSID: ${data.eosID}`) + + // }) + // this.logParser.on('RESOLVED_EOS_ID', (data) => { + // Logger.verbose("SquadServer", 1, `Resolved EOSID. ChainID: ${data.chainID} - Suffix: ${data.suffix} - EOSID: ${data.eosID}`) + // }) + // this.logParser.on('ADDING_CLIENT_CONNECTION', (data) => { + // Logger.verbose("SquadServer", 1, `Adding client connection`, data) + // }) } async restartLogParser() { @@ -352,7 +381,7 @@ export default class SquadServer extends EventEmitter { } const players = []; - for (const player of await this.rcon.getListPlayers()) + for (const player of await this.rcon.getListPlayers(this)) players.push({ ...oldPlayerInfo[player.steamID], ...player, @@ -380,6 +409,13 @@ export default class SquadServer extends EventEmitter { }); } + if (this.a2sPlayerCount > 0 && players.length === 0) + Logger.verbose( + 'SquadServer', + 1, + `Real Player Count: ${this.a2sPlayerCount} but loaded ${players.length}` + ); + this.emit('UPDATED_PLAYER_INFORMATION'); } catch (err) { Logger.verbose('SquadServer', 1, 'Failed to update player list.', err); @@ -441,53 +477,70 @@ export default class SquadServer extends EventEmitter { ); } - async updateA2SInformation() { + updateA2SInformation() { + return this.updateServerInformation(); + } + + async updateServerInformation() { if (this.updateA2SInformationTimeout) clearTimeout(this.updateA2SInformationTimeout); - Logger.verbose('SquadServer', 1, `Updating A2S information...`); + Logger.verbose('SquadServer', 1, `Updating server information...`); try { - const data = await Gamedig.query({ - type: 'squad', - host: this.options.host, - port: this.options.queryPort - }); + const rawData = await this.rcon.execute(`ShowServerInfo`); + Logger.verbose('SquadServer', 3, `Server information raw data`, rawData); + const data = JSON.parse(rawData); + Logger.verbose('SquadServer', 2, `Server information data`, JSON.data); const info = { - raw: data.raw, - serverName: data.name, + raw: data, + serverName: data.ServerName_s, + + maxPlayers: parseInt(data.MaxPlayers), + publicQueueLimit: parseInt(data.PublicQueueLimit_I), + reserveSlots: parseInt(data.PlayerReserveCount_I), - maxPlayers: parseInt(data.maxplayers), - publicSlots: parseInt(data.raw.rules.NUMPUBCONN), - reserveSlots: parseInt(data.raw.rules.NUMPRIVCONN), + playerCount: parseInt(data.PlayerCount_I), + a2sPlayerCount: parseInt(data.PlayerCount_I), + publicQueue: parseInt(data.PublicQueue_I), + reserveQueue: parseInt(data.ReservedQueue_I), - a2sPlayerCount: parseInt(data.raw.rules.PlayerCount_i), - publicQueue: parseInt(data.raw.rules.PublicQueue_i), - reserveQueue: parseInt(data.raw.rules.ReservedQueue_i), + currentLayer: data.MapName_s, + nextLayer: data.NextLayer_s, - matchTimeout: parseFloat(data.raw.rules.MatchTimeout_f), - gameVersion: data.raw.version + teamOne: data.TeamOne_s?.replace(new RegExp(data.MapName_s, 'i'), '') || '', + teamTwo: data.TeamTwo_s?.replace(new RegExp(data.MapName_s, 'i'), '') || '', + + matchTimeout: parseFloat(data.MatchTimeout_d), + matchStartTime: this.getMatchStartTimeByPlaytime(data.PLAYTIME_I), + gameVersion: data.GameVersion_s }; this.serverName = info.serverName; this.maxPlayers = info.maxPlayers; - this.publicSlots = info.publicSlots; + this.publicSlots = info.maxPlayers - info.reserveSlots; this.reserveSlots = info.reserveSlots; - this.a2sPlayerCount = info.a2sPlayerCount; + this.a2sPlayerCount = info.playerCount; + this.playerCount = info.playerCount; this.publicQueue = info.publicQueue; this.reserveQueue = info.reserveQueue; this.matchTimeout = info.matchTimeout; + this.matchStartTime = info.matchStartTime; this.gameVersion = info.gameVersion; + if (!this.currentLayer) this.currentLayer = Layers.getLayerByClassname(info.currentLayer); + if (!this.nextLayer) this.nextLayer = Layers.getLayerByClassname(info.nextLayer); + this.emit('UPDATED_A2S_INFORMATION', info); + this.emit('UPDATED_SERVER_INFORMATION', info); } catch (err) { - Logger.verbose('SquadServer', 1, 'Failed to update A2S information.', err); + Logger.verbose('SquadServer', 1, 'Failed to update server information.', err); } - Logger.verbose('SquadServer', 1, `Updated A2S information.`); + Logger.verbose('SquadServer', 1, `Updated server information.`); this.updateA2SInformationTimeout = setTimeout( this.updateA2SInformation, @@ -542,6 +595,10 @@ export default class SquadServer extends EventEmitter { return this.getPlayerByCondition((player) => player.steamID === steamID, forceUpdate); } + async getPlayerByEOSID(eosID, forceUpdate) { + return this.getPlayerByCondition((player) => player.eosID === eosID, forceUpdate); + } + async getPlayerByName(name, forceUpdate) { return this.getPlayerByCondition((player) => player.name === name, forceUpdate); } @@ -606,4 +663,8 @@ export default class SquadServer extends EventEmitter { this.pingSquadJSAPITimeout = setTimeout(this.pingSquadJSAPI, this.pingSquadJSAPIInterval); } + + getMatchStartTimeByPlaytime(playtime) { + return new Date(Date.now() - +playtime * 1000); + } } diff --git a/squad-server/log-parser/adding-client-connection.js b/squad-server/log-parser/adding-client-connection.js new file mode 100644 index 00000000..968cf8b9 --- /dev/null +++ b/squad-server/log-parser/adding-client-connection.js @@ -0,0 +1,20 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: AddClientConnection: Added client connection: \[UNetConnection\] RemoteAddr: ([\d.]+):[0-9]+, Name: (EOSIpNetConnection_[0-9]+), Driver: GameNetDriver (EOSNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: INVALID/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: args[2], + // steamID: args[ 3 ], + ip: args[3], + connection: args[4], + driver: args[5] + }; + /* This is Called when unreal engine adds a client connection + First Step in Adding a Player to server + */ + logParser.eventStore['last-connection'] = data; + logParser.emit('ADDING_CLIENT_CONNECTION', data); + } +}; diff --git a/squad-server/log-parser/check-permission-resolve-eosid.js b/squad-server/log-parser/check-permission-resolve-eosid.js new file mode 100644 index 00000000..6579be52 --- /dev/null +++ b/squad-server/log-parser/check-permission-resolve-eosid.js @@ -0,0 +1,15 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadCommon: SQCommonStatics Check Permissions, UniqueId:([\da-f]+)$/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + eosID: args[3] + }; + + logParser.eventStore.joinRequests[data.chainID].eosID = data.eosID; + logParser.emit('RESOLVED_EOS_ID', { ...logParser.eventStore.joinRequests[data.chainID] }); + } +}; diff --git a/squad-server/log-parser/client-external-account-info.js b/squad-server/log-parser/client-external-account-info.js new file mode 100644 index 00000000..38ff55f2 --- /dev/null +++ b/squad-server/log-parser/client-external-account-info.js @@ -0,0 +1,21 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]+)]LogEOS: Verbose: \[LogEOSConnect] FConnectClient::CacheExternalAccountInfo - ProductUserId: (?[0-9a-f]{32}), AccountType: (\d), AccountId: (?[0-9]{17}), DisplayName: /, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: args[2], + eosID: args.groups.eosId, + steamID: args.groups.steamId + }; + + logParser.eventStore.players[data.steamID] = { + eosID: data.eosID, + steamID: data.steamID + }; + logParser.eventStore.playersEOS[data.eosID] = logParser.eventStore.players[data.steamID]; + + logParser.emit('CLIENT_EXTERNAL_ACCOUNT_INFO', data); + } +}; diff --git a/squad-server/log-parser/client-login.js b/squad-server/log-parser/client-login.js index 20b84d72..e98456c4 100644 --- a/squad-server/log-parser/client-login.js +++ b/squad-server/log-parser/client-login.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Login: NewPlayer: SteamNetConnection \/Engine\/Transient\.(SteamNetConnection_[0-9]+)/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Login: NewPlayer: EOSIpNetConnection \/Engine\/Transient\.(EOSIpNetConnection_[0-9]+)/, onMatch: (args, logParser) => { const data = { raw: args[0], @@ -13,7 +13,7 @@ export default { 2nd Step in player connected path */ - logParser.eventStore['client-login'] = logParser.eventStore.clients[args[3]]; + logParser.eventStore.joinRequests[data.chainID].connection = data.connection; delete logParser.eventStore.clients[args[3]]; logParser.emit('CLIENT_LOGIN', data); } diff --git a/squad-server/log-parser/index.js b/squad-server/log-parser/index.js index 594734c4..fa9b3737 100644 --- a/squad-server/log-parser/index.js +++ b/squad-server/log-parser/index.js @@ -16,10 +16,15 @@ import RoundEnded from './round-ended.js'; import RoundTickets from './round-tickets.js'; import RoundWinner from './round-winner.js'; import ServerTickRate from './server-tick-rate.js'; -import ClientConnected from './client-connected.js'; +import AddingClientConnection from './adding-client-connection.js'; import ClientLogin from './client-login.js'; import PendingConnectionDestroyed from './pending-connection-destroyed.js'; - +import ClientExternalAccountInfo from './client-external-account-info.js'; +import SendingAuthResult from './sending-auth-result.js'; +import LoginRequest from './login-request.js'; +import JoinRequest from './join-request.js'; +import PlayerJoinSucceeded from './player-join-succeeded.js'; +import CheckPermissionResolveEosid from './check-permission-resolve-eosid.js'; export default class SquadLogParser extends LogParser { constructor(options) { super('SquadGame.log', options); @@ -43,9 +48,15 @@ export default class SquadLogParser extends LogParser { RoundTickets, RoundWinner, ServerTickRate, - ClientConnected, + AddingClientConnection, ClientLogin, - PendingConnectionDestroyed + PendingConnectionDestroyed, + ClientExternalAccountInfo, + SendingAuthResult, + LoginRequest, + JoinRequest, + PlayerJoinSucceeded, + CheckPermissionResolveEosid ]; } } diff --git a/squad-server/log-parser/join-request.js b/squad-server/log-parser/join-request.js new file mode 100644 index 00000000..5cd2e249 --- /dev/null +++ b/squad-server/log-parser/join-request.js @@ -0,0 +1,15 @@ +export default { + regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join request: .+\?Name=(.+)\?SplitscreenCount=\d$/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + suffix: args[3] + }; + + logParser.eventStore.joinRequests[data.chainID] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_JOIN_REQUEST', data); + } +}; diff --git a/squad-server/log-parser/login-request.js b/squad-server/log-parser/login-request.js new file mode 100644 index 00000000..905f6812 --- /dev/null +++ b/squad-server/log-parser/login-request.js @@ -0,0 +1,17 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Login request: \?Name=(.+?)(?:\?PASSWORD=(?:.+?))? userId: RedpointEOS:([\da-f]{32}) platform: RedpointEOS/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + suffix: args[3], + eosID: args[4] + }; + + // logParser.eventStore.loginRequests[ data.chainID ] = data; + // console.log(logParser.eventStore.loginRequests[ data.chainID ]) + logParser.emit('CLIENT_LOGIN_REQUEST', data); + } +}; diff --git a/squad-server/log-parser/pending-connection-destroyed.js b/squad-server/log-parser/pending-connection-destroyed.js index 3d4aa1c6..5ec32398 100644 --- a/squad-server/log-parser/pending-connection-destroyed.js +++ b/squad-server/log-parser/pending-connection-destroyed.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UNetConnection::PendingConnectionLost\. \[UNetConnection\] RemoteAddr: ([0-9]{17}):[0-9]+, Name: (SteamNetConnection_[0-9]+), Driver: GameNetDriver (SteamNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: (?:Steam:UNKNOWN \[.+\]|INVALID) bPendingDestroy=0/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UNetConnection::PendingConnectionLost\. \[UNetConnection\] RemoteAddr: ([0-9a-f]{32}):[0-9]+, Name: (SteamNetConnection_[0-9]+), Driver: GameNetDriver (SteamNetDriver_[0-9]+), IsServer: YES, PC: NULL, Owner: NULL, UniqueId: (?:Steam:UNKNOWN \[.+\]|INVALID) bPendingDestroy=0/, onMatch: (args, logParser) => { const data = { raw: args[0], diff --git a/squad-server/log-parser/player-connected.js b/squad-server/log-parser/player-connected.js index 279257d7..65682271 100644 --- a/squad-server/log-parser/player-connected.js +++ b/squad-server/log-parser/player-connected.js @@ -1,27 +1,25 @@ export default { - regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join succeeded: (.+)/, + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: PostLogin: NewPlayer: BP_PlayerController_C .+PersistentLevel\.([^\s]+) \(IP: ([\d.]+) \| Online IDs: EOS: ([0-9a-f]{32}) steam: (\d+)\)/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], - chainID: args[2], - playerSuffix: args[3], - steamID: logParser.eventStore['client-login'], // player connected - controller: logParser.eventStore['player-controller'] // playercontroller connected + chainID: +args[2], + playercontroller: args[3], + ip: args[4], + eosID: args[5], + steamID: args[6] }; - delete logParser.eventStore['client-login']; - delete logParser.eventStore['player-controller']; + const joinRequestData = logParser.eventStore.joinRequests[+args[2]]; + data.connection = joinRequestData.connection; + data.playerSuffix = joinRequestData.suffix; + + if (!logParser.eventStore.players[data.steamID]) + logParser.eventStore.players[data.steamID] = {}; + logParser.eventStore.players[data.steamID].controller = data.playercontroller; - // Handle Reconnecting players - if (logParser.eventStore.disconnected[data.steamID]) { - delete logParser.eventStore.disconnected[data.steamID]; - } logParser.emit('PLAYER_CONNECTED', data); - logParser.eventStore.players[data.steamID] = { - steamID: data.steamID, - suffix: data.playerSuffix, - controller: data.controller - }; } }; diff --git a/squad-server/log-parser/player-damaged.js b/squad-server/log-parser/player-damaged.js index b1057e2c..9f293b61 100644 --- a/squad-server/log-parser/player-damaged.js +++ b/squad-server/log-parser/player-damaged.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Player:(.+) ActualDamage=([0-9.]+) from (.+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: Player:(.+) ActualDamage=([0-9.]+) from (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17}) \| Player Controller ID: ([^ ]+)\)caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { raw: args[0], @@ -9,11 +9,18 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerName: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + attackerController: args[8], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; + if (!logParser.eventStore.players[data.attackerSteamID]) + logParser.eventStore.players[data.attackerSteamID] = {}; + logParser.eventStore.players[data.attackerSteamID].controller = data.attackerController; + logParser.emit('PLAYER_DAMAGED', data); } }; diff --git a/squad-server/log-parser/player-died.js b/squad-server/log-parser/player-died.js index c9f15201..98557561 100644 --- a/squad-server/log-parser/player-died.js +++ b/squad-server/log-parser/player-died.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Die\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Die\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17}) \| Contoller ID: ([\w\d]+)\) caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -11,7 +11,9 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerPlayerController: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; diff --git a/squad-server/log-parser/player-disconnected.js b/squad-server/log-parser/player-disconnected.js index 352a0124..20ef0100 100644 --- a/squad-server/log-parser/player-disconnected.js +++ b/squad-server/log-parser/player-disconnected.js @@ -1,16 +1,18 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([0-9]{17}):[0-9]+, Name: SteamNetConnection_[0-9]+, Driver: GameNetDriver SteamNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: UChannel::Close: Sending CloseBunch\. ChIndex == [0-9]+\. Name: \[UChannel\] ChIndex: [0-9]+, Closing: [0-9]+ \[UNetConnection\] RemoteAddr: ([\d.]+):[\d]+, Name: EOSIpNetConnection_[0-9]+, Driver: GameNetDriver EOSNetDriver_[0-9]+, IsServer: YES, PC: ([^ ]+PlayerController_C_[0-9]+), Owner: [^ ]+PlayerController_C_[0-9]+, UniqueId: RedpointEOS:([\d\w]+)/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], chainID: args[2], - steamID: args[3], - playerController: args[4] + ip: args[3], + playerController: args[4], + eosID: args[5] }; logParser.eventStore.disconnected[data.steamID] = true; + logParser.emit('PLAYER_DISCONNECTED', data); } }; diff --git a/squad-server/log-parser/player-join-succeeded.js b/squad-server/log-parser/player-join-succeeded.js new file mode 100644 index 00000000..cae145a2 --- /dev/null +++ b/squad-server/log-parser/player-join-succeeded.js @@ -0,0 +1,28 @@ +export default { + regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogNet: Join succeeded: (.+)/, + onMatch: (args, logParser) => { + const data = { + raw: args[0], + time: args[1], + chainID: +args[2], + playerSuffix: args[3] + }; + + const joinRequestsData = { ...logParser.eventStore.joinRequests[data.chainID] }; + + data.eosID = joinRequestsData.eosID; + data.controller = joinRequestsData.controller; + data.steamID = `${logParser.eventStore.connectionIdToSteamID.get(joinRequestsData.connection)}`; + + logParser.eventStore.connectionIdToSteamID.delete(joinRequestsData.connection); + + delete logParser.eventStore.joinRequests[+data.chainID]; + + // Handle Reconnecting players + if (logParser.eventStore.disconnected[data.steamID]) { + delete logParser.eventStore.disconnected[data.steamID]; + } + + logParser.emit('JOIN_SUCCEEDED', data); + } +}; diff --git a/squad-server/log-parser/player-possess.js b/squad-server/log-parser/player-possess.js index 12ceb84c..f6302eb9 100644 --- a/squad-server/log-parser/player-possess.js +++ b/squad-server/log-parser/player-possess.js @@ -1,13 +1,15 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnPossess\(\): PC=(.+) Pawn=([A-z0-9_]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnPossess\(\): PC=(.+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17})\) Pawn=([A-z0-9_]+)_C/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], chainID: args[2], playerSuffix: args[3], - possessClassname: args[4], + playerEOSID: args[4], + playerSteamID: args[5], + possessClassname: args[6], pawn: args[5] }; diff --git a/squad-server/log-parser/player-revived.js b/squad-server/log-parser/player-revived.js index bb412ee4..819a2d7d 100644 --- a/squad-server/log-parser/player-revived.js +++ b/squad-server/log-parser/player-revived.js @@ -1,6 +1,7 @@ export default { // the names are currently the wrong way around in these logs - regex: /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: (.+) has revived (.+)\./, + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquad: (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})\) has revived (.+) \(Online IDs: EOS: ([0-9a-f]{32}) steam: (\d{17})\)\./, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -8,7 +9,11 @@ export default { time: args[1], chainID: args[2], reviverName: args[3], - victimName: args[4] + reviverEOSID: args[4], + reviverSteamID: args[5], + victimName: args[6], + victimEOSID: args[7], + victimSteamID: args[8] }; logParser.emit('PLAYER_REVIVED', data); diff --git a/squad-server/log-parser/player-un-possess.js b/squad-server/log-parser/player-un-possess.js index d2b9bc40..9df7e00d 100644 --- a/squad-server/log-parser/player-un-possess.js +++ b/squad-server/log-parser/player-un-possess.js @@ -1,14 +1,16 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnUnPossess\(\): PC=(.+)/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQPlayerController::)?OnUnPossess\(\): PC=(.+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17})\)/, onMatch: (args, logParser) => { const data = { raw: args[0], time: args[1], chainID: args[2], playerSuffix: args[3], + playerEOSID: args[4], + playerSteamID: args[5], switchPossess: - args[3] in logParser.eventStore.session && logParser.eventStore.session[args[3]] === args[2] + args[4] in logParser.eventStore.session && logParser.eventStore.session[args[4]] === args[2] }; delete logParser.eventStore.session[args[3]]; diff --git a/squad-server/log-parser/player-wounded.js b/squad-server/log-parser/player-wounded.js index 9a832dc0..ef61b26f 100644 --- a/squad-server/log-parser/player-wounded.js +++ b/squad-server/log-parser/player-wounded.js @@ -1,6 +1,6 @@ export default { regex: - /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Wound\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) caused by ([A-z_0-9-]+)_C/, + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogSquadTrace: \[DedicatedServer](?:ASQSoldier::)?Wound\(\): Player:(.+) KillingDamage=(?:-)*([0-9.]+) from ([A-z_0-9]+) \(Online IDs: EOS: ([\w\d]{32}) steam: (\d{17}) \| Controller ID: ([\w\d]+)\) caused by ([A-z_0-9-]+)_C/, onMatch: (args, logParser) => { const data = { ...logParser.eventStore.session[args[3]], @@ -10,7 +10,9 @@ export default { victimName: args[3], damage: parseFloat(args[4]), attackerPlayerController: args[5], - weapon: args[6] + attackerEOSID: args[6], + attackerSteamID: args[7], + weapon: args[9] }; logParser.eventStore.session[args[3]] = data; diff --git a/squad-server/log-parser/playercontroller-connected.js b/squad-server/log-parser/playercontroller-connected.js index 18a0216f..68451abc 100644 --- a/squad-server/log-parser/playercontroller-connected.js +++ b/squad-server/log-parser/playercontroller-connected.js @@ -5,12 +5,11 @@ export default { const data = { raw: args[0], time: args[1], - chainID: args[2], + chainID: +args[2], controller: args[3] }; - logParser.eventStore['player-controller'] = args[3]; - + logParser.eventStore.joinRequests[data.chainID].controller = data.controller; logParser.emit('PLAYER_CONTROLLER_CONNECTED', data); } }; diff --git a/squad-server/log-parser/sending-auth-result.js b/squad-server/log-parser/sending-auth-result.js new file mode 100644 index 00000000..c8f75d1a --- /dev/null +++ b/squad-server/log-parser/sending-auth-result.js @@ -0,0 +1,21 @@ +export default { + regex: + /^\[([0-9.:-]+)]\[([ 0-9]*)]LogOnline: STEAM: AUTH HANDLER: Sending auth result to user (\d{17}) with flag success\? 1/, + onMatch: (args, logParser) => { + if (!logParser.eventStore['last-connection']) return; + + const data = { + ...logParser.eventStore['last-connection'], + steamID: args[3] + }; + /* This is Called when unreal engine adds a client connection + First Step in Adding a Player to server + */ + + logParser.eventStore.clients[data.connection] = data.steamID; + logParser.eventStore.connectionIdToSteamID.set(data.connection, data.steamID); + logParser.emit('CLIENT_CONNECTED', data); + + delete logParser.eventStore['last-connection']; + } +}; diff --git a/squad-server/plugins/db-log.js b/squad-server/plugins/db-log.js index e3eb64af..2e681632 100644 --- a/squad-server/plugins/db-log.js +++ b/squad-server/plugins/db-log.js @@ -2,7 +2,7 @@ import Sequelize from 'sequelize'; import BasePlugin from './base-plugin.js'; -const { DataTypes } = Sequelize; +const { DataTypes, QueryTypes } = Sequelize; export default class DBLog extends BasePlugin { static get description() { @@ -146,6 +146,44 @@ export default class DBLog extends BasePlugin { } ); + this.createModel( + 'Player', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + eosID: { + type: DataTypes.STRING, + unique: true + }, + steamID: { + type: DataTypes.STRING, + notNull: true, + unique: true + }, + lastName: { + type: DataTypes.STRING + }, + lastIP: { + type: DataTypes.STRING + } + }, + { + charset: 'utf8mb4', + collate: 'utf8mb4_unicode_ci', + indexes: [ + { + fields: ['eosID'] + }, + { + fields: ['steamID'] + } + ] + } + ); + this.createModel( 'Wound', { @@ -329,37 +367,44 @@ export default class DBLog extends BasePlugin { onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Wound, { + this.models.Player.hasMany(this.models.Wound, { + sourceKey: 'steamID', foreignKey: { name: 'attacker' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Wound, { + this.models.Player.hasMany(this.models.Wound, { + sourceKey: 'steamID', foreignKey: { name: 'victim' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Death, { + this.models.Player.hasMany(this.models.Death, { + sourceKey: 'steamID', foreignKey: { name: 'attacker' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Death, { + this.models.Player.hasMany(this.models.Death, { + sourceKey: 'steamID', foreignKey: { name: 'victim' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Revive, { + this.models.Player.hasMany(this.models.Revive, { + sourceKey: 'steamID', foreignKey: { name: 'attacker' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Revive, { + this.models.Player.hasMany(this.models.Revive, { + sourceKey: 'steamID', foreignKey: { name: 'victim' }, onDelete: 'CASCADE' }); - this.models.SteamUser.hasMany(this.models.Revive, { + this.models.Player.hasMany(this.models.Revive, { + sourceKey: 'steamID', foreignKey: { name: 'reviver' }, onDelete: 'CASCADE' }); @@ -392,9 +437,12 @@ export default class DBLog extends BasePlugin { this.onTickRate = this.onTickRate.bind(this); this.onUpdatedA2SInformation = this.onUpdatedA2SInformation.bind(this); this.onNewGame = this.onNewGame.bind(this); + this.onPlayerConnected = this.onPlayerConnected.bind(this); this.onPlayerWounded = this.onPlayerWounded.bind(this); this.onPlayerDied = this.onPlayerDied.bind(this); this.onPlayerRevived = this.onPlayerRevived.bind(this); + this.migrateSteamUsersIntoPlayers = this.migrateSteamUsersIntoPlayers.bind(this); + this.dropAllForeignKeys = this.dropAllForeignKeys.bind(this); } createModel(name, schema) { @@ -409,12 +457,15 @@ export default class DBLog extends BasePlugin { await this.models.TickRate.sync(); await this.models.PlayerCount.sync(); await this.models.SteamUser.sync(); + await this.models.Player.sync(); await this.models.Wound.sync(); await this.models.Death.sync(); await this.models.Revive.sync(); } async mount() { + await this.migrateSteamUsersIntoPlayers(); + await this.models.Server.upsert({ id: this.options.overrideServerID || this.server.id, name: this.server.serverName @@ -427,6 +478,7 @@ export default class DBLog extends BasePlugin { this.server.on('TICK_RATE', this.onTickRate); this.server.on('UPDATED_A2S_INFORMATION', this.onUpdatedA2SInformation); this.server.on('NEW_GAME', this.onNewGame); + this.server.on('PLAYER_CONNECTED', this.onPlayerConnected); this.server.on('PLAYER_WOUNDED', this.onPlayerWounded); this.server.on('PLAYER_DIED', this.onPlayerDied); this.server.on('PLAYER_REVIVED', this.onPlayerRevived); @@ -436,6 +488,7 @@ export default class DBLog extends BasePlugin { this.server.removeEventListener('TICK_RATE', this.onTickRate); this.server.removeEventListener('UPDATED_A2S_INFORMATION', this.onTickRate); this.server.removeEventListener('NEW_GAME', this.onNewGame); + this.server.removeEventListener('PLAYER_CONNECTED', this.onPlayerConnected); this.server.removeEventListener('PLAYER_WOUNDED', this.onPlayerWounded); this.server.removeEventListener('PLAYER_DIED', this.onPlayerDied); this.server.removeEventListener('PLAYER_REVIVED', this.onPlayerRevived); @@ -479,15 +532,27 @@ export default class DBLog extends BasePlugin { async onPlayerWounded(info) { if (info.attacker) - await this.models.SteamUser.upsert({ - steamID: info.attacker.steamID, - lastName: info.attacker.name - }); + await this.models.Player.upsert( + { + eosID: info.attacker.eosID, + steamID: info.attacker.steamID, + lastName: info.attacker.name + }, + { + conflictFields: ['steamID'] + } + ); if (info.victim) - await this.models.SteamUser.upsert({ - steamID: info.victim.steamID, - lastName: info.victim.name - }); + await this.models.Player.upsert( + { + eosID: info.victim.eosID, + steamID: info.victim.steamID, + lastName: info.victim.name + }, + { + conflictFields: ['steamID'] + } + ); await this.models.Wound.create({ server: this.options.overrideServerID || this.server.id, @@ -509,15 +574,27 @@ export default class DBLog extends BasePlugin { async onPlayerDied(info) { if (info.attacker) - await this.models.SteamUser.upsert({ - steamID: info.attacker.steamID, - lastName: info.attacker.name - }); + await this.models.Player.upsert( + { + eosID: info.attacker.eosID, + steamID: info.attacker.steamID, + lastName: info.attacker.name + }, + { + conflictFields: ['steamID'] + } + ); if (info.victim) - await this.models.SteamUser.upsert({ - steamID: info.victim.steamID, - lastName: info.victim.name - }); + await this.models.Player.upsert( + { + eosID: info.victim.eosID, + steamID: info.victim.steamID, + lastName: info.victim.name + }, + { + conflictFields: ['steamID'] + } + ); await this.models.Death.create({ server: this.options.overrideServerID || this.server.id, @@ -540,20 +617,38 @@ export default class DBLog extends BasePlugin { async onPlayerRevived(info) { if (info.attacker) - await this.models.SteamUser.upsert({ - steamID: info.attacker.steamID, - lastName: info.attacker.name - }); + await this.models.Player.upsert( + { + eosID: info.attacker.eosID, + steamID: info.attacker.steamID, + lastName: info.attacker.name + }, + { + conflictFields: ['steamID'] + } + ); if (info.victim) - await this.models.SteamUser.upsert({ - steamID: info.victim.steamID, - lastName: info.victim.name - }); + await this.models.Player.upsert( + { + eosID: info.victim.eosID, + steamID: info.victim.steamID, + lastName: info.victim.name + }, + { + conflictFields: ['steamID'] + } + ); if (info.reviver) - await this.models.SteamUser.upsert({ - steamID: info.reviver.steamID, - lastName: info.reviver.name - }); + await this.models.Player.upsert( + { + eosID: info.reviver.eosID, + steamID: info.reviver.steamID, + lastName: info.reviver.name + }, + { + conflictFields: ['steamID'] + } + ); await this.models.Revive.create({ server: this.options.overrideServerID || this.server.id, @@ -577,4 +672,89 @@ export default class DBLog extends BasePlugin { reviverSquadID: info.reviver ? info.reviver.squadID : null }); } + + async onPlayerConnected(info) { + await this.models.Player.upsert( + { + eosID: info.eosID, + steamID: info.player.steamID, + lastName: info.player.name, + lastIP: info.ip + }, + { + conflictFields: ['steamID'] + } + ); + } + + async migrateSteamUsersIntoPlayers() { + try { + const steamUsersCount = await this.models.SteamUser.count(); + const playersCount = await this.models.Player.count(); + + if (steamUsersCount < playersCount) { + this.verbose( + 1, + `Skipping migration from SteamUsers to Players due to a previous successful migration.` + ); + return; + } + + await this.dropAllForeignKeys(); + + const steamUsers = (await this.models.SteamUser.findAll()).map((u) => u.dataValues); + await this.models.Player.bulkCreate(steamUsers); + + this.verbose(1, `Migration from SteamUsers to Players successful`); + } catch (error) { + this.verbose(1, `Error during Migration from SteamUsers to Players: ${error}`); + } + } + + async dropAllForeignKeys() { + this.verbose( + 1, + `Starting to drop constraints on DB: ${this.options.database.config.database} related to DBLog_SteamUsers deptecated table.` + ); + for (const modelName in this.models) { + const model = this.models[modelName]; + const tableName = model.tableName; + + try { + const result = await this.options.database.query( + `SELECT * FROM information_schema.key_column_usage WHERE referenced_table_name IS NOT NULL AND table_schema = '${this.options.database.config.database}' AND table_name = '${tableName}';`, + { type: QueryTypes.SELECT } + ); + + for (const r of result) { + if (r.REFERENCED_TABLE_NAME === 'DBLog_SteamUsers') { + this.verbose( + 1, + `Found constraint ${r.COLUMN_NAME} on table ${tableName}, referencing ${r.REFERENCED_COLUMN_NAME} on ${r.REFERENCED_TABLE_NAME}` + ); + + await this.options.database + .query(`ALTER TABLE ${tableName} DROP FOREIGN KEY ${r.CONSTRAINT_NAME}`, { + type: QueryTypes.RAW + }) + .then(() => { + this.verbose(1, `Dropped foreign key ${r.COLUMN_NAME} on table ${tableName}`); + }) + .catch((e) => { + this.verbose( + 1, + `Error dropping foreign key ${r.COLUMN_NAME} on table ${tableName}:`, + e + ); + }); + } + } + } catch (error) { + this.verbose(1, `Error dropping foreign keys for table ${tableName}:`, error); + } finally { + model.sync(); + } + } + await this.models.Player.sync(); + } } diff --git a/squad-server/rcon.js b/squad-server/rcon.js index 7209d7a0..29626529 100644 --- a/squad-server/rcon.js +++ b/squad-server/rcon.js @@ -4,7 +4,7 @@ import Rcon from 'core/rcon'; export default class SquadRcon extends Rcon { processChatPacket(decodedPacket) { const matchChat = decodedPacket.body.match( - /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[SteamID:([0-9]{17})] (.+?) : (.*)/ + /\[(ChatAll|ChatTeam|ChatSquad|ChatAdmin)] \[Online IDs:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+?) : (.*)/ ); if (matchChat) { Logger.verbose('SquadRcon', 2, `Matched chat message: ${decodedPacket.body}`); @@ -12,9 +12,10 @@ export default class SquadRcon extends Rcon { this.emit('CHAT_MESSAGE', { raw: decodedPacket.body, chat: matchChat[1], - steamID: matchChat[2], - name: matchChat[3], - message: matchChat[4], + eosID: matchChat[2], + steamID: matchChat[3], + name: matchChat[4], + message: matchChat[5], time: new Date() }); @@ -22,14 +23,14 @@ export default class SquadRcon extends Rcon { } const matchPossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9]{17})] (.+?) has possessed admin camera./ + /\[Online Ids:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+) has possessed admin camera\./ ); if (matchPossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('POSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchPossessedAdminCam[1], - name: matchPossessedAdminCam[2], + steamID: matchPossessedAdminCam[2], + name: matchPossessedAdminCam[3], time: new Date() }); @@ -37,14 +38,14 @@ export default class SquadRcon extends Rcon { } const matchUnpossessedAdminCam = decodedPacket.body.match( - /\[SteamID:([0-9]{17})] (.+?) has unpossessed admin camera./ + /\[Online IDs:EOS: ([0-9a-f]{32}) steam: (\d{17})\] (.+) has unpossessed admin camera\./ ); if (matchUnpossessedAdminCam) { Logger.verbose('SquadRcon', 2, `Matched admin camera possessed: ${decodedPacket.body}`); this.emit('UNPOSSESSED_ADMIN_CAMERA', { raw: decodedPacket.body, - steamID: matchUnpossessedAdminCam[1], - name: matchUnpossessedAdminCam[2], + steamID: matchUnpossessedAdminCam[2], + name: matchUnpossessedAdminCam[3], time: new Date() }); @@ -68,7 +69,7 @@ export default class SquadRcon extends Rcon { } const matchKick = decodedPacket.body.match( - /Kicked player ([0-9]+)\. \[steamid=([0-9]{17})] (.*)/ + /Kicked player ([0-9]+)\. \[Online IDs= EOS: ([0-9a-f]{32}) steam: (\d{17})] (.*)/ ); if (matchKick) { Logger.verbose('SquadRcon', 2, `Matched kick message: ${decodedPacket.body}`); @@ -76,8 +77,8 @@ export default class SquadRcon extends Rcon { this.emit('PLAYER_KICKED', { raw: decodedPacket.body, playerID: matchKick[1], - steamID: matchKick[2], - name: matchKick[3], + steamID: matchKick[3], + name: matchKick[4], time: new Date() }); @@ -85,18 +86,14 @@ export default class SquadRcon extends Rcon { } const matchSqCreated = decodedPacket.body.match( - /(.+) \(Steam ID: ([0-9]{17})\) has created Squad (\d+) \(Squad Name: (.+)\) on (.+)/ + /(?.+) \(Online IDs: EOS: (?[\da-f]{32})(?: steam: (?\d{17}))?\) has created Squad (?\d+) \(Squad Name: (?.+)\) on (?.+)/ ); if (matchSqCreated) { Logger.verbose('SquadRcon', 2, `Matched Squad Created: ${decodedPacket.body}`); this.emit('SQUAD_CREATED', { time: new Date(), - playerName: matchSqCreated[1], - playerSteamID: matchSqCreated[2], - squadID: matchSqCreated[3], - squadName: matchSqCreated[4], - teamName: matchSqCreated[5] + ...matchSqCreated.groups }); return; @@ -143,19 +140,17 @@ export default class SquadRcon extends Rcon { for (const line of response.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| SteamID: ([0-9]{17}) \| Name: (.+) \| Team ID: ([0-9]+) \| Squad ID: ([0-9]+|N\/A) \| Is Leader: (True|False) \| Role: ([A-Za-z0-9_]*)\b/ + /^ID: (?\d+) \| Online IDs: EOS: (?[a-f\d]{32}) (?:steam: (?\d{17}) )?\| Name: (?.+) \| Team ID: (?\d|N\/A) \| Squad ID: (?\d+|N\/A) \| Is Leader: (?True|False) \| Role: (?.+)$/ ); if (!match) continue; - players.push({ - playerID: match[1], - steamID: match[2], - name: match[3], - teamID: match[4], - squadID: match[5] !== 'N/A' ? match[5] : null, - isLeader: match[6] === 'True', - role: match[7] - }); + const data = match.groups; + data.playerID = +data.playerID; + data.isLeader = data.isLeader === 'True'; + data.teamID = data.teamID !== 'N/A' ? +data.teamID : null; + data.squadID = data.squadID !== 'N/A' ? +data.squadID : null; + + players.push(data); } return players; @@ -172,21 +167,17 @@ export default class SquadRcon extends Rcon { for (const line of responseSquad.split('\n')) { const match = line.match( - /ID: ([0-9]+) \| Name: (.+) \| Size: ([0-9]+) \| Locked: (True|False) \| Creator Name: (.+) \| Creator Steam ID: ([0-9]{17})/ + /ID: (?\d+) \| Name: (?.+) \| Size: (?\d+) \| Locked: (?True|False) \| Creator Name: (?.+) \| Creator Online IDs: EOS: (?[a-f\d]{32})(?: steam: (?\d{17}))?/ ); - const matchSide = line.match(/Team ID: (1|2) \((.+)\)/); + const matchSide = line.match(/Team ID: (\d) \((.+)\)/); if (matchSide) { - teamID = matchSide[1]; + teamID = +matchSide[1]; teamName = matchSide[2]; } if (!match) continue; + match.groups.squadID = +match.groups.squadID; squads.push({ - squadID: match[1], - squadName: match[2], - size: match[3], - locked: match[4], - creatorName: match[5], - creatorSteamID: match[6], + ...match.groups, teamID: teamID, teamName: teamName });