teeworlds 2.3.9 → 2.4.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/README.md +1 -1
- package/lib/client.js +289 -253
- package/lib/client.ts +277 -210
- package/lib/components/game.js +143 -0
- package/lib/components/game.ts +122 -0
- package/lib/movement.js +1 -1
- package/lib/movement.ts +1 -1
- package/lib/snapshot.js +5 -4
- package/lib/snapshot.ts +6 -5
- package/package.json +1 -1
package/lib/client.ts
CHANGED
|
@@ -10,6 +10,7 @@ import Movement from './movement';
|
|
|
10
10
|
import { MsgPacker } from './MsgPacker';
|
|
11
11
|
import { Snapshot } from './snapshot';
|
|
12
12
|
import Huffman from "./huffman";
|
|
13
|
+
import { Game } from "./components/game";
|
|
13
14
|
|
|
14
15
|
const huff = new Huffman();
|
|
15
16
|
|
|
@@ -24,7 +25,7 @@ enum States {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
enum
|
|
28
|
+
enum NETMSG_Game {
|
|
28
29
|
EX,
|
|
29
30
|
SV_MOTD,
|
|
30
31
|
SV_BROADCAST,
|
|
@@ -60,12 +61,58 @@ enum NETMSGTYPE {
|
|
|
60
61
|
NUM
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
enum NETMSG_Sys {
|
|
65
|
+
NETMSG_EX = 0,
|
|
66
|
+
|
|
67
|
+
// the first thing sent by the client
|
|
68
|
+
// contains the version info for the client
|
|
69
|
+
NETMSG_INFO = 1,
|
|
70
|
+
|
|
71
|
+
// sent by server
|
|
72
|
+
NETMSG_MAP_CHANGE, // sent when client should switch map
|
|
73
|
+
NETMSG_MAP_DATA, // map transfer, contains a chunk of the map file
|
|
74
|
+
NETMSG_CON_READY, // connection is ready, client should send start info
|
|
75
|
+
NETMSG_SNAP, // normal snapshot, multiple parts
|
|
76
|
+
NETMSG_SNAPEMPTY, // empty snapshot
|
|
77
|
+
NETMSG_SNAPSINGLE, // ?
|
|
78
|
+
NETMSG_SNAPSMALL, //
|
|
79
|
+
NETMSG_INPUTTIMING, // reports how off the input was
|
|
80
|
+
NETMSG_RCON_AUTH_STATUS, // result of the authentication
|
|
81
|
+
NETMSG_RCON_LINE, // line that should be printed to the remote console
|
|
82
|
+
|
|
83
|
+
NETMSG_AUTH_CHALLANGE, //
|
|
84
|
+
NETMSG_AUTH_RESULT, //
|
|
85
|
+
|
|
86
|
+
// sent by client
|
|
87
|
+
NETMSG_READY, //
|
|
88
|
+
NETMSG_ENTERGAME,
|
|
89
|
+
NETMSG_INPUT, // contains the inputdata from the client
|
|
90
|
+
NETMSG_RCON_CMD, //
|
|
91
|
+
NETMSG_RCON_AUTH, //
|
|
92
|
+
NETMSG_REQUEST_MAP_DATA, //
|
|
93
|
+
|
|
94
|
+
NETMSG_AUTH_START, //
|
|
95
|
+
NETMSG_AUTH_RESPONSE, //
|
|
96
|
+
|
|
97
|
+
// sent by both
|
|
98
|
+
NETMSG_PING,
|
|
99
|
+
NETMSG_PING_REPLY,
|
|
100
|
+
NETMSG_ERROR,
|
|
101
|
+
|
|
102
|
+
// sent by server (todo: move it up)
|
|
103
|
+
NETMSG_RCON_CMD_ADD,
|
|
104
|
+
NETMSG_RCON_CMD_REM,
|
|
105
|
+
|
|
106
|
+
NUM_NETMSGS,
|
|
107
|
+
}
|
|
108
|
+
|
|
63
109
|
interface chunk {
|
|
64
110
|
bytes: number,
|
|
65
111
|
flags: number,
|
|
66
112
|
sequence?: number,
|
|
67
113
|
seq?: number,
|
|
68
|
-
type: 'sys' | 'game',
|
|
114
|
+
// type: 'sys' | 'game',
|
|
115
|
+
sys: Boolean,
|
|
69
116
|
msgid: number,
|
|
70
117
|
msg: string,
|
|
71
118
|
raw: Buffer,
|
|
@@ -127,6 +174,12 @@ declare interface iMessage {
|
|
|
127
174
|
message: string
|
|
128
175
|
}
|
|
129
176
|
|
|
177
|
+
declare interface iEmoticon {
|
|
178
|
+
client_id: number,
|
|
179
|
+
emoticon: number,
|
|
180
|
+
author?: { ClientInfo?: ClientInfo, PlayerInfo?: PlayerInfo }
|
|
181
|
+
}
|
|
182
|
+
|
|
130
183
|
declare interface iKillMsg {
|
|
131
184
|
killer_id: number,
|
|
132
185
|
killer?: { ClientInfo?: ClientInfo, PlayerInfo?: PlayerInfo },
|
|
@@ -146,59 +199,61 @@ declare interface iOptions {
|
|
|
146
199
|
}
|
|
147
200
|
|
|
148
201
|
export declare interface Client {
|
|
149
|
-
host: string;
|
|
150
|
-
port: number;
|
|
151
|
-
name: string;
|
|
152
|
-
State: number; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
153
|
-
ack: number;
|
|
154
|
-
clientAck: number;
|
|
155
|
-
receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
|
|
156
|
-
lastMsg: string;
|
|
157
|
-
socket: net.Socket | undefined;
|
|
158
|
-
TKEN: Buffer;
|
|
159
|
-
time: number;
|
|
160
|
-
SnapUnpacker: Snapshot;
|
|
161
|
-
|
|
162
|
-
timer: number;
|
|
163
|
-
PredGameTick: number;
|
|
164
|
-
AckGameTick: number;
|
|
165
202
|
|
|
166
|
-
SnapshotParts: number;
|
|
167
|
-
currentSnapshotGameTick: number;
|
|
168
|
-
|
|
169
|
-
movement: Movement;
|
|
170
|
-
|
|
171
|
-
snaps: Buffer[];
|
|
172
|
-
|
|
173
|
-
sentChunkQueue: Buffer[];
|
|
174
|
-
queueChunkEx: MsgPacker[];
|
|
175
|
-
lastSendTime: number;
|
|
176
|
-
lastRecvTime: number;
|
|
177
|
-
|
|
178
|
-
lastSentMessages: {msg: MsgPacker, ack: number}[];
|
|
179
|
-
|
|
180
|
-
// eSnapHolder: eSnap[];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
options?: iOptions;
|
|
184
203
|
|
|
185
204
|
on(event: 'connected', listener: () => void): this;
|
|
186
205
|
on(event: 'disconnect', listener: (reason: string) => void): this;
|
|
187
206
|
|
|
207
|
+
on(event: 'emote', listener: (message: iEmoticon) => void): this;
|
|
188
208
|
on(event: 'message', listener: (message: iMessage) => void): this;
|
|
189
209
|
on(event: 'broadcast', listener: (message: string) => void): this;
|
|
190
210
|
on(event: 'kill', listener: (kill: iKillMsg) => void): this;
|
|
191
211
|
on(event: 'motd', listener: (message: string) => void): this;
|
|
192
212
|
|
|
193
|
-
requestResend: boolean;
|
|
194
213
|
}
|
|
195
214
|
|
|
215
|
+
export class Client extends EventEmitter {
|
|
196
216
|
|
|
217
|
+
private host: string;
|
|
218
|
+
private port: number;
|
|
219
|
+
private name: string;
|
|
220
|
+
private State: number; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
221
|
+
private ack: number;
|
|
222
|
+
private clientAck: number;
|
|
223
|
+
private receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
|
|
224
|
+
private socket: net.Socket | undefined;
|
|
225
|
+
private TKEN: Buffer;
|
|
226
|
+
private time: number;
|
|
227
|
+
private SnapUnpacker: Snapshot;
|
|
228
|
+
|
|
229
|
+
private PredGameTick: number;
|
|
230
|
+
private AckGameTick: number;
|
|
231
|
+
|
|
232
|
+
private SnapshotParts: number;
|
|
233
|
+
private currentSnapshotGameTick: number;
|
|
197
234
|
|
|
198
|
-
|
|
235
|
+
|
|
236
|
+
private snaps: Buffer[];
|
|
237
|
+
|
|
238
|
+
private sentChunkQueue: Buffer[];
|
|
239
|
+
private queueChunkEx: MsgPacker[];
|
|
240
|
+
private lastSendTime: number;
|
|
241
|
+
private lastRecvTime: number;
|
|
242
|
+
|
|
243
|
+
private lastSentMessages: {msg: MsgPacker, ack: number}[];
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
public movement: Movement;
|
|
247
|
+
public game: Game;
|
|
248
|
+
|
|
249
|
+
private VoteList: string[];
|
|
250
|
+
// eSnapHolder: eSnap[];
|
|
199
251
|
|
|
200
252
|
|
|
253
|
+
public readonly options?: iOptions;
|
|
254
|
+
private requestResend: boolean;
|
|
201
255
|
|
|
256
|
+
|
|
202
257
|
constructor(ip: string, port: number, nickname: string, options?: iOptions) {
|
|
203
258
|
super();
|
|
204
259
|
this.host = ip;
|
|
@@ -214,13 +269,12 @@ export class Client extends EventEmitter {
|
|
|
214
269
|
// this.eSnapHolder = [];
|
|
215
270
|
this.requestResend = false;
|
|
216
271
|
|
|
272
|
+
this.VoteList = [];
|
|
273
|
+
|
|
217
274
|
if (options)
|
|
218
275
|
this.options = options;
|
|
219
276
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.movement = new Movement();
|
|
223
|
-
|
|
277
|
+
|
|
224
278
|
this.snaps = [];
|
|
225
279
|
|
|
226
280
|
this.sentChunkQueue = [];
|
|
@@ -230,8 +284,7 @@ export class Client extends EventEmitter {
|
|
|
230
284
|
this.ack = 0; // ack of messages the client has received
|
|
231
285
|
this.clientAck = 0; // ack of messages the client has sent
|
|
232
286
|
this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
233
|
-
this.
|
|
234
|
-
this.socket = net.createSocket("udp4")
|
|
287
|
+
this.socket = net.createSocket("udp4");
|
|
235
288
|
this.socket.bind();
|
|
236
289
|
|
|
237
290
|
this.TKEN = Buffer.from([255, 255, 255, 255])
|
|
@@ -240,10 +293,14 @@ export class Client extends EventEmitter {
|
|
|
240
293
|
this.lastRecvTime = new Date().getTime();
|
|
241
294
|
|
|
242
295
|
this.lastSentMessages = [];
|
|
296
|
+
this.movement = new Movement();
|
|
297
|
+
|
|
298
|
+
this.game = new Game(this);
|
|
299
|
+
|
|
243
300
|
|
|
244
301
|
}
|
|
245
302
|
|
|
246
|
-
ResendAfter(lastAck: number) {
|
|
303
|
+
private ResendAfter(lastAck: number) {
|
|
247
304
|
this.clientAck = lastAck;
|
|
248
305
|
|
|
249
306
|
|
|
@@ -256,7 +313,7 @@ export class Client extends EventEmitter {
|
|
|
256
313
|
this.SendMsgEx(toResend);
|
|
257
314
|
}
|
|
258
315
|
|
|
259
|
-
Unpack(packet: Buffer): _packet {
|
|
316
|
+
private Unpack(packet: Buffer): _packet {
|
|
260
317
|
var unpacked: _packet = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0]&0xf)<<8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
|
|
261
318
|
|
|
262
319
|
|
|
@@ -285,7 +342,8 @@ export class Client extends EventEmitter {
|
|
|
285
342
|
packet = packet.slice(3) // remove flags & size
|
|
286
343
|
} else
|
|
287
344
|
packet = packet.slice(2)
|
|
288
|
-
chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
345
|
+
// chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
346
|
+
chunk.sys = Boolean(packet[0] & 1); // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
289
347
|
chunk.msgid = (packet[0] - (packet[0] & 1)) / 2;
|
|
290
348
|
chunk.msg = messageTypes[packet[0] & 1][chunk.msgid];
|
|
291
349
|
chunk.raw = packet.slice(1, chunk.bytes)
|
|
@@ -301,6 +359,8 @@ export class Client extends EventEmitter {
|
|
|
301
359
|
}
|
|
302
360
|
return unpacked
|
|
303
361
|
}
|
|
362
|
+
|
|
363
|
+
/* Send a Control Msg to the server. (used for disconnect)*/
|
|
304
364
|
SendControlMsg(msg: number, ExtraMsg: string = "") {
|
|
305
365
|
this.lastSendTime = new Date().getTime();
|
|
306
366
|
return new Promise((resolve, reject) => {
|
|
@@ -320,6 +380,7 @@ export class Client extends EventEmitter {
|
|
|
320
380
|
})
|
|
321
381
|
}
|
|
322
382
|
|
|
383
|
+
/* Send a Msg (or Msg[]) to the server.*/
|
|
323
384
|
SendMsgEx(Msgs: MsgPacker[] | MsgPacker) {
|
|
324
385
|
if (this.State == States.STATE_OFFLINE)
|
|
325
386
|
return;
|
|
@@ -378,11 +439,11 @@ export class Client extends EventEmitter {
|
|
|
378
439
|
return;
|
|
379
440
|
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
380
441
|
}
|
|
381
|
-
|
|
442
|
+
/* Queue a chunk (It will get sent in the next packet). */
|
|
382
443
|
QueueChunkEx(Msg: MsgPacker) {
|
|
383
444
|
this.queueChunkEx.push(Msg);
|
|
384
445
|
}
|
|
385
|
-
|
|
446
|
+
/* Send a Raw Buffer (as chunk) to the server. */
|
|
386
447
|
SendMsgRaw(chunks: Buffer[]) {
|
|
387
448
|
if (this.State == States.STATE_OFFLINE)
|
|
388
449
|
return;
|
|
@@ -399,7 +460,7 @@ export class Client extends EventEmitter {
|
|
|
399
460
|
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
400
461
|
}
|
|
401
462
|
|
|
402
|
-
MsgToChunk(packet: Buffer) {
|
|
463
|
+
private MsgToChunk(packet: Buffer) {
|
|
403
464
|
var chunk: chunk = {} as chunk;
|
|
404
465
|
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
405
466
|
chunk.flags = (packet[0] >> 6) & 3;
|
|
@@ -410,7 +471,8 @@ export class Client extends EventEmitter {
|
|
|
410
471
|
packet = packet.slice(3) // remove flags & size
|
|
411
472
|
} else
|
|
412
473
|
packet = packet.slice(2)
|
|
413
|
-
chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
474
|
+
// chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
475
|
+
chunk.sys = Boolean(packet[0] & 1); // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
414
476
|
chunk.msgid = (packet[0]-(packet[0]&1))/2;
|
|
415
477
|
chunk.msg = messageTypes[packet[0]&1][chunk.msgid];
|
|
416
478
|
chunk.raw = packet.slice(1, chunk.bytes)
|
|
@@ -423,6 +485,7 @@ export class Client extends EventEmitter {
|
|
|
423
485
|
return chunk;
|
|
424
486
|
}
|
|
425
487
|
|
|
488
|
+
/* Connect the client to the server. */
|
|
426
489
|
connect() {
|
|
427
490
|
this.State = States.STATE_CONNECTING;
|
|
428
491
|
|
|
@@ -475,14 +538,15 @@ export class Client extends EventEmitter {
|
|
|
475
538
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
476
539
|
|
|
477
540
|
if (this.socket)
|
|
478
|
-
this.socket.on("message", (
|
|
541
|
+
this.socket.on("message", (packet, rinfo) => {
|
|
479
542
|
if (this.State == 0 || rinfo.address != this.host || rinfo.port != this.port)
|
|
480
543
|
return;
|
|
481
544
|
clearInterval(connectInterval)
|
|
482
|
-
|
|
483
|
-
|
|
545
|
+
|
|
546
|
+
if (packet.toJSON().data[0] == 0x10) {
|
|
547
|
+
if (packet.toString().includes("TKEN") || packet.toJSON().data[3] == 0x2) {
|
|
484
548
|
clearInterval(connectInterval);
|
|
485
|
-
this.TKEN = Buffer.from(
|
|
549
|
+
this.TKEN = Buffer.from(packet.toJSON().data.slice(packet.toJSON().data.length - 4, packet.toJSON().data.length))
|
|
486
550
|
this.SendControlMsg(3);
|
|
487
551
|
this.State = States.STATE_LOADING; // loading state
|
|
488
552
|
this.receivedSnaps = 0;
|
|
@@ -507,28 +571,26 @@ export class Client extends EventEmitter {
|
|
|
507
571
|
}
|
|
508
572
|
|
|
509
573
|
this.SendMsgEx([client_version, info])
|
|
510
|
-
} else if (
|
|
574
|
+
} else if (packet.toJSON().data[3] == 0x4) {
|
|
511
575
|
// disconnected
|
|
512
576
|
this.State = States.STATE_OFFLINE;
|
|
513
|
-
let reason: string = (unpackString(
|
|
577
|
+
let reason: string = (unpackString(packet.toJSON().data.slice(4)).result);
|
|
514
578
|
this.emit("disconnect", reason);
|
|
515
579
|
}
|
|
516
|
-
if (
|
|
580
|
+
if (packet.toJSON().data[3] !== 0x0) { // keepalive
|
|
517
581
|
this.lastRecvTime = new Date().getTime();
|
|
518
582
|
}
|
|
519
583
|
} else {
|
|
520
584
|
this.lastRecvTime = new Date().getTime();
|
|
521
|
-
|
|
522
585
|
}
|
|
523
|
-
|
|
524
586
|
|
|
525
|
-
var unpacked: _packet = this.Unpack(
|
|
526
|
-
unpacked.chunks = unpacked.chunks.filter(
|
|
527
|
-
|
|
528
|
-
unpacked.chunks.forEach(
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
531
|
-
this.ack =
|
|
587
|
+
var unpacked: _packet = this.Unpack(packet);
|
|
588
|
+
unpacked.chunks = unpacked.chunks.filter(chunk => ((chunk.flags & 2) && (chunk.flags & 1)) ? chunk.seq! > this.ack : true); // filter out already received chunks
|
|
589
|
+
|
|
590
|
+
unpacked.chunks.forEach(chunk => {
|
|
591
|
+
if (chunk.flags & 1 && (chunk.flags !== 15)) { // vital and not connless
|
|
592
|
+
if (chunk.seq === (this.ack+1)%(1<<10)) { // https://github.com/nobody-mb/twchatonly/blob/master/chatonly.cpp#L237
|
|
593
|
+
this.ack = chunk.seq!;
|
|
532
594
|
|
|
533
595
|
this.requestResend = false;
|
|
534
596
|
}
|
|
@@ -536,10 +598,10 @@ export class Client extends EventEmitter {
|
|
|
536
598
|
let Bottom = (this.ack - (1<<10)/2);
|
|
537
599
|
|
|
538
600
|
if(Bottom < 0) {
|
|
539
|
-
if((
|
|
601
|
+
if((chunk.seq! <= this.ack) || (chunk.seq! >= (Bottom + (1<<10))))
|
|
540
602
|
return;
|
|
541
603
|
} else {
|
|
542
|
-
if(
|
|
604
|
+
if(chunk.seq! <= this.ack && chunk.seq! >= Bottom)
|
|
543
605
|
return;
|
|
544
606
|
}
|
|
545
607
|
this.requestResend = true;
|
|
@@ -548,11 +610,6 @@ export class Client extends EventEmitter {
|
|
|
548
610
|
}
|
|
549
611
|
|
|
550
612
|
})
|
|
551
|
-
unpacked.chunks.filter(a => a.msgid == NETMSGTYPE.SV_BROADCAST && a.type == 'game').forEach(a => {
|
|
552
|
-
let unpacker = new MsgUnpacker(a.raw.toJSON().data);
|
|
553
|
-
|
|
554
|
-
this.emit("broadcast", unpacker.unpackString());
|
|
555
|
-
})
|
|
556
613
|
this.sentChunkQueue.forEach((buff, i) => {
|
|
557
614
|
let chunk = this.MsgToChunk(buff);
|
|
558
615
|
if (chunk.flags & 1) {
|
|
@@ -560,16 +617,61 @@ export class Client extends EventEmitter {
|
|
|
560
617
|
this.sentChunkQueue.splice(i, 1);
|
|
561
618
|
}
|
|
562
619
|
})
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
unpacked.chunks.forEach((chunk, index) => {
|
|
623
|
+
if (chunk.sys) {
|
|
624
|
+
// system messages
|
|
625
|
+
if (chunk.msgid == NETMSG_Sys.NETMSG_PING) { // ping
|
|
626
|
+
let packer = new MsgPacker(NETMSG_Sys.NETMSG_PING_REPLY, true, 0);
|
|
627
|
+
|
|
628
|
+
this.SendMsgEx(packer); // send ping reply
|
|
629
|
+
} else if (chunk.msgid == NETMSG_Sys.NETMSG_PING_REPLY) { // Ping reply
|
|
630
|
+
this.game._ping_resolve(new Date().getTime())
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// packets neccessary for connection
|
|
634
|
+
// https://ddnet.org/docs/libtw2/connection/
|
|
635
|
+
|
|
636
|
+
if (chunk.msgid == NETMSG_Sys.NETMSG_MAP_CHANGE) {
|
|
637
|
+
var Msg = new MsgPacker(NETMSG_Sys.NETMSG_READY, true, 1); /* ready */
|
|
638
|
+
this.SendMsgEx(Msg);
|
|
639
|
+
} else if (chunk.msgid == NETMSG_Sys.NETMSG_CON_READY) {
|
|
640
|
+
var info = new MsgPacker(NETMSG_Game.CL_STARTINFO, false, 1);
|
|
641
|
+
if (this.options?.identity) {
|
|
642
|
+
info.AddString(this.options.identity.name);
|
|
643
|
+
info.AddString(this.options.identity.clan);
|
|
644
|
+
info.AddInt(this.options.identity.country);
|
|
645
|
+
info.AddString(this.options.identity.skin);
|
|
646
|
+
info.AddInt(this.options.identity.use_custom_color);
|
|
647
|
+
info.AddInt(this.options.identity.color_body);
|
|
648
|
+
info.AddInt(this.options.identity.color_feet);
|
|
649
|
+
} else {
|
|
650
|
+
info.AddString(this.name); /* name */
|
|
651
|
+
info.AddString(""); /* clan */
|
|
652
|
+
info.AddInt(-1); /* country */
|
|
653
|
+
info.AddString("greyfox"); /* skin */
|
|
654
|
+
info.AddInt(1); /* use custom color */
|
|
655
|
+
info.AddInt(10346103); /* color body */
|
|
656
|
+
info.AddInt(65535); /* color feet */
|
|
657
|
+
|
|
658
|
+
}
|
|
659
|
+
var crashmeplx = new MsgPacker(17, true, 1); // rcon
|
|
660
|
+
crashmeplx.AddString("crashmeplx"); // 64 player support message
|
|
661
|
+
this.SendMsgEx([info, crashmeplx]);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (chunk.msgid >= NETMSG_Sys.NETMSG_SNAP && chunk.msgid <= NETMSG_Sys.NETMSG_SNAPSINGLE) {
|
|
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
|
+
if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
|
|
570
672
|
this.PredGameTick = this.AckGameTick + 1;
|
|
571
673
|
|
|
572
|
-
snapChunks.forEach(chunk => {
|
|
674
|
+
// snapChunks.forEach(chunk => {
|
|
573
675
|
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
574
676
|
|
|
575
677
|
let NumParts = 1;
|
|
@@ -623,15 +725,65 @@ export class Client extends EventEmitter {
|
|
|
623
725
|
}
|
|
624
726
|
|
|
625
727
|
|
|
626
|
-
})
|
|
728
|
+
// })
|
|
729
|
+
|
|
730
|
+
|
|
627
731
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (
|
|
633
|
-
|
|
634
|
-
|
|
732
|
+
} else {
|
|
733
|
+
// game messages
|
|
734
|
+
|
|
735
|
+
// vote list:
|
|
736
|
+
if (chunk.msgid == NETMSG_Game.SV_VOTECLEAROPTIONS) {
|
|
737
|
+
this.VoteList = [];
|
|
738
|
+
} else if (chunk.msgid == NETMSG_Game.SV_VOTEOPTIONLISTADD) {
|
|
739
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data)
|
|
740
|
+
let NumOptions = unpacker.unpackInt()
|
|
741
|
+
let list: string[] = [];
|
|
742
|
+
for (let i = 0; i < 15; i++) {
|
|
743
|
+
list.push(unpacker.unpackString());
|
|
744
|
+
}
|
|
745
|
+
list = list.slice(NumOptions);
|
|
746
|
+
|
|
747
|
+
this.VoteList.push(...list);
|
|
748
|
+
} else if (chunk.msgid == NETMSG_Game.SV_VOTEOPTIONADD) {
|
|
749
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data)
|
|
750
|
+
|
|
751
|
+
this.VoteList.push(unpacker.unpackString());
|
|
752
|
+
} else if (chunk.msgid == NETMSG_Game.SV_VOTEOPTIONREMOVE) {
|
|
753
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data)
|
|
754
|
+
|
|
755
|
+
let index = this.VoteList.indexOf(unpacker.unpackString());
|
|
756
|
+
|
|
757
|
+
if (index > -1)
|
|
758
|
+
this.VoteList = this.VoteList.splice(index, 1);
|
|
759
|
+
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// events
|
|
763
|
+
if (chunk.msgid == NETMSG_Game.SV_EMOTICON) {
|
|
764
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
765
|
+
let unpacked = {
|
|
766
|
+
client_id: unpacker.unpackInt(),
|
|
767
|
+
emoticon: unpacker.unpackInt()
|
|
768
|
+
} as iEmoticon;
|
|
769
|
+
|
|
770
|
+
if (unpacked.client_id != -1) {
|
|
771
|
+
unpacked.author = {
|
|
772
|
+
ClientInfo: this.client_info(unpacked.client_id),
|
|
773
|
+
PlayerInfo: this.player_info(unpacked.client_id)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
this.emit("emote", unpacked)
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
} else if (chunk.msgid == NETMSG_Game.SV_BROADCAST) {
|
|
781
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
782
|
+
|
|
783
|
+
this.emit("broadcast", unpacker.unpackString());
|
|
784
|
+
} if (chunk.msgid == NETMSG_Game.SV_CHAT) {
|
|
785
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
786
|
+
let unpacked: iMessage = {
|
|
635
787
|
team: unpacker.unpackInt(),
|
|
636
788
|
client_id: unpacker.unpackInt(),
|
|
637
789
|
message: unpacker.unpackString()
|
|
@@ -644,14 +796,9 @@ export class Client extends EventEmitter {
|
|
|
644
796
|
}
|
|
645
797
|
}
|
|
646
798
|
this.emit("message", unpacked)
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG" || a.msg == "SV_MOTD");
|
|
651
|
-
chat.forEach(a => {
|
|
652
|
-
if (a.msg == "SV_KILL_MSG") {
|
|
653
|
-
var unpacked: iKillMsg = {} as iKillMsg;
|
|
654
|
-
let unpacker = new MsgUnpacker(a.raw.toJSON().data);
|
|
799
|
+
} else if (chunk.msgid == NETMSG_Game.SV_KILLMSG) {
|
|
800
|
+
let unpacked: iKillMsg = {} as iKillMsg;
|
|
801
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
655
802
|
unpacked.killer_id = unpacker.unpackInt();
|
|
656
803
|
unpacked.victim_id = unpacker.unpackInt();
|
|
657
804
|
unpacked.weapon = unpacker.unpackInt();
|
|
@@ -663,59 +810,22 @@ export class Client extends EventEmitter {
|
|
|
663
810
|
if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
|
|
664
811
|
unpacked.killer = { ClientInfo: this.client_info(unpacked.killer_id), PlayerInfo: this.player_info(unpacked.killer_id) }
|
|
665
812
|
this.emit("kill", unpacked)
|
|
666
|
-
} else if (
|
|
667
|
-
let unpacker = new MsgUnpacker(
|
|
813
|
+
} else if (chunk.msgid == NETMSG_Game.SV_MOTD) {
|
|
814
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
668
815
|
let message = unpacker.unpackString();
|
|
669
816
|
this.emit("motd", message);
|
|
670
817
|
}
|
|
671
|
-
})
|
|
672
|
-
|
|
673
|
-
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
674
|
-
var Msg = new MsgPacker(15, true, 1); /* entergame */
|
|
675
|
-
this.SendMsgEx(Msg);
|
|
676
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
677
|
-
// send ready
|
|
678
|
-
var Msg = new MsgPacker(14, true, 1); /* ready */
|
|
679
|
-
this.SendMsgEx(Msg);
|
|
680
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY"))) {
|
|
681
|
-
var info = new MsgPacker(20, false, 1);
|
|
682
|
-
if (this.options?.identity) {
|
|
683
|
-
info.AddString(this.options.identity.name);
|
|
684
|
-
info.AddString(this.options.identity.clan);
|
|
685
|
-
info.AddInt(this.options.identity.country);
|
|
686
|
-
info.AddString(this.options.identity.skin);
|
|
687
|
-
info.AddInt(this.options.identity.use_custom_color);
|
|
688
|
-
info.AddInt(this.options.identity.color_body);
|
|
689
|
-
info.AddInt(this.options.identity.color_feet);
|
|
690
|
-
} else {
|
|
691
|
-
info.AddString(this.name); /* name */
|
|
692
|
-
info.AddString(""); /* clan */
|
|
693
|
-
info.AddInt(-1); /* country */
|
|
694
|
-
info.AddString("greyfox"); /* skin */
|
|
695
|
-
info.AddInt(1); /* use custom color */
|
|
696
|
-
info.AddInt(10346103); /* color body */
|
|
697
|
-
info.AddInt(65535); /* color feet */
|
|
698
818
|
|
|
819
|
+
// packets neccessary for connection
|
|
820
|
+
// https://ddnet.org/docs/libtw2/connection/
|
|
821
|
+
if (chunk.msgid == NETMSG_Game.SV_READYTOENTER) {
|
|
822
|
+
var Msg = new MsgPacker(15, true, 1); /* entergame */
|
|
823
|
+
this.SendMsgEx(Msg);
|
|
824
|
+
}
|
|
699
825
|
}
|
|
700
|
-
|
|
701
|
-
crashmeplx.AddString("crashmeplx"); // 64 player support message
|
|
702
|
-
this.SendMsgEx([info, crashmeplx]);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
} else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
707
|
-
var info = new MsgPacker(23, true, 1);
|
|
708
|
-
this.SendMsgEx(info)
|
|
709
|
-
}
|
|
710
|
-
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
711
|
-
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
712
|
-
if (this.receivedSnaps == 2) {
|
|
713
|
-
if (this.State != States.STATE_ONLINE)
|
|
714
|
-
this.emit('connected')
|
|
715
|
-
this.State = States.STATE_ONLINE;
|
|
716
|
-
}
|
|
826
|
+
})
|
|
717
827
|
|
|
718
|
-
|
|
828
|
+
|
|
719
829
|
if (new Date().getTime() - this.time >= 1000 && this.State == States.STATE_ONLINE) {
|
|
720
830
|
this.time = new Date().getTime();
|
|
721
831
|
this.SendControlMsg(0);
|
|
@@ -723,6 +833,7 @@ export class Client extends EventEmitter {
|
|
|
723
833
|
})
|
|
724
834
|
}
|
|
725
835
|
|
|
836
|
+
/* Sending the input. (automatically done unless options.lightweight is on) */
|
|
726
837
|
sendInput(input = this.movement.input) {
|
|
727
838
|
if (this.State != States.STATE_ONLINE)
|
|
728
839
|
return;
|
|
@@ -754,6 +865,7 @@ export class Client extends EventEmitter {
|
|
|
754
865
|
return this.movement.input;
|
|
755
866
|
}
|
|
756
867
|
|
|
868
|
+
/* Disconnect the client. */
|
|
757
869
|
Disconnect() {
|
|
758
870
|
return new Promise((resolve) => {
|
|
759
871
|
this.SendControlMsg(4).then(() => {
|
|
@@ -766,64 +878,11 @@ export class Client extends EventEmitter {
|
|
|
766
878
|
})
|
|
767
879
|
}
|
|
768
880
|
|
|
769
|
-
|
|
770
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_SAY, false, 1);
|
|
771
|
-
packer.AddInt(team ? 1 : 0); // team
|
|
772
|
-
packer.AddString(message);
|
|
773
|
-
if (!this.options?.lightweight)
|
|
774
|
-
this.QueueChunkEx(packer);
|
|
775
|
-
else
|
|
776
|
-
this.SendMsgEx(packer);
|
|
777
|
-
}
|
|
778
|
-
Vote(vote: boolean) {
|
|
779
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_VOTE, false, 1);
|
|
780
|
-
packer.AddInt(vote ? 1 : -1);
|
|
781
|
-
if (!this.options?.lightweight)
|
|
782
|
-
this.QueueChunkEx(packer);
|
|
783
|
-
else
|
|
784
|
-
this.SendMsgEx(packer);
|
|
785
|
-
}
|
|
786
|
-
ChangePlayerInfo(playerInfo: ClientInfo) {
|
|
787
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_CHANGEINFO, false, 1);
|
|
788
|
-
packer.AddString(playerInfo.name); //m_pName);
|
|
789
|
-
packer.AddString(playerInfo.clan); //m_pClan);
|
|
790
|
-
packer.AddInt(playerInfo.country); //m_Country);
|
|
791
|
-
packer.AddString(playerInfo.skin); //m_pSkin);
|
|
792
|
-
packer.AddInt(playerInfo.use_custom_color ? 1 : 0); //m_UseCustomColor);
|
|
793
|
-
packer.AddInt(playerInfo.color_body); //m_ColorBody);
|
|
794
|
-
packer.AddInt(playerInfo.color_feet); //m_ColorFeet);
|
|
795
|
-
if (!this.options?.lightweight)
|
|
796
|
-
this.QueueChunkEx(packer);
|
|
797
|
-
else
|
|
798
|
-
this.SendMsgEx(packer);
|
|
799
|
-
}
|
|
800
|
-
Kill() {
|
|
801
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_KILL, false, 1);
|
|
802
|
-
if (!this.options?.lightweight)
|
|
803
|
-
this.QueueChunkEx(packer);
|
|
804
|
-
else
|
|
805
|
-
this.SendMsgEx(packer);
|
|
806
|
-
}
|
|
807
|
-
ChangeTeam(team: number) {
|
|
808
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_SETTEAM, false, 1);
|
|
809
|
-
packer.AddInt(team);
|
|
810
|
-
if (!this.options?.lightweight)
|
|
811
|
-
this.QueueChunkEx(packer);
|
|
812
|
-
else
|
|
813
|
-
this.SendMsgEx(packer);
|
|
814
|
-
}
|
|
815
|
-
Emote(emote: number) {
|
|
816
|
-
var packer = new MsgPacker(NETMSGTYPE.CL_EMOTICON, false, 1);
|
|
817
|
-
packer.AddInt(emote);
|
|
818
|
-
if (!this.options?.lightweight)
|
|
819
|
-
this.QueueChunkEx(packer);
|
|
820
|
-
else
|
|
821
|
-
this.SendMsgEx(packer);
|
|
822
|
-
}
|
|
881
|
+
/* Get the client_info from a specific player id. */
|
|
823
882
|
client_info(id: number) {
|
|
824
|
-
let delta = this.SnapUnpacker.deltas.filter(
|
|
825
|
-
|
|
826
|
-
&&
|
|
883
|
+
let delta = this.SnapUnpacker.deltas.filter(_delta =>
|
|
884
|
+
_delta.type_id == 11
|
|
885
|
+
&& _delta.id == id
|
|
827
886
|
);
|
|
828
887
|
|
|
829
888
|
if (delta.length == 0)
|
|
@@ -832,25 +891,33 @@ export class Client extends EventEmitter {
|
|
|
832
891
|
// .sort((a, b) => a.id - b.id)
|
|
833
892
|
// .map(a => a.parsed as ClientInfo);
|
|
834
893
|
}
|
|
894
|
+
|
|
895
|
+
/* Get all client infos. */
|
|
835
896
|
get client_infos(): ClientInfo[] {
|
|
836
897
|
|
|
837
|
-
return this.SnapUnpacker.deltas.filter(
|
|
838
|
-
|
|
839
|
-
|
|
898
|
+
return this.SnapUnpacker.deltas.filter(_delta => _delta.type_id == 11)
|
|
899
|
+
.sort((a, b) => a.id - b.id)
|
|
900
|
+
.map(a => a.parsed as ClientInfo);
|
|
840
901
|
}
|
|
902
|
+
/* Get the player info from a specific player id. */
|
|
841
903
|
player_info(id: number) {
|
|
842
|
-
let delta = this.SnapUnpacker.deltas.filter(
|
|
843
|
-
|
|
844
|
-
&&
|
|
904
|
+
let delta = this.SnapUnpacker.deltas.filter(_delta =>
|
|
905
|
+
_delta.type_id == 10
|
|
906
|
+
&& _delta.id == id
|
|
845
907
|
);
|
|
846
908
|
|
|
847
909
|
if (delta.length == 0)
|
|
848
910
|
return undefined;
|
|
849
911
|
return delta[0].parsed as PlayerInfo;
|
|
850
912
|
}
|
|
913
|
+
/* Get all player infos. */
|
|
851
914
|
get player_infos(): PlayerInfo[] {
|
|
852
|
-
return this.SnapUnpacker.deltas.filter(
|
|
915
|
+
return this.SnapUnpacker.deltas.filter(_delta => _delta.type_id == 10)
|
|
853
916
|
.sort((a, b) => a.id - b.id)
|
|
854
|
-
.map(
|
|
917
|
+
.map(player => player.parsed as PlayerInfo);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
get VoteOptionList(): string[] {
|
|
921
|
+
return this.VoteList;
|
|
855
922
|
}
|
|
856
923
|
}
|