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