diff --git a/README.md b/README.md
index e1b720e3..b137e4f9 100644
--- a/README.md
+++ b/README.md
@@ -210,7 +210,7 @@ Interested in creating your own plugin? [See more here](./squad-server/plugins/r
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
+ Join a squad, you are unassigned and will be kicked
kickMessage
Description
Message to send to players when they are kicked
diff --git a/config.json b/config.json
index e68f5e58..c3e82bc6 100644
--- a/config.json
+++ b/config.json
@@ -43,7 +43,7 @@
{
"plugin": "AutoKickUnassigned",
"enabled": true,
- "warningMessage": "Join a squad, you are are unassigned and will be kicked",
+ "warningMessage": "Join a squad, you are unassigned and will be kicked",
"kickMessage": "Unassigned - automatically removed",
"frequencyOfWarnings": 30,
"unassignedTimer": 360,
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 61b20309..770aa800 100644
--- a/core/rcon.js
+++ b/core/rcon.js
@@ -28,13 +28,15 @@ export default class Rcon extends EventEmitter {
// bind methods
this.connect = this.connect.bind(this); // we bind this as we call it on the auto reconnect timeout
- this.onData = this.onData.bind(this);
+ this.onPacket = this.onPacket.bind(this);
this.onClose = this.onClose.bind(this);
this.onError = this.onError.bind(this);
+ this.decodeData = this.decodeData.bind(this);
+ this.encodePacket = this.encodePacket.bind(this);
// setup socket
this.client = new net.Socket();
- this.client.on('data', this.onData);
+ this.client.on('data', this.decodeData);
this.client.on('close', this.onClose);
this.client.on('error', this.onError);
@@ -48,85 +50,105 @@ export default class Rcon extends EventEmitter {
this.incomingData = Buffer.from([]);
this.incomingResponse = [];
-
this.responseCallbackQueue = [];
+ // Used For tracking Callbacks
+ this.callbackIds = [];
+ this.count = 1;
+ this.loggedin = false;
}
- onData(data) {
- Logger.verbose('RCON', 4, `Got data: ${this.bufToHexString(data)}`);
-
+ onPacket(decodedPacket) {
// the logic in this method simply splits data sent via the data event into packets regardless of how they're
// distributed in the event calls
- const packets = this.decodeData(data);
-
- for (const packet of packets) {
- Logger.verbose('RCON', 4, `Processing packet: ${this.bufToHexString(packet)}`);
+ Logger.verbose(
+ 'RCON',
+ 2,
+ `Processing decoded packet: ${this.decodedPacketToString(decodedPacket)}`
+ );
+
+ switch (decodedPacket.type) {
+ case SERVERDATA_RESPONSE_VALUE:
+ case SERVERDATA_AUTH_RESPONSE:
+ switch (decodedPacket.id) {
+ case MID_PACKET_ID:
+ this.incomingResponse.push(decodedPacket);
+
+ break;
+ case END_PACKET_ID:
+ this.callbackIds = this.callbackIds.filter((p) => p.id !== decodedPacket.count);
+
+ this.responseCallbackQueue.shift()(
+ this.incomingResponse.map((packet) => packet.body).join()
+ );
+ this.incomingResponse = [];
+
+ break;
+ default:
+ Logger.verbose(
+ 'RCON',
+ 1,
+ `Unknown packet ID ${decodedPacket.id} in: ${this.decodedPacketToString(
+ decodedPacket
+ )}`
+ );
+ this.onClose('Unknown Packet');
+ }
+ break;
- const decodedPacket = this.decodePacket(packet);
- Logger.verbose(
- 'RCON',
- 3,
- `Processing decoded packet: ${this.decodedPacketToString(decodedPacket)}`
- );
+ case SERVERDATA_CHAT_VALUE:
+ this.processChatPacket(decodedPacket);
+ break;
- switch (decodedPacket.type) {
- case SERVERDATA_RESPONSE_VALUE:
- case SERVERDATA_AUTH_RESPONSE:
- switch (decodedPacket.id) {
- case MID_PACKET_ID:
- this.incomingResponse.push(decodedPacket);
- break;
- case END_PACKET_ID:
- this.responseCallbackQueue.shift()(
- this.incomingResponse.map((packet) => packet.body).join()
- );
- this.incomingResponse = [];
- break;
- default:
- Logger.verbose(
- 'RCON',
- 1,
- `Unknown packet ID ${decodedPacket.id} in: ${this.decodedPacketToString(
- decodedPacket
- )}`
- );
- }
- break;
-
- case SERVERDATA_CHAT_VALUE:
- this.processChatPacket(decodedPacket);
- break;
-
- default:
- Logger.verbose(
- 'RCON',
- 1,
- `Unknown packet type ${decodedPacket.type} in: ${this.decodedPacketToString(
- decodedPacket
- )}`
- );
- }
+ default:
+ Logger.verbose(
+ 'RCON',
+ 1,
+ `Unknown packet type ${decodedPacket.type} in: ${this.decodedPacketToString(
+ decodedPacket
+ )}`
+ );
+ this.onClose('Unknown Packet');
}
}
decodeData(data) {
- this.incomingData = Buffer.concat([this.incomingData, data]);
+ Logger.verbose('RCON', 4, `Got data: ${this.bufToHexString(data)}`);
- const packets = [];
+ this.incomingData = Buffer.concat([this.incomingData, data]);
- // we check that it's greater than 4 as if it's not then the length header is not fully present which breaks the
- // rest of the code. We just need to wait for more data.
while (this.incomingData.byteLength >= 4) {
const size = this.incomingData.readInt32LE(0);
const packetSize = size + 4;
+ if (this.incomingData.byteLength < packetSize) {
+ Logger.verbose(
+ 'RCON',
+ 4,
+ `Waiting for more data... Have: ${this.incomingData.byteLength} Expected: ${packetSize}`
+ );
+ break;
+ }
+ const packet = this.incomingData.slice(0, packetSize);
+
+ Logger.verbose('RCON', 4, `Processing packet: ${this.bufToHexString(packet)}`);
+ const decodedPacket = this.decodePacket(packet);
+
+ const matchCount = this.callbackIds.filter((d) => d.id === decodedPacket.count);
+
+ if (
+ matchCount.length > 0 ||
+ [SERVERDATA_AUTH_RESPONSE, SERVERDATA_CHAT_VALUE].includes(decodedPacket.type)
+ ) {
+ this.onPacket(decodedPacket);
+ this.incomingData = this.incomingData.slice(packetSize);
+ continue;
+ }
// The packet following an empty packet will report to be 10 long (14 including the size header bytes), but in
// it should report 17 long (21 including the size header bytes). Therefore, if the packet is 10 in size
// and there's enough data for it to be a longer packet then we need to probe to check it's this broken packet.
- const probeSize = 17;
const probePacketSize = 21;
- if (size === 10 && this.incomingData.byteLength >= probeSize) {
+ if (size === 10 && this.incomingData.byteLength >= 21) {
// copy the section of the incoming data of interest
const probeBuf = this.incomingData.slice(0, probePacketSize);
// decode it
@@ -141,25 +163,17 @@ export default class Rcon extends EventEmitter {
}
}
- if (this.incomingData.byteLength < packetSize) {
- Logger.verbose('RCON', 4, `Waiting for more data...`);
- break;
- }
-
- const packet = this.incomingData.slice(0, packetSize);
- packets.push(packet);
-
- this.incomingData = this.incomingData.slice(packetSize);
+ // We should only get this far into the loop when we are done processing packets from this onData event.
+ break;
}
-
- return packets;
}
decodePacket(packet) {
return {
- size: packet.readInt32LE(0),
- id: packet.readInt32LE(4),
- type: packet.readInt32LE(8),
+ size: packet.readUInt32LE(0),
+ id: packet.readUInt8(4),
+ count: packet.readUInt16LE(6),
+ type: packet.readUInt32LE(8),
body: packet.toString('utf8', 12, packet.byteLength - 2)
};
}
@@ -168,8 +182,34 @@ export default class Rcon extends EventEmitter {
onClose(hadError) {
this.connected = false;
+ this.loggedin = false;
+ Logger.verbose(
+ 'RCON',
+ 1,
+ `Socket closed ${hadError ? 'with' : 'without'} an error. ${hadError}`
+ );
+
+ // Cleanup all local state onClose
+ if (this.incomingData.length > 0) {
+ Logger.verbose('RCON', 2, `Clearing Buffered Data`);
+ this.incomingData = Buffer.from([]);
+ }
+ if (this.incomingResponse.length > 0) {
+ Logger.verbose('RCON', 2, `Clearing Buffered Response Data`);
+ this.incomingResponse = [];
+ }
+ if (this.responseCallbackQueue.length > 0) {
+ Logger.verbose('RCON', 2, `Clearing Pending Callbacks`);
- Logger.verbose('RCON', 1, `Socket closed ${hadError ? 'without' : 'with'} an error.`);
+ // Cleanup Pending Callbacks; We should maybe retry these on next connection
+ // However, depending on the reason we got disconnected it may be a while.
+ // IE, Squad server crash, Squad server shutdown for multiple minutes.
+
+ while (this.responseCallbackQueue.length > 0) {
+ this.responseCallbackQueue.shift()(new Error('RCON DISCONNECTED'));
+ }
+ this.callbackIds = [];
+ }
if (this.autoReconnect) {
Logger.verbose('RCON', 1, `Sleeping ${this.autoReconnectDelay}ms before reconnecting.`);
@@ -267,6 +307,13 @@ export default class Rcon extends EventEmitter {
return;
}
+ if (!this.loggedin && type !== SERVERDATA_AUTH) {
+ reject(new Error('RCON not Logged in'));
+ return;
+ }
+
+ Logger.verbose('RCON', 2, `Writing packet with type "${type}" and body "${body}".`);
+
const encodedPacket = this.encodePacket(
type,
type !== SERVERDATA_AUTH ? MID_PACKET_ID : END_PACKET_ID,
@@ -287,9 +334,10 @@ export default class Rcon extends EventEmitter {
};
// the auth packet also sends a normal response, so we add an extra empty action to ignore it
+
if (type === SERVERDATA_AUTH) {
- Logger.verbose('RCON', 2, `Writing Auth Packet`);
- Logger.verbose('RCON', 4, `Writing packet with type "${type}" and body "${body}".`);
+ this.callbackIds.push({ id: this.count, cmd: body });
+
this.responseCallbackQueue.push(() => {});
this.responseCallbackQueue.push((decodedPacket) => {
this.client.removeListener('error', onError);
@@ -298,26 +346,36 @@ export default class Rcon extends EventEmitter {
reject(new Error('Authentication failed.'));
} else {
Logger.verbose('RCON', 1, 'Authentication succeeded.');
+ this.loggedin = true;
resolve();
}
});
} 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);
- Logger.verbose(
- 'RCON',
- 2,
- `Returning complete response: ${response.replace(/\r\n|\r|\n/g, '\\n')}`
- );
+ if (response instanceof Error) {
+ // Called from onClose()
+ reject(response);
+ } else {
+ Logger.verbose(
+ 'RCON',
+ 2,
+ `Returning complete response: ${response.replace(/\r\n|\r|\n/g, '\\n')}`
+ );
- resolve(response);
+ resolve(response);
+ }
});
}
this.client.once('error', onError);
+ if (this.count + 1 > 65535) {
+ this.count = 1;
+ }
+
Logger.verbose('RCON', 4, `Sending packet: ${this.bufToHexString(encodedPacket)}`);
this.client.write(encodedPacket);
@@ -328,6 +386,7 @@ export default class Rcon extends EventEmitter {
`Sending empty packet: ${this.bufToHexString(encodedEmptyPacket)}`
);
this.client.write(encodedEmptyPacket);
+ this.count++;
}
});
}
@@ -336,11 +395,13 @@ export default class Rcon extends EventEmitter {
const size = Buffer.byteLength(body) + 14;
const buf = Buffer.alloc(size);
- buf.writeInt32LE(size - 4, 0);
- buf.writeInt32LE(id, 4);
- buf.writeInt32LE(type, 8);
+ buf.writeUInt32LE(size - 4, 0);
+ buf.writeUInt8(id, 4);
+ buf.writeUInt8(0, 5);
+ buf.writeUInt16LE(this.count, 6);
+ buf.writeUInt32LE(type, 8);
buf.write(body, 12, size - 2, encoding);
- buf.writeInt16LE(0, size - 2);
+ buf.writeUInt16LE(0, size - 2);
return buf;
}
diff --git a/package.json b/package.json
index ec1b126a..5fd1e1bc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "SquadJS",
- "version": "3.8.1",
+ "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 09976979..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]+/,
- onMatch: (args, logParser) => {
- const data = {
- raw: args[0],
- time: args[1],
- chainID: args[2],
- steamID: args[3],
- playerController: args[4]
- };
+ /^\[([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],
+ ip: args[3],
+ playerController: args[4],
+ eosID: args[5]
+ };
- logParser.eventStore.disconnected[data.steamID] = true;
- logParser.emit('PLAYER_DISCONNECTED', data);
- }
+ 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/auto-kick-unassigned.js b/squad-server/plugins/auto-kick-unassigned.js
index 8a8d784e..1a4c81bb 100644
--- a/squad-server/plugins/auto-kick-unassigned.js
+++ b/squad-server/plugins/auto-kick-unassigned.js
@@ -17,7 +17,7 @@ export default class AutoKickUnassigned extends BasePlugin {
warningMessage: {
required: false,
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'
+ default: 'Join a squad, you are unassigned and will be kicked'
},
kickMessage: {
required: false,
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 b618ba49..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;
@@ -139,23 +136,21 @@ export default class SquadRcon extends Rcon {
const players = [];
- if(!response || response.length < 1) return players;
-
+ if (!response || response.length < 1) return players;
+
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;
@@ -168,25 +163,21 @@ export default class SquadRcon extends Rcon {
let teamName;
let teamID;
- if(!responseSquad || responseSquad.length < 1) return squads;
+ if (!responseSquad || responseSquad.length < 1) return squads;
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
});