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