From 94c263669dcc9de33d7cee9b758cd8c1beddce87 Mon Sep 17 00:00:00 2001 From: mmorrison Date: Tue, 22 Jan 2019 00:11:39 -0600 Subject: [PATCH] Improve packet ordering and deduplication of players from gamespy1 protocol --- protocols/core.js | 4 ++ protocols/gamespy1.js | 115 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/protocols/core.js b/protocols/core.js index ec52272..94e6bf1 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -109,6 +109,10 @@ class Core extends EventEmitter { delete state.gameHost; delete state.gamePort; + this.logger.debug(log => { + log("Size of players array: " + state.players.length); + log("Size of bots array: " + state.bots.length); + }); return state; } diff --git a/protocols/gamespy1.js b/protocols/gamespy1.js index 5a1ed46..39da5ae 100644 --- a/protocols/gamespy1.js +++ b/protocols/gamespy1.js @@ -23,8 +23,8 @@ class Gamespy1 extends Core { } { const data = await this.sendPacket('players'); - const players = {}; - const teams = {}; + const playersById = {}; + const teamNamesById = {}; for (const ident of Object.keys(data)) { const split = ident.split('_'); let key = split[0]; @@ -32,26 +32,74 @@ class Gamespy1 extends Core { let value = data[ident]; if (key === 'teamname') { - teams[id] = value; + teamNamesById[id] = value; } else { - if (!(id in players)) players[id] = {}; + if (!(id in playersById)) playersById[id] = {}; if (key === 'playername') key = 'name'; else if (key === 'team') value = parseInt(value); - else if (key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value); - players[id][key] = value; + else if (key === 'score' || key === 'ping' || key === 'deaths' || key === 'kills') value = parseInt(value); + playersById[id][key] = value; } } + state.raw.teams = teamNamesById; - state.raw.teams = teams; - for (const id of Object.keys(players)) { - state.players.push(players[id]); + const players = Object.values(playersById); + + // Determine which team id might be for spectators + let specTeamId = null; + for (const player of players) { + if (!player.team) { + continue; + } else if (teamNamesById[player.team]) { + continue; + } else if (teamNamesById[player.team-1] && (specTeamId === null || specTeamId === player.team)) { + specTeamId = player.team; + } else { + specTeamId = null; + break; + } + } + this.logger.debug(log => { + if (specTeamId === null) { + log("Could not detect a team ID for spectators"); + } else { + log("Detected that team ID " + specTeamId + " is probably for spectators"); + } + }); + + const seenHashes = new Set(); + for (const player of players) { + // Some servers (bf1942) report the same player multiple times (bug?) + // Ignore these duplicates + if (player.keyhash) { + if (seenHashes.has(player.keyhash)) { + this.logger.debug("Rejected player with hash " + player.keyhash + " (Duplicate keyhash)"); + continue; + } else { + seenHashes.add(player.keyhash); + } + } + + // Convert player's team ID to team name if possible + if (player.team) { + if (teamNamesById[player.team]) { + player.team = teamNamesById[player.team]; + } else if (player.team === specTeamId) { + player.team = "spec"; + } + } + + state.players.push(player); } } } async sendPacket(type) { - const queryId = ''; + let receivedQueryId; const output = {}; + const parts = new Set(); + let maxPartNum = 0; + return await this.udpSend('\\'+type+'\\', buffer => { const reader = this.reader(buffer); const str = reader.string({length:buffer.length}); @@ -63,12 +111,47 @@ class Gamespy1 extends Core { const value = split.shift() || ''; data[key] = value; } - if(!('queryid' in data)) return; - if(queryId && data.queryid !== queryId) return; - for(const i of Object.keys(data)) output[i] = data[i]; - if('final' in output) { - delete output.final; - delete output.queryid; + + let queryId, partNum; + const partFinal = ('final' in data); + if (data.queryid) { + const split = data.queryid.split('.'); + if (split.length >= 2) { + partNum = parseInt(split[1]); + } + queryId = split[0]; + } + delete data.final; + delete data.queryid; + this.logger.debug("Received part num=" + partNum + " queryId=" + queryId + " final=" + partFinal); + + if (queryId) { + if (receivedQueryId && receivedQueryId !== queryId) { + this.logger.debug("Rejected packet (Wrong query ID)"); + return; + } else if (!receivedQueryId) { + receivedQueryId = queryId; + } + } + if (!partNum) { + partNum = parts.size; + this.logger.debug("No part number received (assigned #" + partNum + ")"); + } + if (parts.has(partNum)) { + this.logger.debug("Rejected packet (Duplicate part)"); + return; + } + parts.add(partNum); + if (partFinal) { + maxPartNum = partNum; + } + + this.logger.debug("Received part #" + partNum + " of " + (maxPartNum ? maxPartNum : "?")); + for(const i of Object.keys(data)) { + output[i] = data[i]; + } + if (maxPartNum && parts.size === maxPartNum) { + this.logger.debug("Received all parts"); return output; } });