Remove Players::setNum and stabilize field numplayers (#389)

* Remove Players Set Num

* Stabilize numplayers on armagetron

* Stabilize numplayers on ase

* Stabilize numplayers on assettocorsa

* Optimize away a variable declaration

* Stabilize numplayers on buildandshoot

* Stabilize numplayers on cs2d

* Fix wrong raw field parsed on Doom3

* Updated CHANGELOG and README regarding doom3 fix and numplayers

* Stabilize numplayers on doom3

* Stabilize numplayers on eco

* Stabilize numplayers on ffow

* Stabilize numplayers on quake2

* Stabilize numplayers on gamespy1

* Stabilize numplayers on gamespy2

* Stabilize numplayers on gamespy3

* Remove reductant numplayers setter in jc2mp

* Stabilize numplayers on kspdmp

* Stabilize numplayers on mafia2mp

* Stabilize numplayers on minecraftvanilla and remove players empty placeholders

* Stabilize numplayers on nadeo

* Stabilize numplayers on samp and reduce unused setters

* Stabilize numplayers on terraria

* Stabilize numplayers on tribes1

* Stabilize numplayers on unreal2

* Stabilize numplayers on valve

* Stabilize numplayers on ventrilo

* Battlefield: Set numplayers from info, not players

* Stabilize numplayers on minecraft

* Stabilize numplayers on teamspeak2

* Stabilize numplayers on teamspeak3

* Update CHANGELOG.md to add removal of players placeholders

* Replaced minecraft gamespy numplayers
This commit is contained in:
CosminPerRam 2023-10-27 19:48:56 +03:00 committed by GitHub
parent c51a75effb
commit da7a4a6334
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 64 additions and 63 deletions

View file

@ -32,6 +32,10 @@ The Specialists, Vampire Slayer, Warfork (2018), Wurm Unlimited (2015).
* Also added support: The Forest (2014), Operation: Harsh Doorstop (2023),
Insurgency: Modern Infantry Combat (2007), Counter-Strike 2 (2023), The Front (2023).
* Capitalized 'Unturned' in game.txt
* Removed the players::setNum method, the library will no longer add empty players as
a placeholder in the `players` field.
* Fixed wrong field being parsed for `maxplayers` on Doom3.
* Stabilized field `numplayers`.
### 4.1.0
* Replace `compressjs` dependency by `seek-bzip` to solve some possible import issues.

View file

@ -59,6 +59,7 @@ The returned state object will contain the following keys:
* **name**: string - Server name
* **map**: string - Current server game map
* **password**: boolean - If a password is required
* **numplayers**: number
* **maxplayers**: number
* **players**: array of objects
* **name**: string - If the player's name is unknown, the string will be empty.

View file

@ -14,16 +14,6 @@ export class Player {
}
export class Players extends Array {
setNum (num) {
// If the server specified some ridiculous number of players (billions), we don't want to
// run out of ram allocating these objects.
num = Math.min(num, 10000)
while (this.length < num) {
this.push({})
}
}
push (data) {
super.push(new Player(data))
}

View file

@ -18,7 +18,7 @@ export default class armagetron extends Core {
state.gamePort = this.readUInt(reader)
state.raw.hostname = this.readString(reader)
state.name = this.stripColorCodes(this.readString(reader))
state.raw.numplayers = this.readUInt(reader)
state.numplayers = this.readUInt(reader)
state.raw.versionmin = this.readUInt(reader)
state.raw.versionmax = this.readUInt(reader)
state.raw.version = this.readString(reader)
@ -42,7 +42,7 @@ export default class armagetron extends Core {
const a = reader.uint(2)
const b = reader.uint(2)
return (b << 16) + a
}
}
readString (reader) {
const len = reader.uint(2)
@ -57,7 +57,7 @@ export default class armagetron extends Core {
}
return out
}
}
stripColorCodes (str) {
return str.replace(/0x[0-9a-f]{6}/g, '')

View file

@ -16,7 +16,7 @@ export default class ase extends Core {
state.map = this.readString(reader)
state.raw.version = this.readString(reader)
state.password = this.readString(reader) === '1'
state.raw.numplayers = parseInt(this.readString(reader))
state.numplayers = parseInt(this.readString(reader))
state.maxplayers = parseInt(this.readString(reader))
while (!reader.done()) {

View file

@ -34,5 +34,7 @@ export default class assettocorsa extends Core {
})
}
}
state.numplayers = carInfo.Cars.length
}
}

View file

@ -11,7 +11,7 @@ export default class battlefield extends Core {
{
const data = await this.query(socket, ['serverInfo'])
state.name = data.shift()
state.raw.numplayers = parseInt(data.shift())
state.numplayers = parseInt(data.shift())
state.maxplayers = parseInt(data.shift())
state.raw.gametype = data.shift()
state.map = data.shift()

View file

@ -20,7 +20,7 @@ export default class buildandshoot extends Core {
m = body.match(/Current players: (\d+)\/(\d+)/)
if (m) {
state.raw.numplayers = m[1]
state.numplayers = parseInt(m[1])
state.maxplayers = m[2]
}

View file

@ -17,7 +17,7 @@ export default class cs2d extends Core {
state.raw.forceLight = this.readFlag(flags, 7)
state.name = this.readString(reader)
state.map = this.readString(reader)
state.raw.numplayers = reader.uint(1)
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)
if (flags & 32) {
state.raw.gamemode = reader.uint(1)

View file

@ -4,7 +4,7 @@ export default class doom3 extends Core {
constructor () {
super()
this.encoding = 'latin1'
}
}
async run (state) {
const body = await this.udpSend('\xff\xffgetInfo\x00PiNGPoNg\x00', packet => {
@ -60,6 +60,7 @@ export default class doom3 extends Core {
let players;
[players, reader] = playerResult
state.numplayers = players.length
for (const player of players) {
if (!player.ping || player.typeflag) { state.bots.push(player) } else { state.players.push(player) }
}
@ -82,7 +83,7 @@ export default class doom3 extends Core {
if (state.raw.si_name) state.name = state.raw.si_name
if (state.raw.si_map) state.map = state.raw.si_map
if (state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers)
if (state.raw.si_maxPlayers) state.maxplayers = parseInt(state.raw.si_maxplayers)
if (state.raw.si_maxPlayers) state.maxplayers = parseInt(state.raw.si_maxPlayers)
if (state.raw.si_usepass === '1') state.password = true
if (state.raw.si_needPass === '1') state.password = true
if (this.options.port === 27733) state.gamePort = 3074 // etqw has a different query and game port

View file

@ -11,6 +11,7 @@ export default class eco extends Core {
const serverInfo = request.Info
state.name = serverInfo.Description
state.numplayers = serverInfo.OnlinePlayers;
state.maxplayers = serverInfo.TotalPlayers
state.password = serverInfo.HasPassword
state.gamePort = serverInfo.GamePort

View file

@ -5,7 +5,7 @@ export default class ffow extends valve {
super()
this.byteorder = 'be'
this.legacyChallenge = true
}
}
async queryInfo (state) {
this.logger.debug('Requesting ffow info ...')
@ -24,7 +24,7 @@ export default class ffow extends valve {
state.raw.description = reader.string()
state.raw.version = reader.string()
state.gamePort = reader.uint(2)
state.raw.numplayers = reader.uint(1)
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)
state.raw.listentype = String.fromCharCode(reader.uint(1))
state.raw.environment = String.fromCharCode(reader.uint(1))

View file

@ -111,6 +111,8 @@ export default class gamespy1 extends Core {
state.players.push(player)
}
state.numplayers = state.players.length
}
async sendPacket (type) {

View file

@ -32,6 +32,9 @@ export default class gamespy2 extends Core {
for (const rawPlayer of this.readFieldData(reader)) {
state.players.push(rawPlayer)
}
if ('numplayers' in state.raw) state.numplayers = parseInt(state.raw.numplayers)
else state.numplayers = state.players.length
}
// Parse teams

View file

@ -127,6 +127,9 @@ export default class gamespy3 extends Core {
state.players.push(player)
}
}
if ('numplayers' in state.raw) state.numplayers = parseInt(state.raw.numplayers)
else state.numplayers = state.players.length
}
async sendPacket (type, challenge, payload, assemble) {

View file

@ -28,7 +28,7 @@ export default class geneshift extends Core {
state.raw.country = found[1]
state.name = found[4]
state.map = found[5]
state.players.setNum(parseInt(found[6]))
state.numplayers = parseInt(found[6])
state.maxplayers = parseInt(found[7])
// fields[8] is unknown?
state.raw.rules = found[9]

View file

@ -8,12 +8,9 @@ export default class jc2mp extends gamespy3 {
this.useOnlySingleSplit = true
this.isJc2mp = true
this.encoding = 'utf8'
}
}
async run (state) {
await super.run(state)
if (!state.players.length && parseInt(state.raw.numplayers)) {
state.players.setNum(parseInt(state.raw.numplayers))
}
}
}

View file

@ -23,5 +23,6 @@ export default class kspdmp extends Core {
state.players.push({ name })
}
}
state.numplayers = state.players.length
}
}

View file

@ -18,7 +18,7 @@ export default class mafia2mp extends Core {
const reader = this.reader(body)
state.name = this.readString(reader)
state.raw.numplayers = this.readString(reader)
state.numplayers = parseInt(this.readString(reader))
state.maxplayers = parseInt(this.readString(reader))
state.raw.gamemode = this.readString(reader)
state.password = !!reader.uint(1)

View file

@ -74,19 +74,22 @@ export default class minecraft extends Core {
}
state.name = name
} catch (e) {}
if (vanillaState.numplayers) state.numplayers = vanillaState.numplayers
if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers
if (vanillaState.players.length) state.players = vanillaState.players
if (vanillaState.ping) this.registerRtt(vanillaState.ping)
}
if (gamespyState) {
if (gamespyState.name) state.name = gamespyState.name
if (gamespyState.numplayers) state.numplayers = gamespyState.numplayers
if (gamespyState.maxplayers) state.maxplayers = gamespyState.maxplayers
if (gamespyState.players.length) state.players = gamespyState.players
else if (gamespyState.raw.numplayers) state.players.setNum(parseInt(gamespyState.raw.numplayers))
else if (gamespyState.numplayers) state.numplayers = gamespyState.numplayers
if (gamespyState.ping) this.registerRtt(gamespyState.ping)
}
if (bedrockState) {
if (bedrockState.name) state.name = bedrockState.name
if (bedrockState.numplayers) state.numplayers = bedrockState.numplayers
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers
if (bedrockState.map) state.map = bedrockState.map
if (bedrockState.ping) this.registerRtt(bedrockState.ping)

View file

@ -57,7 +57,7 @@ export default class minecraftbedrock extends Core {
state.name = split.shift()
state.raw.protocolVersion = split.shift()
state.raw.mcVersion = split.shift()
state.players.setNum(parseInt(split.shift()))
state.numplayers = parseInt(split.shift())
state.maxplayers = parseInt(split.shift())
if (split.length) state.raw.serverId = split.shift()
if (split.length) state.map = split.shift()

View file

@ -47,6 +47,7 @@ export default class minecraftvanilla extends Core {
state.raw = json
state.maxplayers = json.players.max
state.numplayers = json.players.online
if (json.players.sample) {
for (const player of json.players.sample) {
@ -56,18 +57,11 @@ export default class minecraftvanilla extends Core {
})
}
}
// players.sample may not contain all players or no players at all, depending on how many players are online.
// Insert a dummy player object for every online player that is not listed in players.sample.
// Limit player amount to 10.000 players for performance reasons.
for (let i = state.players.length; i < Math.min(json.players.online, 10000); i++) {
state.players.push({})
}
}
varIntBuffer (num) {
return Buffer.from(Varint.encode(num))
}
}
buildPacket (id, data) {
if (!data) data = Buffer.from([])

View file

@ -17,7 +17,7 @@ export default class mumbleping extends Core {
state.raw.versionMinor = reader.uint(1)
state.raw.versionPatch = reader.uint(1)
reader.skip(8)
state.players.setNum(reader.uint(4))
state.numplayers = reader.uint(4)
state.maxplayers = reader.uint(4)
state.raw.allowedbandwidth = reader.uint(4)
}

View file

@ -55,6 +55,7 @@ export default class nadeo extends Core {
name: this.stripColors(player.Name || player.NickName)
})
}
state.numplayers = state.players.length
})
}

View file

@ -34,7 +34,7 @@ export default class openttd extends Core {
state.password = !!reader.uint(1)
state.maxplayers = reader.uint(1)
state.players.setNum(reader.uint(1))
state.numplayers = reader.uint(1)
state.raw.numspectators = reader.uint(1)
state.map = reader.string()
state.raw.map_width = reader.uint(2)

View file

@ -82,5 +82,7 @@ export default class quake2 extends Core {
if ('maxclients' in state.raw) state.maxplayers = state.raw.maxclients
if ('sv_hostname' in state.raw) state.name = state.raw.sv_hostname
if ('hostname' in state.raw) state.name = state.raw.hostname
if ('clients' in state.raw) state.numplayers = state.raw.clients
else state.numplayers = state.players.length + state.bots.length
}
}

View file

@ -22,7 +22,7 @@ export default class rfactor extends Core {
state.raw.ping = reader.uint(2)
state.raw.packedFlags = reader.uint(1)
state.raw.rate = reader.uint(1)
state.players.setNum(reader.uint(1))
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)
state.raw.bots = reader.uint(1)
state.raw.packedSpecial = reader.uint(1)

View file

@ -18,7 +18,7 @@ export default class samp extends Core {
state.raw.version = this.reader(consumed).string()
}
state.password = !!reader.uint(1)
state.raw.numplayers = reader.uint(2)
state.numplayers = reader.uint(2)
state.maxplayers = reader.uint(2)
state.name = reader.pascalString(4)
state.raw.gamemode = reader.pascalString(4)
@ -39,12 +39,10 @@ export default class samp extends Core {
// read players
// don't even bother if > 100 players, because the server won't respond
let gotPlayerData = false
if (state.raw.numplayers < 100) {
if (state.numplayers < 100) {
if (this.isVcmp) {
const reader = await this.sendPacket('c', true)
if (reader !== null) {
gotPlayerData = true
const playerCount = reader.uint(2)
for (let i = 0; i < playerCount; i++) {
const player = {}
@ -55,7 +53,6 @@ export default class samp extends Core {
} else {
const reader = await this.sendPacket('d', true)
if (reader !== null) {
gotPlayerData = true
const playerCount = reader.uint(2)
for (let i = 0; i < playerCount; i++) {
const player = {}
@ -68,10 +65,7 @@ export default class samp extends Core {
}
}
}
if (!gotPlayerData) {
state.players.setNum(state.raw.numplayers)
}
}
}
async sendPacket (type, allowTimeout) {
const outBuffer = Buffer.alloc(11)

View file

@ -7,7 +7,7 @@ export default class savage2 extends Core {
reader.skip(12)
state.name = this.stripColorCodes(reader.string())
state.players.setNum(reader.uint(1))
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)
state.raw.time = reader.string()
state.map = reader.string()

View file

@ -61,7 +61,7 @@ export default class starmade extends Core {
if (typeof data[2] === 'string') state.name = data[2]
if (typeof data[3] === 'string') state.raw.description = data[3]
if (typeof data[4] === 'number') state.raw.startTime = data[4]
if (typeof data[5] === 'number') state.players.setNum(data[5])
if (typeof data[5] === 'number') state.numplayers = data[5]
if (typeof data[6] === 'number') state.maxplayers = data[6]
}
}

View file

@ -37,6 +37,7 @@ export default class teamspeak2 extends Core {
})
state.players.push(player)
}
state.numplayers = state.players.length
}
{

View file

@ -16,6 +16,7 @@ export default class teamspeak3 extends Core {
state.raw = data[0]
if ('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name
if ('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients
if ('virtualserver_clientsonline' in state.raw) state.numplayers = state.raw.virtualserver_clientsonline
}
{
@ -54,8 +55,8 @@ export default class teamspeak3 extends Core {
for (const field of split) {
const equals = field.indexOf('=')
const key = equals === -1 ? field : field.substring(0, equals)
const value = equals === -1
? ''
const value = equals === -1
? ''
: field.substring(equals + 1)
.replace(/\\s/g, ' ').replace(/\\\//g, '/')
unit[key] = value

View file

@ -19,6 +19,6 @@ export default class terraria extends Core {
state.name = json.name
state.gamePort = json.port
state.raw.numplayers = json.playercount
state.numplayers = json.playercount
}
}

View file

@ -43,7 +43,7 @@ export default class tribes1 extends Core {
state.raw.dedicated = !!reader.uint(1)
state.raw.dropInProgress = !!reader.uint(1)
state.raw.gameInProgress = !!reader.uint(1)
state.raw.playerCount = reader.uint(4)
state.numplayers = reader.uint(4)
state.maxplayers = reader.uint(4)
state.raw.teamPlay = reader.uint(1)
state.map = this.readString(reader)
@ -127,7 +127,7 @@ export default class tribes1 extends Core {
}
state.players.push(playerInfo)
}
}
}
readFieldList (reader) {
const str = this.readString(reader)
@ -137,7 +137,7 @@ export default class tribes1 extends Core {
.map((a) => a.substring(1).trim().toLowerCase())
.map((a) => a === 'team name' ? 'name' : a)
.map((a) => a === 'player name' ? 'name' : a)
}
}
readValues (reader) {
const str = this.readString(reader)
@ -145,7 +145,7 @@ export default class tribes1 extends Core {
return str
.split('\t')
.map((a) => a.trim())
}
}
readString (reader) {
return reader.pascalString(1)

View file

@ -18,7 +18,7 @@ export default class unreal2 extends Core {
state.name = this.readUnrealString(reader, true)
state.map = this.readUnrealString(reader, true)
state.raw.gametype = this.readUnrealString(reader, true)
state.raw.numplayers = reader.uint(4)
state.numplayers = reader.uint(4)
state.maxplayers = reader.uint(4)
this.logger.debug(log => {
log('UNREAL2 EXTRA INFO', reader.buffer.slice(reader.i))

View file

@ -67,7 +67,7 @@ export default class valve extends Core {
state.raw.folder = reader.string()
state.raw.game = reader.string()
if (!this.goldsrcInfo) state.raw.appId = reader.uint(2)
state.raw.numplayers = reader.uint(1)
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)
if (this.goldsrcInfo) state.raw.protocol = reader.uint(1)
@ -301,7 +301,7 @@ export default class valve extends Core {
state.name = rules.bat_name_s
delete rules.bat_name_s
if ('bat_player_count_s' in rules) {
state.raw.numplayers = parseInt(rules.bat_player_count_s)
state.numplayers = parseInt(rules.bat_player_count_s)
delete rules.bat_player_count_s
}
if ('bat_max_players_i' in rules) {
@ -427,12 +427,11 @@ export default class valve extends Core {
})
delete state.raw.players
const numBots = state.raw.numbots || 0
const numPlayers = state.raw.numplayers - numBots
while (state.bots.length < numBots) {
if (sortedPlayers.length) state.bots.push(sortedPlayers.pop())
else state.bots.push({})
}
while (state.players.length < numPlayers || sortedPlayers.length) {
while (state.players.length < state.numplayers - numBots || sortedPlayers.length) {
if (sortedPlayers.length) state.players.push(sortedPlayers.pop())
else state.players.push({})
}

View file

@ -17,6 +17,7 @@ export default class ventrilo extends Core {
state.players.push(client)
}
delete state.raw.CLIENTS
state.numplayers = state.players.length
if ('NAME' in state.raw) state.name = state.raw.NAME
if ('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS