teeworlds 2.1.3 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.ts CHANGED
@@ -1,20 +1,42 @@
1
1
  import { randomBytes } from "crypto";
2
2
 
3
3
  import net from 'dgram';
4
- import fs from 'fs';
5
4
  import { EventEmitter } from 'stream';
6
- import { spawn } from 'child_process';
7
5
 
8
- import { unpackInt, unpackString, MsgUnpacker } from "./MsgUnpacker";
6
+ import { unpackString, MsgUnpacker } from "./MsgUnpacker";
9
7
 
8
+ import Movement from './movement';
10
9
 
11
- import MsgPacker from './MsgPacker';
10
+ import { MsgPacker } from './MsgPacker';
12
11
  import { Snapshot } from './snapshot';
13
12
  import Huffman from "./huffman";
14
13
 
15
14
  const huff = new Huffman();
16
15
  const SnapUnpacker = new Snapshot();
17
16
 
17
+ enum States {
18
+ STATE_OFFLINE = 0,
19
+ STATE_CONNECTING,
20
+ STATE_LOADING,
21
+ STATE_ONLINE,
22
+ STATE_DEMOPLAYBACK,
23
+ STATE_QUITTING,
24
+ STATE_RESTARTING
25
+ }
26
+
27
+ interface NetObj_PlayerInput {
28
+ m_Direction: number,
29
+ m_TargetX: number,
30
+ m_TargetY: number,
31
+ m_Jump: number,
32
+ m_Fire: number,
33
+ m_Hook: number,
34
+ m_PlayerFlags: number,
35
+ m_WantedWeapon: number,
36
+ m_NextWeapon: number,
37
+ m_PrevWeapon: number
38
+ };
39
+
18
40
  enum NETMSGTYPE {
19
41
  EX,
20
42
  SV_MOTD,
@@ -118,21 +140,21 @@ function arrStartsWith(arr: number[], arrStart: number[], start = 0) {
118
140
  return true;
119
141
  }
120
142
  declare interface PlayerInfo {
121
- local: number,
122
- client_id: number,
123
- team: number,
124
- score: number,
125
- latency: number,
143
+ local: number,
144
+ client_id: number,
145
+ team: number,
146
+ score: number,
147
+ latency: number,
126
148
  }
127
149
 
128
150
  declare interface ClientInfo {
129
- name: string,
130
- clan: string,
131
- country: number,
132
- skin: string,
133
- use_custom_color: number,
134
- color_body: number,
135
- color_feet: number,
151
+ name: string,
152
+ clan: string,
153
+ country: number,
154
+ skin: string,
155
+ use_custom_color: number,
156
+ color_body: number,
157
+ color_feet: number,
136
158
  }
137
159
  declare interface iMessage {
138
160
  team: number,
@@ -150,7 +172,13 @@ declare interface iKillMsg {
150
172
  special_mode: number
151
173
  }
152
174
 
153
- declare interface Client {
175
+ declare interface iOptions {
176
+ identity?: ClientInfo,
177
+ password?: string,
178
+ ddnet_version?: {version: number, release_version: string},
179
+ }
180
+
181
+ export declare interface Client {
154
182
  host: string;
155
183
  port: number;
156
184
  name: string;
@@ -159,15 +187,25 @@ declare interface Client {
159
187
  clientAck: number;
160
188
  receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
161
189
  lastMsg: string;
162
- _port: number;
163
190
  socket: net.Socket | undefined;
164
191
  TKEN: Buffer;
165
192
  time: number;
166
193
 
194
+ timer: number;
195
+ PredGameTick: number;
196
+ AckGameTick: number;
197
+
198
+ movement: Movement;
199
+
167
200
  snaps: Buffer[];
168
201
  client_infos: ClientInfo[];
169
202
  player_infos: PlayerInfo[];
170
203
 
204
+ sentChunkQueue: Buffer[];
205
+
206
+ lastSendTime: number;
207
+
208
+ options?: iOptions;
171
209
 
172
210
  on(event: 'connected', listener: () => void): this;
173
211
  on(event: 'disconnect', listener: (reason: string) => void): this;
@@ -176,34 +214,49 @@ declare interface Client {
176
214
  on(event: 'kill', listener: (kill: iKillMsg) => void): this;
177
215
 
178
216
  }
179
- class Client extends EventEmitter {
180
217
 
181
218
 
182
219
 
183
- constructor(ip: string, port: number, nickname: string) {
220
+ export class Client extends EventEmitter {
221
+
222
+
223
+
224
+ constructor(ip: string, port: number, nickname: string, options?: iOptions) {
184
225
  super();
185
226
  this.host = ip;
186
227
  this.port = port;
187
228
  this.name = nickname;
229
+ this.AckGameTick = 0;
230
+ this.PredGameTick = 0;
231
+
232
+ if (options)
233
+ this.options = options;
234
+
235
+ this.timer = 0;
236
+
237
+ this.movement = new Movement();
188
238
 
189
239
  this.snaps = [];
190
240
  this.client_infos = [];
191
241
  this.player_infos = [];
192
242
 
193
- this.State = 0; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
243
+ this.sentChunkQueue = [];
244
+
245
+ this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
194
246
  this.ack = 0; // ack of messages the client has received
195
247
  this.clientAck = 0; // ack of messages the client has sent
196
248
  this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
197
249
  this.lastMsg = "";
198
- this._port = Math.floor(Math.random() * 65535)
199
250
  this.socket = net.createSocket("udp4")
200
251
  this.socket.bind();
201
252
 
202
253
  this.TKEN = Buffer.from([255, 255, 255, 255])
203
254
  this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
204
- this.State = 0;
255
+
256
+ this.lastSendTime = new Date().getTime();
257
+
205
258
  }
206
- async Unpack(packet: Buffer): Promise<_packet> {
259
+ Unpack(packet: Buffer): _packet {
207
260
  var unpacked: _packet = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
208
261
 
209
262
 
@@ -215,10 +268,10 @@ class Client extends EventEmitter {
215
268
  if (packet.length == 1 && packet[0] == -1)
216
269
  return unpacked
217
270
  }
218
- // return unpacked;
271
+
219
272
  for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
220
273
  var chunk: chunk = {} as chunk;
221
- chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1)); // idk what this shit is but it works
274
+ chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
222
275
  chunk.flags = (packet[0] >> 6) & 3;
223
276
  chunk.sequence = -1;
224
277
 
@@ -234,17 +287,17 @@ class Client extends EventEmitter {
234
287
  Object.values(messageUUIDs).forEach((a, i) => {
235
288
  if (a.compare(packet.slice(0, 16)) == 0) {
236
289
  chunk.extended_msgid = a;
237
- // chunk.type = 'sys';
238
290
  chunk.msg = Object.keys(messageUUIDs)[i];
239
291
  }
240
292
  })
241
293
 
242
- packet = packet.slice(chunk.bytes) // +1 cuz it adds an extra \x00 for easier parsing i guess
294
+ packet = packet.slice(chunk.bytes)
243
295
  unpacked.chunks.push(chunk)
244
296
  }
245
297
  return unpacked
246
298
  }
247
299
  SendControlMsg(msg: number, ExtraMsg: string = "") {
300
+ this.lastSendTime = new Date().getTime();
248
301
  return new Promise((resolve, reject) => {
249
302
  if (this.socket) {
250
303
  var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x00, msg])
@@ -261,227 +314,356 @@ class Client extends EventEmitter {
261
314
  */
262
315
  })
263
316
  }
264
- SendMsgEx(Msg: MsgPacker, Flags: number) {
265
- if (this.State == -1)
266
- throw new Error("Client is not connected");
267
- if (!this.socket)
268
- return;
269
- var header = []
270
- header[0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
271
- header[1] = (Msg.size & 0xf);
272
- if (Flags & 1) {
273
- this.clientAck = (this.clientAck + 1) % (1 << 10);
274
- header[1] |= (this.clientAck >> 2) & 0xf0;
275
- header[2] = this.clientAck & 0xff;
276
- }
277
- var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
278
- var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
279
317
 
280
- this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host)
281
- }
282
- SendMsgExWithChunks(Msgs: MsgPacker[], Flags: number) {
283
- if (this.State == -1)
284
- throw new Error("Client is not connected");
318
+ SendMsgEx(Msgs: MsgPacker[] | MsgPacker, Flags: number) {
319
+ if (this.State == States.STATE_OFFLINE)
320
+ return;
285
321
  if (!this.socket)
286
322
  return;
287
- var header: any[][] = [];
288
-
289
- Msgs.forEach((Msg: MsgPacker, index) => {
290
- header[index] = new Array(2);
323
+ let _Msgs: MsgPacker[];
324
+ if (Msgs instanceof Array)
325
+ _Msgs = Msgs;
326
+ else
327
+ _Msgs = [Msgs];
328
+ this.lastSendTime = new Date().getTime();
329
+ var header: Buffer[] = [];
330
+ _Msgs.forEach((Msg: MsgPacker, index) => {
331
+ header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
291
332
  header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
292
333
  header[index][1] = (Msg.size & 0xf);
293
334
  if (Flags & 1) {
294
335
  this.clientAck = (this.clientAck + 1) % (1 << 10);
295
336
  header[index][1] |= (this.clientAck >> 2) & 0xf0;
296
337
  header[index][2] = this.clientAck & 0xff;
338
+ header[index][0] = (((Flags | 2)&3)<<6)|((Msg.size>>4)&0x3f); // 2 is resend flag (ugly hack for queue)
339
+
340
+ this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
341
+ header[index][0] = (((Flags)&3)<<6)|((Msg.size>>4)&0x3f);
297
342
  }
298
343
  })
299
- var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, Msgs.length]);
344
+ var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
300
345
  var chunks = Buffer.from([]);
301
- Msgs.forEach((Msg: MsgPacker, index) => {
346
+ _Msgs.forEach((Msg: MsgPacker, index) => {
302
347
  chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
303
348
  })
304
349
  var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
305
350
 
306
351
  this.socket.send(packet, 0, packet.length, this.port, this.host)
307
352
  }
353
+ SendMsgRaw(chunks: Buffer[]) {
354
+ if (this.State == States.STATE_OFFLINE)
355
+ return;
356
+ if (!this.socket)
357
+ return;
358
+
359
+ this.lastSendTime = new Date().getTime();
360
+
361
+ var packetHeader = Buffer.from([0x0+(((16<<4)&0xf0)|((this.ack>>8)&0xf)), this.ack&0xff, chunks.length]);
362
+
363
+ var packet = Buffer.concat([(packetHeader), Buffer.concat(chunks), this.TKEN]);
364
+
365
+ this.socket.send(packet, 0, packet.length, this.port, this.host)
366
+ }
367
+
368
+ MsgToChunk(packet: Buffer) {
369
+ var chunk: chunk = {} as chunk;
370
+ chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
371
+ chunk.flags = (packet[0] >> 6) & 3;
372
+ chunk.sequence = -1;
373
+
374
+ if (chunk.flags & 1) {
375
+ chunk.seq = ((packet[1]&0xf0)<<2) | packet[2];
376
+ packet = packet.slice(3) // remove flags & size
377
+ } else
378
+ packet = packet.slice(2)
379
+ chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
380
+ chunk.msgid = (packet[0]-(packet[0]&1))/2;
381
+ chunk.msg = messageTypes[packet[0]&1][chunk.msgid];
382
+ chunk.raw = packet.slice(1, chunk.bytes)
383
+ Object.values(messageUUIDs).forEach((a, i) => {
384
+ if (a.compare(packet.slice(0, 16)) === 0) {
385
+ chunk.extended_msgid = a;
386
+ chunk.msg = Object.keys(messageUUIDs)[i];
387
+ }
388
+ })
389
+ return chunk;
390
+ }
391
+
308
392
  connect() {
393
+
394
+ this.State = States.STATE_CONNECTING;
395
+
396
+ let predTimer = setInterval(() => {
397
+ if (this.State == States.STATE_ONLINE) {
398
+ if (this.AckGameTick > 0)
399
+ this.PredGameTick++;
400
+ } else if (this.State == States.STATE_OFFLINE)
401
+ clearInterval(predTimer);
402
+
403
+ }, 20);
404
+
309
405
  this.SendControlMsg(1, "TKEN")
310
406
  let connectInterval = setInterval(() => {
311
- if (this.State == 0)
407
+ if (this.State == States.STATE_CONNECTING)
312
408
  this.SendControlMsg(1, "TKEN")
313
409
  else
314
410
  clearInterval(connectInterval)
411
+ }, 500);
412
+
413
+ let inputInterval = setInterval(() => {
414
+ if (this.State == States.STATE_OFFLINE)
415
+ clearInterval(inputInterval)
416
+ if (this.State != States.STATE_ONLINE)
417
+ return;
418
+ this.time = new Date().getTime();
419
+ this.sendInput();
315
420
  }, 500)
421
+
422
+ let resendTimeout = setInterval(() => {
423
+ if (this.State != States.STATE_OFFLINE) {
424
+ if (((new Date().getTime()) - this.lastSendTime) > 900 && this.sentChunkQueue.length > 0) {
425
+ this.SendMsgRaw([this.sentChunkQueue[0]]);
426
+ }
427
+ } else
428
+ clearInterval(resendTimeout)
429
+ }, 1000)
430
+
431
+
316
432
  this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
433
+
317
434
  if (this.socket)
318
- this.socket.on("message", async (a) => {
319
- var unpacked: _packet = await this.Unpack(a)
320
- if (unpacked.twprotocol.flags != 128 && unpacked.twprotocol.ack) {
435
+ this.socket.on("message", (a, rinfo) => {
436
+ if (this.State == 0 || rinfo.address != this.host || rinfo.port != this.port)
437
+ return;
438
+ clearInterval(connectInterval)
439
+ if (a.toJSON().data[0] == 0x10) {
440
+ if (a.toString().includes("TKEN") || a.toJSON().data[3] == 0x2) {
441
+ clearInterval(connectInterval);
442
+ this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
443
+ this.SendControlMsg(3);
444
+ this.State = States.STATE_LOADING; // loading state
445
+ this.receivedSnaps = 0;
446
+
447
+ var info = new MsgPacker(1, true);
448
+ info.AddString("0.6 626fce9a778df4d4");
449
+ info.AddString(this.options?.password === undefined ? "" : this.options?.password); // password
450
+
451
+ var client_version = new MsgPacker(0, true);
452
+ client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
453
+ let randomUuid = Buffer.alloc(16);
454
+
455
+ randomBytes(16).copy(randomUuid);
456
+
457
+ client_version.AddBuffer(randomUuid);
458
+ if (this.options?.ddnet_version !== undefined) {
459
+ client_version.AddInt(this.options?.ddnet_version.version);
460
+ client_version.AddString("DDNet " + this.options?.ddnet_version.release_version);
461
+ } else {
462
+ client_version.AddInt(16003);
463
+ client_version.AddString("DDNet 16.0.3");
464
+ }
465
+
466
+ this.SendMsgEx([client_version, info], 1)
467
+ } else if (a.toJSON().data[3] == 0x4) {
468
+ // disconnected
469
+ this.State = States.STATE_OFFLINE;
470
+ let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
471
+ this.emit("disconnect", reason);
472
+ }
473
+
474
+ }
475
+
476
+ var unpacked: _packet = this.Unpack(a)
321
477
  unpacked.chunks.forEach(a => {
322
- if (a.msg && !a.msg.startsWith("SNAP")) {
478
+ if (a.flags & 1) { // vital
323
479
  if (a.seq != undefined && a.seq != -1)
324
480
  this.ack = a.seq
325
-
326
- }
327
- })
328
- }
329
- var chunkMessages = unpacked.chunks.map(a => a.msg)
330
- if (chunkMessages.includes("SV_CHAT")) {
331
- var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
332
- chat.forEach(a => {
333
- if (a.msg == "SV_CHAT") {
334
- var unpacked: iMessage = {} as iMessage;
335
- unpacked.team = unpackInt(a.raw.toJSON().data).result;
336
- var remaining: number[] = unpackInt(a.raw.toJSON().data).remaining;
337
- unpacked.client_id = unpackInt(remaining).result;
338
- remaining = unpackInt(remaining).remaining;
339
- unpacked.message = unpackString(remaining).result;
340
- if (unpacked.client_id != -1)
341
- unpacked.author = { ClientInfo: this.client_infos[unpacked.client_id], PlayerInfo: this.player_infos[unpacked.client_id] }
342
- // console.log(unpacked)
343
- this.emit("message", unpacked)
344
481
  }
345
482
  })
346
- }
347
- if (chunkMessages.includes("SV_KILL_MSG")) {
348
- var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
349
- chat.forEach(a => {
350
- if (a.msg == "SV_KILL_MSG") {
351
- var unpacked: iKillMsg = {} as iKillMsg;
352
- let unpacker = new MsgUnpacker(a.raw.toJSON().data);
353
- unpacked.killer_id = unpacker.unpackInt();
354
- unpacked.victim_id = unpacker.unpackInt();
355
- unpacked.weapon = unpacker.unpackInt();
356
- unpacked.special_mode = unpacker.unpackInt();
357
- if (unpacked.victim_id != -1)
358
- unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
359
- if (unpacked.killer_id != -1)
360
- unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
361
- // console.log(unpacked)
362
- this.emit("kill", unpacked)
363
- }
483
+ this.sentChunkQueue.forEach((buff, i) => {
484
+ let chunk = this.MsgToChunk(buff);
485
+ if (chunk.flags & 1) {
486
+ if (chunk.seq && chunk.seq < this.ack) {
487
+ this.sentChunkQueue.splice(i, 1);
488
+ }
489
+ }
364
490
  })
365
- }
366
- if (a.toJSON().data[0] == 0x10) {
367
- if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
368
- clearInterval(connectInterval);
369
- this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
370
- this.SendControlMsg(3);
371
- this.State = 2; // loading state
372
- var info = new MsgPacker(1, true);
373
- info.AddString("0.6 626fce9a778df4d4");
374
- info.AddString(""); // password
375
-
376
- var client_version = new MsgPacker(0, true);
377
- client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
378
- let randomUuid = new Uint8Array(16);
379
-
380
- randomBytes(16).copy(randomUuid);
381
-
382
- client_version.AddBuffer(Buffer.from(randomUuid));
383
- client_version.AddInt(15091);
384
- client_version.AddString("DDNet 15.9.1");
385
-
386
- this.SendMsgExWithChunks([client_version, info], 1)
387
- } else if (a.toJSON().data[3] == 0x4) {
388
- // disconnected
389
- this.State = 0;
390
- let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
391
- this.State = -1;
392
- this.emit("disconnect", reason);
393
- }
394
-
395
- }
396
- if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
397
- var Msg = new MsgPacker(15, true); /* entergame */
398
- this.SendMsgEx(Msg, 1);
399
- } else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
400
- // send ready
401
- var Msg = new MsgPacker(14, true); /* ready */
402
- this.SendMsgEx(Msg, 1);
403
- } else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
404
- var info = new MsgPacker(20, false);
405
- info.AddString(this.name); /* name */
406
- info.AddString(""); /* clan */
407
- info.AddInt(-1); /* country */
408
- info.AddString("greyfox"); /* skin */
409
- info.AddInt(1); /* use custom color */
410
- info.AddInt(10346103); /* color body */
411
- info.AddInt(65535); /* color feet */
412
- this.SendMsgEx(info, 1);
413
-
414
-
415
- } else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
416
-
417
- if (this.State != 3) {
418
- this.emit('connected');
419
- }
420
- this.State = 3
421
- } else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
422
- var info = new MsgPacker(23, true);
423
- this.SendMsgEx(info, 1)
424
- }
425
- if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
426
- this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
427
- if (this.receivedSnaps >= 2) {
428
- if (this.State != 3)
429
- this.emit('connected')
430
- this.State = 3
431
- }
432
-
433
- var chunks = unpacked.chunks.filter(a => a.msg == "SNAP" || a.msg == "SNAP_SINGLE" || a.msg == "SNAP_EMPTY");
434
- if (chunks.length > 0) {
491
+ var snapChunks = unpacked.chunks.filter(a => a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY");
492
+ if (snapChunks.length > 0) {
435
493
  let part = 0;
436
494
  let num_parts = 1;
437
- chunks.forEach(chunk => {
438
- let AckGameTick = (unpackInt(chunk.raw.toJSON().data).result);
439
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
440
- let DeltaTick = unpackInt(chunk?.raw?.toJSON().data).result
441
- if (chunk.msg == "SNAP") {
442
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
443
- num_parts = (unpackInt(chunk?.raw?.toJSON().data).result)
444
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
445
- part = (unpackInt(chunk?.raw?.toJSON().data).result)
495
+ snapChunks.forEach(chunk => {
496
+ let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
497
+
498
+ let AckGameTick = unpacker.unpackInt();
499
+ if (AckGameTick > this.AckGameTick) {
500
+ this.AckGameTick = AckGameTick;
501
+ if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
502
+ this.PredGameTick = AckGameTick + 1;
503
+ }
504
+
505
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
506
+ let DeltaTick = AckGameTick - unpacker.unpackInt();
507
+ let num_parts = 1;
508
+ let part = 0;
509
+
510
+ if (chunk.msg === "SNAP") {
511
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
512
+ num_parts = unpacker.unpackInt();
513
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
514
+ part = unpacker.unpackInt();
446
515
  }
447
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // part
448
- if (chunk.msg != "SNAP_EMPTY")
449
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
450
- chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
451
- if (part == 0 || this.snaps.length > 30) {
516
+
517
+ let crc = 0;
518
+ let part_size = 0;
519
+ if (chunk.msg != "SNAP_EMPTY") {
520
+ crc = unpacker.unpackInt(); // crc
521
+ part_size = unpacker.unpackInt();
522
+ }
523
+ if (part === 0 || this.snaps.length > 30) {
452
524
  this.snaps = [];
453
525
  }
526
+ chunk.raw = Buffer.from(unpacker.remaining);
454
527
  this.snaps.push(chunk.raw)
455
528
 
456
- if ((num_parts - 1) == part && this.snaps.length == num_parts) {
529
+ if ((num_parts - 1) === part && this.snaps.length === num_parts) {
457
530
  let mergedSnaps = Buffer.concat(this.snaps);
531
+ // mergedSnaps = Buffer.from(unpackInt(mergedSnaps.toJSON().data).remaining);
458
532
  let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
459
-
533
+ // console.log(snapUnpacked.items, toHexStream(mergedSnaps));
460
534
  snapUnpacked.items.forEach((a, i) => {
461
- if (a.type_id == items.OBJ_CLIENT_INFO) {
535
+ if (a.type_id === items.OBJ_CLIENT_INFO) {
462
536
  this.client_infos[a.id] = a.parsed as ClientInfo;
463
- // console.log(a.parsed, i)
464
- // console.log(this.client_infos[a.id])
465
- } else if (a.type_id == items.OBJ_PLAYER_INFO) {
466
- this.player_infos[i] = a.parsed as PlayerInfo;
467
- } else if (a.type_id == items.OBJ_EX || a.type_id > 0x4000) {
468
- if (a.data.length == 5 && ((a.parsed as DdnetCharacter).freeze_end > 0 || (a.parsed as DdnetCharacter).freeze_end == -1)) {
469
- // var packer = new MsgPacker(22, false)
470
- // this.SendMsgEx(packer, 1)
537
+ if (this.client_infos[a.id].name.includes("�") || this.client_infos[a.id].clan.includes("�")) {
538
+ console.log("bad name", this.client_infos[a.id], toHexStream(mergedSnaps), chunk, AckGameTick, DeltaTick, crc, part_size);
471
539
  }
540
+ console.log(this.client_infos[a.id])
472
541
  }
473
542
  })
474
543
  }
475
544
 
545
+
546
+ })
547
+ }
548
+ var chunkMessages = unpacked.chunks.map(a => a.msg)
549
+ if (chunkMessages.includes("SV_CHAT")) {
550
+ var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
551
+ chat.forEach(a => {
552
+ if (a.msg == "SV_CHAT") {
553
+ let unpacker = new MsgUnpacker(a.raw.toJSON().data);
554
+ var unpacked: iMessage = {
555
+ team: unpacker.unpackInt(),
556
+ client_id: unpacker.unpackInt(),
557
+ message: unpacker.unpackString()
558
+ } as iMessage;
559
+
560
+ if (unpacked.client_id != -1)
561
+ unpacked.author = {
562
+ ClientInfo: this.client_infos[unpacked.client_id],
563
+ PlayerInfo: this.player_infos[unpacked.client_id]
564
+ }
565
+ this.emit("message", unpacked)
566
+ }
567
+ })
568
+ }
569
+ if (chunkMessages.includes("SV_KILL_MSG")) {
570
+ var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
571
+ chat.forEach(a => {
572
+ if (a.msg == "SV_KILL_MSG") {
573
+ var unpacked: iKillMsg = {} as iKillMsg;
574
+ let unpacker = new MsgUnpacker(a.raw.toJSON().data);
575
+ unpacked.killer_id = unpacker.unpackInt();
576
+ unpacked.victim_id = unpacker.unpackInt();
577
+ unpacked.weapon = unpacker.unpackInt();
578
+ unpacked.special_mode = unpacker.unpackInt();
579
+ if (unpacked.victim_id != -1)
580
+ unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
581
+ if (unpacked.killer_id != -1)
582
+ unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
583
+ this.emit("kill", unpacked)
584
+ }
476
585
  })
477
586
  }
478
587
 
479
- }
480
- if (new Date().getTime() - this.time >= 1000) {
481
- this.time = new Date().getTime();
482
- this.SendControlMsg(0);
483
- }
484
- })
588
+ if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
589
+ var Msg = new MsgPacker(15, true); /* entergame */
590
+ this.SendMsgEx(Msg, 1);
591
+ } else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
592
+ // send ready
593
+ var Msg = new MsgPacker(14, true); /* ready */
594
+ this.SendMsgEx(Msg, 1);
595
+ } else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
596
+ var info = new MsgPacker(20, false);
597
+ if (this.options?.identity) {
598
+ info.AddString(this.options.identity.name);
599
+ info.AddString(this.options.identity.clan);
600
+ info.AddInt(this.options.identity.country);
601
+ info.AddString(this.options.identity.skin);
602
+ info.AddInt(this.options.identity.use_custom_color);
603
+ info.AddInt(this.options.identity.color_body);
604
+ info.AddInt(this.options.identity.color_feet);
605
+ } else {
606
+ info.AddString(this.name); /* name */
607
+ info.AddString(""); /* clan */
608
+ info.AddInt(-1); /* country */
609
+ info.AddString("greyfox"); /* skin */
610
+ info.AddInt(1); /* use custom color */
611
+ info.AddInt(10346103); /* color body */
612
+ info.AddInt(65535); /* color feet */
613
+
614
+ }
615
+ this.SendMsgEx(info, 1);
616
+
617
+
618
+ } else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
619
+ var info = new MsgPacker(23, true);
620
+ this.SendMsgEx(info, 1)
621
+ }
622
+ if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
623
+ this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
624
+ if (this.receivedSnaps == 2) {
625
+ if (this.State != States.STATE_ONLINE)
626
+ this.emit('connected')
627
+ this.State = States.STATE_ONLINE;
628
+ }
629
+
630
+ }
631
+ if (new Date().getTime() - this.time >= 1000 && this.State == States.STATE_ONLINE) {
632
+ this.time = new Date().getTime();
633
+ this.SendControlMsg(0);
634
+ }
635
+ })
636
+ }
637
+
638
+ sendInput(input = this.movement.input) {
639
+ if (this.State != States.STATE_ONLINE)
640
+ return;
641
+
642
+ let inputMsg = new MsgPacker(16, true);
643
+ inputMsg.AddInt(this.AckGameTick);
644
+ inputMsg.AddInt(this.PredGameTick);
645
+ inputMsg.AddInt(40);
646
+
647
+ let input_data = [
648
+
649
+ input.m_Direction,
650
+ input.m_TargetX,
651
+ input.m_TargetY,
652
+ input.m_Jump,
653
+ input.m_Fire,
654
+ input.m_Hook,
655
+ input.m_PlayerFlags,
656
+ input.m_WantedWeapon,
657
+ input.m_NextWeapon,
658
+ input.m_PrevWeapon
659
+ ]
660
+ input_data.forEach(a => {
661
+ inputMsg.AddInt(a);
662
+ });
663
+ this.SendMsgEx(inputMsg, 0);
664
+ }
665
+ get input() {
666
+ return this.movement.input;
485
667
  }
486
668
 
487
669
  Disconnect() {
@@ -491,7 +673,7 @@ class Client extends EventEmitter {
491
673
  if (this.socket)
492
674
  this.socket.close();
493
675
  this.socket = undefined
494
- this.State = -1;
676
+ this.State = States.STATE_OFFLINE;
495
677
  })
496
678
  })
497
679
  }
@@ -533,6 +715,5 @@ class Client extends EventEmitter {
533
715
  this.SendMsgEx(packer, 1);
534
716
  }
535
717
 
718
+
536
719
  }
537
- export = Client;
538
- // module.exports = Client;