teeworlds 2.3.9 → 2.4.0
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 +44 -93
- package/lib/client.ts +94 -135
- 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/README.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -25,6 +25,7 @@ var movement_1 = __importDefault(require("./movement"));
|
|
|
25
25
|
var MsgPacker_1 = require("./MsgPacker");
|
|
26
26
|
var snapshot_1 = require("./snapshot");
|
|
27
27
|
var huffman_1 = __importDefault(require("./huffman"));
|
|
28
|
+
var game_1 = require("./components/game");
|
|
28
29
|
var huff = new huffman_1.default();
|
|
29
30
|
var States;
|
|
30
31
|
(function (States) {
|
|
@@ -115,8 +116,6 @@ var Client = /** @class */ (function (_super) {
|
|
|
115
116
|
_this.requestResend = false;
|
|
116
117
|
if (options)
|
|
117
118
|
_this.options = options;
|
|
118
|
-
_this.timer = 0;
|
|
119
|
-
_this.movement = new movement_1.default();
|
|
120
119
|
_this.snaps = [];
|
|
121
120
|
_this.sentChunkQueue = [];
|
|
122
121
|
_this.queueChunkEx = [];
|
|
@@ -124,7 +123,6 @@ var Client = /** @class */ (function (_super) {
|
|
|
124
123
|
_this.ack = 0; // ack of messages the client has received
|
|
125
124
|
_this.clientAck = 0; // ack of messages the client has sent
|
|
126
125
|
_this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
127
|
-
_this.lastMsg = "";
|
|
128
126
|
_this.socket = dgram_1.default.createSocket("udp4");
|
|
129
127
|
_this.socket.bind();
|
|
130
128
|
_this.TKEN = Buffer.from([255, 255, 255, 255]);
|
|
@@ -132,6 +130,8 @@ var Client = /** @class */ (function (_super) {
|
|
|
132
130
|
_this.lastSendTime = new Date().getTime();
|
|
133
131
|
_this.lastRecvTime = new Date().getTime();
|
|
134
132
|
_this.lastSentMessages = [];
|
|
133
|
+
_this.movement = new movement_1.default();
|
|
134
|
+
_this.game = new game_1.Game(_this);
|
|
135
135
|
return _this;
|
|
136
136
|
}
|
|
137
137
|
Client.prototype.ResendAfter = function (lastAck) {
|
|
@@ -183,6 +183,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
183
183
|
}
|
|
184
184
|
return unpacked;
|
|
185
185
|
};
|
|
186
|
+
/* Send a Control Msg to the server. (used for disconnect)*/
|
|
186
187
|
Client.prototype.SendControlMsg = function (msg, ExtraMsg) {
|
|
187
188
|
var _this = this;
|
|
188
189
|
if (ExtraMsg === void 0) { ExtraMsg = ""; }
|
|
@@ -202,6 +203,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
202
203
|
*/
|
|
203
204
|
});
|
|
204
205
|
};
|
|
206
|
+
/* Send a Msg (or Msg[]) to the server.*/
|
|
205
207
|
Client.prototype.SendMsgEx = function (Msgs) {
|
|
206
208
|
var _this = this;
|
|
207
209
|
if (this.State == States.STATE_OFFLINE)
|
|
@@ -260,9 +262,11 @@ var Client = /** @class */ (function (_super) {
|
|
|
260
262
|
return;
|
|
261
263
|
this.socket.send(packet, 0, packet.length, this.port, this.host);
|
|
262
264
|
};
|
|
265
|
+
/* Queue a chunk (It will get sent in the next packet). */
|
|
263
266
|
Client.prototype.QueueChunkEx = function (Msg) {
|
|
264
267
|
this.queueChunkEx.push(Msg);
|
|
265
268
|
};
|
|
269
|
+
/* Send a Raw Buffer (as chunk) to the server. */
|
|
266
270
|
Client.prototype.SendMsgRaw = function (chunks) {
|
|
267
271
|
if (this.State == States.STATE_OFFLINE)
|
|
268
272
|
return;
|
|
@@ -298,6 +302,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
298
302
|
});
|
|
299
303
|
return chunk;
|
|
300
304
|
};
|
|
305
|
+
/* Connect the client to the server. */
|
|
301
306
|
Client.prototype.connect = function () {
|
|
302
307
|
var _this = this;
|
|
303
308
|
var _a;
|
|
@@ -348,15 +353,15 @@ var Client = /** @class */ (function (_super) {
|
|
|
348
353
|
}, 5000);
|
|
349
354
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
350
355
|
if (this.socket)
|
|
351
|
-
this.socket.on("message", function (
|
|
356
|
+
this.socket.on("message", function (packet, rinfo) {
|
|
352
357
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
353
358
|
if (_this.State == 0 || rinfo.address != _this.host || rinfo.port != _this.port)
|
|
354
359
|
return;
|
|
355
360
|
clearInterval(connectInterval);
|
|
356
|
-
if (
|
|
357
|
-
if (
|
|
361
|
+
if (packet.toJSON().data[0] == 0x10) {
|
|
362
|
+
if (packet.toString().includes("TKEN") || packet.toJSON().data[3] == 0x2) {
|
|
358
363
|
clearInterval(connectInterval);
|
|
359
|
-
_this.TKEN = Buffer.from(
|
|
364
|
+
_this.TKEN = Buffer.from(packet.toJSON().data.slice(packet.toJSON().data.length - 4, packet.toJSON().data.length));
|
|
360
365
|
_this.SendControlMsg(3);
|
|
361
366
|
_this.State = States.STATE_LOADING; // loading state
|
|
362
367
|
_this.receivedSnaps = 0;
|
|
@@ -378,42 +383,42 @@ var Client = /** @class */ (function (_super) {
|
|
|
378
383
|
}
|
|
379
384
|
_this.SendMsgEx([client_version, info]);
|
|
380
385
|
}
|
|
381
|
-
else if (
|
|
386
|
+
else if (packet.toJSON().data[3] == 0x4) {
|
|
382
387
|
// disconnected
|
|
383
388
|
_this.State = States.STATE_OFFLINE;
|
|
384
|
-
var reason = (MsgUnpacker_1.unpackString(
|
|
389
|
+
var reason = (MsgUnpacker_1.unpackString(packet.toJSON().data.slice(4)).result);
|
|
385
390
|
_this.emit("disconnect", reason);
|
|
386
391
|
}
|
|
387
|
-
if (
|
|
392
|
+
if (packet.toJSON().data[3] !== 0x0) { // keepalive
|
|
388
393
|
_this.lastRecvTime = new Date().getTime();
|
|
389
394
|
}
|
|
390
395
|
}
|
|
391
396
|
else {
|
|
392
397
|
_this.lastRecvTime = new Date().getTime();
|
|
393
398
|
}
|
|
394
|
-
var unpacked = _this.Unpack(
|
|
395
|
-
unpacked.chunks = unpacked.chunks.filter(function (
|
|
396
|
-
unpacked.chunks.forEach(function (
|
|
397
|
-
if (
|
|
398
|
-
if (
|
|
399
|
-
_this.ack =
|
|
399
|
+
var unpacked = _this.Unpack(packet);
|
|
400
|
+
unpacked.chunks = unpacked.chunks.filter(function (chunk) { return ((chunk.flags & 2) && (chunk.flags & 1)) ? chunk.seq > _this.ack : true; }); // filter out already received chunks
|
|
401
|
+
unpacked.chunks.forEach(function (chunk) {
|
|
402
|
+
if (chunk.flags & 1 && (chunk.flags !== 15)) { // vital and not connless
|
|
403
|
+
if (chunk.seq === (_this.ack + 1) % (1 << 10)) { // https://github.com/nobody-mb/twchatonly/blob/master/chatonly.cpp#L237
|
|
404
|
+
_this.ack = chunk.seq;
|
|
400
405
|
_this.requestResend = false;
|
|
401
406
|
}
|
|
402
407
|
else { //IsSeqInBackroom (old packet that we already got)
|
|
403
408
|
var Bottom = (_this.ack - (1 << 10) / 2);
|
|
404
409
|
if (Bottom < 0) {
|
|
405
|
-
if ((
|
|
410
|
+
if ((chunk.seq <= _this.ack) || (chunk.seq >= (Bottom + (1 << 10))))
|
|
406
411
|
return;
|
|
407
412
|
}
|
|
408
413
|
else {
|
|
409
|
-
if (
|
|
414
|
+
if (chunk.seq <= _this.ack && chunk.seq >= Bottom)
|
|
410
415
|
return;
|
|
411
416
|
}
|
|
412
417
|
_this.requestResend = true;
|
|
413
418
|
}
|
|
414
419
|
}
|
|
415
420
|
});
|
|
416
|
-
unpacked.chunks.filter(function (
|
|
421
|
+
unpacked.chunks.filter(function (chunk) { return chunk.msgid == NETMSGTYPE.SV_BROADCAST && chunk.type == 'game'; }).forEach(function (a) {
|
|
417
422
|
var unpacker = new MsgUnpacker_1.MsgUnpacker(a.raw.toJSON().data);
|
|
418
423
|
_this.emit("broadcast", unpacker.unpackString());
|
|
419
424
|
});
|
|
@@ -426,10 +431,8 @@ var Client = /** @class */ (function (_super) {
|
|
|
426
431
|
});
|
|
427
432
|
var snapChunks = [];
|
|
428
433
|
if (((_g = _this.options) === null || _g === void 0 ? void 0 : _g.lightweight) !== true)
|
|
429
|
-
snapChunks = unpacked.chunks.filter(function (
|
|
434
|
+
snapChunks = unpacked.chunks.filter(function (chunk) { return chunk.msg === "SNAP" || chunk.msg === "SNAP_SINGLE" || chunk.msg === "SNAP_EMPTY"; });
|
|
430
435
|
if (snapChunks.length > 0) {
|
|
431
|
-
var part = 0;
|
|
432
|
-
var num_parts = 1;
|
|
433
436
|
if (Math.abs(_this.PredGameTick - _this.AckGameTick) > 10)
|
|
434
437
|
_this.PredGameTick = _this.AckGameTick + 1;
|
|
435
438
|
snapChunks.forEach(function (chunk) {
|
|
@@ -474,6 +477,9 @@ var Client = /** @class */ (function (_super) {
|
|
|
474
477
|
});
|
|
475
478
|
}
|
|
476
479
|
var chunkMessages = unpacked.chunks.map(function (a) { return a.msg; });
|
|
480
|
+
if (unpacked.chunks.findIndex(function (chunk) { return chunk.msgid == 23 && chunk.type == "sys"; }) !== -1) {
|
|
481
|
+
_this.game._ping_resolve(new Date().getTime());
|
|
482
|
+
}
|
|
477
483
|
if (chunkMessages.includes("SV_CHAT")) {
|
|
478
484
|
var chat = unpacked.chunks.filter(function (a) { return a.msg == "SV_CHAT"; });
|
|
479
485
|
chat.forEach(function (a) {
|
|
@@ -494,7 +500,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
494
500
|
}
|
|
495
501
|
});
|
|
496
502
|
}
|
|
497
|
-
var chat = unpacked.chunks.filter(function (
|
|
503
|
+
var chat = unpacked.chunks.filter(function (chunk) { return chunk.msg == "SV_KILL_MSG" || chunk.msg == "SV_MOTD"; });
|
|
498
504
|
chat.forEach(function (a) {
|
|
499
505
|
if (a.msg == "SV_KILL_MSG") {
|
|
500
506
|
var unpacked = {};
|
|
@@ -567,6 +573,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
567
573
|
}
|
|
568
574
|
});
|
|
569
575
|
};
|
|
576
|
+
/* Sending the input. (automatically done unless options.lightweight is on) */
|
|
570
577
|
Client.prototype.sendInput = function (input) {
|
|
571
578
|
if (input === void 0) { input = this.movement.input; }
|
|
572
579
|
if (this.State != States.STATE_ONLINE)
|
|
@@ -599,6 +606,7 @@ var Client = /** @class */ (function (_super) {
|
|
|
599
606
|
enumerable: false,
|
|
600
607
|
configurable: true
|
|
601
608
|
});
|
|
609
|
+
/* Disconnect the client. */
|
|
602
610
|
Client.prototype.Disconnect = function () {
|
|
603
611
|
var _this = this;
|
|
604
612
|
return new Promise(function (resolve) {
|
|
@@ -611,71 +619,11 @@ var Client = /** @class */ (function (_super) {
|
|
|
611
619
|
});
|
|
612
620
|
});
|
|
613
621
|
};
|
|
614
|
-
|
|
615
|
-
var _a;
|
|
616
|
-
if (team === void 0) { team = false; }
|
|
617
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_SAY, false, 1);
|
|
618
|
-
packer.AddInt(team ? 1 : 0); // team
|
|
619
|
-
packer.AddString(message);
|
|
620
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
621
|
-
this.QueueChunkEx(packer);
|
|
622
|
-
else
|
|
623
|
-
this.SendMsgEx(packer);
|
|
624
|
-
};
|
|
625
|
-
Client.prototype.Vote = function (vote) {
|
|
626
|
-
var _a;
|
|
627
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_VOTE, false, 1);
|
|
628
|
-
packer.AddInt(vote ? 1 : -1);
|
|
629
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
630
|
-
this.QueueChunkEx(packer);
|
|
631
|
-
else
|
|
632
|
-
this.SendMsgEx(packer);
|
|
633
|
-
};
|
|
634
|
-
Client.prototype.ChangePlayerInfo = function (playerInfo) {
|
|
635
|
-
var _a;
|
|
636
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_CHANGEINFO, false, 1);
|
|
637
|
-
packer.AddString(playerInfo.name); //m_pName);
|
|
638
|
-
packer.AddString(playerInfo.clan); //m_pClan);
|
|
639
|
-
packer.AddInt(playerInfo.country); //m_Country);
|
|
640
|
-
packer.AddString(playerInfo.skin); //m_pSkin);
|
|
641
|
-
packer.AddInt(playerInfo.use_custom_color ? 1 : 0); //m_UseCustomColor);
|
|
642
|
-
packer.AddInt(playerInfo.color_body); //m_ColorBody);
|
|
643
|
-
packer.AddInt(playerInfo.color_feet); //m_ColorFeet);
|
|
644
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
645
|
-
this.QueueChunkEx(packer);
|
|
646
|
-
else
|
|
647
|
-
this.SendMsgEx(packer);
|
|
648
|
-
};
|
|
649
|
-
Client.prototype.Kill = function () {
|
|
650
|
-
var _a;
|
|
651
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_KILL, false, 1);
|
|
652
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
653
|
-
this.QueueChunkEx(packer);
|
|
654
|
-
else
|
|
655
|
-
this.SendMsgEx(packer);
|
|
656
|
-
};
|
|
657
|
-
Client.prototype.ChangeTeam = function (team) {
|
|
658
|
-
var _a;
|
|
659
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_SETTEAM, false, 1);
|
|
660
|
-
packer.AddInt(team);
|
|
661
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
662
|
-
this.QueueChunkEx(packer);
|
|
663
|
-
else
|
|
664
|
-
this.SendMsgEx(packer);
|
|
665
|
-
};
|
|
666
|
-
Client.prototype.Emote = function (emote) {
|
|
667
|
-
var _a;
|
|
668
|
-
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_EMOTICON, false, 1);
|
|
669
|
-
packer.AddInt(emote);
|
|
670
|
-
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
671
|
-
this.QueueChunkEx(packer);
|
|
672
|
-
else
|
|
673
|
-
this.SendMsgEx(packer);
|
|
674
|
-
};
|
|
622
|
+
/* Get the client_info from a specific player id. */
|
|
675
623
|
Client.prototype.client_info = function (id) {
|
|
676
|
-
var delta = this.SnapUnpacker.deltas.filter(function (
|
|
677
|
-
return
|
|
678
|
-
&&
|
|
624
|
+
var delta = this.SnapUnpacker.deltas.filter(function (_delta) {
|
|
625
|
+
return _delta.type_id == 11
|
|
626
|
+
&& _delta.id == id;
|
|
679
627
|
});
|
|
680
628
|
if (delta.length == 0)
|
|
681
629
|
return undefined;
|
|
@@ -684,28 +632,31 @@ var Client = /** @class */ (function (_super) {
|
|
|
684
632
|
// .map(a => a.parsed as ClientInfo);
|
|
685
633
|
};
|
|
686
634
|
Object.defineProperty(Client.prototype, "client_infos", {
|
|
635
|
+
/* Get all client infos. */
|
|
687
636
|
get: function () {
|
|
688
|
-
return this.SnapUnpacker.deltas.filter(function (
|
|
637
|
+
return this.SnapUnpacker.deltas.filter(function (_delta) { return _delta.type_id == 11; })
|
|
689
638
|
.sort(function (a, b) { return a.id - b.id; })
|
|
690
639
|
.map(function (a) { return a.parsed; });
|
|
691
640
|
},
|
|
692
641
|
enumerable: false,
|
|
693
642
|
configurable: true
|
|
694
643
|
});
|
|
644
|
+
/* Get the player info from a specific player id. */
|
|
695
645
|
Client.prototype.player_info = function (id) {
|
|
696
|
-
var delta = this.SnapUnpacker.deltas.filter(function (
|
|
697
|
-
return
|
|
698
|
-
&&
|
|
646
|
+
var delta = this.SnapUnpacker.deltas.filter(function (_delta) {
|
|
647
|
+
return _delta.type_id == 10
|
|
648
|
+
&& _delta.id == id;
|
|
699
649
|
});
|
|
700
650
|
if (delta.length == 0)
|
|
701
651
|
return undefined;
|
|
702
652
|
return delta[0].parsed;
|
|
703
653
|
};
|
|
704
654
|
Object.defineProperty(Client.prototype, "player_infos", {
|
|
655
|
+
/* Get all player infos. */
|
|
705
656
|
get: function () {
|
|
706
|
-
return this.SnapUnpacker.deltas.filter(function (
|
|
657
|
+
return this.SnapUnpacker.deltas.filter(function (_delta) { return _delta.type_id == 10; })
|
|
707
658
|
.sort(function (a, b) { return a.id - b.id; })
|
|
708
|
-
.map(function (
|
|
659
|
+
.map(function (player) { return player.parsed; });
|
|
709
660
|
},
|
|
710
661
|
enumerable: false,
|
|
711
662
|
configurable: true
|
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
|
|
|
@@ -146,41 +147,7 @@ declare interface iOptions {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
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
150
|
|
|
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
151
|
|
|
185
152
|
on(event: 'connected', listener: () => void): this;
|
|
186
153
|
on(event: 'disconnect', listener: (reason: string) => void): this;
|
|
@@ -190,15 +157,49 @@ export declare interface Client {
|
|
|
190
157
|
on(event: 'kill', listener: (kill: iKillMsg) => void): this;
|
|
191
158
|
on(event: 'motd', listener: (message: string) => void): this;
|
|
192
159
|
|
|
193
|
-
requestResend: boolean;
|
|
194
160
|
}
|
|
195
161
|
|
|
162
|
+
export class Client extends EventEmitter {
|
|
196
163
|
|
|
164
|
+
private host: string;
|
|
165
|
+
private port: number;
|
|
166
|
+
private name: string;
|
|
167
|
+
private State: number; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
168
|
+
private ack: number;
|
|
169
|
+
private clientAck: number;
|
|
170
|
+
private receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
|
|
171
|
+
private socket: net.Socket | undefined;
|
|
172
|
+
private TKEN: Buffer;
|
|
173
|
+
private time: number;
|
|
174
|
+
private SnapUnpacker: Snapshot;
|
|
175
|
+
|
|
176
|
+
private PredGameTick: number;
|
|
177
|
+
private AckGameTick: number;
|
|
178
|
+
|
|
179
|
+
private SnapshotParts: number;
|
|
180
|
+
private currentSnapshotGameTick: number;
|
|
197
181
|
|
|
198
|
-
|
|
182
|
+
|
|
183
|
+
private snaps: Buffer[];
|
|
184
|
+
|
|
185
|
+
private sentChunkQueue: Buffer[];
|
|
186
|
+
private queueChunkEx: MsgPacker[];
|
|
187
|
+
private lastSendTime: number;
|
|
188
|
+
private lastRecvTime: number;
|
|
189
|
+
|
|
190
|
+
private lastSentMessages: {msg: MsgPacker, ack: number}[];
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
public movement: Movement;
|
|
194
|
+
public game: Game;
|
|
195
|
+
|
|
196
|
+
// eSnapHolder: eSnap[];
|
|
199
197
|
|
|
200
198
|
|
|
199
|
+
public readonly options?: iOptions;
|
|
200
|
+
private requestResend: boolean;
|
|
201
201
|
|
|
202
|
+
|
|
202
203
|
constructor(ip: string, port: number, nickname: string, options?: iOptions) {
|
|
203
204
|
super();
|
|
204
205
|
this.host = ip;
|
|
@@ -217,10 +218,7 @@ export class Client extends EventEmitter {
|
|
|
217
218
|
if (options)
|
|
218
219
|
this.options = options;
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.movement = new Movement();
|
|
223
|
-
|
|
221
|
+
|
|
224
222
|
this.snaps = [];
|
|
225
223
|
|
|
226
224
|
this.sentChunkQueue = [];
|
|
@@ -230,8 +228,7 @@ export class Client extends EventEmitter {
|
|
|
230
228
|
this.ack = 0; // ack of messages the client has received
|
|
231
229
|
this.clientAck = 0; // ack of messages the client has sent
|
|
232
230
|
this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
233
|
-
this.
|
|
234
|
-
this.socket = net.createSocket("udp4")
|
|
231
|
+
this.socket = net.createSocket("udp4");
|
|
235
232
|
this.socket.bind();
|
|
236
233
|
|
|
237
234
|
this.TKEN = Buffer.from([255, 255, 255, 255])
|
|
@@ -240,10 +237,14 @@ export class Client extends EventEmitter {
|
|
|
240
237
|
this.lastRecvTime = new Date().getTime();
|
|
241
238
|
|
|
242
239
|
this.lastSentMessages = [];
|
|
240
|
+
this.movement = new Movement();
|
|
241
|
+
|
|
242
|
+
this.game = new Game(this);
|
|
243
|
+
|
|
243
244
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
ResendAfter(lastAck: number) {
|
|
247
|
+
private ResendAfter(lastAck: number) {
|
|
247
248
|
this.clientAck = lastAck;
|
|
248
249
|
|
|
249
250
|
|
|
@@ -256,7 +257,7 @@ export class Client extends EventEmitter {
|
|
|
256
257
|
this.SendMsgEx(toResend);
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
Unpack(packet: Buffer): _packet {
|
|
260
|
+
private Unpack(packet: Buffer): _packet {
|
|
260
261
|
var unpacked: _packet = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0]&0xf)<<8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
|
|
261
262
|
|
|
262
263
|
|
|
@@ -301,6 +302,8 @@ export class Client extends EventEmitter {
|
|
|
301
302
|
}
|
|
302
303
|
return unpacked
|
|
303
304
|
}
|
|
305
|
+
|
|
306
|
+
/* Send a Control Msg to the server. (used for disconnect)*/
|
|
304
307
|
SendControlMsg(msg: number, ExtraMsg: string = "") {
|
|
305
308
|
this.lastSendTime = new Date().getTime();
|
|
306
309
|
return new Promise((resolve, reject) => {
|
|
@@ -320,6 +323,7 @@ export class Client extends EventEmitter {
|
|
|
320
323
|
})
|
|
321
324
|
}
|
|
322
325
|
|
|
326
|
+
/* Send a Msg (or Msg[]) to the server.*/
|
|
323
327
|
SendMsgEx(Msgs: MsgPacker[] | MsgPacker) {
|
|
324
328
|
if (this.State == States.STATE_OFFLINE)
|
|
325
329
|
return;
|
|
@@ -378,11 +382,11 @@ export class Client extends EventEmitter {
|
|
|
378
382
|
return;
|
|
379
383
|
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
380
384
|
}
|
|
381
|
-
|
|
385
|
+
/* Queue a chunk (It will get sent in the next packet). */
|
|
382
386
|
QueueChunkEx(Msg: MsgPacker) {
|
|
383
387
|
this.queueChunkEx.push(Msg);
|
|
384
388
|
}
|
|
385
|
-
|
|
389
|
+
/* Send a Raw Buffer (as chunk) to the server. */
|
|
386
390
|
SendMsgRaw(chunks: Buffer[]) {
|
|
387
391
|
if (this.State == States.STATE_OFFLINE)
|
|
388
392
|
return;
|
|
@@ -399,7 +403,7 @@ export class Client extends EventEmitter {
|
|
|
399
403
|
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
400
404
|
}
|
|
401
405
|
|
|
402
|
-
MsgToChunk(packet: Buffer) {
|
|
406
|
+
private MsgToChunk(packet: Buffer) {
|
|
403
407
|
var chunk: chunk = {} as chunk;
|
|
404
408
|
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
405
409
|
chunk.flags = (packet[0] >> 6) & 3;
|
|
@@ -423,6 +427,7 @@ export class Client extends EventEmitter {
|
|
|
423
427
|
return chunk;
|
|
424
428
|
}
|
|
425
429
|
|
|
430
|
+
/* Connect the client to the server. */
|
|
426
431
|
connect() {
|
|
427
432
|
this.State = States.STATE_CONNECTING;
|
|
428
433
|
|
|
@@ -475,14 +480,15 @@ export class Client extends EventEmitter {
|
|
|
475
480
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
476
481
|
|
|
477
482
|
if (this.socket)
|
|
478
|
-
this.socket.on("message", (
|
|
483
|
+
this.socket.on("message", (packet, rinfo) => {
|
|
479
484
|
if (this.State == 0 || rinfo.address != this.host || rinfo.port != this.port)
|
|
480
485
|
return;
|
|
481
486
|
clearInterval(connectInterval)
|
|
482
|
-
|
|
483
|
-
|
|
487
|
+
|
|
488
|
+
if (packet.toJSON().data[0] == 0x10) {
|
|
489
|
+
if (packet.toString().includes("TKEN") || packet.toJSON().data[3] == 0x2) {
|
|
484
490
|
clearInterval(connectInterval);
|
|
485
|
-
this.TKEN = Buffer.from(
|
|
491
|
+
this.TKEN = Buffer.from(packet.toJSON().data.slice(packet.toJSON().data.length - 4, packet.toJSON().data.length))
|
|
486
492
|
this.SendControlMsg(3);
|
|
487
493
|
this.State = States.STATE_LOADING; // loading state
|
|
488
494
|
this.receivedSnaps = 0;
|
|
@@ -507,28 +513,26 @@ export class Client extends EventEmitter {
|
|
|
507
513
|
}
|
|
508
514
|
|
|
509
515
|
this.SendMsgEx([client_version, info])
|
|
510
|
-
} else if (
|
|
516
|
+
} else if (packet.toJSON().data[3] == 0x4) {
|
|
511
517
|
// disconnected
|
|
512
518
|
this.State = States.STATE_OFFLINE;
|
|
513
|
-
let reason: string = (unpackString(
|
|
519
|
+
let reason: string = (unpackString(packet.toJSON().data.slice(4)).result);
|
|
514
520
|
this.emit("disconnect", reason);
|
|
515
521
|
}
|
|
516
|
-
if (
|
|
522
|
+
if (packet.toJSON().data[3] !== 0x0) { // keepalive
|
|
517
523
|
this.lastRecvTime = new Date().getTime();
|
|
518
524
|
}
|
|
519
525
|
} else {
|
|
520
526
|
this.lastRecvTime = new Date().getTime();
|
|
521
|
-
|
|
522
527
|
}
|
|
523
|
-
|
|
524
528
|
|
|
525
|
-
var unpacked: _packet = this.Unpack(
|
|
526
|
-
unpacked.chunks = unpacked.chunks.filter(
|
|
527
|
-
|
|
528
|
-
unpacked.chunks.forEach(
|
|
529
|
-
if (
|
|
530
|
-
if (
|
|
531
|
-
this.ack =
|
|
529
|
+
var unpacked: _packet = this.Unpack(packet);
|
|
530
|
+
unpacked.chunks = unpacked.chunks.filter(chunk => ((chunk.flags & 2) && (chunk.flags & 1)) ? chunk.seq! > this.ack : true); // filter out already received chunks
|
|
531
|
+
|
|
532
|
+
unpacked.chunks.forEach(chunk => {
|
|
533
|
+
if (chunk.flags & 1 && (chunk.flags !== 15)) { // vital and not connless
|
|
534
|
+
if (chunk.seq === (this.ack+1)%(1<<10)) { // https://github.com/nobody-mb/twchatonly/blob/master/chatonly.cpp#L237
|
|
535
|
+
this.ack = chunk.seq!;
|
|
532
536
|
|
|
533
537
|
this.requestResend = false;
|
|
534
538
|
}
|
|
@@ -536,10 +540,10 @@ export class Client extends EventEmitter {
|
|
|
536
540
|
let Bottom = (this.ack - (1<<10)/2);
|
|
537
541
|
|
|
538
542
|
if(Bottom < 0) {
|
|
539
|
-
if((
|
|
543
|
+
if((chunk.seq! <= this.ack) || (chunk.seq! >= (Bottom + (1<<10))))
|
|
540
544
|
return;
|
|
541
545
|
} else {
|
|
542
|
-
if(
|
|
546
|
+
if(chunk.seq! <= this.ack && chunk.seq! >= Bottom)
|
|
543
547
|
return;
|
|
544
548
|
}
|
|
545
549
|
this.requestResend = true;
|
|
@@ -548,7 +552,7 @@ export class Client extends EventEmitter {
|
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
})
|
|
551
|
-
unpacked.chunks.filter(
|
|
555
|
+
unpacked.chunks.filter(chunk => chunk.msgid == NETMSGTYPE.SV_BROADCAST && chunk.type == 'game').forEach(a => {
|
|
552
556
|
let unpacker = new MsgUnpacker(a.raw.toJSON().data);
|
|
553
557
|
|
|
554
558
|
this.emit("broadcast", unpacker.unpackString());
|
|
@@ -562,10 +566,8 @@ export class Client extends EventEmitter {
|
|
|
562
566
|
})
|
|
563
567
|
let snapChunks: chunk[] = [];
|
|
564
568
|
if (this.options?.lightweight !== true)
|
|
565
|
-
snapChunks = unpacked.chunks.filter(
|
|
569
|
+
snapChunks = unpacked.chunks.filter(chunk => chunk.msg === "SNAP" || chunk.msg === "SNAP_SINGLE" || chunk.msg === "SNAP_EMPTY");
|
|
566
570
|
if (snapChunks.length > 0) {
|
|
567
|
-
let part = 0;
|
|
568
|
-
let num_parts = 1;
|
|
569
571
|
if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
|
|
570
572
|
this.PredGameTick = this.AckGameTick + 1;
|
|
571
573
|
|
|
@@ -625,7 +627,11 @@ export class Client extends EventEmitter {
|
|
|
625
627
|
|
|
626
628
|
})
|
|
627
629
|
}
|
|
630
|
+
|
|
628
631
|
var chunkMessages = unpacked.chunks.map(a => a.msg)
|
|
632
|
+
if (unpacked.chunks.findIndex(chunk => chunk.msgid == 23 && chunk.type == "sys") !== -1) {
|
|
633
|
+
this.game._ping_resolve(new Date().getTime())
|
|
634
|
+
}
|
|
629
635
|
if (chunkMessages.includes("SV_CHAT")) {
|
|
630
636
|
var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
|
|
631
637
|
chat.forEach(a => {
|
|
@@ -647,7 +653,7 @@ export class Client extends EventEmitter {
|
|
|
647
653
|
}
|
|
648
654
|
})
|
|
649
655
|
}
|
|
650
|
-
var chat = unpacked.chunks.filter(
|
|
656
|
+
var chat = unpacked.chunks.filter(chunk => chunk.msg == "SV_KILL_MSG" || chunk.msg == "SV_MOTD");
|
|
651
657
|
chat.forEach(a => {
|
|
652
658
|
if (a.msg == "SV_KILL_MSG") {
|
|
653
659
|
var unpacked: iKillMsg = {} as iKillMsg;
|
|
@@ -723,6 +729,7 @@ export class Client extends EventEmitter {
|
|
|
723
729
|
})
|
|
724
730
|
}
|
|
725
731
|
|
|
732
|
+
/* Sending the input. (automatically done unless options.lightweight is on) */
|
|
726
733
|
sendInput(input = this.movement.input) {
|
|
727
734
|
if (this.State != States.STATE_ONLINE)
|
|
728
735
|
return;
|
|
@@ -754,6 +761,7 @@ export class Client extends EventEmitter {
|
|
|
754
761
|
return this.movement.input;
|
|
755
762
|
}
|
|
756
763
|
|
|
764
|
+
/* Disconnect the client. */
|
|
757
765
|
Disconnect() {
|
|
758
766
|
return new Promise((resolve) => {
|
|
759
767
|
this.SendControlMsg(4).then(() => {
|
|
@@ -766,64 +774,11 @@ export class Client extends EventEmitter {
|
|
|
766
774
|
})
|
|
767
775
|
}
|
|
768
776
|
|
|
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
|
-
}
|
|
777
|
+
/* Get the client_info from a specific player id. */
|
|
823
778
|
client_info(id: number) {
|
|
824
|
-
let delta = this.SnapUnpacker.deltas.filter(
|
|
825
|
-
|
|
826
|
-
&&
|
|
779
|
+
let delta = this.SnapUnpacker.deltas.filter(_delta =>
|
|
780
|
+
_delta.type_id == 11
|
|
781
|
+
&& _delta.id == id
|
|
827
782
|
);
|
|
828
783
|
|
|
829
784
|
if (delta.length == 0)
|
|
@@ -832,25 +787,29 @@ export class Client extends EventEmitter {
|
|
|
832
787
|
// .sort((a, b) => a.id - b.id)
|
|
833
788
|
// .map(a => a.parsed as ClientInfo);
|
|
834
789
|
}
|
|
790
|
+
|
|
791
|
+
/* Get all client infos. */
|
|
835
792
|
get client_infos(): ClientInfo[] {
|
|
836
793
|
|
|
837
|
-
return this.SnapUnpacker.deltas.filter(
|
|
838
|
-
|
|
839
|
-
|
|
794
|
+
return this.SnapUnpacker.deltas.filter(_delta => _delta.type_id == 11)
|
|
795
|
+
.sort((a, b) => a.id - b.id)
|
|
796
|
+
.map(a => a.parsed as ClientInfo);
|
|
840
797
|
}
|
|
798
|
+
/* Get the player info from a specific player id. */
|
|
841
799
|
player_info(id: number) {
|
|
842
|
-
let delta = this.SnapUnpacker.deltas.filter(
|
|
843
|
-
|
|
844
|
-
&&
|
|
800
|
+
let delta = this.SnapUnpacker.deltas.filter(_delta =>
|
|
801
|
+
_delta.type_id == 10
|
|
802
|
+
&& _delta.id == id
|
|
845
803
|
);
|
|
846
804
|
|
|
847
805
|
if (delta.length == 0)
|
|
848
806
|
return undefined;
|
|
849
807
|
return delta[0].parsed as PlayerInfo;
|
|
850
808
|
}
|
|
809
|
+
/* Get all player infos. */
|
|
851
810
|
get player_infos(): PlayerInfo[] {
|
|
852
|
-
return this.SnapUnpacker.deltas.filter(
|
|
811
|
+
return this.SnapUnpacker.deltas.filter(_delta => _delta.type_id == 10)
|
|
853
812
|
.sort((a, b) => a.id - b.id)
|
|
854
|
-
.map(
|
|
813
|
+
.map(player => player.parsed as PlayerInfo);
|
|
855
814
|
}
|
|
856
815
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Game = void 0;
|
|
4
|
+
var MsgPacker_1 = require("../MsgPacker");
|
|
5
|
+
var NETMSGTYPE;
|
|
6
|
+
(function (NETMSGTYPE) {
|
|
7
|
+
NETMSGTYPE[NETMSGTYPE["EX"] = 0] = "EX";
|
|
8
|
+
NETMSGTYPE[NETMSGTYPE["SV_MOTD"] = 1] = "SV_MOTD";
|
|
9
|
+
NETMSGTYPE[NETMSGTYPE["SV_BROADCAST"] = 2] = "SV_BROADCAST";
|
|
10
|
+
NETMSGTYPE[NETMSGTYPE["SV_CHAT"] = 3] = "SV_CHAT";
|
|
11
|
+
NETMSGTYPE[NETMSGTYPE["SV_KILLMSG"] = 4] = "SV_KILLMSG";
|
|
12
|
+
NETMSGTYPE[NETMSGTYPE["SV_SOUNDGLOBAL"] = 5] = "SV_SOUNDGLOBAL";
|
|
13
|
+
NETMSGTYPE[NETMSGTYPE["SV_TUNEPARAMS"] = 6] = "SV_TUNEPARAMS";
|
|
14
|
+
NETMSGTYPE[NETMSGTYPE["SV_EXTRAPROJECTILE"] = 7] = "SV_EXTRAPROJECTILE";
|
|
15
|
+
NETMSGTYPE[NETMSGTYPE["SV_READYTOENTER"] = 8] = "SV_READYTOENTER";
|
|
16
|
+
NETMSGTYPE[NETMSGTYPE["SV_WEAPONPICKUP"] = 9] = "SV_WEAPONPICKUP";
|
|
17
|
+
NETMSGTYPE[NETMSGTYPE["SV_EMOTICON"] = 10] = "SV_EMOTICON";
|
|
18
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTECLEAROPTIONS"] = 11] = "SV_VOTECLEAROPTIONS";
|
|
19
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTEOPTIONLISTADD"] = 12] = "SV_VOTEOPTIONLISTADD";
|
|
20
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTEOPTIONADD"] = 13] = "SV_VOTEOPTIONADD";
|
|
21
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTEOPTIONREMOVE"] = 14] = "SV_VOTEOPTIONREMOVE";
|
|
22
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTESET"] = 15] = "SV_VOTESET";
|
|
23
|
+
NETMSGTYPE[NETMSGTYPE["SV_VOTESTATUS"] = 16] = "SV_VOTESTATUS";
|
|
24
|
+
NETMSGTYPE[NETMSGTYPE["CL_SAY"] = 17] = "CL_SAY";
|
|
25
|
+
NETMSGTYPE[NETMSGTYPE["CL_SETTEAM"] = 18] = "CL_SETTEAM";
|
|
26
|
+
NETMSGTYPE[NETMSGTYPE["CL_SETSPECTATORMODE"] = 19] = "CL_SETSPECTATORMODE";
|
|
27
|
+
NETMSGTYPE[NETMSGTYPE["CL_STARTINFO"] = 20] = "CL_STARTINFO";
|
|
28
|
+
NETMSGTYPE[NETMSGTYPE["CL_CHANGEINFO"] = 21] = "CL_CHANGEINFO";
|
|
29
|
+
NETMSGTYPE[NETMSGTYPE["CL_KILL"] = 22] = "CL_KILL";
|
|
30
|
+
NETMSGTYPE[NETMSGTYPE["CL_EMOTICON"] = 23] = "CL_EMOTICON";
|
|
31
|
+
NETMSGTYPE[NETMSGTYPE["CL_VOTE"] = 24] = "CL_VOTE";
|
|
32
|
+
NETMSGTYPE[NETMSGTYPE["CL_CALLVOTE"] = 25] = "CL_CALLVOTE";
|
|
33
|
+
NETMSGTYPE[NETMSGTYPE["CL_ISDDNETLEGACY"] = 26] = "CL_ISDDNETLEGACY";
|
|
34
|
+
NETMSGTYPE[NETMSGTYPE["SV_DDRACETIMELEGACY"] = 27] = "SV_DDRACETIMELEGACY";
|
|
35
|
+
NETMSGTYPE[NETMSGTYPE["SV_RECORDLEGACY"] = 28] = "SV_RECORDLEGACY";
|
|
36
|
+
NETMSGTYPE[NETMSGTYPE["UNUSED"] = 29] = "UNUSED";
|
|
37
|
+
NETMSGTYPE[NETMSGTYPE["SV_TEAMSSTATELEGACY"] = 30] = "SV_TEAMSSTATELEGACY";
|
|
38
|
+
NETMSGTYPE[NETMSGTYPE["CL_SHOWOTHERSLEGACY"] = 31] = "CL_SHOWOTHERSLEGACY";
|
|
39
|
+
NETMSGTYPE[NETMSGTYPE["NUM"] = 32] = "NUM";
|
|
40
|
+
})(NETMSGTYPE || (NETMSGTYPE = {}));
|
|
41
|
+
;
|
|
42
|
+
var Game = /** @class */ (function () {
|
|
43
|
+
function Game(_client) {
|
|
44
|
+
// this.SendMsgEx = callback;
|
|
45
|
+
this._client = _client;
|
|
46
|
+
this._ping_resolve = function () { };
|
|
47
|
+
}
|
|
48
|
+
Game.prototype.send = function (packer) {
|
|
49
|
+
var _a;
|
|
50
|
+
if (!((_a = this._client.options) === null || _a === void 0 ? void 0 : _a.lightweight))
|
|
51
|
+
this._client.QueueChunkEx(packer);
|
|
52
|
+
else
|
|
53
|
+
this._client.SendMsgEx(packer);
|
|
54
|
+
};
|
|
55
|
+
Game.prototype.Say = function (message, team) {
|
|
56
|
+
if (team === void 0) { team = false; }
|
|
57
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_SAY, false, 1);
|
|
58
|
+
packer.AddInt(team ? 1 : 0); // team
|
|
59
|
+
packer.AddString(message);
|
|
60
|
+
this.send(packer);
|
|
61
|
+
};
|
|
62
|
+
/* Set the team of an bot. (-1 spectator team, 0 team red/normal team, 1 team blue) */
|
|
63
|
+
Game.prototype.SetTeam = function (team) {
|
|
64
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_SETTEAM, false, 1);
|
|
65
|
+
packer.AddInt(team);
|
|
66
|
+
this.send(packer);
|
|
67
|
+
};
|
|
68
|
+
/* Spectate an player, taking their id as parameter. pretty useless */
|
|
69
|
+
Game.prototype.SpectatorMode = function (SpectatorID) {
|
|
70
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_SETSPECTATORMODE, false, 1);
|
|
71
|
+
packer.AddInt(SpectatorID);
|
|
72
|
+
this.send(packer);
|
|
73
|
+
};
|
|
74
|
+
/* Change the player info */
|
|
75
|
+
Game.prototype.ChangePlayerInfo = function (playerInfo) {
|
|
76
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_CHANGEINFO, false, 1);
|
|
77
|
+
packer.AddString(playerInfo.name);
|
|
78
|
+
packer.AddString(playerInfo.clan);
|
|
79
|
+
packer.AddInt(playerInfo.country);
|
|
80
|
+
packer.AddString(playerInfo.skin);
|
|
81
|
+
packer.AddInt(playerInfo.use_custom_color ? 1 : 0);
|
|
82
|
+
packer.AddInt(playerInfo.color_body);
|
|
83
|
+
packer.AddInt(playerInfo.color_feet);
|
|
84
|
+
this.send(packer);
|
|
85
|
+
};
|
|
86
|
+
/* Kill */
|
|
87
|
+
Game.prototype.Kill = function () {
|
|
88
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_KILL, false, 1);
|
|
89
|
+
this.send(packer);
|
|
90
|
+
};
|
|
91
|
+
/* Send emote */
|
|
92
|
+
Game.prototype.Emote = function (emote) {
|
|
93
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_EMOTICON, false, 1);
|
|
94
|
+
packer.AddInt(emote);
|
|
95
|
+
this.send(packer);
|
|
96
|
+
};
|
|
97
|
+
/* Vote for an already running vote (f3 / f4) */
|
|
98
|
+
Game.prototype.Vote = function (vote) {
|
|
99
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_VOTE, false, 1);
|
|
100
|
+
packer.AddInt(vote ? 1 : -1);
|
|
101
|
+
this.send(packer);
|
|
102
|
+
};
|
|
103
|
+
Game.prototype.CallVote = function (Type, Value, Reason) {
|
|
104
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_CALLVOTE, false, 1);
|
|
105
|
+
packer.AddString(Type);
|
|
106
|
+
packer.AddString(String(Value));
|
|
107
|
+
packer.AddString(Reason);
|
|
108
|
+
this.send(packer);
|
|
109
|
+
};
|
|
110
|
+
/* Call a vote for an server option (for example ddnet maps) */
|
|
111
|
+
Game.prototype.CallVoteOption = function (Value, Reason) {
|
|
112
|
+
this.CallVote("option", Value, Reason);
|
|
113
|
+
};
|
|
114
|
+
/* Call a vote to kick a player. Requires the player id */
|
|
115
|
+
Game.prototype.CallVoteKick = function (PlayerID, Reason) {
|
|
116
|
+
this.CallVote("kick", PlayerID, Reason);
|
|
117
|
+
};
|
|
118
|
+
/* Call a vote to set a player in spectator mode. Requires the player id */
|
|
119
|
+
Game.prototype.CallVoteSpectate = function (PlayerID, Reason) {
|
|
120
|
+
this.CallVote("spectate", PlayerID, Reason);
|
|
121
|
+
};
|
|
122
|
+
/** probably some verification of using ddnet client.*/
|
|
123
|
+
Game.prototype.IsDDNetLegacy = function () {
|
|
124
|
+
var packer = new MsgPacker_1.MsgPacker(NETMSGTYPE.CL_ISDDNETLEGACY, false, 1);
|
|
125
|
+
this.send(packer);
|
|
126
|
+
};
|
|
127
|
+
/* returns the ping in ms */
|
|
128
|
+
Game.prototype.Ping = function () {
|
|
129
|
+
var _this = this;
|
|
130
|
+
return new Promise(function (resolve, reject) {
|
|
131
|
+
var packer = new MsgPacker_1.MsgPacker(22, true, 0);
|
|
132
|
+
var startTime = new Date().getTime();
|
|
133
|
+
_this.send(packer);
|
|
134
|
+
var callback = function (_time) {
|
|
135
|
+
resolve(_time - startTime);
|
|
136
|
+
_this._ping_resolve = function () { };
|
|
137
|
+
};
|
|
138
|
+
_this._ping_resolve = callback;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
return Game;
|
|
142
|
+
}());
|
|
143
|
+
exports.Game = Game;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
|
|
2
|
+
import { MsgPacker } from "../MsgPacker";
|
|
3
|
+
|
|
4
|
+
import { Client } from "../client";
|
|
5
|
+
enum NETMSGTYPE { EX, SV_MOTD, SV_BROADCAST, SV_CHAT, SV_KILLMSG, SV_SOUNDGLOBAL, SV_TUNEPARAMS, SV_EXTRAPROJECTILE, SV_READYTOENTER, SV_WEAPONPICKUP, SV_EMOTICON, SV_VOTECLEAROPTIONS, SV_VOTEOPTIONLISTADD, SV_VOTEOPTIONADD, SV_VOTEOPTIONREMOVE, SV_VOTESET, SV_VOTESTATUS, CL_SAY, CL_SETTEAM, CL_SETSPECTATORMODE, CL_STARTINFO, CL_CHANGEINFO, CL_KILL, CL_EMOTICON, CL_VOTE, CL_CALLVOTE, CL_ISDDNETLEGACY, SV_DDRACETIMELEGACY, SV_RECORDLEGACY, UNUSED, SV_TEAMSSTATELEGACY, CL_SHOWOTHERSLEGACY, NUM };
|
|
6
|
+
|
|
7
|
+
export class Game {
|
|
8
|
+
// SendMsgEx: (Msgs: MsgPacker[] | MsgPacker) => void;
|
|
9
|
+
private _client: Client;
|
|
10
|
+
_ping_resolve: (_time: number) => void;
|
|
11
|
+
constructor(_client: Client) {
|
|
12
|
+
// this.SendMsgEx = callback;
|
|
13
|
+
this._client = _client;
|
|
14
|
+
this._ping_resolve = () => {};
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
private send(packer: MsgPacker) {
|
|
18
|
+
if (!this._client.options?.lightweight)
|
|
19
|
+
this._client.QueueChunkEx(packer);
|
|
20
|
+
else
|
|
21
|
+
this._client.SendMsgEx(packer);
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Say(message: string, team = false) {
|
|
26
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_SAY, false, 1);
|
|
27
|
+
packer.AddInt(team ? 1 : 0); // team
|
|
28
|
+
packer.AddString(message);
|
|
29
|
+
this.send(packer);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Set the team of an bot. (-1 spectator team, 0 team red/normal team, 1 team blue) */
|
|
33
|
+
SetTeam(team: number) {
|
|
34
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_SETTEAM, false, 1);
|
|
35
|
+
packer.AddInt(team);
|
|
36
|
+
this.send(packer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Spectate an player, taking their id as parameter. pretty useless */
|
|
40
|
+
SpectatorMode(SpectatorID: number) {
|
|
41
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_SETSPECTATORMODE, false, 1);
|
|
42
|
+
packer.AddInt(SpectatorID);
|
|
43
|
+
this.send(packer);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
/* Change the player info */
|
|
48
|
+
ChangePlayerInfo(playerInfo: ClientInfo) {
|
|
49
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_CHANGEINFO, false, 1);
|
|
50
|
+
packer.AddString(playerInfo.name);
|
|
51
|
+
packer.AddString(playerInfo.clan);
|
|
52
|
+
packer.AddInt(playerInfo.country);
|
|
53
|
+
packer.AddString(playerInfo.skin);
|
|
54
|
+
packer.AddInt(playerInfo.use_custom_color ? 1 : 0);
|
|
55
|
+
packer.AddInt(playerInfo.color_body);
|
|
56
|
+
packer.AddInt(playerInfo.color_feet);
|
|
57
|
+
this.send(packer);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Kill */
|
|
61
|
+
Kill() {
|
|
62
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_KILL, false, 1);
|
|
63
|
+
this.send(packer);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Send emote */
|
|
67
|
+
Emote(emote: number) {
|
|
68
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_EMOTICON, false, 1);
|
|
69
|
+
packer.AddInt(emote);
|
|
70
|
+
this.send(packer);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Vote for an already running vote (f3 / f4) */
|
|
74
|
+
Vote(vote: boolean) {
|
|
75
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_VOTE, false, 1);
|
|
76
|
+
packer.AddInt(vote ? 1 : -1);
|
|
77
|
+
this.send(packer);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private CallVote(Type: "option" | "kick" | "spectate", Value: string|number, Reason: string) {
|
|
81
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_CALLVOTE, false, 1);
|
|
82
|
+
packer.AddString(Type);
|
|
83
|
+
packer.AddString(String(Value));
|
|
84
|
+
packer.AddString(Reason);
|
|
85
|
+
this.send(packer);
|
|
86
|
+
}
|
|
87
|
+
/* Call a vote for an server option (for example ddnet maps) */
|
|
88
|
+
CallVoteOption(Value: string, Reason: string) {
|
|
89
|
+
this.CallVote("option", Value, Reason)
|
|
90
|
+
}
|
|
91
|
+
/* Call a vote to kick a player. Requires the player id */
|
|
92
|
+
CallVoteKick(PlayerID: string|number, Reason: string) {
|
|
93
|
+
this.CallVote("kick", PlayerID, Reason)
|
|
94
|
+
}
|
|
95
|
+
/* Call a vote to set a player in spectator mode. Requires the player id */
|
|
96
|
+
CallVoteSpectate(PlayerID: string|number, Reason: string) {
|
|
97
|
+
this.CallVote("spectate", PlayerID, Reason)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/** probably some verification of using ddnet client.*/
|
|
102
|
+
IsDDNetLegacy() {
|
|
103
|
+
var packer = new MsgPacker(NETMSGTYPE.CL_ISDDNETLEGACY, false, 1);
|
|
104
|
+
this.send(packer);
|
|
105
|
+
}
|
|
106
|
+
/* returns the ping in ms */
|
|
107
|
+
Ping(): Promise<number> {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
var packer = new MsgPacker(22, true, 0);
|
|
110
|
+
let startTime = new Date().getTime();
|
|
111
|
+
this.send(packer);
|
|
112
|
+
|
|
113
|
+
let callback = (_time: number) => {
|
|
114
|
+
resolve(_time - startTime);
|
|
115
|
+
this._ping_resolve = () => {};
|
|
116
|
+
}
|
|
117
|
+
this._ping_resolve = callback;
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
}
|
package/lib/movement.js
CHANGED
|
@@ -19,7 +19,7 @@ var Movement = /** @class */ (function () {
|
|
|
19
19
|
this.input.m_Jump = state ? 1 : 0;
|
|
20
20
|
};
|
|
21
21
|
Movement.prototype.Fire = function () {
|
|
22
|
-
this.input.m_Fire
|
|
22
|
+
this.input.m_Fire++;
|
|
23
23
|
};
|
|
24
24
|
Movement.prototype.Hook = function (state) {
|
|
25
25
|
if (state === void 0) { state = true; }
|
package/lib/movement.ts
CHANGED
package/lib/snapshot.js
CHANGED
|
@@ -74,7 +74,7 @@ var Snapshot = /** @class */ (function () {
|
|
|
74
74
|
pStr = pStr.replace(/\0.*/g, ''); // Remove content from first null char to end.
|
|
75
75
|
return pStr;
|
|
76
76
|
};
|
|
77
|
-
Snapshot.prototype.parseItem = function (data, Type) {
|
|
77
|
+
Snapshot.prototype.parseItem = function (data, Type, id) {
|
|
78
78
|
var _item = {};
|
|
79
79
|
switch (Type) {
|
|
80
80
|
case items.OBJ_EX:
|
|
@@ -212,6 +212,7 @@ var Snapshot = /** @class */ (function () {
|
|
|
212
212
|
use_custom_color: Number(data.slice(14, 15)),
|
|
213
213
|
color_body: Number(data.slice(15, 16)),
|
|
214
214
|
color_feet: Number(data.slice(16, 17)),
|
|
215
|
+
id: id
|
|
215
216
|
};
|
|
216
217
|
break;
|
|
217
218
|
case items.OBJ_SPECTATOR_INFO:
|
|
@@ -381,7 +382,7 @@ var Snapshot = /** @class */ (function () {
|
|
|
381
382
|
data = out;
|
|
382
383
|
} // else no previous, use new data
|
|
383
384
|
}
|
|
384
|
-
var parsed = this_2.parseItem(data, type_id);
|
|
385
|
+
var parsed = this_2.parseItem(data, type_id, id);
|
|
385
386
|
this_2.eSnapHolder.push({ Snapshot: { Data: data, Key: key }, ack: recvTick });
|
|
386
387
|
items.items.push({ data: data, parsed: parsed, type_id: type_id, id: id, key: key });
|
|
387
388
|
};
|
|
@@ -396,11 +397,11 @@ var Snapshot = /** @class */ (function () {
|
|
|
396
397
|
if (deltatick > -1) {
|
|
397
398
|
var ____index = this_3.deltas.findIndex(function (delta) { return delta.key == newSnap.Snapshot.Key; });
|
|
398
399
|
if (____index > -1) {
|
|
399
|
-
this_3.deltas[____index] = { data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_3.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff)) };
|
|
400
|
+
this_3.deltas[____index] = { data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_3.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff), ((newSnap.Snapshot.Key) & 0xffff)) };
|
|
400
401
|
return "continue";
|
|
401
402
|
}
|
|
402
403
|
} // else
|
|
403
|
-
this_3.deltas.push({ data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_3.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff)) });
|
|
404
|
+
this_3.deltas.push({ data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this_3.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff), ((newSnap.Snapshot.Key) & 0xffff)) });
|
|
404
405
|
};
|
|
405
406
|
var this_3 = this;
|
|
406
407
|
for (var _i = 0, newSnaps_1 = newSnaps; _i < newSnaps_1.length; _i++) {
|
package/lib/snapshot.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class Snapshot {
|
|
|
77
77
|
pStr = pStr.replace(/\0.*/g, ''); // Remove content from first null char to end.
|
|
78
78
|
return pStr;
|
|
79
79
|
}
|
|
80
|
-
private parseItem(data: number[], Type: number): Item {
|
|
80
|
+
private parseItem(data: number[], Type: number, id: number): Item {
|
|
81
81
|
var _item = {} as Item;
|
|
82
82
|
switch (Type) {
|
|
83
83
|
case items.OBJ_EX:
|
|
@@ -216,6 +216,7 @@ export class Snapshot {
|
|
|
216
216
|
use_custom_color: Number(data.slice(14, 15)),
|
|
217
217
|
color_body: Number(data.slice(15, 16)),
|
|
218
218
|
color_feet: Number(data.slice(16, 17)),
|
|
219
|
+
id: id
|
|
219
220
|
} as ClientInfo
|
|
220
221
|
break;
|
|
221
222
|
case items.OBJ_SPECTATOR_INFO:
|
|
@@ -375,7 +376,7 @@ export class Snapshot {
|
|
|
375
376
|
} else
|
|
376
377
|
_size = unpacker.unpackInt();
|
|
377
378
|
|
|
378
|
-
let data = [];
|
|
379
|
+
let data: number[] = [];
|
|
379
380
|
for (let j = 0; j < _size; j++) {
|
|
380
381
|
if (unpacker.remaining.length > 0)
|
|
381
382
|
data.push(unpacker.unpackInt());
|
|
@@ -393,7 +394,7 @@ export class Snapshot {
|
|
|
393
394
|
} // else no previous, use new data
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
let parsed = this.parseItem(data, type_id)
|
|
397
|
+
let parsed = this.parseItem(data, type_id, id)
|
|
397
398
|
this.eSnapHolder.push({Snapshot: {Data: data, Key: key}, ack: recvTick});
|
|
398
399
|
|
|
399
400
|
items.items.push({data, parsed, type_id, id, key})
|
|
@@ -409,11 +410,11 @@ export class Snapshot {
|
|
|
409
410
|
let ____index = this.deltas.findIndex(delta => delta.key == newSnap.Snapshot.Key)
|
|
410
411
|
|
|
411
412
|
if (____index > -1) {
|
|
412
|
-
this.deltas[____index] = {data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff))};
|
|
413
|
+
this.deltas[____index] = {data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff), ((newSnap.Snapshot.Key) & 0xffff))};
|
|
413
414
|
continue;
|
|
414
415
|
}
|
|
415
416
|
} // else
|
|
416
|
-
this.deltas.push({data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff))});
|
|
417
|
+
this.deltas.push({data: newSnap.Snapshot.Data, key: newSnap.Snapshot.Key, id: newSnap.Snapshot.Key & 0xffff, type_id: ((newSnap.Snapshot.Key >> 16) & 0xffff), parsed: this.parseItem(newSnap.Snapshot.Data, ((newSnap.Snapshot.Key >> 16) & 0xffff), ((newSnap.Snapshot.Key) & 0xffff))});
|
|
417
418
|
}
|
|
418
419
|
|
|
419
420
|
|