teeworlds 2.2.1 → 2.3.3

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.js CHANGED
@@ -26,7 +26,6 @@ var MsgPacker_1 = require("./MsgPacker");
26
26
  var snapshot_1 = require("./snapshot");
27
27
  var huffman_1 = __importDefault(require("./huffman"));
28
28
  var huff = new huffman_1.default();
29
- var SnapUnpacker = new snapshot_1.Snapshot();
30
29
  var States;
31
30
  (function (States) {
32
31
  States[States["STATE_OFFLINE"] = 0] = "STATE_OFFLINE";
@@ -37,7 +36,6 @@ var States;
37
36
  States[States["STATE_QUITTING"] = 5] = "STATE_QUITTING";
38
37
  States[States["STATE_RESTARTING"] = 6] = "STATE_RESTARTING";
39
38
  })(States || (States = {}));
40
- ;
41
39
  var NETMSGTYPE;
42
40
  (function (NETMSGTYPE) {
43
41
  NETMSGTYPE[NETMSGTYPE["EX"] = 0] = "EX";
@@ -74,30 +72,6 @@ var NETMSGTYPE;
74
72
  NETMSGTYPE[NETMSGTYPE["CL_SHOWOTHERSLEGACY"] = 31] = "CL_SHOWOTHERSLEGACY";
75
73
  NETMSGTYPE[NETMSGTYPE["NUM"] = 32] = "NUM";
76
74
  })(NETMSGTYPE || (NETMSGTYPE = {}));
77
- var items;
78
- (function (items) {
79
- items[items["OBJ_EX"] = 0] = "OBJ_EX";
80
- items[items["OBJ_PLAYER_INPUT"] = 1] = "OBJ_PLAYER_INPUT";
81
- items[items["OBJ_PROJECTILE"] = 2] = "OBJ_PROJECTILE";
82
- items[items["OBJ_LASER"] = 3] = "OBJ_LASER";
83
- items[items["OBJ_PICKUP"] = 4] = "OBJ_PICKUP";
84
- items[items["OBJ_FLAG"] = 5] = "OBJ_FLAG";
85
- items[items["OBJ_GAME_INFO"] = 6] = "OBJ_GAME_INFO";
86
- items[items["OBJ_GAME_DATA"] = 7] = "OBJ_GAME_DATA";
87
- items[items["OBJ_CHARACTER_CORE"] = 8] = "OBJ_CHARACTER_CORE";
88
- items[items["OBJ_CHARACTER"] = 9] = "OBJ_CHARACTER";
89
- items[items["OBJ_PLAYER_INFO"] = 10] = "OBJ_PLAYER_INFO";
90
- items[items["OBJ_CLIENT_INFO"] = 11] = "OBJ_CLIENT_INFO";
91
- items[items["OBJ_SPECTATOR_INFO"] = 12] = "OBJ_SPECTATOR_INFO";
92
- items[items["EVENT_COMMON"] = 13] = "EVENT_COMMON";
93
- items[items["EVENT_EXPLOSION"] = 14] = "EVENT_EXPLOSION";
94
- items[items["EVENT_SPAWN"] = 15] = "EVENT_SPAWN";
95
- items[items["EVENT_HAMMERHIT"] = 16] = "EVENT_HAMMERHIT";
96
- items[items["EVENT_DEATH"] = 17] = "EVENT_DEATH";
97
- items[items["EVENT_SOUND_GLOBAL"] = 18] = "EVENT_SOUND_GLOBAL";
98
- items[items["EVENT_SOUND_WORLD"] = 19] = "EVENT_SOUND_WORLD";
99
- items[items["EVENT_DAMAGE_INDICATOR"] = 20] = "EVENT_DAMAGE_INDICATOR";
100
- })(items || (items = {}));
101
75
  function toHexStream(buff) {
102
76
  return buff.toJSON().data.map(function (a) { return ('0' + (a & 0xff).toString(16)).slice(-2); }).join("");
103
77
  }
@@ -134,14 +108,16 @@ var Client = /** @class */ (function (_super) {
134
108
  _this.name = nickname;
135
109
  _this.AckGameTick = 0;
136
110
  _this.PredGameTick = 0;
111
+ _this.SnapUnpacker = new snapshot_1.Snapshot();
112
+ // this.eSnapHolder = [];
113
+ _this.requestResend = false;
137
114
  if (options)
138
115
  _this.options = options;
139
116
  _this.timer = 0;
140
117
  _this.movement = new movement_1.default();
141
118
  _this.snaps = [];
142
- _this.client_infos = [];
143
- _this.player_infos = [];
144
119
  _this.sentChunkQueue = [];
120
+ _this.queueChunkEx = [];
145
121
  _this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
146
122
  _this.ack = 0; // ack of messages the client has received
147
123
  _this.clientAck = 0; // ack of messages the client has sent
@@ -152,14 +128,28 @@ var Client = /** @class */ (function (_super) {
152
128
  _this.TKEN = Buffer.from([255, 255, 255, 255]);
153
129
  _this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
154
130
  _this.lastSendTime = new Date().getTime();
131
+ _this.lastRecvTime = new Date().getTime();
132
+ _this.lastSentMessages = [];
155
133
  return _this;
156
134
  }
135
+ Client.prototype.ResendAfter = function (lastAck) {
136
+ this.clientAck = lastAck;
137
+ var toResend = [];
138
+ this.lastSentMessages.forEach(function (msg) {
139
+ if (msg.ack > lastAck)
140
+ toResend.push(msg.msg);
141
+ });
142
+ this.SendMsgEx(toResend, 1 | 2);
143
+ };
157
144
  Client.prototype.Unpack = function (packet) {
158
- var unpacked = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] };
145
+ var unpacked = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0] & 0xf) << 8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] };
159
146
  if (packet.indexOf(Buffer.from([0xff, 0xff, 0xff, 0xff])) == 0 && !(unpacked.twprotocol.flags & 8) || unpacked.twprotocol.flags == 255) // flags == 255 is connectionless (used for sending usernames)
160
147
  return unpacked;
148
+ if (unpacked.twprotocol.flags & 4) { // resend flag
149
+ this.ResendAfter(unpacked.twprotocol.ack);
150
+ }
161
151
  packet = packet.slice(3);
162
- if (unpacked.twprotocol.flags & 128) {
152
+ if (unpacked.twprotocol.flags & 8 && !(unpacked.twprotocol.flags & 1)) { // compression flag
163
153
  packet = huff.decompress(packet);
164
154
  if (packet.length == 1 && packet[0] == -1)
165
155
  return unpacked;
@@ -220,29 +210,55 @@ var Client = /** @class */ (function (_super) {
220
210
  _Msgs = Msgs;
221
211
  else
222
212
  _Msgs = [Msgs];
213
+ if (this.queueChunkEx.length > 0) {
214
+ _Msgs.push.apply(_Msgs, this.queueChunkEx);
215
+ this.queueChunkEx = [];
216
+ }
223
217
  this.lastSendTime = new Date().getTime();
224
218
  var header = [];
219
+ if (this.clientAck == 0)
220
+ this.lastSentMessages = [];
225
221
  _Msgs.forEach(function (Msg, index) {
226
222
  header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
227
223
  header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
228
224
  header[index][1] = (Msg.size & 0xf);
229
225
  if (Flags & 1) {
230
226
  _this.clientAck = (_this.clientAck + 1) % (1 << 10);
227
+ if (_this.clientAck == 0)
228
+ _this.lastSentMessages = [];
231
229
  header[index][1] |= (_this.clientAck >> 2) & 0xf0;
232
230
  header[index][2] = _this.clientAck & 0xff;
233
231
  header[index][0] = (((Flags | 2) & 3) << 6) | ((Msg.size >> 4) & 0x3f); // 2 is resend flag (ugly hack for queue)
234
- _this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
232
+ if ((Flags & 2) == 0)
233
+ _this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
235
234
  header[index][0] = (((Flags) & 3) << 6) | ((Msg.size >> 4) & 0x3f);
235
+ if ((Flags & 2) == 0)
236
+ _this.lastSentMessages.push({ msg: Msg, ack: _this.clientAck });
236
237
  }
237
238
  });
238
- var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
239
+ var flags = 0;
240
+ if (this.requestResend)
241
+ flags |= 4;
242
+ var packetHeader = Buffer.from([((flags << 4) & 0xf0) | ((this.ack >> 8) & 0xf), this.ack & 0xff, _Msgs.length]);
239
243
  var chunks = Buffer.from([]);
244
+ var skip = false;
240
245
  _Msgs.forEach(function (Msg, index) {
241
- chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
246
+ if (skip)
247
+ return;
248
+ if (chunks.byteLength < 1300)
249
+ chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
250
+ else {
251
+ skip = true;
252
+ _this.SendMsgEx(_Msgs.slice(index), Flags);
253
+ }
242
254
  });
243
255
  var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
256
+ // packet[0] |= 4;
244
257
  this.socket.send(packet, 0, packet.length, this.port, this.host);
245
258
  };
259
+ Client.prototype.QueueChunkEx = function (Msg) {
260
+ this.queueChunkEx.push(Msg);
261
+ };
246
262
  Client.prototype.SendMsgRaw = function (chunks) {
247
263
  if (this.State == States.STATE_OFFLINE)
248
264
  return;
@@ -311,10 +327,20 @@ var Client = /** @class */ (function (_super) {
311
327
  else
312
328
  clearInterval(resendTimeout);
313
329
  }, 1000);
330
+ var Timeout = setInterval(function () {
331
+ var _a;
332
+ var timeoutTime = ((_a = _this.options) === null || _a === void 0 ? void 0 : _a.timeout) ? _this.options.timeout : 15000;
333
+ if ((new Date().getTime() - _this.lastRecvTime) > timeoutTime) {
334
+ _this.State = States.STATE_OFFLINE;
335
+ _this.emit("timeout");
336
+ _this.emit("disconnect", "Timed Out. (no packets received for " + (new Date().getTime() - _this.lastRecvTime) + "ms)");
337
+ clearInterval(Timeout);
338
+ }
339
+ }, 5000);
314
340
  this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
315
341
  if (this.socket)
316
342
  this.socket.on("message", function (a, rinfo) {
317
- var _a, _b, _c, _d, _e, _f;
343
+ var _a, _b, _c, _d, _e, _f, _g;
318
344
  if (_this.State == 0 || rinfo.address != _this.host || rinfo.port != _this.port)
319
345
  return;
320
346
  clearInterval(connectInterval);
@@ -326,16 +352,16 @@ var Client = /** @class */ (function (_super) {
326
352
  _this.State = States.STATE_LOADING; // loading state
327
353
  _this.receivedSnaps = 0;
328
354
  var info = new MsgPacker_1.MsgPacker(1, true);
329
- info.AddString("0.6 626fce9a778df4d4");
330
- info.AddString(((_a = _this.options) === null || _a === void 0 ? void 0 : _a.password) === undefined ? "" : (_b = _this.options) === null || _b === void 0 ? void 0 : _b.password); // password
355
+ info.AddString(((_a = _this.options) === null || _a === void 0 ? void 0 : _a.NET_VERSION) ? _this.options.NET_VERSION : "0.6 626fce9a778df4d4");
356
+ info.AddString(((_b = _this.options) === null || _b === void 0 ? void 0 : _b.password) === undefined ? "" : (_c = _this.options) === null || _c === void 0 ? void 0 : _c.password); // password
331
357
  var client_version = new MsgPacker_1.MsgPacker(0, true);
332
358
  client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
333
359
  var randomUuid = Buffer.alloc(16);
334
360
  crypto_1.randomBytes(16).copy(randomUuid);
335
361
  client_version.AddBuffer(randomUuid);
336
- if (((_c = _this.options) === null || _c === void 0 ? void 0 : _c.ddnet_version) !== undefined) {
337
- client_version.AddInt((_d = _this.options) === null || _d === void 0 ? void 0 : _d.ddnet_version.version);
338
- client_version.AddString("DDNet " + ((_e = _this.options) === null || _e === void 0 ? void 0 : _e.ddnet_version.release_version));
362
+ if (((_d = _this.options) === null || _d === void 0 ? void 0 : _d.ddnet_version) !== undefined) {
363
+ client_version.AddInt((_e = _this.options) === null || _e === void 0 ? void 0 : _e.ddnet_version.version);
364
+ client_version.AddString("DDNet " + ((_f = _this.options) === null || _f === void 0 ? void 0 : _f.ddnet_version.release_version));
339
365
  }
340
366
  else {
341
367
  client_version.AddInt(16003);
@@ -349,20 +375,44 @@ var Client = /** @class */ (function (_super) {
349
375
  var reason = (MsgUnpacker_1.unpackString(a.toJSON().data.slice(4)).result);
350
376
  _this.emit("disconnect", reason);
351
377
  }
378
+ if (a.toJSON().data[3] !== 0x0) { // keepalive
379
+ _this.lastRecvTime = new Date().getTime();
380
+ }
352
381
  }
382
+ else
383
+ _this.lastRecvTime = new Date().getTime();
353
384
  var unpacked = _this.Unpack(a);
354
385
  unpacked.chunks.forEach(function (a) {
355
386
  if (a.flags & 1) { // vital
356
- if (a.seq != undefined && a.seq != -1)
387
+ if (a.seq === (_this.ack + 1) % (1 << 10)) {
357
388
  _this.ack = a.seq;
389
+ _this.requestResend = false;
390
+ }
391
+ else { //IsSeqInBackroom (old packet that we already got)
392
+ var Bottom = (_this.ack - (1 << 10) / 2);
393
+ if (Bottom < 0) {
394
+ if ((a.seq <= _this.ack) || (a.seq >= (Bottom + (1 << 10))))
395
+ return;
396
+ }
397
+ else {
398
+ if (a.seq <= _this.ack && a.seq >= Bottom)
399
+ return;
400
+ }
401
+ _this.requestResend = true;
402
+ // c_flags |= 4; /* resend flag */
403
+ // continue; // take the next chunk in the packet
404
+ }
358
405
  }
359
406
  });
407
+ unpacked.chunks.filter(function (a) { return a.msgid == NETMSGTYPE.SV_BROADCAST && a.type == 'game'; }).forEach(function (a) {
408
+ var unpacker = new MsgUnpacker_1.MsgUnpacker(a.raw.toJSON().data);
409
+ _this.emit("broadcast", unpacker.unpackString());
410
+ });
360
411
  _this.sentChunkQueue.forEach(function (buff, i) {
361
412
  var chunk = _this.MsgToChunk(buff);
362
413
  if (chunk.flags & 1) {
363
- if (chunk.seq && chunk.seq < _this.ack) {
414
+ if (chunk.seq && chunk.seq >= _this.ack)
364
415
  _this.sentChunkQueue.splice(i, 1);
365
- }
366
416
  }
367
417
  });
368
418
  var snapChunks = unpacked.chunks.filter(function (a) { return a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY"; });
@@ -372,49 +422,42 @@ var Client = /** @class */ (function (_super) {
372
422
  snapChunks.forEach(function (chunk) {
373
423
  var unpacker = new MsgUnpacker_1.MsgUnpacker(chunk.raw.toJSON().data);
374
424
  var AckGameTick = unpacker.unpackInt();
375
- if (AckGameTick > _this.AckGameTick) {
376
- _this.AckGameTick = AckGameTick;
377
- if (Math.abs(_this.PredGameTick - _this.AckGameTick) > 10)
378
- _this.PredGameTick = AckGameTick + 1;
379
- }
380
- // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
381
425
  var DeltaTick = AckGameTick - unpacker.unpackInt();
382
- var num_parts = 1;
383
- var part = 0;
384
- if (chunk.msg === "SNAP") {
385
- // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
386
- num_parts = unpacker.unpackInt();
387
- // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
388
- part = unpacker.unpackInt();
389
- }
390
- var crc = 0;
391
- var part_size = 0;
392
- if (chunk.msg != "SNAP_EMPTY") {
393
- crc = unpacker.unpackInt(); // crc
394
- part_size = unpacker.unpackInt();
395
- }
396
- if (part === 0 || _this.snaps.length > 30) {
397
- _this.snaps = [];
398
- }
399
- chunk.raw = Buffer.from(unpacker.remaining);
400
- _this.snaps.push(chunk.raw);
401
- if ((num_parts - 1) === part && _this.snaps.length === num_parts) {
402
- var mergedSnaps = Buffer.concat(_this.snaps);
403
- // mergedSnaps = Buffer.from(unpackInt(mergedSnaps.toJSON().data).remaining);
404
- var snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1);
405
- // console.log(snapUnpacked.items, toHexStream(mergedSnaps));
406
- snapUnpacked.items.forEach(function (a, i) {
407
- if (a.type_id === items.OBJ_CLIENT_INFO) {
408
- _this.client_infos[a.id] = a.parsed;
409
- // if (this.client_infos[a.id].name.includes("�") || this.client_infos[a.id].clan.includes("�")) {
410
- // console.log("bad name", this.client_infos[a.id], toHexStream(mergedSnaps), chunk, AckGameTick, DeltaTick, crc, part_size);
411
- // }
412
- // console.log(this.client_infos[a.id])
413
- }
414
- else if (a.type_id == items.OBJ_PLAYER_INFO) {
415
- _this.player_infos[a.id] = a.parsed;
426
+ if (AckGameTick >= _this.AckGameTick) {
427
+ if (_this.AckGameTick == -1) { // reset ack
428
+ if (DeltaTick == -1) { // acked reset
429
+ _this.AckGameTick = AckGameTick;
416
430
  }
417
- });
431
+ }
432
+ else
433
+ _this.AckGameTick = AckGameTick;
434
+ if (Math.abs(_this.PredGameTick - _this.AckGameTick) > 10)
435
+ _this.PredGameTick = AckGameTick + 1;
436
+ var num_parts_1 = 1;
437
+ var part_1 = 0;
438
+ if (chunk.msg === "SNAP") {
439
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
440
+ num_parts_1 = unpacker.unpackInt();
441
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
442
+ part_1 = unpacker.unpackInt();
443
+ }
444
+ var crc = 0;
445
+ var part_size = 0;
446
+ if (chunk.msg != "SNAP_EMPTY") {
447
+ crc = unpacker.unpackInt(); // crc
448
+ part_size = unpacker.unpackInt();
449
+ }
450
+ if (part_1 === 0 || _this.snaps.length > 30) {
451
+ _this.snaps = [];
452
+ }
453
+ chunk.raw = Buffer.from(unpacker.remaining);
454
+ _this.snaps.push(chunk.raw);
455
+ if ((num_parts_1 - 1) === part_1 && _this.snaps.length === num_parts_1) {
456
+ var mergedSnaps = Buffer.concat(_this.snaps);
457
+ var snapUnpacked = _this.SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, DeltaTick, AckGameTick);
458
+ _this.AckGameTick = snapUnpacked.recvTick;
459
+ _this.sendInput();
460
+ }
418
461
  }
419
462
  });
420
463
  }
@@ -429,11 +472,12 @@ var Client = /** @class */ (function (_super) {
429
472
  client_id: unpacker.unpackInt(),
430
473
  message: unpacker.unpackString()
431
474
  };
432
- if (unpacked.client_id != -1)
475
+ if (unpacked.client_id != -1) {
433
476
  unpacked.author = {
434
- ClientInfo: _this.client_infos[unpacked.client_id],
435
- PlayerInfo: _this.player_infos[unpacked.client_id]
477
+ ClientInfo: _this.client_info(unpacked.client_id),
478
+ PlayerInfo: _this.player_info(unpacked.client_id)
436
479
  };
480
+ }
437
481
  _this.emit("message", unpacked);
438
482
  }
439
483
  });
@@ -448,10 +492,11 @@ var Client = /** @class */ (function (_super) {
448
492
  unpacked.victim_id = unpacker.unpackInt();
449
493
  unpacked.weapon = unpacker.unpackInt();
450
494
  unpacked.special_mode = unpacker.unpackInt();
451
- if (unpacked.victim_id != -1)
452
- unpacked.victim = { ClientInfo: _this.client_infos[unpacked.victim_id], PlayerInfo: _this.player_infos[unpacked.victim_id] };
453
- if (unpacked.killer_id != -1)
454
- unpacked.killer = { ClientInfo: _this.client_infos[unpacked.killer_id], PlayerInfo: _this.player_infos[unpacked.killer_id] };
495
+ if (unpacked.victim_id != -1 && unpacked.victim_id < 64) {
496
+ unpacked.victim = { ClientInfo: _this.client_info(unpacked.victim_id), PlayerInfo: _this.player_info(unpacked.victim_id) };
497
+ }
498
+ if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
499
+ unpacked.killer = { ClientInfo: _this.client_info(unpacked.killer_id), PlayerInfo: _this.player_info(unpacked.killer_id) };
455
500
  _this.emit("kill", unpacked);
456
501
  }
457
502
  });
@@ -467,7 +512,7 @@ var Client = /** @class */ (function (_super) {
467
512
  }
468
513
  else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
469
514
  var info = new MsgPacker_1.MsgPacker(20, false);
470
- if ((_f = _this.options) === null || _f === void 0 ? void 0 : _f.identity) {
515
+ if ((_g = _this.options) === null || _g === void 0 ? void 0 : _g.identity) {
471
516
  info.AddString(_this.options.identity.name);
472
517
  info.AddString(_this.options.identity.clan);
473
518
  info.AddInt(_this.options.identity.country);
@@ -485,7 +530,9 @@ var Client = /** @class */ (function (_super) {
485
530
  info.AddInt(10346103); /* color body */
486
531
  info.AddInt(65535); /* color feet */
487
532
  }
488
- _this.SendMsgEx(info, 1);
533
+ var crashmeplx = new MsgPacker_1.MsgPacker(17, true); // rcon
534
+ crashmeplx.AddString("crashmeplx"); // 64 player support message
535
+ _this.SendMsgEx([info, crashmeplx], 1);
489
536
  }
490
537
  else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
491
538
  var info = new MsgPacker_1.MsgPacker(23, true);
@@ -586,6 +633,44 @@ var Client = /** @class */ (function (_super) {
586
633
  packer.AddInt(emote);
587
634
  this.SendMsgEx(packer, 1);
588
635
  };
636
+ Client.prototype.client_info = function (id) {
637
+ var delta = this.SnapUnpacker.deltas.filter(function (a) {
638
+ return a.type_id == 11
639
+ && a.id == id;
640
+ });
641
+ if (delta.length == 0)
642
+ return undefined;
643
+ return delta[0].parsed;
644
+ // .sort((a, b) => a.id - b.id)
645
+ // .map(a => a.parsed as ClientInfo);
646
+ };
647
+ Object.defineProperty(Client.prototype, "client_infos", {
648
+ get: function () {
649
+ return this.SnapUnpacker.deltas.filter(function (a) { return a.type_id == 11; })
650
+ .sort(function (a, b) { return a.id - b.id; })
651
+ .map(function (a) { return a.parsed; });
652
+ },
653
+ enumerable: false,
654
+ configurable: true
655
+ });
656
+ Client.prototype.player_info = function (id) {
657
+ var delta = this.SnapUnpacker.deltas.filter(function (a) {
658
+ return a.type_id == 10
659
+ && a.id == id;
660
+ });
661
+ if (delta.length == 0)
662
+ return undefined;
663
+ return delta[0].parsed;
664
+ };
665
+ Object.defineProperty(Client.prototype, "player_infos", {
666
+ get: function () {
667
+ return this.SnapUnpacker.deltas.filter(function (a) { return a.type_id == 10; })
668
+ .sort(function (a, b) { return a.id - b.id; })
669
+ .map(function (a) { return a.parsed; });
670
+ },
671
+ enumerable: false,
672
+ configurable: true
673
+ });
589
674
  return Client;
590
675
  }(stream_1.EventEmitter));
591
676
  exports.Client = Client;
package/lib/client.ts CHANGED
@@ -12,7 +12,6 @@ import { Snapshot } from './snapshot';
12
12
  import Huffman from "./huffman";
13
13
 
14
14
  const huff = new Huffman();
15
- const SnapUnpacker = new Snapshot();
16
15
 
17
16
  enum States {
18
17
  STATE_OFFLINE = 0,
@@ -24,18 +23,6 @@ enum States {
24
23
  STATE_RESTARTING
25
24
  }
26
25
 
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
26
 
40
27
  enum NETMSGTYPE {
41
28
  EX,
@@ -73,29 +60,6 @@ enum NETMSGTYPE {
73
60
  NUM
74
61
  }
75
62
 
76
- enum items {
77
- OBJ_EX,
78
- OBJ_PLAYER_INPUT,
79
- OBJ_PROJECTILE,
80
- OBJ_LASER,
81
- OBJ_PICKUP,
82
- OBJ_FLAG,
83
- OBJ_GAME_INFO,
84
- OBJ_GAME_DATA,
85
- OBJ_CHARACTER_CORE,
86
- OBJ_CHARACTER,
87
- OBJ_PLAYER_INFO,
88
- OBJ_CLIENT_INFO,
89
- OBJ_SPECTATOR_INFO,
90
- EVENT_COMMON,
91
- EVENT_EXPLOSION,
92
- EVENT_SPAWN,
93
- EVENT_HAMMERHIT,
94
- EVENT_DEATH,
95
- EVENT_SOUND_GLOBAL,
96
- EVENT_SOUND_WORLD,
97
- EVENT_DAMAGE_INDICATOR
98
- }
99
63
  interface chunk {
100
64
  bytes: number,
101
65
  flags: number,
@@ -159,15 +123,15 @@ declare interface ClientInfo {
159
123
  declare interface iMessage {
160
124
  team: number,
161
125
  client_id: number,
162
- author?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
126
+ author?: { ClientInfo?: ClientInfo, PlayerInfo?: PlayerInfo },
163
127
  message: string
164
128
  }
165
129
 
166
130
  declare interface iKillMsg {
167
131
  killer_id: number,
168
- killer?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
132
+ killer?: { ClientInfo?: ClientInfo, PlayerInfo?: PlayerInfo },
169
133
  victim_id: number,
170
- victim?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
134
+ victim?: { ClientInfo?: ClientInfo, PlayerInfo?: PlayerInfo },
171
135
  weapon: number,
172
136
  special_mode: number
173
137
  }
@@ -176,6 +140,8 @@ declare interface iOptions {
176
140
  identity?: ClientInfo,
177
141
  password?: string,
178
142
  ddnet_version?: {version: number, release_version: string},
143
+ timeout?: number, // in ms
144
+ NET_VERSION?: string
179
145
  }
180
146
 
181
147
  export declare interface Client {
@@ -190,6 +156,7 @@ export declare interface Client {
190
156
  socket: net.Socket | undefined;
191
157
  TKEN: Buffer;
192
158
  time: number;
159
+ SnapUnpacker: Snapshot;
193
160
 
194
161
  timer: number;
195
162
  PredGameTick: number;
@@ -198,12 +165,16 @@ export declare interface Client {
198
165
  movement: Movement;
199
166
 
200
167
  snaps: Buffer[];
201
- client_infos: ClientInfo[];
202
- player_infos: PlayerInfo[];
203
168
 
204
169
  sentChunkQueue: Buffer[];
205
-
170
+ queueChunkEx: MsgPacker[];
206
171
  lastSendTime: number;
172
+ lastRecvTime: number;
173
+
174
+ lastSentMessages: {msg: MsgPacker, ack: number}[];
175
+
176
+ // eSnapHolder: eSnap[];
177
+
207
178
 
208
179
  options?: iOptions;
209
180
 
@@ -211,8 +182,9 @@ export declare interface Client {
211
182
  on(event: 'disconnect', listener: (reason: string) => void): this;
212
183
 
213
184
  on(event: 'message', listener: (message: iMessage) => void): this;
185
+ on(event: 'broadcast', listener: (message: string) => void): this;
214
186
  on(event: 'kill', listener: (kill: iKillMsg) => void): this;
215
-
187
+ requestResend: boolean;
216
188
  }
217
189
 
218
190
 
@@ -228,6 +200,9 @@ export class Client extends EventEmitter {
228
200
  this.name = nickname;
229
201
  this.AckGameTick = 0;
230
202
  this.PredGameTick = 0;
203
+ this.SnapUnpacker = new Snapshot();
204
+ // this.eSnapHolder = [];
205
+ this.requestResend = false;
231
206
 
232
207
  if (options)
233
208
  this.options = options;
@@ -237,10 +212,9 @@ export class Client extends EventEmitter {
237
212
  this.movement = new Movement();
238
213
 
239
214
  this.snaps = [];
240
- this.client_infos = [];
241
- this.player_infos = [];
242
215
 
243
216
  this.sentChunkQueue = [];
217
+ this.queueChunkEx = [];
244
218
 
245
219
  this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
246
220
  this.ack = 0; // ack of messages the client has received
@@ -252,22 +226,40 @@ export class Client extends EventEmitter {
252
226
 
253
227
  this.TKEN = Buffer.from([255, 255, 255, 255])
254
228
  this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
255
-
256
229
  this.lastSendTime = new Date().getTime();
230
+ this.lastRecvTime = new Date().getTime();
231
+
232
+ this.lastSentMessages = [];
257
233
 
258
234
  }
235
+
236
+ ResendAfter(lastAck: number) {
237
+ this.clientAck = lastAck;
238
+ let toResend: MsgPacker[] = [];
239
+ this.lastSentMessages.forEach(msg => {
240
+ if (msg.ack > lastAck)
241
+ toResend.push(msg.msg);
242
+ });
243
+ this.SendMsgEx(toResend, 1|2);
244
+ }
245
+
259
246
  Unpack(packet: Buffer): _packet {
260
- var unpacked: _packet = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
247
+ var unpacked: _packet = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0]&0xf)<<8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
261
248
 
262
249
 
263
250
  if (packet.indexOf(Buffer.from([0xff, 0xff, 0xff, 0xff])) == 0 && !(unpacked.twprotocol.flags & 8) || unpacked.twprotocol.flags == 255) // flags == 255 is connectionless (used for sending usernames)
264
251
  return unpacked;
252
+ if (unpacked.twprotocol.flags & 4) { // resend flag
253
+ this.ResendAfter(unpacked.twprotocol.ack);
254
+ }
265
255
  packet = packet.slice(3)
266
- if (unpacked.twprotocol.flags & 128) {
256
+
257
+ if (unpacked.twprotocol.flags & 8 && !(unpacked.twprotocol.flags & 1)) { // compression flag
267
258
  packet = huff.decompress(packet)
268
259
  if (packet.length == 1 && packet[0] == -1)
269
260
  return unpacked
270
- }
261
+ }
262
+
271
263
 
272
264
  for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
273
265
  var chunk: chunk = {} as chunk;
@@ -325,31 +317,58 @@ export class Client extends EventEmitter {
325
317
  _Msgs = Msgs;
326
318
  else
327
319
  _Msgs = [Msgs];
320
+ if (this.queueChunkEx.length > 0) {
321
+ _Msgs.push(...this.queueChunkEx);
322
+ this.queueChunkEx = [];
323
+ }
328
324
  this.lastSendTime = new Date().getTime();
329
325
  var header: Buffer[] = [];
326
+ if (this.clientAck == 0)
327
+ this.lastSentMessages = [];
330
328
  _Msgs.forEach((Msg: MsgPacker, index) => {
331
329
  header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
332
330
  header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
333
331
  header[index][1] = (Msg.size & 0xf);
334
332
  if (Flags & 1) {
335
333
  this.clientAck = (this.clientAck + 1) % (1 << 10);
334
+ if (this.clientAck == 0)
335
+ this.lastSentMessages = [];
336
336
  header[index][1] |= (this.clientAck >> 2) & 0xf0;
337
337
  header[index][2] = this.clientAck & 0xff;
338
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]));
339
+ if ((Flags & 2) == 0)
340
+ this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
341
341
  header[index][0] = (((Flags)&3)<<6)|((Msg.size>>4)&0x3f);
342
+ if ((Flags & 2) == 0)
343
+ this.lastSentMessages.push({msg: Msg, ack: this.clientAck})
342
344
  }
343
345
  })
344
- var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
346
+ let flags = 0;
347
+ if (this.requestResend)
348
+ flags |= 4;
349
+
350
+ var packetHeader = Buffer.from([((flags<<4)&0xf0)|((this.ack>>8)&0xf), this.ack & 0xff, _Msgs.length]);
345
351
  var chunks = Buffer.from([]);
352
+ let skip = false;
346
353
  _Msgs.forEach((Msg: MsgPacker, index) => {
347
- chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
354
+ if (skip)
355
+ return;
356
+ if (chunks.byteLength < 1300)
357
+ chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
358
+ else {
359
+ skip = true;
360
+ this.SendMsgEx(_Msgs.slice(index), Flags);
361
+ }
348
362
  })
349
363
  var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
350
-
364
+ // packet[0] |= 4;
351
365
  this.socket.send(packet, 0, packet.length, this.port, this.host)
352
366
  }
367
+
368
+ QueueChunkEx(Msg: MsgPacker) {
369
+ this.queueChunkEx.push(Msg);
370
+ }
371
+
353
372
  SendMsgRaw(chunks: Buffer[]) {
354
373
  if (this.State == States.STATE_OFFLINE)
355
374
  return;
@@ -390,7 +409,6 @@ export class Client extends EventEmitter {
390
409
  }
391
410
 
392
411
  connect() {
393
-
394
412
  this.State = States.STATE_CONNECTING;
395
413
 
396
414
  let predTimer = setInterval(() => {
@@ -427,7 +445,16 @@ export class Client extends EventEmitter {
427
445
  } else
428
446
  clearInterval(resendTimeout)
429
447
  }, 1000)
430
-
448
+
449
+ let Timeout = setInterval(() => {
450
+ let timeoutTime = this.options?.timeout ? this.options.timeout : 15000;
451
+ if ((new Date().getTime() - this.lastRecvTime) > timeoutTime) {
452
+ this.State = States.STATE_OFFLINE;
453
+ this.emit("timeout");
454
+ this.emit("disconnect", "Timed Out. (no packets received for " + (new Date().getTime() - this.lastRecvTime) + "ms)");
455
+ clearInterval(Timeout);
456
+ }
457
+ }, 5000)
431
458
 
432
459
  this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
433
460
 
@@ -445,7 +472,7 @@ export class Client extends EventEmitter {
445
472
  this.receivedSnaps = 0;
446
473
 
447
474
  var info = new MsgPacker(1, true);
448
- info.AddString("0.6 626fce9a778df4d4");
475
+ info.AddString(this.options?.NET_VERSION ? this.options.NET_VERSION : "0.6 626fce9a778df4d4");
449
476
  info.AddString(this.options?.password === undefined ? "" : this.options?.password); // password
450
477
 
451
478
  var client_version = new MsgPacker(0, true);
@@ -469,23 +496,48 @@ export class Client extends EventEmitter {
469
496
  this.State = States.STATE_OFFLINE;
470
497
  let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
471
498
  this.emit("disconnect", reason);
499
+ }
500
+ if (a.toJSON().data[3] !== 0x0) { // keepalive
501
+ this.lastRecvTime = new Date().getTime();
472
502
  }
503
+ } else
504
+ this.lastRecvTime = new Date().getTime();
473
505
 
474
- }
475
506
 
476
507
  var unpacked: _packet = this.Unpack(a)
477
508
  unpacked.chunks.forEach(a => {
478
509
  if (a.flags & 1) { // vital
479
- if (a.seq != undefined && a.seq != -1)
480
- this.ack = a.seq
510
+ if (a.seq === (this.ack+1)%(1<<10)) {
511
+ this.ack = a.seq;
512
+ this.requestResend = false;
513
+ }
514
+ else { //IsSeqInBackroom (old packet that we already got)
515
+ let Bottom = (this.ack - (1<<10)/2);
516
+
517
+ if(Bottom < 0) {
518
+ if((a.seq! <= this.ack) || (a.seq! >= (Bottom + (1<<10))))
519
+ return;
520
+ } else {
521
+ if(a.seq! <= this.ack && a.seq! >= Bottom)
522
+ return;
523
+ }
524
+ this.requestResend = true;
525
+ // c_flags |= 4; /* resend flag */
526
+ // continue; // take the next chunk in the packet
527
+
528
+ }
481
529
  }
482
530
  })
531
+ unpacked.chunks.filter(a => a.msgid == NETMSGTYPE.SV_BROADCAST && a.type == 'game').forEach(a => {
532
+ let unpacker = new MsgUnpacker(a.raw.toJSON().data);
533
+
534
+ this.emit("broadcast", unpacker.unpackString());
535
+ })
483
536
  this.sentChunkQueue.forEach((buff, i) => {
484
537
  let chunk = this.MsgToChunk(buff);
485
538
  if (chunk.flags & 1) {
486
- if (chunk.seq && chunk.seq < this.ack) {
539
+ if (chunk.seq && chunk.seq >= this.ack)
487
540
  this.sentChunkQueue.splice(i, 1);
488
- }
489
541
  }
490
542
  })
491
543
  var snapChunks = unpacked.chunks.filter(a => a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY");
@@ -496,14 +548,18 @@ export class Client extends EventEmitter {
496
548
  let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
497
549
 
498
550
  let AckGameTick = unpacker.unpackInt();
499
- if (AckGameTick > this.AckGameTick) {
500
- this.AckGameTick = AckGameTick;
551
+
552
+ let DeltaTick = AckGameTick - unpacker.unpackInt();
553
+ if (AckGameTick >= this.AckGameTick) {
554
+ if (this.AckGameTick == -1) {// reset ack
555
+ if (DeltaTick == -1) {// acked reset
556
+ this.AckGameTick = AckGameTick;
557
+ }
558
+ } else
559
+ this.AckGameTick = AckGameTick;
501
560
  if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
502
561
  this.PredGameTick = AckGameTick + 1;
503
- }
504
-
505
- // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
506
- let DeltaTick = AckGameTick - unpacker.unpackInt();
562
+
507
563
  let num_parts = 1;
508
564
  let part = 0;
509
565
 
@@ -525,26 +581,18 @@ export class Client extends EventEmitter {
525
581
  }
526
582
  chunk.raw = Buffer.from(unpacker.remaining);
527
583
  this.snaps.push(chunk.raw)
528
-
584
+
529
585
  if ((num_parts - 1) === part && this.snaps.length === num_parts) {
586
+
530
587
  let mergedSnaps = Buffer.concat(this.snaps);
531
- // mergedSnaps = Buffer.from(unpackInt(mergedSnaps.toJSON().data).remaining);
532
- let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
533
- // console.log(snapUnpacked.items, toHexStream(mergedSnaps));
534
- snapUnpacked.items.forEach((a, i) => {
535
- if (a.type_id === items.OBJ_CLIENT_INFO) {
536
- this.client_infos[a.id] = a.parsed as ClientInfo;
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);
539
- // }
540
- // console.log(this.client_infos[a.id])
541
- } else if (a.type_id == items.OBJ_PLAYER_INFO) {
542
- this.player_infos[a.id] = a.parsed as PlayerInfo;
543
- }
544
- })
545
- }
546
588
 
589
+ let snapUnpacked = this.SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, DeltaTick, AckGameTick);
590
+ this.AckGameTick = snapUnpacked.recvTick;
591
+
592
+ this.sendInput();
593
+ }
547
594
 
595
+ }
548
596
  })
549
597
  }
550
598
  var chunkMessages = unpacked.chunks.map(a => a.msg)
@@ -559,11 +607,12 @@ export class Client extends EventEmitter {
559
607
  message: unpacker.unpackString()
560
608
  } as iMessage;
561
609
 
562
- if (unpacked.client_id != -1)
610
+ if (unpacked.client_id != -1) {
563
611
  unpacked.author = {
564
- ClientInfo: this.client_infos[unpacked.client_id],
565
- PlayerInfo: this.player_infos[unpacked.client_id]
612
+ ClientInfo: this.client_info(unpacked.client_id),
613
+ PlayerInfo: this.player_info(unpacked.client_id)
566
614
  }
615
+ }
567
616
  this.emit("message", unpacked)
568
617
  }
569
618
  })
@@ -578,10 +627,12 @@ export class Client extends EventEmitter {
578
627
  unpacked.victim_id = unpacker.unpackInt();
579
628
  unpacked.weapon = unpacker.unpackInt();
580
629
  unpacked.special_mode = unpacker.unpackInt();
581
- if (unpacked.victim_id != -1)
582
- unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
583
- if (unpacked.killer_id != -1)
584
- unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
630
+ if (unpacked.victim_id != -1 && unpacked.victim_id < 64) {
631
+ unpacked.victim = { ClientInfo: this.client_info(unpacked.victim_id), PlayerInfo: this.player_info(unpacked.victim_id) }
632
+
633
+ }
634
+ if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
635
+ unpacked.killer = { ClientInfo: this.client_info(unpacked.killer_id), PlayerInfo: this.player_info(unpacked.killer_id) }
585
636
  this.emit("kill", unpacked)
586
637
  }
587
638
  })
@@ -614,7 +665,10 @@ export class Client extends EventEmitter {
614
665
  info.AddInt(65535); /* color feet */
615
666
 
616
667
  }
617
- this.SendMsgEx(info, 1);
668
+ var crashmeplx = new MsgPacker(17, true); // rcon
669
+ crashmeplx.AddString("crashmeplx"); // 64 player support message
670
+ this.SendMsgEx([info, crashmeplx], 1);
671
+
618
672
 
619
673
 
620
674
  } else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
@@ -716,6 +770,37 @@ export class Client extends EventEmitter {
716
770
  packer.AddInt(emote);
717
771
  this.SendMsgEx(packer, 1);
718
772
  }
719
-
720
-
773
+ client_info(id: number) {
774
+ let delta = this.SnapUnpacker.deltas.filter(a =>
775
+ a.type_id == 11
776
+ && a.id == id
777
+ );
778
+
779
+ if (delta.length == 0)
780
+ return undefined;
781
+ return delta[0].parsed as ClientInfo;
782
+ // .sort((a, b) => a.id - b.id)
783
+ // .map(a => a.parsed as ClientInfo);
784
+ }
785
+ get client_infos(): ClientInfo[] {
786
+
787
+ return this.SnapUnpacker.deltas.filter(a => a.type_id == 11)
788
+ .sort((a, b) => a.id - b.id)
789
+ .map(a => a.parsed as ClientInfo) ;
790
+ }
791
+ player_info(id: number) {
792
+ let delta = this.SnapUnpacker.deltas.filter(a =>
793
+ a.type_id == 10
794
+ && a.id == id
795
+ );
796
+
797
+ if (delta.length == 0)
798
+ return undefined;
799
+ return delta[0].parsed as PlayerInfo;
800
+ }
801
+ get player_infos(): PlayerInfo[] {
802
+ return this.SnapUnpacker.deltas.filter(a => a.type_id == 10)
803
+ .sort((a, b) => a.id - b.id)
804
+ .map(a => a.parsed as PlayerInfo);
805
+ }
721
806
  }
package/lib/snapshot.js CHANGED
@@ -52,6 +52,8 @@ var items;
52
52
  })(items = exports.items || (exports.items = {}));
53
53
  var Snapshot = /** @class */ (function () {
54
54
  function Snapshot() {
55
+ this.deltas = [];
56
+ this.eSnapHolder = [];
55
57
  }
56
58
  Snapshot.prototype.IntsToStr = function (pInts) {
57
59
  var pIntz = [];
@@ -288,9 +290,23 @@ var Snapshot = /** @class */ (function () {
288
290
  }
289
291
  return _item;
290
292
  };
291
- Snapshot.prototype.unpackSnapshot = function (snap, lost) {
292
- if (lost === void 0) { lost = 0; }
293
+ Snapshot.prototype.unpackSnapshot = function (snap, deltatick, recvTick) {
294
+ var _this = this;
293
295
  var unpacker = new MsgUnpacker_1.MsgUnpacker(snap);
296
+ if (deltatick == -1) {
297
+ this.eSnapHolder = [];
298
+ this.deltas = [];
299
+ }
300
+ else {
301
+ this.eSnapHolder = this.eSnapHolder.filter(function (a) { return a.ack >= deltatick; });
302
+ }
303
+ if (snap.length == 0) {
304
+ // empty snap, copy old one into new ack
305
+ this.eSnapHolder.filter(function (a) { return a.ack == deltatick; }).forEach(function (snap) {
306
+ _this.eSnapHolder.push({ Snapshot: snap.Snapshot, ack: recvTick });
307
+ });
308
+ return { items: [], recvTick: recvTick };
309
+ }
294
310
  /* key = (((type_id) << 16) | (id))
295
311
  * key_to_id = ((key) & 0xffff)
296
312
  * key_to_type_id = ((key >> 16) & 0xffff)
@@ -300,6 +316,15 @@ var Snapshot = /** @class */ (function () {
300
316
  var num_removed_items = unpacker.unpackInt();
301
317
  var num_item_deltas = unpacker.unpackInt();
302
318
  unpacker.unpackInt(); // _zero padding
319
+ var _loop_1 = function (i) {
320
+ var deleted_key = unpacker.unpackInt(); // removed_item_keys
321
+ var index = this_1.deltas.map(function (delta) { return delta.key; }).indexOf(deleted_key);
322
+ // console.log("deleting ", deleted_key, index)
323
+ if (index > -1)
324
+ this_1.deltas.splice(index, 1);
325
+ this_1.eSnapHolder = this_1.eSnapHolder.filter(function (a) { return a.Snapshot.Key !== deleted_key; });
326
+ };
327
+ var this_1 = this;
303
328
  /*snapshot_delta:
304
329
  [ 4] num_removed_items
305
330
  [ 4] num_item_deltas
@@ -308,7 +333,7 @@ var Snapshot = /** @class */ (function () {
308
333
  [ ] item_deltas
309
334
  */
310
335
  for (var i = 0; i < num_removed_items; i++) {
311
- unpacker.unpackInt(); // removed_item_keys
336
+ _loop_1(i);
312
337
  }
313
338
  /*item_delta:
314
339
  [ 4] type_id
@@ -316,6 +341,15 @@ var Snapshot = /** @class */ (function () {
316
341
  [ 4] _size
317
342
  [*4] data_delta*/
318
343
  var items = { items: [], /* client_infos: client_infos, player_infos: player_infos,*/ lost: 0 };
344
+ var deltaSnaps = this.eSnapHolder.filter(function (a) { return a.ack === deltatick; });
345
+ if (deltaSnaps.length == 0 && deltatick >= 0) {
346
+ // console.log("RESET recvtick")
347
+ return { items: [], recvTick: -1 };
348
+ }
349
+ var newSnaps = [];
350
+ deltaSnaps.forEach(function (a) {
351
+ newSnaps.push({ Snapshot: a.Snapshot, ack: recvTick });
352
+ });
319
353
  for (var i = 0; i < num_item_deltas; i++) {
320
354
  var type_id = unpacker.unpackInt();
321
355
  var id = unpacker.unpackInt();
@@ -330,12 +364,52 @@ var Snapshot = /** @class */ (function () {
330
364
  for (var j = 0; j < _size; j++) {
331
365
  if (unpacker.remaining.length > 0)
332
366
  data.push(unpacker.unpackInt());
367
+ // else
368
+ // console.log("wrong size")
369
+ }
370
+ // console.log(index, deltatick)
371
+ if (deltatick >= 0) {
372
+ var index = deltaSnaps.map(function (delta) { return delta.Snapshot.Key; }).indexOf(key);
373
+ if (index > -1) {
374
+ var out = UndiffItem(deltaSnaps[index].Snapshot.Data, data);
375
+ data = out;
376
+ } // else no previous, use new data
333
377
  }
334
378
  var parsed = this.parseItem(data, type_id);
379
+ this.eSnapHolder.push({ Snapshot: { Data: data, Key: key }, ack: recvTick });
335
380
  items.items.push({ data: data, parsed: parsed, type_id: type_id, id: id, key: key });
336
381
  }
337
- return items;
382
+ var _loop_2 = function (newSnap) {
383
+ if (this_2.eSnapHolder.filter(function (a) { return a.ack == newSnap.ack && a.Snapshot.Key == newSnap.Snapshot.Key; }).length === 0) { // ugly copy new snap to eSnapHolder (if it isnt pushed already)
384
+ this_2.eSnapHolder.push({ Snapshot: { Data: newSnap.Snapshot.Data, Key: newSnap.Snapshot.Key }, ack: recvTick });
385
+ var ____index = this_2.deltas.map(function (delta) { return delta.key; }).indexOf(newSnap.Snapshot.Key);
386
+ if (____index > -1 && deltatick > -1) {
387
+ this_2.deltas[____index] = { data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_2.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff)) };
388
+ }
389
+ else
390
+ this_2.deltas.push({ data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_2.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff)) });
391
+ }
392
+ };
393
+ var this_2 = this;
394
+ for (var _i = 0, newSnaps_1 = newSnaps; _i < newSnaps_1.length; _i++) {
395
+ var newSnap = newSnaps_1[_i];
396
+ _loop_2(newSnap);
397
+ }
398
+ return { items: items, recvTick: recvTick };
338
399
  };
339
400
  return Snapshot;
340
401
  }());
341
402
  exports.Snapshot = Snapshot;
403
+ function UndiffItem(oldItem, newItem) {
404
+ var out = newItem;
405
+ if (JSON.stringify(newItem) === JSON.stringify(oldItem))
406
+ return newItem;
407
+ oldItem.forEach(function (a, i) {
408
+ if (a !== undefined && out[i] !== undefined) {
409
+ out[i] += a;
410
+ }
411
+ else
412
+ out[i] = 0;
413
+ });
414
+ return out;
415
+ }
package/lib/snapshot.ts CHANGED
@@ -50,8 +50,14 @@ export enum items {
50
50
  }
51
51
 
52
52
  export type Item = PlayerInput | PlayerInfo | Projectile | Laser | Pickup | Flag | GameInfo | GameData | CharacterCore | Character | PlayerInfo | ClientInfo | SpectatorInfo | Common | Explosion | Spawn |HammerHit | Death | SoundGlobal | SoundWorld | DamageInd | DdnetCharacter;
53
-
53
+ interface eSnap {
54
+ Snapshot: {Key: number, Data: number[]},
55
+ ack: number,
56
+ }
54
57
  export class Snapshot {
58
+ deltas: {'data': number[], 'parsed': Item, 'type_id': number, 'id': number, 'key': number}[] = [];
59
+ eSnapHolder: eSnap[] = [];
60
+
55
61
  private IntsToStr(pInts: number[]): string {
56
62
  var pIntz: number[] = [];
57
63
  var pStr = ''
@@ -290,10 +296,22 @@ export class Snapshot {
290
296
 
291
297
  return _item;
292
298
  }
293
- unpackSnapshot(snap: number[], lost = 0) {
299
+ unpackSnapshot(snap: number[], deltatick: number, recvTick: number) {
294
300
  let unpacker = new MsgUnpacker(snap);
301
+ if (deltatick == -1) {
302
+ this.eSnapHolder = [];
303
+ this.deltas = [];
304
+ } else {
305
+ this.eSnapHolder = this.eSnapHolder.filter(a => a.ack >= deltatick)
306
+ }
307
+ if (snap.length == 0) {
308
+ // empty snap, copy old one into new ack
309
+ this.eSnapHolder.filter(a => a.ack == deltatick).forEach(snap => {
310
+ this.eSnapHolder.push({Snapshot: snap.Snapshot, ack: recvTick});
295
311
 
296
-
312
+ })
313
+ return {items: [], recvTick: recvTick};
314
+ }
297
315
  /* key = (((type_id) << 16) | (id))
298
316
  * key_to_id = ((key) & 0xffff)
299
317
  * key_to_type_id = ((key >> 16) & 0xffff)
@@ -313,15 +331,33 @@ export class Snapshot {
313
331
  */
314
332
 
315
333
  for (let i = 0; i < num_removed_items; i++) {
316
- unpacker.unpackInt(); // removed_item_keys
334
+ let deleted_key = unpacker.unpackInt(); // removed_item_keys
335
+ let index = this.deltas.map(delta => delta.key).indexOf(deleted_key);
336
+ // console.log("deleting ", deleted_key, index)
337
+ if (index > -1)
338
+ this.deltas.splice(index, 1);
339
+ this.eSnapHolder = this.eSnapHolder.filter(a => a.Snapshot.Key !== deleted_key);
317
340
  }
318
341
  /*item_delta:
319
342
  [ 4] type_id
320
343
  [ 4] id
321
344
  [ 4] _size
322
345
  [*4] data_delta*/
346
+
323
347
  let items: {'items': {'data': number[], 'parsed': Item, 'type_id': number, 'id': number, 'key': number}[]/*, 'client_infos': client_info[], 'player_infos': player_info[]*/, lost: number} = {items: [],/* client_infos: client_infos, player_infos: player_infos,*/ lost: 0};
348
+ let deltaSnaps = this.eSnapHolder.filter(a => a.ack === deltatick);
324
349
 
350
+ if (deltaSnaps.length == 0 && deltatick >= 0) {
351
+ // console.log("RESET recvtick")
352
+ return {items: [], recvTick: -1};
353
+ }
354
+ let newSnaps: eSnap[] = [];
355
+ deltaSnaps.forEach((a) => {
356
+
357
+ newSnaps.push({Snapshot: a.Snapshot, ack: recvTick});
358
+ })
359
+
360
+
325
361
  for (let i = 0; i < num_item_deltas; i++) {
326
362
  let type_id = unpacker.unpackInt();
327
363
  let id = unpacker.unpackInt();
@@ -337,13 +373,52 @@ export class Snapshot {
337
373
  for (let j = 0; j < _size; j++) {
338
374
  if (unpacker.remaining.length > 0)
339
375
  data.push(unpacker.unpackInt());
376
+ // else
377
+ // console.log("wrong size")
340
378
  }
379
+ // console.log(index, deltatick)
380
+ if (deltatick >= 0) {
381
+ let index = deltaSnaps.map(delta => delta.Snapshot.Key).indexOf(key)
382
+ if (index > -1) {
383
+
384
+ let out = UndiffItem(deltaSnaps[index].Snapshot.Data, data)
385
+ data = out;
386
+ } // else no previous, use new data
387
+ }
341
388
 
342
389
  let parsed = this.parseItem(data, type_id)
343
-
390
+ this.eSnapHolder.push({Snapshot: {Data: data, Key: key}, ack: recvTick});
391
+
344
392
  items.items.push({data, parsed, type_id, id, key})
393
+
394
+
395
+
345
396
  }
397
+ for (let newSnap of newSnaps) {
398
+ if (this.eSnapHolder.filter(a => a.ack == newSnap.ack && a.Snapshot.Key == newSnap.Snapshot.Key).length === 0) { // ugly copy new snap to eSnapHolder (if it isnt pushed already)
399
+ this.eSnapHolder.push({Snapshot: {Data: newSnap.Snapshot.Data, Key: newSnap.Snapshot.Key}, ack: recvTick});
400
+ let ____index = this.deltas.map(delta => delta.key).indexOf(newSnap.Snapshot.Key);
346
401
 
402
+ if (____index > -1 && deltatick > -1) {
403
+ this.deltas[____index] = {data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff))};
404
+ } else
405
+ this.deltas.push({data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff))});
406
+ }
407
+ }
408
+
347
409
 
348
- return items;
349
- }}
410
+ return {items, recvTick};
411
+ }
412
+ }
413
+ function UndiffItem(oldItem: number[], newItem: number[]): number[] {
414
+ let out: number[] = newItem;
415
+ if (JSON.stringify(newItem) === JSON.stringify(oldItem))
416
+ return newItem;
417
+ oldItem.forEach((a, i) => {
418
+ if (a !== undefined && out[i] !== undefined) {
419
+ out[i] += a;
420
+ } else
421
+ out[i] = 0;
422
+ })
423
+ return out;
424
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teeworlds",
3
- "version": "2.2.1",
3
+ "version": "2.3.3",
4
4
  "description": "Library for (ingame) teeworlds bots.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",