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 }