1 /*
2  * Copyright (c) 2017 SEL
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU Lesser General Public License for more details.
13  * 
14  */
15 /**
16  * Packets related to a player. The first field of every packet is an `hub id`
17  * that uniquely identifies a player in the hub and never changes until it disconnects.
18  */
19 module sel.hncom.player;
20 
21 import std.json : JSONValue;
22 import std.socket : Address;
23 import std.typecons : Tuple;
24 import std.uuid : UUID;
25 
26 import sel.hncom.about;
27 static import sel.hncom.io;
28 
29 mixin template IO(E...) {
30 
31 	mixin sel.hncom.io.IOImpl!(E);
32 
33 	ubyte[] encode() {
34 		ubyte[] buffer;
35 		sel.hncom.io.encodeType(ID, buffer);
36 		sel.hncom.io.encodeType(hubId, buffer);
37 		encodeValues(buffer);
38 		return buffer;
39 	}
40 
41 	void addTo(ref Packets packets) {
42 		auto packet = typeof(Packets.packets.init[0])(ID, []);
43 		encodeValues(packet.payload);
44 		packets.packets ~= packet;
45 	}
46 
47 	typeof(this) decode(ubyte[] buffer) {
48 		size_t index = 0;
49 		hubId = sel.hncom.io.decodeType!uint(buffer, index);
50 		decodeValues(buffer, index);
51 		return this;
52 	}
53 
54 	static typeof(this) fromBuffer(ubyte[] buffer) {
55 		return typeof(this)().decode(buffer);
56 	}
57 
58 	static typeof(this) fromBuffer(uint hubId, ubyte[] buffer) {
59 		size_t index = 0;
60 		auto ret = typeof(this)(hubId);
61 		ret.decodeValues(buffer, index);
62 		return ret;
63 	}
64 
65 }
66 
67 /**
68  * Adds a player to the node.
69  */
70 @clientbound struct Add {
71 
72 	enum ubyte ID = 25;
73 
74 	alias ServerAddress = Tuple!(string, "ip", ushort, "port");
75 
76 	alias Skin = Tuple!(string, "name", ubyte[], "data", ubyte[], "cape", string, "geometryName", ubyte[], "geometryData");
77 
78 	// reason
79 	enum : ubyte {
80 
81 		FIRST_JOIN,				/// The player has been automatically put on this node because it's a non-full main node.
82 		TRANSFERRED,			/// The player has been transferred to this node.
83 		FORCIBLY_TRANSFERRED,	/// The player was on a node that has wrongly disconnected (probably crashing) and the player has been transferred to the first non-full main node.
84 
85 	}
86 
87 	// permission level
88 	enum : ubyte {
89 
90 		USER = 0,
91 		OPERATOR = 1,
92 		HOST = 2,
93 		AUTOMATION = 3,
94 		ADMIN = 4,
95 
96 	}
97 
98 	/**
99 	 * Player's unique id given by the hub.
100 	 */
101 	uint hubId;
102 
103 	/**
104 	 * Reason for which the player has been added to the node.
105 	 */
106 	ubyte reason;
107 
108 	/**
109 	 * Game used by the player, which could either be Minecraft or Minecraft: Pocket Edition.
110 	 * It should be one of the keys given in NodeInfo's acceptedGames field.
111 	 */
112 	ubyte type;
113 
114 	/**
115 	 * Version of the protocol used by the client. Should be contained in the list given
116 	 * to the hub in the NodeInfo's acceptedGames field.
117 	 */
118 	uint protocol;
119 
120 	/**
121 	 * Version of the game used by the client, usually in the format major.minor[.patch],
122 	 * calculated by the server or passed by the client during the authentication process.
123 	 * The node should verify that the version exists and matches the protocol indicated
124 	 * in the previous field.
125 	 */
126 	string version_;
127 
128 	/**
129 	 * Client's UUID, given by Mojang's or Microsoft's services if the server is in
130 	 * online mode or given by the client (and not verified) if the server is in offline mode.
131 	 */
132 	UUID uuid;
133 
134 	/**
135 	 * Username of the player.
136 	 */
137 	string username;
138 
139 	/**
140 	 * Display name of the player, which can contain formatting codes. By default it's equals
141 	 * to the username but it can be updated by the node using the UpdateDisplayName packet.
142 	 */
143 	string displayName;
144 
145 	ubyte permissionLevel;
146 
147 	/**
148 	 * Dimension in which the player was playing before being transferred in the MCPE format
149 	 * (0: overworld, 1: nether, 2: end). It shouldn't be considered if the client just joined
150 	 * the server instead of being transferred.
151 	 */
152 	ubyte dimension;
153 
154 	/**
155 	 * Client's view distance (or chunk radius).
156 	 */
157 	uint viewDistance;
158 
159 	/**
160 	 * Remote address of the player.
161 	 */
162 	Address clientAddress;
163 
164 	/**
165 	 * Address used by the client to connect to the server. The value of this field is the address
166 	 * the client has saved in its servers list. For example a client that joins through `localhost`
167 	 * and a client that joins through `127.0.0.1` will connect to the same server with the same ip
168 	 * but the field of this value will be different (`localhost` for the first client and
169 	 * `127.0.0.1` for the latter).
170 	 */
171 	ServerAddress serverAddress;
172 
173 	/**
174 	 * Client's skin, given by the client or downloaded from Mojang's services in online mode.
175 	 */
176 	Skin skin;
177 
178 	/**
179 	 * Client's language, in the same format as HubInfo's language field, which should be updated
180 	 * from the node when the client changes it.
181 	 */
182 	string language;
183 
184 	/**
185 	 * Example:
186 	 * ---
187 	 * // pocket
188 	 * {
189 	 *    "edu": false,
190 	 *    "GameVersion": "1.1.1.3",
191 	 *    "DeviceOS": 1,
192 	 *    "DeviceModel": "ONEPLUS A0001"
193 	 * }
194 	 * ---
195 	 */
196 	JSONValue gameData;
197 
198 	mixin IO!(reason, type, protocol, version_, uuid, username, displayName, permissionLevel, dimension, viewDistance, clientAddress, serverAddress, skin, language, gameData);
199 
200 }
201 
202 /**
203  * Removes a player from the node.
204  * If the player is removed using Kick or Transfer this packet is not sent.
205  */
206 @clientbound struct Remove {
207 
208 	enum ubyte ID = 26;
209 
210 	// reason
211 	enum : ubyte {
212 
213 		LEFT,			/// The player has closed the connection.
214 		TIMED_OUT,		/// The hub has closed the connection because didn't had any response from the client for too long.
215 		KICKED,			/// The player has been manually kicked.
216 		TRANSFERRED,	/// The player has been transferred by the hub
217 
218 	}
219 
220 	/**
221 	 * Player's unique id given by the hub.
222 	 */
223 	uint hubId;
224 
225 	/**
226 	 * Reason of the disconnection.
227 	 */
228 	ubyte reason;
229 
230 	mixin IO!(reason);
231 
232 }
233 
234 /**
235  * Kicks a player from the node and the whole server. When a player is disconnected
236  * from the node using this packet the hub will not send the Remove packet.
237  */
238 @serverbound struct Kick {
239 
240 	enum ubyte ID = 27;
241 
242 	/**
243 	 * Player to be kicked.
244 	 */
245 	uint hubId;
246 
247 	/**
248 	 * Reason of the disconnection that will be displayed in the client's
249 	 * disconnection screen.
250 	 */
251 	string reason;
252 
253 	/**
254 	 * Whether the previous string should be translated client-side or not.
255 	 */
256 	bool translation;
257 
258 	/**
259 	 * Optional parameters for the translation (Only for java clients).
260 	 */
261 	string[] parameters;
262 
263 	mixin IO!(reason, translation, parameters);
264 
265 }
266 
267 /**
268  * Transfers a player to another node. When a player is transferred from the node the hub
269  * will not send the Remove packet and there's no way, for the node, to know whether the
270  * player was disconnected or successfully transferred, if not using messages through a
271  * user-defined protocol.
272  */
273 @serverbound struct Transfer {
274 
275 	enum ubyte ID = 28;
276 
277 	// on fail
278 	enum : ubyte {
279 
280 		DISCONNECT,		/// Disconnect with `End of Stream` message.
281 		AUTO,			/// Connect to the first available node or disconnects if there isn't one.
282 		RECONNECT,		/// Connect to the same node, but as a new player.
283 
284 	}
285 
286 	/**
287 	 * Player to be transferred.
288 	 */
289 	uint hubId;
290 
291 	/**
292 	 * Id of the node that player will be transferred to. It should be an id of a
293 	 * connected node (which can be calculated using AddNode and RemoveNode packets),
294 	 * otherwise the player will be disconnected or moved to another node (see the following field).
295 	 */
296 	uint node;
297 
298 	/**
299 	 * Indicates the action to be taken when a transfer fails because the indicated node is
300 	 * not connected anymore or it cannot accept the given player's game type or protocol.
301 	 * If the indicated node is full the player will be simply disconnected with the `Server Full` message.
302 	 */
303 	ubyte onFail = DISCONNECT;
304 
305 	mixin IO!(node, onFail);
306 
307 }
308 
309 /**
310  * Updates the player's display name when it is changed by the node.
311  */
312 @serverbound struct UpdateDisplayName {
313 
314 	enum ubyte ID = 29;
315 
316 	/**
317 	 * Player's unique id given by the hub.
318 	 */
319 	uint hubId;
320 
321 	/**
322 	 * Player's display name that can contain formatting codes. Prefixes and suffixes should be avoided.
323 	 */
324 	string displayName;
325 
326 	mixin IO!(displayName);
327 
328 }
329 
330 /**
331  * Updates player's world. The player's dimension should be updated by
332  * the hub using worldId to identify the world added with AddWorld and
333  * removed with RemoveWorld.
334  */
335 @serverbound struct UpdateWorld {
336 
337 	enum ubyte ID = 30;
338 
339 	/**
340 	 * Player's unique id given by the hub.
341 	 */
342 	uint hubId;
343 
344 	/**
345 	 * World's id, that should have been previously added with the
346 	 * AddWorld packet.
347 	 */
348 	uint worldId;
349 
350 	mixin IO!(worldId);
351 
352 }
353 
354 @serverbound struct UpdatePermissionLevel {
355 	
356 	// permission level
357 	enum : ubyte {
358 		
359 		USER = 0,
360 		OPERATOR = 1,
361 		HOST = 2,
362 		AUTOMATION = 3,
363 		ADMIN = 4,
364 		
365 	}
366 
367 	enum ubyte ID = 31;
368 
369 	/**
370 	 * Player's unique id given by the hub.
371 	 */
372 	uint hubId;
373 
374 	ubyte permissionLevel;
375 
376 	mixin IO!(permissionLevel);
377 
378 }
379 
380 /**
381  * Notifies the node that the player's view distance has been updated client-side.
382  * The node may decide to not accept the new view distance and not send the required chunks.
383  */
384 @clientbound struct UpdateViewDistance {
385 
386 	enum ubyte ID = 32;
387 
388 	/**
389 	 * Player's unique id given by the hub.
390 	 */
391 	uint hubId;
392 
393 	/**
394 	 * Player's new view distance as indicated by the client.
395 	 */
396 	uint viewDistance;
397 
398 	mixin IO!(viewDistance);
399 
400 }
401 
402 /**
403  * Updates the player's language when the client changes it.
404  */
405 @clientbound struct UpdateLanguage {
406 
407 	enum ubyte ID = 33;
408 
409 	/**
410 	 * Player's unique id given by the hub.
411 	 */
412 	uint hubId;
413 
414 	/**
415 	 * Player's language in the same format as HubInfo's language field.
416 	 */
417 	string language;
418 
419 	mixin IO!(language);
420 
421 }
422 
423 /**
424  * Updates the latency between the player and the hub.
425  */
426 @clientbound struct UpdateLatency {
427 
428 	enum ubyte ID = 34;
429 
430 	/**
431 	 * Player's unique id given by the hub.
432 	 */
433 	uint hubId;
434 
435 	/**
436 	 * Player's latency in milliseconds. The latency between the client and the
437 	 * node is then calculated adding the latency between the node and the hub
438 	 * to this field's value.
439 	 */
440 	uint latency;
441 
442 	mixin IO!(latency);
443 
444 }
445 
446 /**
447  * Updates the packet loss between the player and the hub.
448  */
449 @clientbound struct UpdatePacketLoss {
450 
451 	enum ubyte ID = 35;
452 
453 	/**
454 	 * Player's unique id given by the hub.
455 	 */
456 	uint hubId;
457 	float packetLoss;
458 
459 	mixin IO!(packetLoss);
460 
461 }
462 
463 @clientbound @serverbound struct GamePacket {
464 	
465 	enum ubyte ID = 36;
466 
467 	/**
468 	 * Player's unique id given by the hub.
469 	 */
470 	uint hubId;
471 	ubyte[] payload;
472 	
473 	mixin IO!(payload);
474 	
475 }
476 
477 @serverbound struct SerializedGamePacket {
478 
479 	enum ubyte ID = 37;
480 
481 	/**
482 	 * Player's unique id given by the hub.
483 	 */
484 	uint hubId;
485 	ubyte[] payload;
486 
487 	mixin IO!(payload);
488 
489 }
490 
491 @serverbound struct OrderedGamePacket {
492 
493 	enum ubyte ID = 38;
494 
495 	/**
496 	 * Player's unique id given by the hub.
497 	 */
498 	uint hubId;
499 	uint order;
500 	ubyte[] payload;
501 
502 	mixin IO!(order, payload);
503 
504 }
505 
506 @clientbound @serverbound struct Packets {
507 
508 	enum ubyte ID = 39;
509 
510 	/**
511 	 * Player's unique id given by the hub.
512 	 */
513 	uint hubId;
514 	Tuple!(ubyte, "id", ubyte[], "payload")[] packets;
515 
516 	mixin IO!(packets);
517 
518 }
519 
520 unittest {
521 
522 	auto packets = Packets(42);
523 	UpdateDisplayName(42, "Steve").addTo(packets);
524 	UpdateViewDistance(42, 16).addTo(packets);
525 	import std.conv;
526 	assert(packets.encode() == [Packets.ID, 42, 2, UpdateDisplayName.ID, 6, 5, 'S', 't', 'e', 'v', 'e', UpdateViewDistance.ID, 1, 16]);
527 
528 	packets = Packets.fromBuffer([100, 3, UpdateLatency.ID, 1, 1, UpdateLatency.ID, 1, 2, UpdateLatency.ID, 2, 130, 1]);
529 	assert(packets.hubId == 100);
530 	foreach(packet ; packets.packets) {
531 		assert(packet.id == UpdateLatency.ID);
532 		assert(UpdateLatency.fromBuffer(packets.hubId, packet.payload).latency >= 1);
533 	}
534 
535 }