teeworlds 2.5.5 → 2.5.6
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.ts → index.d.ts} +6 -6
- package/index.js +1 -0
- package/index.js.map +1 -0
- package/lib/MsgPacker.d.ts +11 -0
- package/lib/MsgPacker.js +1 -0
- package/lib/MsgPacker.js.map +1 -0
- package/lib/MsgUnpacker.d.ts +16 -0
- package/lib/MsgUnpacker.js +1 -0
- package/lib/MsgUnpacker.js.map +1 -0
- package/lib/UUIDManager.d.ts +27 -0
- package/lib/UUIDManager.js +1 -0
- package/lib/UUIDManager.js.map +1 -0
- package/lib/client.d.ts +135 -0
- package/lib/client.js +5 -4
- package/lib/client.js.map +1 -0
- package/lib/components/game.d.ts +32 -0
- package/lib/components/game.js +1 -0
- package/lib/components/game.js.map +1 -0
- package/lib/components/movement.d.ts +34 -0
- package/lib/components/movement.js +1 -0
- package/lib/components/movement.js.map +1 -0
- package/lib/components/rcon.d.ts +33 -0
- package/lib/components/rcon.js +1 -0
- package/lib/components/rcon.js.map +1 -0
- package/lib/components/snapshot.d.ts +59 -0
- package/lib/components/snapshot.js +1 -0
- package/lib/components/snapshot.js.map +1 -0
- package/lib/enums_types/protocol.d.ts +120 -0
- package/lib/enums_types/protocol.js +1 -0
- package/lib/enums_types/protocol.js.map +1 -0
- package/lib/huffman.d.ts +24 -0
- package/lib/huffman.js +1 -0
- package/lib/huffman.js.map +1 -0
- package/lib/snapshot.d.ts +49 -0
- package/lib/snapshot.js +1 -0
- package/lib/snapshot.js.map +1 -0
- package/package.json +21 -25
- package/docs/documentation.md +0 -371
- package/docs/examples/chat_bot.js +0 -25
- package/docs/examples/kill_on_freeze.js +0 -20
- package/lib/MsgPacker.ts +0 -45
- package/lib/MsgUnpacker.ts +0 -58
- package/lib/UUIDManager.ts +0 -45
- package/lib/client.ts +0 -947
- package/lib/components/game.ts +0 -133
- package/lib/components/movement.ts +0 -88
- package/lib/components/rcon.ts +0 -114
- package/lib/components/snapshot.ts +0 -187
- package/lib/enums_types/protocol.ts +0 -145
- package/lib/enums_types/types.d.ts +0 -236
- package/lib/huffman.ts +0 -226
- package/lib/snapshot.ts +0 -632
- package/tsconfig.json +0 -69
package/lib/client.ts
DELETED
|
@@ -1,947 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { randomBytes } from "crypto";
|
|
4
|
-
import { lookup as dnsLookup } from 'dns';
|
|
5
|
-
import net from 'dgram';
|
|
6
|
-
import { EventEmitter } from 'stream';
|
|
7
|
-
|
|
8
|
-
import { unpackString, MsgUnpacker } from "./MsgUnpacker";
|
|
9
|
-
let { version } = require('../package.json');
|
|
10
|
-
|
|
11
|
-
import Movement from './components/movement';
|
|
12
|
-
import { _Packet, Chunk, DeltaItem, SnapshotItemTypes } from './enums_types/types';
|
|
13
|
-
|
|
14
|
-
import { MsgPacker } from './MsgPacker';
|
|
15
|
-
import { Snapshot } from './snapshot';
|
|
16
|
-
import { Huffman } from "./huffman";
|
|
17
|
-
import { Game } from "./components/game";
|
|
18
|
-
import { SnapshotWrapper } from "./components/snapshot";
|
|
19
|
-
|
|
20
|
-
import { UUIDManager } from "./UUIDManager";
|
|
21
|
-
import { NETMSG, States } from "./enums_types/protocol";
|
|
22
|
-
import { Rcon } from "./components/rcon";
|
|
23
|
-
|
|
24
|
-
const huff = new Huffman();
|
|
25
|
-
|
|
26
|
-
var messageTypes = [
|
|
27
|
-
["none, starts at 1", "SV_MOTD", "SV_BROADCAST", "SV_CHAT", "SV_KILL_MSG", "SV_SOUND_GLOBAL", "SV_TUNE_PARAMS", "SV_EXTRA_PROJECTILE", "SV_READY_TO_ENTER", "SV_WEAPON_PICKUP", "SV_EMOTICON", "SV_VOTE_CLEAR_OPTIONS", "SV_VOTE_OPTION_LIST_ADD", "SV_VOTE_OPTION_ADD", "SV_VOTE_OPTION_REMOVE", "SV_VOTE_SET", "SV_VOTE_STATUS", "CL_SAY", "CL_SET_TEAM", "CL_SET_SPECTATOR_MODE", "CL_START_INFO", "CL_CHANGE_INFO", "CL_KILL", "CL_EMOTICON", "CL_VOTE", "CL_CALL_VOTE", "CL_IS_DDNET", "SV_DDRACE_TIME", "SV_RECORD", "UNUSED", "SV_TEAMS_STATE", "CL_SHOW_OTHERS_LEGACY"],
|
|
28
|
-
["none, starts at 1", "INFO", "MAP_CHANGE", "MAP_DATA", "CON_READY", "SNAP", "SNAP_EMPTY", "SNAP_SINGLE", "INPUT_TIMING", "RCON_AUTH_STATUS", "RCON_LINE", "READY", "ENTER_GAME", "INPUT", "RCON_CMD", "RCON_AUTH", "REQUEST_MAP_DATA", "PING", "PING_REPLY", "RCON_CMD_ADD", "RCON_CMD_REMOVE"]
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
declare interface iMessage {
|
|
33
|
-
team: number,
|
|
34
|
-
client_id: number,
|
|
35
|
-
author?: { ClientInfo?: SnapshotItemTypes.ClientInfo, PlayerInfo?: SnapshotItemTypes.PlayerInfo },
|
|
36
|
-
message: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
declare interface iEmoticon {
|
|
40
|
-
client_id: number,
|
|
41
|
-
emoticon: number,
|
|
42
|
-
author?: { ClientInfo?: SnapshotItemTypes.ClientInfo, PlayerInfo?: SnapshotItemTypes.PlayerInfo }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
declare interface iKillMsg {
|
|
46
|
-
killer_id: number,
|
|
47
|
-
killer?: { ClientInfo?: SnapshotItemTypes.ClientInfo, PlayerInfo?: SnapshotItemTypes.PlayerInfo },
|
|
48
|
-
victim_id: number,
|
|
49
|
-
victim?: { ClientInfo?: SnapshotItemTypes.ClientInfo, PlayerInfo?: SnapshotItemTypes.PlayerInfo },
|
|
50
|
-
weapon: number,
|
|
51
|
-
special_mode: number
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
declare interface iOptions {
|
|
55
|
-
identity?: SnapshotItemTypes.ClientInfo,
|
|
56
|
-
password?: string,
|
|
57
|
-
ddnet_version?: {version: number, release_version: string},
|
|
58
|
-
timeout?: number, // in ms
|
|
59
|
-
NET_VERSION?: string,
|
|
60
|
-
lightweight?: boolean // experimental, only sends keepalive's (sendinput has to be called manually)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface ClientEvents {
|
|
64
|
-
connected: () => void;
|
|
65
|
-
disconnect: (reason: string) => void;
|
|
66
|
-
emote: (message: iEmoticon) => void;
|
|
67
|
-
message: (message: iMessage) => void;
|
|
68
|
-
broadcast: (message: string) => void;
|
|
69
|
-
kill: (kill: iKillMsg) => void;
|
|
70
|
-
motd: (message: string) => void;
|
|
71
|
-
map_details: (message: { map_name: string, map_sha256: Buffer, map_crc: number, map_size: number, map_url: string }) => void;
|
|
72
|
-
capabilities: (message: { ChatTimeoutCode: boolean, AnyPlayerFlag: boolean, PingEx: boolean, AllowDummy: boolean, SyncWeaponInput: boolean }) => void;
|
|
73
|
-
snapshot: (items: DeltaItem[]) => void;
|
|
74
|
-
rcon_line: (line: string) => void;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export class Client extends EventEmitter {
|
|
78
|
-
on<K extends keyof ClientEvents>(event: K, listener: ClientEvents[K]): this {
|
|
79
|
-
return super.on(event, listener);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
emit<K extends keyof ClientEvents>(event: K, ...args: Parameters<ClientEvents[K]>): boolean {
|
|
83
|
-
return super.emit(event, ...args);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public rcon: Rcon;
|
|
87
|
-
private host: string;
|
|
88
|
-
private port: number;
|
|
89
|
-
private name: string;
|
|
90
|
-
private State: number; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
91
|
-
private ack: number;
|
|
92
|
-
private clientAck: number;
|
|
93
|
-
private lastCheckedChunkAck: number;
|
|
94
|
-
private receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
|
|
95
|
-
private socket: net.Socket | undefined;
|
|
96
|
-
private TKEN: Buffer;
|
|
97
|
-
private time: number;
|
|
98
|
-
private SnapUnpacker: Snapshot;
|
|
99
|
-
|
|
100
|
-
public SnapshotUnpacker: SnapshotWrapper;
|
|
101
|
-
|
|
102
|
-
private PredGameTick: number;
|
|
103
|
-
private AckGameTick: number;
|
|
104
|
-
|
|
105
|
-
private SnapshotParts: number;
|
|
106
|
-
private currentSnapshotGameTick: number;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
private snaps: Buffer[];
|
|
110
|
-
|
|
111
|
-
private sentChunkQueue: Buffer[];
|
|
112
|
-
private queueChunkEx: MsgPacker[];
|
|
113
|
-
private lastSendTime: number;
|
|
114
|
-
private lastRecvTime: number;
|
|
115
|
-
|
|
116
|
-
private lastSentMessages: {msg: MsgPacker, ack: number}[];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
public movement: Movement;
|
|
120
|
-
public game: Game;
|
|
121
|
-
|
|
122
|
-
private VoteList: string[];
|
|
123
|
-
// eSnapHolder: eSnap[];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
public readonly options?: iOptions;
|
|
127
|
-
private requestResend: boolean;
|
|
128
|
-
|
|
129
|
-
private UUIDManager: UUIDManager;
|
|
130
|
-
|
|
131
|
-
constructor(ip: string, port: number, nickname: string, options?: iOptions) {
|
|
132
|
-
super();
|
|
133
|
-
this.host = ip;
|
|
134
|
-
this.port = port;
|
|
135
|
-
this.name = nickname;
|
|
136
|
-
this.AckGameTick = 0;
|
|
137
|
-
this.PredGameTick = 0;
|
|
138
|
-
this.currentSnapshotGameTick = 0;
|
|
139
|
-
|
|
140
|
-
this.SnapshotParts = 0;
|
|
141
|
-
this.rcon = new Rcon(this);
|
|
142
|
-
this.SnapUnpacker = new Snapshot(this);
|
|
143
|
-
// this.eSnapHolder = [];
|
|
144
|
-
this.requestResend = false;
|
|
145
|
-
|
|
146
|
-
this.VoteList = [];
|
|
147
|
-
|
|
148
|
-
if (options)
|
|
149
|
-
this.options = options;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.snaps = [];
|
|
153
|
-
|
|
154
|
-
this.sentChunkQueue = [];
|
|
155
|
-
this.queueChunkEx = [];
|
|
156
|
-
|
|
157
|
-
this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
158
|
-
this.ack = 0; // ack of messages the client has received
|
|
159
|
-
this.clientAck = 0; // ack of messages the client has sent
|
|
160
|
-
this.lastCheckedChunkAck = 0; // this.ack gets reset to this when flushing - used for resetting tick on e.g. map change
|
|
161
|
-
this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
162
|
-
this.socket = net.createSocket("udp4");
|
|
163
|
-
this.socket.bind();
|
|
164
|
-
|
|
165
|
-
this.TKEN = Buffer.from([255, 255, 255, 255])
|
|
166
|
-
this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
|
|
167
|
-
this.lastSendTime = new Date().getTime();
|
|
168
|
-
this.lastRecvTime = new Date().getTime();
|
|
169
|
-
|
|
170
|
-
this.lastSentMessages = [];
|
|
171
|
-
|
|
172
|
-
this.movement = new Movement();
|
|
173
|
-
|
|
174
|
-
this.game = new Game(this);
|
|
175
|
-
this.SnapshotUnpacker = new SnapshotWrapper(this);
|
|
176
|
-
|
|
177
|
-
this.UUIDManager = new UUIDManager();
|
|
178
|
-
|
|
179
|
-
this.UUIDManager.RegisterName("what-is@ddnet.tw", NETMSG.System.NETMSG_WHATIS);
|
|
180
|
-
this.UUIDManager.RegisterName("it-is@ddnet.tw", NETMSG.System.NETMSG_ITIS);
|
|
181
|
-
this.UUIDManager.RegisterName("i-dont-know@ddnet.tw", NETMSG.System.NETMSG_IDONTKNOW);
|
|
182
|
-
|
|
183
|
-
this.UUIDManager.RegisterName("rcon-type@ddnet.tw", NETMSG.System.NETMSG_RCONTYPE);
|
|
184
|
-
this.UUIDManager.RegisterName("map-details@ddnet.tw", NETMSG.System.NETMSG_MAP_DETAILS);
|
|
185
|
-
this.UUIDManager.RegisterName("capabilities@ddnet.tw", NETMSG.System.NETMSG_CAPABILITIES);
|
|
186
|
-
this.UUIDManager.RegisterName("clientver@ddnet.tw", NETMSG.System.NETMSG_CLIENTVER);
|
|
187
|
-
this.UUIDManager.RegisterName("ping@ddnet.tw", NETMSG.System.NETMSG_PING);
|
|
188
|
-
this.UUIDManager.RegisterName("pong@ddnet.tw", NETMSG.System.NETMSG_PONGEX);
|
|
189
|
-
this.UUIDManager.RegisterName("checksum-request@ddnet.tw", NETMSG.System.NETMSG_CHECKSUM_REQUEST);
|
|
190
|
-
this.UUIDManager.RegisterName("checksum-response@ddnet.tw", NETMSG.System.NETMSG_CHECKSUM_RESPONSE);
|
|
191
|
-
this.UUIDManager.RegisterName("checksum-error@ddnet.tw", NETMSG.System.NETMSG_CHECKSUM_ERROR);
|
|
192
|
-
this.UUIDManager.RegisterName("redirect@ddnet.org", NETMSG.System.NETMSG_REDIRECT);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
this.UUIDManager.RegisterName("rcon-cmd-group-start@ddnet.org", NETMSG.System.NETMSG_RCON_CMD_GROUP_START) // not implemented
|
|
196
|
-
this.UUIDManager.RegisterName("rcon-cmd-group-end@ddnet.org", NETMSG.System.NETMSG_RCON_CMD_GROUP_END) // not implemented
|
|
197
|
-
this.UUIDManager.RegisterName("map-reload@ddnet.org", NETMSG.System.NETMSG_MAP_RELOAD) // not implemented
|
|
198
|
-
this.UUIDManager.RegisterName("reconnect@ddnet.org", NETMSG.System.NETMSG_RECONNECT) // implemented
|
|
199
|
-
this.UUIDManager.RegisterName("sv-maplist-add@ddnet.org", NETMSG.System.NETMSG_MAPLIST_ADD) // not implemented
|
|
200
|
-
this.UUIDManager.RegisterName("sv-maplist-start@ddnet.org", NETMSG.System.NETMSG_MAPLIST_GROUP_START) // not implemented
|
|
201
|
-
this.UUIDManager.RegisterName("sv-maplist-end@ddnet.org", NETMSG.System.NETMSG_MAPLIST_GROUP_END) // not implemented
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.UUIDManager.RegisterName("i-am-npm-package@swarfey.gitlab.io", NETMSG.System.NETMSG_I_AM_NPM_PACKAGE);
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private OnEnterGame() {
|
|
209
|
-
this.snaps = [];
|
|
210
|
-
this.SnapUnpacker = new Snapshot(this);
|
|
211
|
-
this.SnapshotParts = 0;
|
|
212
|
-
this.receivedSnaps = 0;
|
|
213
|
-
this.SnapshotUnpacker = new SnapshotWrapper(this);
|
|
214
|
-
this.currentSnapshotGameTick = 0;
|
|
215
|
-
this.AckGameTick = -1;
|
|
216
|
-
this.PredGameTick = 0;
|
|
217
|
-
}
|
|
218
|
-
private ResendAfter(lastAck: number) {
|
|
219
|
-
this.clientAck = lastAck;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
let toResend: MsgPacker[] = [];
|
|
223
|
-
this.lastSentMessages.forEach(msg => {
|
|
224
|
-
if (msg.ack > lastAck)
|
|
225
|
-
toResend.push(msg.msg);
|
|
226
|
-
});
|
|
227
|
-
toResend.forEach(a => a.flag = 1|2);
|
|
228
|
-
this.SendMsgEx(toResend);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private Unpack(packet: Buffer): _Packet {
|
|
232
|
-
var unpacked: _Packet = { twprotocol: { flags: packet[0] >> 4, ack: ((packet[0]&0xf)<<8) | packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (packet.indexOf(Buffer.from([0xff, 0xff, 0xff, 0xff])) == 0 )// !(unpacked.twprotocol.flags & 8) || unpacked.twprotocol.flags == 255) // flags == 255 is connectionless (used for sending usernames)
|
|
236
|
-
return unpacked;
|
|
237
|
-
if (unpacked.twprotocol.flags & 4) { // resend flag
|
|
238
|
-
this.ResendAfter(unpacked.twprotocol.ack);
|
|
239
|
-
}
|
|
240
|
-
packet = packet.slice(3)
|
|
241
|
-
|
|
242
|
-
if (unpacked.twprotocol.flags & 8 && !(unpacked.twprotocol.flags & 1)) { // compression flag
|
|
243
|
-
packet = huff.decompress(packet)
|
|
244
|
-
if (packet.length == 1 && packet[0] == -1)
|
|
245
|
-
return unpacked
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
|
|
250
|
-
var chunk: Chunk = {} as Chunk;
|
|
251
|
-
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
252
|
-
chunk.flags = (packet[0] >> 6) & 3;
|
|
253
|
-
|
|
254
|
-
if (chunk.flags & 1) {
|
|
255
|
-
chunk.seq = ((packet[1] & 0xf0) << 2) | packet[2];
|
|
256
|
-
packet = packet.slice(3) // remove flags & size
|
|
257
|
-
} else
|
|
258
|
-
packet = packet.slice(2)
|
|
259
|
-
// chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
260
|
-
chunk.sys = Boolean(packet[0] & 1); // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
261
|
-
chunk.msgid = (packet[0] - (packet[0] & 1)) / 2;
|
|
262
|
-
chunk.msg = messageTypes[packet[0] & 1][chunk.msgid];
|
|
263
|
-
chunk.raw = packet.slice(1, chunk.bytes)
|
|
264
|
-
if (chunk.msgid == 0 && chunk.raw.byteLength >= 16) {
|
|
265
|
-
let uuid = this.UUIDManager.LookupUUID(chunk.raw.slice(0, 16));
|
|
266
|
-
if (uuid !== undefined) {
|
|
267
|
-
chunk.extended_msgid = uuid.hash;
|
|
268
|
-
chunk.msg = uuid.name;
|
|
269
|
-
chunk.raw = chunk.raw.slice(16);
|
|
270
|
-
chunk.msgid = uuid.type_id;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
packet = packet.slice(chunk.bytes)
|
|
275
|
-
unpacked.chunks.push(chunk)
|
|
276
|
-
}
|
|
277
|
-
return unpacked
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/** Send a Control Msg to the server. (used for disconnect)*/
|
|
282
|
-
SendControlMsg(msg: number, ExtraMsg: string = "") {
|
|
283
|
-
this.lastSendTime = new Date().getTime();
|
|
284
|
-
return new Promise((resolve, reject) => {
|
|
285
|
-
if (this.socket) {
|
|
286
|
-
var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x00, msg])
|
|
287
|
-
latestBuf = Buffer.concat([latestBuf, Buffer.from(ExtraMsg), this.TKEN]) // move header (latestBuf), optional extraMsg & TKEN into 1 buffer
|
|
288
|
-
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host, (err, bytes) => {
|
|
289
|
-
resolve(bytes)
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
setTimeout(() => { resolve("failed, rip") }, 2000)
|
|
294
|
-
/* after 2 seconds it was probably not able to send,
|
|
295
|
-
so when sending a quit message the user doesnt
|
|
296
|
-
stay stuck not being able to ctrl + c
|
|
297
|
-
*/
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
/** Send a Msg (or Msg[]) to the server.*/
|
|
303
|
-
SendMsgEx(Msgs: MsgPacker[] | MsgPacker, flags = 0) {
|
|
304
|
-
if (this.State == States.STATE_OFFLINE)
|
|
305
|
-
return;
|
|
306
|
-
if (!this.socket)
|
|
307
|
-
return;
|
|
308
|
-
let _Msgs: MsgPacker[];
|
|
309
|
-
if (Msgs instanceof Array)
|
|
310
|
-
_Msgs = Msgs;
|
|
311
|
-
else
|
|
312
|
-
_Msgs = [Msgs];
|
|
313
|
-
if (this.queueChunkEx.length > 0) {
|
|
314
|
-
_Msgs.push(...this.queueChunkEx);
|
|
315
|
-
this.queueChunkEx = [];
|
|
316
|
-
}
|
|
317
|
-
this.lastSendTime = new Date().getTime();
|
|
318
|
-
var header: Buffer[] = [];
|
|
319
|
-
if (this.clientAck == 0)
|
|
320
|
-
this.lastSentMessages = [];
|
|
321
|
-
_Msgs.forEach((Msg: MsgPacker, index) => {
|
|
322
|
-
header[index] = Buffer.alloc((Msg.flag & 1 ? 3 : 2));
|
|
323
|
-
header[index][0] = ((Msg.flag & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
324
|
-
header[index][1] = (Msg.size & 0xf);
|
|
325
|
-
if (Msg.flag & 1) {
|
|
326
|
-
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
327
|
-
if (this.clientAck == 0)
|
|
328
|
-
this.lastSentMessages = [];
|
|
329
|
-
header[index][1] |= (this.clientAck >> 2) & 0xf0;
|
|
330
|
-
header[index][2] = this.clientAck & 0xff;
|
|
331
|
-
header[index][0] = (((Msg.flag | 2)&3)<<6)|((Msg.size>>4)&0x3f); // 2 is resend flag (ugly hack for queue)
|
|
332
|
-
if ((Msg.flag & 2) == 0)
|
|
333
|
-
this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
|
|
334
|
-
header[index][0] = (((Msg.flag)&3)<<6)|((Msg.size>>4)&0x3f);
|
|
335
|
-
if ((Msg.flag & 2) == 0)
|
|
336
|
-
this.lastSentMessages.push({msg: Msg, ack: this.clientAck})
|
|
337
|
-
}
|
|
338
|
-
})
|
|
339
|
-
// let flags = 0;
|
|
340
|
-
if (this.requestResend)
|
|
341
|
-
flags |= 4;
|
|
342
|
-
|
|
343
|
-
var packetHeader = Buffer.from([((flags<<4)&0xf0)|((this.ack>>8)&0xf), this.ack & 0xff, _Msgs.length]);
|
|
344
|
-
var chunks = Buffer.from([]);
|
|
345
|
-
let skip = false;
|
|
346
|
-
_Msgs.forEach((Msg: MsgPacker, index) => {
|
|
347
|
-
if (skip)
|
|
348
|
-
return;
|
|
349
|
-
if (chunks.byteLength < 1300)
|
|
350
|
-
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
351
|
-
else {
|
|
352
|
-
skip = true;
|
|
353
|
-
this.SendMsgEx(_Msgs.slice(index));
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
357
|
-
if (chunks.length < 0)
|
|
358
|
-
return;
|
|
359
|
-
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/** Queue a chunk (instantly sent if flush flag is set - otherwise it will be sent in the next packet). */
|
|
363
|
-
QueueChunkEx(Msg: MsgPacker | MsgPacker[]) {
|
|
364
|
-
if (Msg instanceof Array) {
|
|
365
|
-
for (let chunk of Msg)
|
|
366
|
-
this.QueueChunkEx(chunk);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (this.queueChunkEx.length > 0) {
|
|
370
|
-
let total_size = 0;
|
|
371
|
-
for (let chunk of this.queueChunkEx)
|
|
372
|
-
total_size += chunk.size;
|
|
373
|
-
if (total_size + Msg.size + 3 > 1394 - 4)
|
|
374
|
-
this.Flush();
|
|
375
|
-
}
|
|
376
|
-
this.queueChunkEx.push(Msg);
|
|
377
|
-
if (Msg.flag & 4)
|
|
378
|
-
this.Flush();
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** Send a Raw Buffer (as chunk) to the server. */
|
|
382
|
-
SendMsgRaw(chunks: Buffer[]) {
|
|
383
|
-
if (this.State == States.STATE_OFFLINE)
|
|
384
|
-
return;
|
|
385
|
-
if (!this.socket)
|
|
386
|
-
return;
|
|
387
|
-
|
|
388
|
-
this.lastSendTime = new Date().getTime();
|
|
389
|
-
|
|
390
|
-
var packetHeader = Buffer.from([0x0+(((16<<4)&0xf0)|((this.ack>>8)&0xf)), this.ack&0xff, chunks.length]);
|
|
391
|
-
|
|
392
|
-
var packet = Buffer.concat([(packetHeader), Buffer.concat(chunks), this.TKEN]);
|
|
393
|
-
if (chunks.length < 0)
|
|
394
|
-
return;
|
|
395
|
-
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private MsgToChunk(packet: Buffer) {
|
|
399
|
-
var chunk: Chunk = {} as Chunk;
|
|
400
|
-
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
401
|
-
chunk.flags = (packet[0] >> 6) & 3;
|
|
402
|
-
|
|
403
|
-
if (chunk.flags & 1) {
|
|
404
|
-
chunk.seq = ((packet[1]&0xf0)<<2) | packet[2];
|
|
405
|
-
packet = packet.slice(3) // remove flags & size
|
|
406
|
-
} else
|
|
407
|
-
packet = packet.slice(2)
|
|
408
|
-
// chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
409
|
-
chunk.sys = Boolean(packet[0] & 1); // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
410
|
-
chunk.msgid = (packet[0]-(packet[0]&1))/2;
|
|
411
|
-
chunk.msg = messageTypes[packet[0]&1][chunk.msgid];
|
|
412
|
-
chunk.raw = packet.slice(1, chunk.bytes)
|
|
413
|
-
if (chunk.msgid == 0) {
|
|
414
|
-
let uuid = this.UUIDManager.LookupUUID(chunk.raw.slice(0,16));
|
|
415
|
-
if (uuid !== undefined) {
|
|
416
|
-
chunk.extended_msgid = uuid.hash;
|
|
417
|
-
chunk.msgid = uuid.type_id;
|
|
418
|
-
chunk.msg = uuid.name;
|
|
419
|
-
chunk.raw = chunk.raw.slice(16);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
return chunk;
|
|
424
|
-
}
|
|
425
|
-
Flush() {
|
|
426
|
-
// if (this.queueChunkEx.length == 0)
|
|
427
|
-
console.log("flushing");
|
|
428
|
-
this.SendMsgEx(this.queueChunkEx);
|
|
429
|
-
this.queueChunkEx = [];
|
|
430
|
-
this.ack = this.lastCheckedChunkAck;
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/** Connect the client to the server. */
|
|
435
|
-
async connect() {
|
|
436
|
-
// test via regex whether or not this.host is a domain or an ip
|
|
437
|
-
// if not, resolve it
|
|
438
|
-
if (!this.host.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
|
|
439
|
-
dnsLookup(this.host, 4, (err, address, family) => {
|
|
440
|
-
if (err) throw err;
|
|
441
|
-
this.host = address;
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
this.State = States.STATE_CONNECTING;
|
|
446
|
-
|
|
447
|
-
let predTimer = setInterval(() => {
|
|
448
|
-
if (this.State == States.STATE_ONLINE) {
|
|
449
|
-
if (this.AckGameTick > 0)
|
|
450
|
-
this.PredGameTick++;
|
|
451
|
-
} else if (this.State == States.STATE_OFFLINE)
|
|
452
|
-
clearInterval(predTimer);
|
|
453
|
-
|
|
454
|
-
}, 1000/50); // 50 ticks per second
|
|
455
|
-
|
|
456
|
-
this.SendControlMsg(1, "TKEN")
|
|
457
|
-
let connectInterval = setInterval(() => {
|
|
458
|
-
if (this.State == States.STATE_CONNECTING)
|
|
459
|
-
this.SendControlMsg(1, "TKEN")
|
|
460
|
-
else
|
|
461
|
-
clearInterval(connectInterval)
|
|
462
|
-
}, 500);
|
|
463
|
-
let inputInterval: NodeJS.Timeout;
|
|
464
|
-
if (!this.options?.lightweight) {
|
|
465
|
-
inputInterval = setInterval(() => {
|
|
466
|
-
if (this.State == States.STATE_OFFLINE) {
|
|
467
|
-
clearInterval(inputInterval)
|
|
468
|
-
// console.log("???");
|
|
469
|
-
}
|
|
470
|
-
if (this.State != States.STATE_ONLINE)
|
|
471
|
-
return;
|
|
472
|
-
this.time = new Date().getTime();
|
|
473
|
-
this.sendInput();
|
|
474
|
-
}, 50)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
let resendTimeout = setInterval(() => {
|
|
478
|
-
if (this.State != States.STATE_OFFLINE) {
|
|
479
|
-
if (((new Date().getTime()) - this.lastSendTime) > 900 && this.sentChunkQueue.length > 0) {
|
|
480
|
-
this.SendMsgRaw([this.sentChunkQueue[0]]);
|
|
481
|
-
}
|
|
482
|
-
} else
|
|
483
|
-
clearInterval(resendTimeout)
|
|
484
|
-
}, 1000)
|
|
485
|
-
|
|
486
|
-
let Timeout = setInterval(() => {
|
|
487
|
-
let timeoutTime = this.options?.timeout ? this.options.timeout : 15000;
|
|
488
|
-
if ((new Date().getTime() - this.lastRecvTime) > timeoutTime) {
|
|
489
|
-
this.State = States.STATE_OFFLINE;
|
|
490
|
-
this.emit("disconnect", "Timed Out. (no packets received for " + (new Date().getTime() - this.lastRecvTime) + "ms)");
|
|
491
|
-
clearInterval(Timeout);
|
|
492
|
-
}
|
|
493
|
-
}, 5000)
|
|
494
|
-
|
|
495
|
-
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
496
|
-
|
|
497
|
-
if (this.socket)
|
|
498
|
-
this.socket.on("message", (packet, rinfo) => {
|
|
499
|
-
if (this.State == 0 || rinfo.address != this.host || rinfo.port != this.port)
|
|
500
|
-
return;
|
|
501
|
-
clearInterval(connectInterval)
|
|
502
|
-
|
|
503
|
-
if (packet[0] == 0x10) {
|
|
504
|
-
if (packet.toString().includes("TKEN") || packet[3] == 0x2) {
|
|
505
|
-
clearInterval(connectInterval);
|
|
506
|
-
this.TKEN = packet.slice(-4)
|
|
507
|
-
this.SendControlMsg(3);
|
|
508
|
-
this.State = States.STATE_LOADING; // loading state
|
|
509
|
-
this.receivedSnaps = 0;
|
|
510
|
-
|
|
511
|
-
var info = new MsgPacker(1, true, 1);
|
|
512
|
-
info.AddString(this.options?.NET_VERSION ? this.options.NET_VERSION : "0.6 626fce9a778df4d4");
|
|
513
|
-
info.AddString(this.options?.password === undefined ? "" : this.options?.password); // password
|
|
514
|
-
|
|
515
|
-
var client_version = new MsgPacker(0, true, 1);
|
|
516
|
-
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
517
|
-
let randomUuid = randomBytes(16);
|
|
518
|
-
|
|
519
|
-
client_version.AddBuffer(randomUuid);
|
|
520
|
-
if (this.options?.ddnet_version !== undefined) {
|
|
521
|
-
client_version.AddInt(this.options?.ddnet_version.version);
|
|
522
|
-
client_version.AddString(`DDNet ${this.options?.ddnet_version.release_version}; https://www.npmjs.com/package/teeworlds/v/${version}`);
|
|
523
|
-
} else {
|
|
524
|
-
client_version.AddInt(16050);
|
|
525
|
-
client_version.AddString(`DDNet 16.5.0; https://www.npmjs.com/package/teeworlds/v/${version}`);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
var i_am_npm_package = new MsgPacker(0, true, 1);
|
|
529
|
-
i_am_npm_package.AddBuffer(this.UUIDManager.LookupType(NETMSG.System.NETMSG_I_AM_NPM_PACKAGE)!.hash);
|
|
530
|
-
|
|
531
|
-
i_am_npm_package.AddString(`https://www.npmjs.com/package/teeworlds/v/${version}`);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this.SendMsgEx([i_am_npm_package, client_version, info])
|
|
535
|
-
} else if (packet[3] == 0x4) {
|
|
536
|
-
// disconnected
|
|
537
|
-
this.State = States.STATE_OFFLINE;
|
|
538
|
-
let reason: string = (unpackString(packet.slice(4)).result);
|
|
539
|
-
this.emit("disconnect", reason);
|
|
540
|
-
}
|
|
541
|
-
if (packet[3] !== 0x0) { // keepalive
|
|
542
|
-
this.lastRecvTime = new Date().getTime();
|
|
543
|
-
}
|
|
544
|
-
} else {
|
|
545
|
-
this.lastRecvTime = new Date().getTime();
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
var unpacked: _Packet = this.Unpack(packet);
|
|
549
|
-
// unpacked.chunks = unpacked.chunks.filter(chunk => ((chunk.flags & 2) && (chunk.flags & 1)) ? chunk.seq! > this.ack : true); // filter out already received chunks
|
|
550
|
-
this.sentChunkQueue.forEach((buff, i) => {
|
|
551
|
-
let chunkFlags = (buff[0] >> 6) & 3;
|
|
552
|
-
if (chunkFlags & 1) {
|
|
553
|
-
let chunk = this.MsgToChunk(buff);
|
|
554
|
-
if (chunk.seq && chunk.seq >= this.ack)
|
|
555
|
-
this.sentChunkQueue.splice(i, 1);
|
|
556
|
-
}
|
|
557
|
-
})
|
|
558
|
-
unpacked.chunks.forEach(chunk => {
|
|
559
|
-
if (!(((chunk.flags & 2) && (chunk.flags & 1)) ? chunk.seq! > this.ack : true))
|
|
560
|
-
return; // filter out already received chunks
|
|
561
|
-
if (chunk.flags & 1 && (chunk.flags !== 15)) { // vital and not connless
|
|
562
|
-
this.lastCheckedChunkAck = chunk.seq!;
|
|
563
|
-
if (chunk.seq === (this.ack+1)%(1<<10)) { // https://github.com/nobody-mb/twchatonly/blob/master/chatonly.cpp#L237
|
|
564
|
-
this.ack = chunk.seq!;
|
|
565
|
-
|
|
566
|
-
this.requestResend = false;
|
|
567
|
-
}
|
|
568
|
-
else { //IsSeqInBackroom (old packet that we already got)
|
|
569
|
-
let Bottom = (this.ack - (1<<10)/2);
|
|
570
|
-
|
|
571
|
-
if(Bottom < 0) {
|
|
572
|
-
if((chunk.seq! <= this.ack) || (chunk.seq! >= (Bottom + (1<<10)))) {}
|
|
573
|
-
else
|
|
574
|
-
this.requestResend = true;
|
|
575
|
-
} else {
|
|
576
|
-
if(chunk.seq! <= this.ack && chunk.seq! >= Bottom) {}
|
|
577
|
-
else
|
|
578
|
-
this.requestResend = true;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
if (chunk.sys) {
|
|
584
|
-
// system messages
|
|
585
|
-
if (chunk.msgid == NETMSG.System.NETMSG_PING) { // ping
|
|
586
|
-
let packer = new MsgPacker(NETMSG.System.NETMSG_PING_REPLY, true, 0);
|
|
587
|
-
|
|
588
|
-
this.SendMsgEx(packer); // send ping reply
|
|
589
|
-
} else if (chunk.msgid == NETMSG.System.NETMSG_PING_REPLY) { // Ping reply
|
|
590
|
-
this.game._ping_resolve(new Date().getTime())
|
|
591
|
-
} else if (this.rcon._checkChunks(chunk)) {}
|
|
592
|
-
// packets neccessary for connection
|
|
593
|
-
// https://ddnet.org/docs/libtw2/connection/
|
|
594
|
-
|
|
595
|
-
if (chunk.msgid == NETMSG.System.NETMSG_MAP_CHANGE) {
|
|
596
|
-
this.Flush();
|
|
597
|
-
var Msg = new MsgPacker(NETMSG.System.NETMSG_READY, true, 1); /* ready */
|
|
598
|
-
this.SendMsgEx(Msg);
|
|
599
|
-
} else if (chunk.msgid == NETMSG.System.NETMSG_CON_READY) {
|
|
600
|
-
var info = new MsgPacker(NETMSG.Game.CL_STARTINFO, false, 1);
|
|
601
|
-
if (this.options?.identity) {
|
|
602
|
-
info.AddString(this.options.identity.name);
|
|
603
|
-
info.AddString(this.options.identity.clan);
|
|
604
|
-
info.AddInt(this.options.identity.country);
|
|
605
|
-
info.AddString(this.options.identity.skin);
|
|
606
|
-
info.AddInt(this.options.identity.use_custom_color);
|
|
607
|
-
info.AddInt(this.options.identity.color_body);
|
|
608
|
-
info.AddInt(this.options.identity.color_feet);
|
|
609
|
-
} else {
|
|
610
|
-
info.AddString(this.name); /* name */
|
|
611
|
-
info.AddString(""); /* clan */
|
|
612
|
-
info.AddInt(-1); /* country */
|
|
613
|
-
info.AddString("greyfox"); /* skin */
|
|
614
|
-
info.AddInt(1); /* use custom color */
|
|
615
|
-
info.AddInt(10346103); /* color body */
|
|
616
|
-
info.AddInt(65535); /* color feet */
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
var crashmeplx = new MsgPacker(17, true, 1); // rcon
|
|
620
|
-
crashmeplx.AddString("crashmeplx"); // 64 player support message
|
|
621
|
-
this.SendMsgEx([info, crashmeplx]);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (chunk.msgid >= NETMSG.System.NETMSG_SNAP && chunk.msgid <= NETMSG.System.NETMSG_SNAPSINGLE) {
|
|
625
|
-
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
626
|
-
if (this.receivedSnaps == 2) {
|
|
627
|
-
if (this.State != States.STATE_ONLINE)
|
|
628
|
-
this.emit('connected')
|
|
629
|
-
this.State = States.STATE_ONLINE;
|
|
630
|
-
}
|
|
631
|
-
if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
|
|
632
|
-
this.PredGameTick = this.AckGameTick + 1;
|
|
633
|
-
|
|
634
|
-
// snapChunks.forEach(chunk => {
|
|
635
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
636
|
-
|
|
637
|
-
let NumParts = 1;
|
|
638
|
-
let Part = 0;
|
|
639
|
-
let GameTick = unpacker.unpackInt();
|
|
640
|
-
let DeltaTick = GameTick - unpacker.unpackInt();
|
|
641
|
-
let PartSize = 0;
|
|
642
|
-
let Crc = 0;
|
|
643
|
-
let CompleteSize = 0;
|
|
644
|
-
|
|
645
|
-
if (chunk.msgid == NETMSG.System.NETMSG_SNAP) {
|
|
646
|
-
NumParts = unpacker.unpackInt();
|
|
647
|
-
Part = unpacker.unpackInt();
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
if (chunk.msgid != NETMSG.System.NETMSG_SNAPEMPTY) {
|
|
651
|
-
Crc = unpacker.unpackInt();
|
|
652
|
-
PartSize = unpacker.unpackInt();
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (NumParts < 1 || NumParts > 64 || Part < 0 || Part >= NumParts || PartSize < 0 || PartSize > 900)
|
|
656
|
-
return;
|
|
657
|
-
|
|
658
|
-
if (GameTick >= this.currentSnapshotGameTick) {
|
|
659
|
-
if (GameTick != this.currentSnapshotGameTick) {
|
|
660
|
-
this.snaps = [];
|
|
661
|
-
this.SnapshotParts = 0;
|
|
662
|
-
this.currentSnapshotGameTick = GameTick;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// chunk.raw = Buffer.from(unpacker.remaining);
|
|
666
|
-
this.snaps[Part] = unpacker.remaining;
|
|
667
|
-
|
|
668
|
-
this.SnapshotParts |= 1 << Part;
|
|
669
|
-
|
|
670
|
-
if (this.SnapshotParts == ((1 << NumParts) - 1)) {
|
|
671
|
-
let mergedSnaps = Buffer.concat(this.snaps);
|
|
672
|
-
this.SnapshotParts = 0;
|
|
673
|
-
|
|
674
|
-
let snapUnpacked = this.SnapUnpacker.unpackSnapshot(mergedSnaps, DeltaTick, GameTick, Crc);
|
|
675
|
-
|
|
676
|
-
this.emit("snapshot", snapUnpacked.items);
|
|
677
|
-
this.AckGameTick = snapUnpacked.recvTick;
|
|
678
|
-
if (Math.abs(this.PredGameTick - this.AckGameTick) > 10) {
|
|
679
|
-
this.PredGameTick = this.AckGameTick + 1;
|
|
680
|
-
this.sendInput();
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
// })
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
if (chunk.msgid >= NETMSG.System.NETMSG_WHATIS && chunk.msgid <= NETMSG.System.NETMSG_CHECKSUM_ERROR) {
|
|
694
|
-
if (chunk.msgid == NETMSG.System.NETMSG_WHATIS) {
|
|
695
|
-
let Uuid = chunk.raw.slice(0, 16);
|
|
696
|
-
|
|
697
|
-
let uuid = this.UUIDManager.LookupUUID(Uuid);
|
|
698
|
-
let packer = new MsgPacker(0, true, 1);
|
|
699
|
-
if (uuid !== undefined) {
|
|
700
|
-
// IT_IS msg
|
|
701
|
-
packer.AddBuffer(this.UUIDManager.LookupType(NETMSG.System.NETMSG_ITIS)!.hash);
|
|
702
|
-
|
|
703
|
-
packer.AddBuffer(Uuid);
|
|
704
|
-
packer.AddString(uuid.name);
|
|
705
|
-
} else {
|
|
706
|
-
// dont_know msg
|
|
707
|
-
packer.AddBuffer(this.UUIDManager.LookupType(NETMSG.System.NETMSG_IDONTKNOW)!.hash);
|
|
708
|
-
|
|
709
|
-
packer.AddBuffer(Uuid);
|
|
710
|
-
}
|
|
711
|
-
this.QueueChunkEx(packer)
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
if (chunk.msgid == NETMSG.System.NETMSG_MAP_DETAILS) { // TODO: option for downloading maps
|
|
715
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
716
|
-
|
|
717
|
-
let map_name = unpacker.unpackString();
|
|
718
|
-
let map_sha256: Buffer = Buffer.alloc(32);
|
|
719
|
-
if (unpacker.remaining.length >= 32)
|
|
720
|
-
map_sha256 = unpacker.unpackRaw(32);
|
|
721
|
-
let map_crc = unpacker.unpackInt();
|
|
722
|
-
let map_size = unpacker.unpackInt();
|
|
723
|
-
|
|
724
|
-
let map_url = "";
|
|
725
|
-
if (unpacker.remaining.length)
|
|
726
|
-
map_url = unpacker.unpackString();
|
|
727
|
-
|
|
728
|
-
this.emit("map_details", {map_name, map_sha256, map_crc, map_size, map_url})
|
|
729
|
-
// unpacker.unpack
|
|
730
|
-
|
|
731
|
-
} else if (chunk.msgid == NETMSG.System.NETMSG_CAPABILITIES) {
|
|
732
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
733
|
-
let Version = unpacker.unpackInt();
|
|
734
|
-
let Flags = unpacker.unpackInt();
|
|
735
|
-
if (Version <= 0)
|
|
736
|
-
return;
|
|
737
|
-
let DDNet = false;
|
|
738
|
-
if (Version >= 1) {
|
|
739
|
-
DDNet = Boolean(Flags & 1);
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
let ChatTimeoutCode = DDNet;
|
|
743
|
-
let AnyPlayerFlag = DDNet;
|
|
744
|
-
let PingEx = false;
|
|
745
|
-
let AllowDummy = true;
|
|
746
|
-
let SyncWeaponInput = false;
|
|
747
|
-
if(Version >= 1)
|
|
748
|
-
{
|
|
749
|
-
ChatTimeoutCode = Boolean(Flags & 2);
|
|
750
|
-
}
|
|
751
|
-
if(Version >= 2)
|
|
752
|
-
{
|
|
753
|
-
AnyPlayerFlag = Boolean(Flags & 4);
|
|
754
|
-
}
|
|
755
|
-
if(Version >= 3)
|
|
756
|
-
{
|
|
757
|
-
PingEx = Boolean(Flags & 8);
|
|
758
|
-
}
|
|
759
|
-
if(Version >= 4)
|
|
760
|
-
{
|
|
761
|
-
AllowDummy = Boolean(Flags & 16);
|
|
762
|
-
}
|
|
763
|
-
if(Version >= 5)
|
|
764
|
-
{
|
|
765
|
-
SyncWeaponInput = Boolean(Flags & 32);
|
|
766
|
-
}
|
|
767
|
-
this.emit("capabilities", {ChatTimeoutCode, AnyPlayerFlag, PingEx, AllowDummy, SyncWeaponInput});
|
|
768
|
-
// https://github.com/ddnet/ddnet/blob/06e3eb564150e9ab81b3a5595c48e9fe5952ed32/src/engine/client/client.cpp#L1565
|
|
769
|
-
} else if (chunk.msgid == NETMSG.System.NETMSG_PINGEX) {
|
|
770
|
-
let packer = new MsgPacker(0, true, 2);
|
|
771
|
-
packer.AddBuffer(this.UUIDManager.LookupType(NETMSG.System.NETMSG_PONGEX)!.hash);
|
|
772
|
-
|
|
773
|
-
this.SendMsgEx(packer, 2);
|
|
774
|
-
} else if (chunk.msgid == NETMSG.System.NETMSG_RECONNECT) {
|
|
775
|
-
this.SendControlMsg(4) // sends disconnect packet
|
|
776
|
-
clearInterval(predTimer);
|
|
777
|
-
clearInterval(inputInterval);
|
|
778
|
-
clearInterval(resendTimeout);
|
|
779
|
-
clearInterval(Timeout);
|
|
780
|
-
this.socket?.removeAllListeners("message");
|
|
781
|
-
this.connect();
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
} else {
|
|
790
|
-
// game messages
|
|
791
|
-
|
|
792
|
-
// vote list:
|
|
793
|
-
if (chunk.msgid == NETMSG.Game.SV_VOTECLEAROPTIONS) {
|
|
794
|
-
this.VoteList = [];
|
|
795
|
-
} else if (chunk.msgid == NETMSG.Game.SV_VOTEOPTIONLISTADD) {
|
|
796
|
-
let unpacker = new MsgUnpacker(chunk.raw)
|
|
797
|
-
let NumOptions = unpacker.unpackInt()
|
|
798
|
-
let list: string[] = [];
|
|
799
|
-
for (let i = 0; i < 15; i++) {
|
|
800
|
-
list.push(unpacker.unpackString());
|
|
801
|
-
}
|
|
802
|
-
list = list.slice(0, NumOptions);
|
|
803
|
-
|
|
804
|
-
this.VoteList.push(...list);
|
|
805
|
-
} else if (chunk.msgid == NETMSG.Game.SV_VOTEOPTIONADD) {
|
|
806
|
-
let unpacker = new MsgUnpacker(chunk.raw)
|
|
807
|
-
|
|
808
|
-
this.VoteList.push(unpacker.unpackString());
|
|
809
|
-
} else if (chunk.msgid == NETMSG.Game.SV_VOTEOPTIONREMOVE) {
|
|
810
|
-
let unpacker = new MsgUnpacker(chunk.raw)
|
|
811
|
-
|
|
812
|
-
let index = this.VoteList.indexOf(unpacker.unpackString());
|
|
813
|
-
|
|
814
|
-
if (index > -1)
|
|
815
|
-
this.VoteList = this.VoteList.splice(index, 1);
|
|
816
|
-
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// events
|
|
820
|
-
if (chunk.msgid == NETMSG.Game.SV_EMOTICON) {
|
|
821
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
822
|
-
let unpacked = {
|
|
823
|
-
client_id: unpacker.unpackInt(),
|
|
824
|
-
emoticon: unpacker.unpackInt()
|
|
825
|
-
} as iEmoticon;
|
|
826
|
-
|
|
827
|
-
if (unpacked.client_id != -1) {
|
|
828
|
-
unpacked.author = {
|
|
829
|
-
ClientInfo: this.SnapshotUnpacker.getObjClientInfo(unpacked.client_id),
|
|
830
|
-
PlayerInfo: this.SnapshotUnpacker.getObjPlayerInfo(unpacked.client_id)
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
this.emit("emote", unpacked)
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
} else if (chunk.msgid == NETMSG.Game.SV_BROADCAST) {
|
|
838
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
839
|
-
|
|
840
|
-
this.emit("broadcast", unpacker.unpackString());
|
|
841
|
-
} if (chunk.msgid == NETMSG.Game.SV_CHAT) {
|
|
842
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
843
|
-
let unpacked: iMessage = {
|
|
844
|
-
team: unpacker.unpackInt(),
|
|
845
|
-
client_id: unpacker.unpackInt(),
|
|
846
|
-
message: unpacker.unpackString()
|
|
847
|
-
} as iMessage;
|
|
848
|
-
|
|
849
|
-
if (unpacked.client_id != -1) {
|
|
850
|
-
unpacked.author = {
|
|
851
|
-
ClientInfo: this.SnapshotUnpacker.getObjClientInfo(unpacked.client_id),
|
|
852
|
-
PlayerInfo: this.SnapshotUnpacker.getObjPlayerInfo(unpacked.client_id)
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
this.emit("message", unpacked)
|
|
856
|
-
} else if (chunk.msgid == NETMSG.Game.SV_KILLMSG) {
|
|
857
|
-
let unpacked: iKillMsg = {} as iKillMsg;
|
|
858
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
859
|
-
unpacked.killer_id = unpacker.unpackInt();
|
|
860
|
-
unpacked.victim_id = unpacker.unpackInt();
|
|
861
|
-
unpacked.weapon = unpacker.unpackInt();
|
|
862
|
-
unpacked.special_mode = unpacker.unpackInt();
|
|
863
|
-
if (unpacked.victim_id != -1 && unpacked.victim_id < 64) {
|
|
864
|
-
unpacked.victim = { ClientInfo: this.SnapshotUnpacker.getObjClientInfo(unpacked.victim_id), PlayerInfo: this.SnapshotUnpacker.getObjPlayerInfo(unpacked.victim_id) }
|
|
865
|
-
|
|
866
|
-
}
|
|
867
|
-
if (unpacked.killer_id != -1 && unpacked.killer_id < 64)
|
|
868
|
-
unpacked.killer = { ClientInfo: this.SnapshotUnpacker.getObjClientInfo(unpacked.killer_id), PlayerInfo: this.SnapshotUnpacker.getObjPlayerInfo(unpacked.killer_id) }
|
|
869
|
-
this.emit("kill", unpacked)
|
|
870
|
-
} else if (chunk.msgid == NETMSG.Game.SV_MOTD) {
|
|
871
|
-
let unpacker = new MsgUnpacker(chunk.raw);
|
|
872
|
-
let message = unpacker.unpackString();
|
|
873
|
-
this.emit("motd", message);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// packets neccessary for connection
|
|
877
|
-
// https://ddnet.org/docs/libtw2/connection/
|
|
878
|
-
if (chunk.msgid == NETMSG.Game.SV_READYTOENTER) {
|
|
879
|
-
var Msg = new MsgPacker(NETMSG.System.NETMSG_ENTERGAME, true, 1); /* entergame */
|
|
880
|
-
this.SendMsgEx(Msg);
|
|
881
|
-
this.OnEnterGame();
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
if (this.State == States.STATE_ONLINE) {
|
|
887
|
-
if (new Date().getTime() - this.time >= 500) {
|
|
888
|
-
this.Flush();
|
|
889
|
-
}
|
|
890
|
-
if (new Date().getTime() - this.time >= 1000) {
|
|
891
|
-
this.time = new Date().getTime();
|
|
892
|
-
this.SendControlMsg(0);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
})
|
|
897
|
-
}
|
|
898
|
-
/** Sending the input. (automatically done unless options.lightweight is on) */
|
|
899
|
-
sendInput(input = this.movement.input) {
|
|
900
|
-
if (this.State != States.STATE_ONLINE)
|
|
901
|
-
return;
|
|
902
|
-
|
|
903
|
-
let inputMsg = new MsgPacker(16, true, 0);
|
|
904
|
-
inputMsg.AddInt(this.AckGameTick);
|
|
905
|
-
inputMsg.AddInt(this.PredGameTick);
|
|
906
|
-
inputMsg.AddInt(40);
|
|
907
|
-
|
|
908
|
-
inputMsg.AddInt(input.m_Direction)
|
|
909
|
-
inputMsg.AddInt(input.m_TargetX)
|
|
910
|
-
inputMsg.AddInt(input.m_TargetY)
|
|
911
|
-
inputMsg.AddInt(input.m_Jump)
|
|
912
|
-
inputMsg.AddInt(input.m_Fire)
|
|
913
|
-
inputMsg.AddInt(input.m_Hook)
|
|
914
|
-
inputMsg.AddInt(input.m_PlayerFlags)
|
|
915
|
-
inputMsg.AddInt(input.m_WantedWeapon)
|
|
916
|
-
inputMsg.AddInt(input.m_NextWeapon)
|
|
917
|
-
inputMsg.AddInt(input.m_PrevWeapon)
|
|
918
|
-
|
|
919
|
-
this.SendMsgEx(inputMsg);
|
|
920
|
-
}
|
|
921
|
-
/** returns the movement object of the client */
|
|
922
|
-
get input() {
|
|
923
|
-
return this.movement.input;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
/** Disconnect the client. */
|
|
928
|
-
Disconnect() {
|
|
929
|
-
return new Promise((resolve) => {
|
|
930
|
-
this.SendControlMsg(4).then(() => {
|
|
931
|
-
resolve(true);
|
|
932
|
-
if (this.socket)
|
|
933
|
-
this.socket.close();
|
|
934
|
-
this.socket = undefined
|
|
935
|
-
this.State = States.STATE_OFFLINE;
|
|
936
|
-
})
|
|
937
|
-
})
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
/** Get all available vote options (for example for map voting) */
|
|
941
|
-
get VoteOptionList(): string[] {
|
|
942
|
-
return this.VoteList;
|
|
943
|
-
}
|
|
944
|
-
get rawSnapUnpacker(): Snapshot {
|
|
945
|
-
return this.SnapUnpacker;
|
|
946
|
-
}
|
|
947
|
-
}
|