teeworlds 2.2.1 → 2.3.1

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,17 @@ 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;
114
+ _this.pingStart = 0;
137
115
  if (options)
138
116
  _this.options = options;
139
117
  _this.timer = 0;
140
118
  _this.movement = new movement_1.default();
141
119
  _this.snaps = [];
142
- _this.client_infos = [];
143
- _this.player_infos = [];
144
120
  _this.sentChunkQueue = [];
121
+ _this.queueChunkEx = [];
145
122
  _this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
146
123
  _this.ack = 0; // ack of messages the client has received
147
124
  _this.clientAck = 0; // ack of messages the client has sent
@@ -152,14 +129,28 @@ var Client = /** @class */ (function (_super) {
152
129
  _this.TKEN = Buffer.from([255, 255, 255, 255]);
153
130
  _this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
154
131
  _this.lastSendTime = new Date().getTime();
132
+ _this.lastRecvTime = new Date().getTime();
133
+ _this.lastSentMessages = [];
155
134
  return _this;
156
135
  }
136
+ Client.prototype.ResendAfter = function (lastAck) {
137
+ this.clientAck = lastAck;
138
+ var toResend = [];
139
+ this.lastSentMessages.forEach(function (msg) {
140
+ if (msg.ack > lastAck)
141
+ toResend.push(msg.msg);
142
+ });
143
+ this.SendMsgEx(toResend, 1 | 2);
144
+ };
157
145
  Client.prototype.Unpack = function (packet) {
158
- var unpacked = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] };
146
+ var unpacked = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0] & 0xf) << 8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] };
159
147
  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
148
  return unpacked;
149
+ if (unpacked.twprotocol.flags & 4) { // resend flag
150
+ this.ResendAfter(unpacked.twprotocol.ack);
151
+ }
161
152
  packet = packet.slice(3);
162
- if (unpacked.twprotocol.flags & 128) {
153
+ if (unpacked.twprotocol.flags & 8 && !(unpacked.twprotocol.flags & 1)) { // compression flag
163
154
  packet = huff.decompress(packet);
164
155
  if (packet.length == 1 && packet[0] == -1)
165
156
  return unpacked;
@@ -220,29 +211,55 @@ var Client = /** @class */ (function (_super) {
220
211
  _Msgs = Msgs;
221
212
  else
222
213
  _Msgs = [Msgs];
214
+ if (this.queueChunkEx.length > 0) {
215
+ _Msgs.push.apply(_Msgs, this.queueChunkEx);
216
+ this.queueChunkEx = [];
217
+ }
223
218
  this.lastSendTime = new Date().getTime();
224
219
  var header = [];
220
+ if (this.clientAck == 0)
221
+ this.lastSentMessages = [];
225
222
  _Msgs.forEach(function (Msg, index) {
226
223
  header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
227
224
  header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
228
225
  header[index][1] = (Msg.size & 0xf);
229
226
  if (Flags & 1) {
230
227
  _this.clientAck = (_this.clientAck + 1) % (1 << 10);
228
+ if (_this.clientAck == 0)
229
+ _this.lastSentMessages = [];
231
230
  header[index][1] |= (_this.clientAck >> 2) & 0xf0;
232
231
  header[index][2] = _this.clientAck & 0xff;
233
232
  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]));
233
+ if ((Flags & 2) == 0)
234
+ _this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
235
235
  header[index][0] = (((Flags) & 3) << 6) | ((Msg.size >> 4) & 0x3f);
236
+ if ((Flags & 2) == 0)
237
+ _this.lastSentMessages.push({ msg: Msg, ack: _this.clientAck });
236
238
  }
237
239
  });
238
- var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
240
+ var flags = 0;
241
+ if (this.requestResend)
242
+ flags |= 4;
243
+ var packetHeader = Buffer.from([((flags << 4) & 0xf0) | ((this.ack >> 8) & 0xf), this.ack & 0xff, _Msgs.length]);
239
244
  var chunks = Buffer.from([]);
245
+ var skip = false;
240
246
  _Msgs.forEach(function (Msg, index) {
241
- chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
247
+ if (skip)
248
+ return;
249
+ if (chunks.byteLength < 1300)
250
+ chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
251
+ else {
252
+ skip = true;
253
+ _this.SendMsgEx(_Msgs.slice(index), Flags);
254
+ }
242
255
  });
243
256
  var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
257
+ // packet[0] |= 4;
244
258
  this.socket.send(packet, 0, packet.length, this.port, this.host);
245
259
  };
260
+ Client.prototype.QueueChunkEx = function (Msg) {
261
+ this.queueChunkEx.push(Msg);
262
+ };
246
263
  Client.prototype.SendMsgRaw = function (chunks) {
247
264
  if (this.State == States.STATE_OFFLINE)
248
265
  return;
@@ -311,10 +328,20 @@ var Client = /** @class */ (function (_super) {
311
328
  else
312
329
  clearInterval(resendTimeout);
313
330
  }, 1000);
331
+ var Timeout = setInterval(function () {
332
+ var _a;
333
+ var timeoutTime = ((_a = _this.options) === null || _a === void 0 ? void 0 : _a.timeout) ? _this.options.timeout : 15000;
334
+ if ((new Date().getTime() - _this.lastRecvTime) > timeoutTime) {
335
+ _this.State = States.STATE_OFFLINE;
336
+ _this.emit("timeout");
337
+ _this.emit("disconnect", "Timed Out. (no packets received for " + (new Date().getTime() - _this.lastRecvTime) + "ms)");
338
+ clearInterval(Timeout);
339
+ }
340
+ }, 5000);
314
341
  this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
315
342
  if (this.socket)
316
343
  this.socket.on("message", function (a, rinfo) {
317
- var _a, _b, _c, _d, _e, _f;
344
+ var _a, _b, _c, _d, _e, _f, _g;
318
345
  if (_this.State == 0 || rinfo.address != _this.host || rinfo.port != _this.port)
319
346
  return;
320
347
  clearInterval(connectInterval);
@@ -326,16 +353,16 @@ var Client = /** @class */ (function (_super) {
326
353
  _this.State = States.STATE_LOADING; // loading state
327
354
  _this.receivedSnaps = 0;
328
355
  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
356
+ info.AddString(((_a = _this.options) === null || _a === void 0 ? void 0 : _a.NET_VERSION) ? _this.options.NET_VERSION : "0.6 626fce9a778df4d4");
357
+ 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
358
  var client_version = new MsgPacker_1.MsgPacker(0, true);
332
359
  client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
333
360
  var randomUuid = Buffer.alloc(16);
334
361
  crypto_1.randomBytes(16).copy(randomUuid);
335
362
  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));
363
+ if (((_d = _this.options) === null || _d === void 0 ? void 0 : _d.ddnet_version) !== undefined) {
364
+ client_version.AddInt((_e = _this.options) === null || _e === void 0 ? void 0 : _e.ddnet_version.version);
365
+ client_version.AddString("DDNet " + ((_f = _this.options) === null || _f === void 0 ? void 0 : _f.ddnet_version.release_version));
339
366
  }
340
367
  else {
341
368
  client_version.AddInt(16003);
@@ -349,20 +376,40 @@ var Client = /** @class */ (function (_super) {
349
376
  var reason = (MsgUnpacker_1.unpackString(a.toJSON().data.slice(4)).result);
350
377
  _this.emit("disconnect", reason);
351
378
  }
379
+ if (a.toJSON().data[3] !== 0x0) { // keepalive
380
+ _this.lastRecvTime = new Date().getTime();
381
+ }
352
382
  }
383
+ else
384
+ _this.lastRecvTime = new Date().getTime();
353
385
  var unpacked = _this.Unpack(a);
354
386
  unpacked.chunks.forEach(function (a) {
355
387
  if (a.flags & 1) { // vital
356
- if (a.seq != undefined && a.seq != -1)
388
+ if (a.seq === (_this.ack + 1) % (1 << 10)) {
357
389
  _this.ack = a.seq;
390
+ _this.requestResend = false;
391
+ }
392
+ else { //IsSeqInBackroom (old packet that we already got)
393
+ var Bottom = (_this.ack - (1 << 10) / 2);
394
+ if (Bottom < 0) {
395
+ if ((a.seq <= _this.ack) || (a.seq >= (Bottom + (1 << 10))))
396
+ return;
397
+ }
398
+ else {
399
+ if (a.seq <= _this.ack && a.seq >= Bottom)
400
+ return;
401
+ }
402
+ _this.requestResend = true;
403
+ // c_flags |= 4; /* resend flag */
404
+ // continue; // take the next chunk in the packet
405
+ }
358
406
  }
359
407
  });
360
408
  _this.sentChunkQueue.forEach(function (buff, i) {
361
409
  var chunk = _this.MsgToChunk(buff);
362
410
  if (chunk.flags & 1) {
363
- if (chunk.seq && chunk.seq < _this.ack) {
411
+ if (chunk.seq && chunk.seq >= _this.ack)
364
412
  _this.sentChunkQueue.splice(i, 1);
365
- }
366
413
  }
367
414
  });
368
415
  var snapChunks = unpacked.chunks.filter(function (a) { return a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY"; });
@@ -372,49 +419,42 @@ var Client = /** @class */ (function (_super) {
372
419
  snapChunks.forEach(function (chunk) {
373
420
  var unpacker = new MsgUnpacker_1.MsgUnpacker(chunk.raw.toJSON().data);
374
421
  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
422
  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])
423
+ if (AckGameTick >= _this.AckGameTick) {
424
+ if (_this.AckGameTick == -1) { // reset ack
425
+ if (DeltaTick == -1) { // acked reset
426
+ _this.AckGameTick = AckGameTick;
413
427
  }
414
- else if (a.type_id == items.OBJ_PLAYER_INFO) {
415
- _this.player_infos[a.id] = a.parsed;
416
- }
417
- });
428
+ }
429
+ else
430
+ _this.AckGameTick = AckGameTick;
431
+ if (Math.abs(_this.PredGameTick - _this.AckGameTick) > 10)
432
+ _this.PredGameTick = AckGameTick + 1;
433
+ var num_parts_1 = 1;
434
+ var part_1 = 0;
435
+ if (chunk.msg === "SNAP") {
436
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
437
+ num_parts_1 = unpacker.unpackInt();
438
+ // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
439
+ part_1 = unpacker.unpackInt();
440
+ }
441
+ var crc = 0;
442
+ var part_size = 0;
443
+ if (chunk.msg != "SNAP_EMPTY") {
444
+ crc = unpacker.unpackInt(); // crc
445
+ part_size = unpacker.unpackInt();
446
+ }
447
+ if (part_1 === 0 || _this.snaps.length > 30) {
448
+ _this.snaps = [];
449
+ }
450
+ chunk.raw = Buffer.from(unpacker.remaining);
451
+ _this.snaps.push(chunk.raw);
452
+ if ((num_parts_1 - 1) === part_1 && _this.snaps.length === num_parts_1) {
453
+ var mergedSnaps = Buffer.concat(_this.snaps);
454
+ var snapUnpacked = _this.SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, DeltaTick, AckGameTick);
455
+ _this.AckGameTick = snapUnpacked.recvTick;
456
+ _this.sendInput();
457
+ }
418
458
  }
419
459
  });
420
460
  }
@@ -429,11 +469,12 @@ var Client = /** @class */ (function (_super) {
429
469
  client_id: unpacker.unpackInt(),
430
470
  message: unpacker.unpackString()
431
471
  };
432
- if (unpacked.client_id != -1)
472
+ if (unpacked.client_id != -1) {
433
473
  unpacked.author = {
434
- ClientInfo: _this.client_infos[unpacked.client_id],
435
- PlayerInfo: _this.player_infos[unpacked.client_id]
474
+ ClientInfo: _this.client_info(unpacked.client_id),
475
+ PlayerInfo: _this.player_info(unpacked.client_id)
436
476
  };
477
+ }
437
478
  _this.emit("message", unpacked);
438
479
  }
439
480
  });
@@ -448,10 +489,11 @@ var Client = /** @class */ (function (_super) {
448
489
  unpacked.victim_id = unpacker.unpackInt();
449
490
  unpacked.weapon = unpacker.unpackInt();
450
491
  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] };
492
+ if (unpacked.victim_id != -1 && unpacked.victim_id < 64) {
493
+ unpacked.victim = { ClientInfo: _this.client_info(unpacked.victim_id), PlayerInfo: _this.player_info(unpacked.victim_id) };
494
+ }
495
+ if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
496
+ unpacked.killer = { ClientInfo: _this.client_info(unpacked.killer_id), PlayerInfo: _this.player_info(unpacked.killer_id) };
455
497
  _this.emit("kill", unpacked);
456
498
  }
457
499
  });
@@ -467,7 +509,7 @@ var Client = /** @class */ (function (_super) {
467
509
  }
468
510
  else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
469
511
  var info = new MsgPacker_1.MsgPacker(20, false);
470
- if ((_f = _this.options) === null || _f === void 0 ? void 0 : _f.identity) {
512
+ if ((_g = _this.options) === null || _g === void 0 ? void 0 : _g.identity) {
471
513
  info.AddString(_this.options.identity.name);
472
514
  info.AddString(_this.options.identity.clan);
473
515
  info.AddInt(_this.options.identity.country);
@@ -485,7 +527,9 @@ var Client = /** @class */ (function (_super) {
485
527
  info.AddInt(10346103); /* color body */
486
528
  info.AddInt(65535); /* color feet */
487
529
  }
488
- _this.SendMsgEx(info, 1);
530
+ var crashmeplx = new MsgPacker_1.MsgPacker(17, true); // rcon
531
+ crashmeplx.AddString("crashmeplx"); // 64 player support message
532
+ _this.SendMsgEx([info, crashmeplx], 1);
489
533
  }
490
534
  else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
491
535
  var info = new MsgPacker_1.MsgPacker(23, true);
@@ -586,6 +630,44 @@ var Client = /** @class */ (function (_super) {
586
630
  packer.AddInt(emote);
587
631
  this.SendMsgEx(packer, 1);
588
632
  };
633
+ Client.prototype.client_info = function (id) {
634
+ var delta = this.SnapUnpacker.deltas.filter(function (a) {
635
+ return a.type_id == 11
636
+ && a.id == id;
637
+ });
638
+ if (delta.length == 0)
639
+ return undefined;
640
+ return delta[0].parsed;
641
+ // .sort((a, b) => a.id - b.id)
642
+ // .map(a => a.parsed as ClientInfo);
643
+ };
644
+ Object.defineProperty(Client.prototype, "client_infos", {
645
+ get: function () {
646
+ return this.SnapUnpacker.deltas.filter(function (a) { return a.type_id == 11; })
647
+ .sort(function (a, b) { return a.id - b.id; })
648
+ .map(function (a) { return a.parsed; });
649
+ },
650
+ enumerable: false,
651
+ configurable: true
652
+ });
653
+ Client.prototype.player_info = function (id) {
654
+ var delta = this.SnapUnpacker.deltas.filter(function (a) {
655
+ return a.type_id == 11
656
+ && a.id == id;
657
+ });
658
+ if (delta.length == 0)
659
+ return undefined;
660
+ return delta[0].parsed;
661
+ };
662
+ Object.defineProperty(Client.prototype, "player_infos", {
663
+ get: function () {
664
+ return this.SnapUnpacker.deltas.filter(function (a) { return a.type_id == 11; })
665
+ .sort(function (a, b) { return a.id - b.id; })
666
+ .map(function (a) { return a.parsed; });
667
+ },
668
+ enumerable: false,
669
+ configurable: true
670
+ });
589
671
  return Client;
590
672
  }(stream_1.EventEmitter));
591
673
  exports.Client = Client;
package/lib/client.ts CHANGED
@@ -8,11 +8,10 @@ import { unpackString, MsgUnpacker } from "./MsgUnpacker";
8
8
  import Movement from './movement';
9
9
 
10
10
  import { MsgPacker } from './MsgPacker';
11
- import { Snapshot } from './snapshot';
11
+ import { Item, 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,17 @@ 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
+
178
+ pingStart: number;
207
179
 
208
180
  options?: iOptions;
209
181
 
@@ -212,7 +184,7 @@ export declare interface Client {
212
184
 
213
185
  on(event: 'message', listener: (message: iMessage) => 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,10 @@ 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;
206
+ this.pingStart = 0;
231
207
 
232
208
  if (options)
233
209
  this.options = options;
@@ -237,10 +213,9 @@ export class Client extends EventEmitter {
237
213
  this.movement = new Movement();
238
214
 
239
215
  this.snaps = [];
240
- this.client_infos = [];
241
- this.player_infos = [];
242
216
 
243
217
  this.sentChunkQueue = [];
218
+ this.queueChunkEx = [];
244
219
 
245
220
  this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
246
221
  this.ack = 0; // ack of messages the client has received
@@ -252,22 +227,40 @@ export class Client extends EventEmitter {
252
227
 
253
228
  this.TKEN = Buffer.from([255, 255, 255, 255])
254
229
  this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
255
-
256
230
  this.lastSendTime = new Date().getTime();
231
+ this.lastRecvTime = new Date().getTime();
257
232
 
233
+ this.lastSentMessages = [];
234
+
235
+ }
236
+
237
+ ResendAfter(lastAck: number) {
238
+ this.clientAck = lastAck;
239
+ let toResend: MsgPacker[] = [];
240
+ this.lastSentMessages.forEach(msg => {
241
+ if (msg.ack > lastAck)
242
+ toResend.push(msg.msg);
243
+ });
244
+ this.SendMsgEx(toResend, 1|2);
258
245
  }
246
+
259
247
  Unpack(packet: Buffer): _packet {
260
- var unpacked: _packet = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
248
+ var unpacked: _packet = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0]&0xf)<<8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
261
249
 
262
250
 
263
251
  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
252
  return unpacked;
253
+ if (unpacked.twprotocol.flags & 4) { // resend flag
254
+ this.ResendAfter(unpacked.twprotocol.ack);
255
+ }
265
256
  packet = packet.slice(3)
266
- if (unpacked.twprotocol.flags & 128) {
257
+
258
+ if (unpacked.twprotocol.flags & 8 && !(unpacked.twprotocol.flags & 1)) { // compression flag
267
259
  packet = huff.decompress(packet)
268
260
  if (packet.length == 1 && packet[0] == -1)
269
261
  return unpacked
270
- }
262
+ }
263
+
271
264
 
272
265
  for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
273
266
  var chunk: chunk = {} as chunk;
@@ -325,31 +318,58 @@ export class Client extends EventEmitter {
325
318
  _Msgs = Msgs;
326
319
  else
327
320
  _Msgs = [Msgs];
321
+ if (this.queueChunkEx.length > 0) {
322
+ _Msgs.push(...this.queueChunkEx);
323
+ this.queueChunkEx = [];
324
+ }
328
325
  this.lastSendTime = new Date().getTime();
329
326
  var header: Buffer[] = [];
327
+ if (this.clientAck == 0)
328
+ this.lastSentMessages = [];
330
329
  _Msgs.forEach((Msg: MsgPacker, index) => {
331
330
  header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
332
331
  header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
333
332
  header[index][1] = (Msg.size & 0xf);
334
333
  if (Flags & 1) {
335
334
  this.clientAck = (this.clientAck + 1) % (1 << 10);
335
+ if (this.clientAck == 0)
336
+ this.lastSentMessages = [];
336
337
  header[index][1] |= (this.clientAck >> 2) & 0xf0;
337
338
  header[index][2] = this.clientAck & 0xff;
338
339
  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]));
340
+ if ((Flags & 2) == 0)
341
+ this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
341
342
  header[index][0] = (((Flags)&3)<<6)|((Msg.size>>4)&0x3f);
343
+ if ((Flags & 2) == 0)
344
+ this.lastSentMessages.push({msg: Msg, ack: this.clientAck})
342
345
  }
343
346
  })
344
- var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
347
+ let flags = 0;
348
+ if (this.requestResend)
349
+ flags |= 4;
350
+
351
+ var packetHeader = Buffer.from([((flags<<4)&0xf0)|((this.ack>>8)&0xf), this.ack & 0xff, _Msgs.length]);
345
352
  var chunks = Buffer.from([]);
353
+ let skip = false;
346
354
  _Msgs.forEach((Msg: MsgPacker, index) => {
347
- chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
355
+ if (skip)
356
+ return;
357
+ if (chunks.byteLength < 1300)
358
+ chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
359
+ else {
360
+ skip = true;
361
+ this.SendMsgEx(_Msgs.slice(index), Flags);
362
+ }
348
363
  })
349
364
  var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
350
-
365
+ // packet[0] |= 4;
351
366
  this.socket.send(packet, 0, packet.length, this.port, this.host)
352
367
  }
368
+
369
+ QueueChunkEx(Msg: MsgPacker) {
370
+ this.queueChunkEx.push(Msg);
371
+ }
372
+
353
373
  SendMsgRaw(chunks: Buffer[]) {
354
374
  if (this.State == States.STATE_OFFLINE)
355
375
  return;
@@ -390,7 +410,6 @@ export class Client extends EventEmitter {
390
410
  }
391
411
 
392
412
  connect() {
393
-
394
413
  this.State = States.STATE_CONNECTING;
395
414
 
396
415
  let predTimer = setInterval(() => {
@@ -427,7 +446,16 @@ export class Client extends EventEmitter {
427
446
  } else
428
447
  clearInterval(resendTimeout)
429
448
  }, 1000)
430
-
449
+
450
+ let Timeout = setInterval(() => {
451
+ let timeoutTime = this.options?.timeout ? this.options.timeout : 15000;
452
+ if ((new Date().getTime() - this.lastRecvTime) > timeoutTime) {
453
+ this.State = States.STATE_OFFLINE;
454
+ this.emit("timeout");
455
+ this.emit("disconnect", "Timed Out. (no packets received for " + (new Date().getTime() - this.lastRecvTime) + "ms)");
456
+ clearInterval(Timeout);
457
+ }
458
+ }, 5000)
431
459
 
432
460
  this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
433
461
 
@@ -445,7 +473,7 @@ export class Client extends EventEmitter {
445
473
  this.receivedSnaps = 0;
446
474
 
447
475
  var info = new MsgPacker(1, true);
448
- info.AddString("0.6 626fce9a778df4d4");
476
+ info.AddString(this.options?.NET_VERSION ? this.options.NET_VERSION : "0.6 626fce9a778df4d4");
449
477
  info.AddString(this.options?.password === undefined ? "" : this.options?.password); // password
450
478
 
451
479
  var client_version = new MsgPacker(0, true);
@@ -469,23 +497,43 @@ export class Client extends EventEmitter {
469
497
  this.State = States.STATE_OFFLINE;
470
498
  let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
471
499
  this.emit("disconnect", reason);
500
+ }
501
+ if (a.toJSON().data[3] !== 0x0) { // keepalive
502
+ this.lastRecvTime = new Date().getTime();
472
503
  }
504
+ } else
505
+ this.lastRecvTime = new Date().getTime();
473
506
 
474
- }
475
507
 
476
508
  var unpacked: _packet = this.Unpack(a)
477
509
  unpacked.chunks.forEach(a => {
478
510
  if (a.flags & 1) { // vital
479
- if (a.seq != undefined && a.seq != -1)
480
- this.ack = a.seq
511
+ if (a.seq === (this.ack+1)%(1<<10)) {
512
+ this.ack = a.seq;
513
+ this.requestResend = false;
514
+ }
515
+ else { //IsSeqInBackroom (old packet that we already got)
516
+ let Bottom = (this.ack - (1<<10)/2);
517
+
518
+ if(Bottom < 0) {
519
+ if((a.seq! <= this.ack) || (a.seq! >= (Bottom + (1<<10))))
520
+ return;
521
+ } else {
522
+ if(a.seq! <= this.ack && a.seq! >= Bottom)
523
+ return;
524
+ }
525
+ this.requestResend = true;
526
+ // c_flags |= 4; /* resend flag */
527
+ // continue; // take the next chunk in the packet
528
+
529
+ }
481
530
  }
482
531
  })
483
532
  this.sentChunkQueue.forEach((buff, i) => {
484
533
  let chunk = this.MsgToChunk(buff);
485
534
  if (chunk.flags & 1) {
486
- if (chunk.seq && chunk.seq < this.ack) {
535
+ if (chunk.seq && chunk.seq >= this.ack)
487
536
  this.sentChunkQueue.splice(i, 1);
488
- }
489
537
  }
490
538
  })
491
539
  var snapChunks = unpacked.chunks.filter(a => a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY");
@@ -496,14 +544,18 @@ export class Client extends EventEmitter {
496
544
  let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
497
545
 
498
546
  let AckGameTick = unpacker.unpackInt();
499
- if (AckGameTick > this.AckGameTick) {
500
- this.AckGameTick = AckGameTick;
547
+
548
+ let DeltaTick = AckGameTick - unpacker.unpackInt();
549
+ if (AckGameTick >= this.AckGameTick) {
550
+ if (this.AckGameTick == -1) {// reset ack
551
+ if (DeltaTick == -1) {// acked reset
552
+ this.AckGameTick = AckGameTick;
553
+ }
554
+ } else
555
+ this.AckGameTick = AckGameTick;
501
556
  if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
502
557
  this.PredGameTick = AckGameTick + 1;
503
- }
504
-
505
- // chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
506
- let DeltaTick = AckGameTick - unpacker.unpackInt();
558
+
507
559
  let num_parts = 1;
508
560
  let part = 0;
509
561
 
@@ -525,26 +577,18 @@ export class Client extends EventEmitter {
525
577
  }
526
578
  chunk.raw = Buffer.from(unpacker.remaining);
527
579
  this.snaps.push(chunk.raw)
528
-
580
+
529
581
  if ((num_parts - 1) === part && this.snaps.length === num_parts) {
582
+
530
583
  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
584
 
585
+ let snapUnpacked = this.SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, DeltaTick, AckGameTick);
586
+ this.AckGameTick = snapUnpacked.recvTick;
587
+
588
+ this.sendInput();
589
+ }
547
590
 
591
+ }
548
592
  })
549
593
  }
550
594
  var chunkMessages = unpacked.chunks.map(a => a.msg)
@@ -559,11 +603,12 @@ export class Client extends EventEmitter {
559
603
  message: unpacker.unpackString()
560
604
  } as iMessage;
561
605
 
562
- if (unpacked.client_id != -1)
606
+ if (unpacked.client_id != -1) {
563
607
  unpacked.author = {
564
- ClientInfo: this.client_infos[unpacked.client_id],
565
- PlayerInfo: this.player_infos[unpacked.client_id]
608
+ ClientInfo: this.client_info(unpacked.client_id),
609
+ PlayerInfo: this.player_info(unpacked.client_id)
566
610
  }
611
+ }
567
612
  this.emit("message", unpacked)
568
613
  }
569
614
  })
@@ -578,10 +623,12 @@ export class Client extends EventEmitter {
578
623
  unpacked.victim_id = unpacker.unpackInt();
579
624
  unpacked.weapon = unpacker.unpackInt();
580
625
  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] }
626
+ if (unpacked.victim_id != -1 && unpacked.victim_id < 64) {
627
+ unpacked.victim = { ClientInfo: this.client_info(unpacked.victim_id), PlayerInfo: this.player_info(unpacked.victim_id) }
628
+
629
+ }
630
+ if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
631
+ unpacked.killer = { ClientInfo: this.client_info(unpacked.killer_id), PlayerInfo: this.player_info(unpacked.killer_id) }
585
632
  this.emit("kill", unpacked)
586
633
  }
587
634
  })
@@ -614,7 +661,10 @@ export class Client extends EventEmitter {
614
661
  info.AddInt(65535); /* color feet */
615
662
 
616
663
  }
617
- this.SendMsgEx(info, 1);
664
+ var crashmeplx = new MsgPacker(17, true); // rcon
665
+ crashmeplx.AddString("crashmeplx"); // 64 player support message
666
+ this.SendMsgEx([info, crashmeplx], 1);
667
+
618
668
 
619
669
 
620
670
  } else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
@@ -716,6 +766,37 @@ export class Client extends EventEmitter {
716
766
  packer.AddInt(emote);
717
767
  this.SendMsgEx(packer, 1);
718
768
  }
719
-
720
-
769
+ client_info(id: number) {
770
+ let delta = this.SnapUnpacker.deltas.filter(a =>
771
+ a.type_id == 11
772
+ && a.id == id
773
+ );
774
+
775
+ if (delta.length == 0)
776
+ return undefined;
777
+ return delta[0].parsed as ClientInfo;
778
+ // .sort((a, b) => a.id - b.id)
779
+ // .map(a => a.parsed as ClientInfo);
780
+ }
781
+ get client_infos(): ClientInfo[] {
782
+
783
+ return this.SnapUnpacker.deltas.filter(a => a.type_id == 11)
784
+ .sort((a, b) => a.id - b.id)
785
+ .map(a => a.parsed as ClientInfo) ;
786
+ }
787
+ player_info(id: number) {
788
+ let delta = this.SnapUnpacker.deltas.filter(a =>
789
+ a.type_id == 11
790
+ && a.id == id
791
+ );
792
+
793
+ if (delta.length == 0)
794
+ return undefined;
795
+ return delta[0].parsed as PlayerInfo;
796
+ }
797
+ get player_infos(): PlayerInfo[] {
798
+ return this.SnapUnpacker.deltas.filter(a => a.type_id == 11)
799
+ .sort((a, b) => a.id - b.id)
800
+ .map(a => a.parsed as PlayerInfo);
801
+ }
721
802
  }
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.1",
4
4
  "description": "Library for (ingame) teeworlds bots.",
5
5
  "license": "MIT",
6
6
  "main": "index.js",