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