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 }