Improve gamespy1 (3.0.3)

This commit is contained in:
Michael Morrison 2021-04-27 18:12:22 -05:00
parent 1b11a132b9
commit e8f2c174fb
3 changed files with 98 additions and 62 deletions

View file

@ -1,3 +1,6 @@
### 3.0.3
* Greatly improve gamespy1 protocol, with additional error handling and xserverquery support.
### 3.0.2 ### 3.0.2
* Fix player name extraction for Unreal Tournament (1999) and possibly * Fix player name extraction for Unreal Tournament (1999) and possibly
other gamespy1 games. other gamespy1 games.

View file

@ -24,7 +24,7 @@
], ],
"main": "lib/index.js", "main": "lib/index.js",
"author": "GameDig Contributors", "author": "GameDig Contributors",
"version": "3.0.2", "version": "3.0.3",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/gamedig/node-gamedig.git" "url": "https://github.com/gamedig/node-gamedig.git"

View file

@ -1,5 +1,33 @@
const Core = require('./core'); const Core = require('./core');
const stringKeys = new Set([
'website',
'gametype',
'gamemode',
'player'
]);
function normalizeEntry([key,value]) {
key = key.toLowerCase();
const split = key.split('_');
let keyType;
if (split.length === 2 && !isNaN(parseInt(split[1]))) {
keyType = split[0];
} else {
keyType = key;
}
if (!stringKeys.has(keyType) && !keyType.includes('name')) {
if (value.toLowerCase() === 'true') {
value = true;
} else if (value.toLowerCase() === 'false') {
value = false;
} else if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
}
return [key,value];
}
class Gamespy1 extends Core { class Gamespy1 extends Core {
constructor() { constructor() {
super(); super();
@ -8,41 +36,47 @@ class Gamespy1 extends Core {
} }
async run(state) { async run(state) {
{ const raw = await this.sendPacket('\\status\\xserverquery');
const data = await this.sendPacket('info'); // Convert all keys to lowercase and normalize value types
const data = Object.fromEntries(Object.entries(raw).map(entry => normalizeEntry(entry)));
state.raw = data; state.raw = data;
if ('hostname' in state.raw) state.name = state.raw.hostname; if ('hostname' in data) state.name = data.hostname;
if ('mapname' in state.raw) state.map = state.raw.mapname; if ('mapname' in data) state.map = data.mapname;
if (this.trueTest(state.raw.password)) state.password = true; if (this.trueTest(data.password)) state.password = true;
if ('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); if ('maxplayers' in data) state.maxplayers = parseInt(data.maxplayers);
if ('hostport' in state.raw) state.gamePort = parseInt(state.raw.hostport); if ('hostport' in data) state.gamePort = parseInt(data.hostport);
}
{ const teamOffByOne = data.gameid === 'bf1942';
const data = await this.sendPacket('rules');
state.raw.rules = data;
}
{
const data = await this.sendPacket('players');
const playersById = {}; const playersById = {};
const teamNamesById = {}; const teamNamesById = {};
for (const ident of Object.keys(data)) { for (const ident of Object.keys(data)) {
const split = ident.split('_'); const split = ident.split('_');
let key = split[0]; if (split.length !== 2) continue;
const id = split[1]; let key = split[0].toLowerCase();
const id = parseInt(split[1]);
if (isNaN(id)) continue;
let value = data[ident]; let value = data[ident];
delete data[ident];
if (key !== 'team' && key.startsWith('team')) {
// Info about a team
if (key === 'teamname') { if (key === 'teamname') {
teamNamesById[id] = value; teamNamesById[id] = value;
} else { } else {
// other team info which we don't track
}
} else {
// Info about a player
if (!(id in playersById)) playersById[id] = {}; if (!(id in playersById)) playersById[id] = {};
if (key === 'playername') { if (key === 'playername' || key === 'player') {
key = 'name'; key = 'name';
} else if (key === 'player') { }
key = 'name'; if (key === 'team' && !isNaN(parseInt(value))) {
} else if (key === 'team' && !isNaN(parseInt(value))) {
key = 'teamId'; key = 'teamId';
value = parseInt(value); value = parseInt(value) + (teamOffByOne ? -1 : 0);
} else if (key === 'score' || key === 'ping' || key === 'deaths' || key === 'kills' || key === 'frags') { }
if (key !== 'name' && !isNaN(parseInt(value))) {
value = parseInt(value); value = parseInt(value);
} }
playersById[id][key] = value; playersById[id][key] = value;
@ -68,7 +102,7 @@ class Gamespy1 extends Core {
// Convert player's team ID to team name if possible // Convert player's team ID to team name if possible
if (player.hasOwnProperty('teamId')) { if (player.hasOwnProperty('teamId')) {
if (Object.keys(teamNamesById).length) { if (Object.keys(teamNamesById).length) {
player.team = teamNamesById[player.teamId - 1] || ''; player.team = teamNamesById[player.teamId] || '';
} else { } else {
player.team = player.teamId; player.team = player.teamId;
delete player.teamId; delete player.teamId;
@ -78,7 +112,6 @@ class Gamespy1 extends Core {
state.players.push(player); state.players.push(player);
} }
} }
}
async sendPacket(type) { async sendPacket(type) {
let receivedQueryId; let receivedQueryId;
@ -86,7 +119,7 @@ class Gamespy1 extends Core {
const parts = new Set(); const parts = new Set();
let maxPartNum = 0; let maxPartNum = 0;
return await this.udpSend('\\'+type+'\\', buffer => { return await this.udpSend(type, buffer => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const str = reader.string(buffer.length); const str = reader.string(buffer.length);
const split = str.split('\\'); const split = str.split('\\');