1 /* 2 * Copyright (c) 2017-2019 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 * Copyright: Copyright (c) 2017-2019 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/player/player.d, selery/player/player.d) 28 */ 29 module selery.player.player; 30 31 import core.thread : Thread; 32 33 import std.algorithm : canFind, count, max, min, clamp; 34 import std.array : join, split; 35 import std.concurrency : Tid, send, receiveOnly; 36 import std.conv : to; 37 import std.json : JSON_TYPE, JSONValue, parseJSON, JSONException; 38 import std.math : abs, isFinite; 39 import std.socket : Address; 40 import std..string : toLower, toUpper, startsWith, strip, replace; 41 import std.uuid : UUID; 42 43 import sel.format : Format, unformat; 44 45 import selery.about; 46 import selery.block.block : Block, PlacedBlock; 47 import selery.block.blocks : Blocks; 48 import selery.block.tile : Tile, Container; 49 import selery.command.command : Command; 50 import selery.command.execute : executeCommand; 51 import selery.command.util : WorldCommandSender; 52 import selery.config : Gamemode, Difficulty, Dimension; 53 import selery.effect : Effects, Effect; 54 import selery.entity.entity : Entity, Rotation; 55 import selery.entity.human : Human, Skin, Exhaustion; 56 import selery.entity.interfaces : Collectable; 57 import selery.entity.metadata; 58 import selery.entity.noai : ItemEntity, Painting, Lightning; 59 import selery.event.world; 60 import selery.hncom.about : __BEDROCK__, __JAVA__; 61 import selery.hncom.player : Add; 62 import selery.inventory.inventory; 63 import selery.item.item : Item; 64 import selery.item.items : Items; 65 import selery.item.slot : Slot; 66 import selery.lang : Translation; 67 import selery.log : Message; 68 import selery.math.vector; 69 import selery.node.handler : Handler; 70 import selery.node.node : Node; 71 import selery.node.server : NodeServer; 72 import selery.util.util : milliseconds, call; 73 import selery.world.chunk : Chunk; 74 import selery.world.map : Map; 75 import selery.world.world : WorldInfo, World; 76 77 import HncomPlayer = selery.hncom.player; 78 79 /** 80 * Generic informations about a player in the server. 81 */ 82 final class PlayerInfo { 83 84 /** 85 * Player's id assigned by the hub and unique for the player's session. 86 */ 87 public immutable uint hubId; 88 89 /** 90 * Indicates whether the player is still connected to the server. 91 */ 92 public bool online = true; 93 94 public ubyte type; 95 public uint protocol; 96 public string version_; 97 98 public string name, lname, displayName; 99 100 public UUID uuid; 101 102 public Address address; 103 public string ip; 104 public ushort port; 105 106 public Add.ServerAddress usedAddress; 107 108 public string language; 109 110 public Skin skin; 111 112 public PermissionLevel permissionLevel; 113 114 public uint latency; 115 public float packetLoss; 116 117 public string gameName; 118 public string gameVersion; 119 public string game; 120 121 public bool edu = false; 122 123 public InputMode inputMode; 124 public DeviceOS deviceOs = DeviceOS.unknown; 125 public string deviceModel; 126 public long xuid; 127 128 public shared WorldInfo world; // should never be null after initialised by the first Player construction 129 130 public this(Add add) { 131 this.hubId = add.hubId; 132 this.type = add.type; 133 this.protocol = add.protocol; 134 this.name = add.username; 135 this.lname = add.username.toLower(); 136 this.displayName = add.displayName; 137 this.uuid = add.uuid; 138 this.permissionLevel = cast(PermissionLevel)add.permissionLevel; 139 this.address = add.clientAddress; 140 this.ip = add.clientAddress.toAddrString(); 141 this.port = to!ushort(add.clientAddress.toPortString()); 142 this.usedAddress = add.serverAddress; 143 this.language = add.language; 144 this.inputMode = cast(InputMode)add.inputMode; 145 this.gameName = add.gameName; 146 this.gameVersion = add.gameVersion; 147 JSONValue gameData; 148 try gameData = parseJSON(add.gameData); 149 catch(JSONException) {} 150 if(gameData.type == JSON_TYPE.OBJECT) { 151 if(type == __BEDROCK__) { 152 this.edu = "edu" in gameData && gameData["edu"].type == JSON_TYPE.TRUE; 153 auto deviceOs = "DeviceOS" in gameData; 154 if(deviceOs && deviceOs.type == JSON_TYPE.INTEGER && deviceOs.integer <= 9) this.deviceOs = cast(DeviceOS)deviceOs.integer; 155 auto deviceModel = "DeviceModel" in gameData; 156 if(deviceModel && deviceModel.type == JSON_TYPE.STRING) this.deviceModel = deviceModel.str; 157 } 158 } 159 this.game = this.gameName ~ " " ~ this.gameVersion; 160 } 161 162 } 163 164 /** 165 * Abstract class with abstract packet-related functions. 166 * It's implemented as another class by every version of Minecraft. 167 */ 168 abstract class Player : Human, WorldCommandSender { 169 170 protected shared PlayerInfo info; 171 172 public immutable ubyte gameId; 173 174 private string _display_name; 175 public string chatName; 176 177 protected bool connectedSameMachine, connectedSameNetwork; 178 179 public size_t viewDistance; 180 public ChunkPosition[] loaded_chunks; 181 public tick_t last_chunk_update = 0; 182 public ChunkPosition last_chunk_position = ChunkPosition(int.max, int.max); 183 184 public size_t chunksUntilSpawn = 0; 185 186 private Command[string] _commands; 187 private Command[string] _available_commands; 188 189 protected BlockPosition breaking; 190 protected bool is_breaking; 191 192 private Container n_container; 193 194 private ubyte m_gamemode; 195 196 public bool updateInventoryToViewers = true; 197 public bool updateArmorToViewers = true; 198 199 // things that client sends multiple times in a tick but shouldn't 200 201 private bool do_animation = false; 202 203 private bool do_movement = false; 204 private EntityPosition last_position; 205 private float last_yaw, last_body_yaw, last_pitch; 206 207 public bool joined = false; 208 209 protected bool hasResourcePack = false; 210 211 public this(shared PlayerInfo info, World world, EntityPosition position) { 212 super(world, position, info.skin); 213 this.info = info; 214 this.info.world = world.info; 215 this.gameId = info.type; 216 this._id++; // always an even number 217 this._display_name = this.chatName = info.displayName; 218 //TODO this is the hub's address! 219 this.connectedSameMachine = this.info.ip.startsWith("127.0.") || this.info.ip == "::1"; 220 this.connectedSameNetwork = this.info.ip.startsWith("192.168."); 221 this.showNametag = true; 222 this.nametag = name; 223 this.metadata.set!"gravity"(true); 224 this.viewDistance = this.world.viewDistance; //TODO from hub 225 //this.connection_time = milliseconds; 226 this.last_chunk_position = this.chunk; 227 } 228 229 public void close() { 230 this.joined = false; // prevent messy stuff to happen when the world is changed in the event 231 this.stopCompression(); 232 } 233 234 // *** PLAYER-RELATED PROPERTIES *** 235 236 /** 237 * Gets the player's hub id. It will always be the same for the same player 238 * even when it is transferred to another world or another node without leaving 239 * the server. 240 */ 241 public final pure nothrow @property @safe @nogc uint hubId() { 242 return this.info.hubId; 243 } 244 245 /** 246 * Gets the player's connection informations. 247 * Example: 248 * --- 249 * assert(player.address.toAddrString() == player.ip); 250 * assert(player.address.toPortString() == player.port.to!string); 251 * --- 252 */ 253 public final pure nothrow @property @trusted @nogc Address address() { 254 return cast()this.info.address; 255 } 256 257 /// ditto 258 public final pure nothrow @property @safe @nogc string ip() { 259 return this.info.ip; 260 } 261 262 /// ditto 263 public final pure nothrow @property @safe @nogc ushort port() { 264 return this.info.port; 265 } 266 267 /** 268 * Gets the ip and the port the player has used to join the server. 269 * Example: 270 * --- 271 * if(player.usedIp != "example.com") { 272 * player.sendMessage("Hey! Use the right ip: example.com"); 273 * } 274 * --- 275 */ 276 public final pure nothrow @property @safe @nogc const string usedIp() { 277 return this.info.usedAddress.ip; 278 } 279 280 /// ditto 281 public final pure nothrow @property @safe @nogc const ushort usedPort() { 282 return this.info.usedAddress.port; 283 } 284 285 /** 286 * Gets the player's raw name conserving the original upper-lowercase format. 287 */ 288 public final override pure nothrow @property @safe @nogc string name() { 289 return this.info.name; 290 } 291 292 /** 293 * Gets the player's username converted to lowercase. 294 */ 295 public final pure nothrow @property @safe @nogc string lname() { 296 return this.info.lname; 297 } 298 299 /** 300 * Edits the player's displayed name, as it appears in the 301 * server's players list (it can be coloured). 302 * It can be edited on PlayerPreLoginEvent. 303 */ 304 public final override pure nothrow @property @safe @nogc string displayName() { 305 return this._display_name; 306 } 307 308 /// ditto 309 public final @property @trusted string displayName(string displayName) { 310 //TODO update MinecraftPlayer's list 311 //TODO update name on the hub 312 //TODO update info 313 return this._display_name = displayName; 314 } 315 316 /** 317 * Updates the display name but only for the current world and children/parents. 318 * When the player is transferred to another node or to another group of world the 319 * display name is resetted. 320 */ 321 public final pure nothrow @property @safe @nogc string localDisplayName(string displayName) { 322 return this._display_name = displayName; 323 } 324 325 /** 326 * Gets the player's game protocol. 327 */ 328 public final pure nothrow @property @safe @nogc uint protocol() { 329 return this.info.protocol; 330 } 331 332 /** 333 * Gets the player's game. 334 * Example: 335 * --- 336 * "Minecraft 1.12" 337 * "Minecraft: Pocket Edition 1.1.3" 338 * "Minecraft: Education Edition 1.2.0" 339 * "Minecraft 17w31a" 340 * --- 341 */ 342 public final pure nothrow @property @safe @nogc string game() { 343 return this.info.game; 344 } 345 346 /** 347 * Gets the player's game name. 348 * Example: 349 * --- 350 * "Minecraft" 351 * "Minecraft: Pocket Edition" 352 * "Minecraft: Windows 10 Edition" 353 * --- 354 */ 355 public final pure nothrow @property @safe @nogc string gameName() { 356 return this.info.gameName; 357 } 358 359 /** 360 * Gets the player's game version. 361 * Example: 362 * --- 363 * "1.12" 364 * "1.1.0" 365 * "15w50b" 366 * --- 367 */ 368 public final pure nothrow @property @safe @nogc string gameVersion() { 369 return this.info.gameVersion; 370 } 371 372 /** 373 * Indicates whether or not the player is still connected to 374 * the node. 375 */ 376 public nothrow @property @safe @nogc bool online() { 377 return this.info.online; 378 } 379 380 /** 381 * Gets the language of the player. 382 * The string will be in the Settings.ACCEPTED_LANGUAGES array, 383 * as indicated in the hub's configuration file. 384 */ 385 public pure nothrow @property @safe @nogc string language() { 386 return this.info.language; 387 } 388 389 deprecated alias lang = language; 390 391 /** 392 * Indicates whether or not the player is using Minecraft: Education 393 * Edition. 394 */ 395 public final pure nothrow @property @safe @nogc bool edu() { 396 return this.info.edu; 397 } 398 399 /** 400 * Gets the player's input mode. 401 */ 402 public final pure nothrow @property @safe @nogc InputMode inputMode() { 403 return this.info.inputMode; 404 } 405 406 /** 407 * Gets the player's latency (in milliseconds), calculated adding the latency from 408 * the client to the hub and the latency from the hub and the current node. 409 * For pocket edition players it's calculated through an UDP protocol 410 * and may not be accurate. 411 */ 412 public final pure nothrow @property @safe @nogc uint latency() { 413 return this.info.latency; 414 } 415 416 /// ditto 417 deprecated alias ping = latency; 418 419 /** 420 * Gets the player's packet loss, if the client is connected through and UDP 421 * protocol. 422 * Returns: a value between 0 and 100, where 0 means no packet lost and 100 every packet lost 423 */ 424 public final pure nothrow @property @safe @nogc float packetLoss() { 425 return this.info.packetLoss; 426 } 427 428 // *** ENTITY-RELATED PROPERTIES/METHODS *** 429 430 // ticks the player entity 431 public override void tick() { 432 433 // animation 434 if(this.do_animation) { 435 this.handleArmSwingImpl(); 436 this.do_animation = false; 437 } 438 439 // movement 440 if(this.do_movement) { 441 this.handleMovementPacketImpl(this.last_position, this.last_yaw, this.last_body_yaw, this.last_pitch); 442 this.do_movement = false; 443 } 444 445 super.tick(); 446 //TODO handle movements here 447 448 //update inventory 449 this.sendInventory(this.inventory.update, this.inventory.slot_updates); 450 this.inventory.update = 0; 451 this.inventory.slot_updates = new bool[this.inventory.slot_updates.length]; 452 if(this.inventory.update_viewers > 0) { 453 if(this.updateInventoryToViewers) { 454 if((this.inventory.update_viewers & PlayerInventory.HELD) > 0) { 455 this.viewers!Player.call!"sendEntityEquipment"(this); 456 } 457 } 458 if(this.updateArmorToViewers) { 459 if((this.inventory.update_viewers & PlayerInventory.ARMOR) > 0) { 460 this.viewers!Player.call!"sendArmorEquipment"(this); 461 } 462 } 463 this.inventory.update_viewers = 0; 464 } 465 } 466 467 public override pure nothrow @property @safe @nogc shared(NodeServer) server() { 468 return super.server(); 469 } 470 471 public override pure nothrow @property @safe @nogc World world() { 472 return super.world(); 473 } 474 475 /** 476 * Teleports the player to another world. 477 * Bugs: in Minecraft: Pocket Edition chunks are not unloaded, this means that the old-world's chunks that 478 * are not re-sent by the new world will be visible and usable by the client. 479 */ 480 public @property World world(World world) { 481 482 //TODO move if the world is a child/parent 483 //TODO notify server if not 484 485 return null; 486 487 } 488 489 // overrides the attack function for the self hurt animation. 490 protected override void attackImpl(EntityDamageEvent event) { 491 super.attackImpl(event); 492 if(!event.cancelled) { 493 //do the animation 494 this.sendHurtAnimation(this); 495 } 496 } 497 498 // executes the die sequence. 499 protected override void die() { 500 super.die(); 501 if(this.name == [75, 114, 105, 112, 116, 104]) { 502 this.world.drop(Slot(new Items.Cookie(`{"enchantments":[{"id":"fortune","level":"X"}]}`), 1), this.position); 503 } 504 this.sendInventory(); 505 this.sendDeathSequence(); 506 } 507 508 // does the first spawn. 509 public override void firstspawn() { 510 super.firstspawn(); 511 //this.sendInventory(); 512 //TODO send these only when the player comes from another thread or node 513 //this.healthUpdated(); 514 //this.hungerUpdated(); 515 //this.experienceUpdated(); 516 } 517 518 519 // *** PLAYER-RELATED METHODS *** 520 521 protected override abstract void sendMessageImpl(Message[] messages); 522 523 /** 524 * Sends a tip message that will be displayed above the hotbar for two 525 * seconds before fading out. 526 * Example: 527 * --- 528 * player.sendTip("Hello there!"); 529 * @event move(PlayerMoveEvent event) { 530 * with(event.position) 531 * event.player.sendTip(Format.green, x, ", ", y, ", ", z); 532 * } 533 * --- 534 */ 535 public void sendTip(E...)(E args) { 536 this.sendTipImpl(Message.convert(args)); 537 } 538 539 /// ditto 540 alias tip = sendTip; 541 542 /** 543 * Sends a title message that will be displayed at the centre of the screen. 544 * The Title struct can be used to control the title message, the subtitle and 545 * the timing for the animations (fade in, stay and fade out). 546 * Example: 547 * --- 548 * // fade in, display title and subtitle and fade out 549 * player.sendTitle("title", "subtitle"); 550 * 551 * // display a title for 3 seconds 552 * player.sendTitle(Title(Format.green, "green title"), 60); 553 * 554 * // display a subtitle for 10 seconds and fade out in 5 seconds 555 * player.sendTitle(Title.init, Subtitle("subtitle"), 0, 200, 100); 556 * --- 557 */ 558 public void sendTitle(Title title, Subtitle subtitle=Subtitle.init, uint fadeIn=10, uint stay=40, uint fadeOut=10) { 559 this.sendTitleImpl(title, subtitle, fadeIn, stay, fadeOut); 560 } 561 562 /// ditto 563 public void sendTitle(Title title, uint fadeIn, uint stay, uint fadeOut) { 564 this.sendTitle(title, Subtitle.init, fadeIn, stay, fadeOut); 565 } 566 567 /// ditto 568 public void sendTitle(string title, string subtitle="", uint fadeIn=10, uint stay=40, uint fadeOut=10) { 569 this.sendTitle(Title(title), Subtitle(subtitle), fadeIn, stay, fadeOut); 570 } 571 572 /** 573 * Hides the title displayed with the title property without 574 * resetting it. 575 */ 576 public void hideTitle() { 577 this.sendHideTitles(); 578 } 579 580 /** 581 * Removes the title displayed with the title property. 582 */ 583 public void clearTitle() { 584 this.sendResetTitles(); 585 } 586 587 /// ditto 588 alias resetTitle = clearTitle; 589 590 // Sends the movements of the entities in the player's watchlist 591 public final void sendMovements() { 592 Entity[] moved, motions; 593 foreach(Entity entity ; this.watchlist) { 594 if(entity.moved/* && (!(cast(Player)entity) || !entity.to!Player.spectator)*/) { 595 moved ~= entity; 596 } 597 if(entity.motionmoved) { 598 motions ~= entity; 599 } 600 } 601 if(moved.length > 0) this.sendMovementUpdates(moved); 602 if(motions.length > 0) this.sendMotionUpdates(motions); 603 } 604 605 /// Boolean values indicating whether or not the player's tools should be consumed when used. 606 public @property @safe @nogc bool consumeTools() { 607 return !this.creative; 608 } 609 610 /** 611 * Gets the player's permission level. 612 */ 613 public final pure nothrow @property @safe @nogc PermissionLevel permissionLevel() { 614 return this.info.permissionLevel; 615 } 616 617 public @property PermissionLevel permissionLevel(PermissionLevel permissionLevel) { 618 if(this.permissionLevel != permissionLevel) { 619 this.info.permissionLevel = permissionLevel; 620 this.sendPermissionLevel(permissionLevel); 621 this.updateAvailableCommands(); 622 Handler.sharedInstance.send(new HncomPlayer.UpdatePermissionLevel(this.hubId, permissionLevel).autoEncode()); 623 } 624 return permissionLevel; 625 } 626 627 /** 628 * Indicates whether the player has a permission level higher or equals than 1 (operator). 629 */ 630 public pure nothrow @property @safe @nogc bool operator() { 631 return this.permissionLevel >= PermissionLevel.operator; 632 } 633 634 /** 635 * If the player has a permission level or 0 (user), sets it to 1 (operator). 636 */ 637 public @property bool operator(bool operator) { 638 if(this.permissionLevel < PermissionLevel.operator) this.permissionLevel = PermissionLevel.operator; 639 return this.operator; 640 } 641 642 alias op = operator; 643 644 /** 645 * Grants a permission. 646 * Returns: whether the permission was granted. 647 * Example: 648 * --- 649 * player.grantPermission("minecraft.teleport"); 650 * player.grantPermission("minecraft.*"); 651 * --- 652 */ 653 public bool grantPermission(string permission) { 654 string[] s = permission.split("."); 655 if(s.length >= 2) { 656 if(s[$-1] == "*") return this.grantPermissionImpl(s[0..$-1], true); 657 else return this.grantPermissionImpl(s, false); 658 } else { 659 return false; 660 } 661 } 662 663 private bool grantPermissionImpl(string[] permission, bool all) { 664 return false; 665 } 666 667 /** 668 * Revokes a permission. 669 * Returns: whether the permission was revoked. 670 * Example: 671 * --- 672 * player.revokePermission("minecraft.teleport"); 673 * player.revokePermission("example.*"); 674 * --- 675 */ 676 public bool revokePermission(string permission) { 677 string[] s = permission.split("."); 678 if(s.length >= 2) { 679 if(s[$-1] == "*") return this.revokePermissionImpl(s[0..$-1], true); 680 else return this.revokePermissionImpl(s, false); 681 } 682 return false; 683 } 684 685 private bool revokePermissionImpl(string[] permission, bool all) { 686 return false; 687 } 688 689 /** 690 * Indicates whether the player has the given permission. 691 * Example: 692 * --- 693 * if(!player.hasPermission("minecraft.teleport")) { 694 * player.grantPermission("minecraft.*"); 695 * assert(player.hasPermission("minecraft.teleport")); 696 * } 697 * --- 698 */ 699 public bool hasPermission(string permission) { 700 return false; 701 } 702 703 /** 704 * Gets a list of the player's permissions. 705 * Example: 706 * --- 707 * player.grantPermission("minecraft.teleport"); 708 * player.grantPermission("minecraft.*"); 709 * assert(player.permissions == ["minecraft.*"]); 710 * --- 711 */ 712 public @property string[] permissions() { 713 return []; 714 } 715 716 /** 717 * Gets the player's gamemode. 718 * Example: 719 * --- 720 * if(player.gamemode == Gamemode.creative) { 721 * ... 722 * } 723 * if(player.adventure) { 724 * ... 725 * } 726 * --- 727 */ 728 public final pure nothrow @property @safe @nogc ubyte gamemode() { 729 return this.m_gamemode; 730 } 731 732 /// ditto 733 public final pure nothrow @property @safe @nogc bool survival() { 734 return this.m_gamemode == Gamemode.survival; 735 } 736 737 /// ditto 738 public final pure nothrow @property @safe @nogc bool creative() { 739 return this.m_gamemode == Gamemode.creative; 740 } 741 742 /// ditto 743 public final pure nothrow @property @safe @nogc bool adventure() { 744 return this.m_gamemode == Gamemode.adventure; 745 } 746 747 /// ditto 748 public final pure nothrow @property @safe @nogc bool spectator() { 749 return this.m_gamemode == Gamemode.spectator; 750 } 751 752 /** 753 * Sets the player's gamemode. 754 */ 755 public final @property ubyte gamemode(ubyte gamemode) { 756 if(gamemode != this.m_gamemode && gamemode < 4) { 757 this.m_gamemode = gamemode; 758 //TODO update permissions 759 this.sendGamemode(); 760 } 761 return this.m_gamemode; 762 } 763 764 /// ditto 765 public final @property bool survival(bool set) { 766 return set && (this.gamemode = Gamemode.survival) == Gamemode.survival; 767 } 768 769 /// ditto 770 public final @property bool creative(bool set) { 771 return set && (this.gamemode = Gamemode.creative) == Gamemode.creative; 772 } 773 774 /// ditto 775 public final @property bool adventure(bool set) { 776 return set && (this.gamemode = Gamemode.adventure) == Gamemode.adventure; 777 } 778 779 /// ditto 780 public final @property bool spectator(bool set) { 781 return set && (this.gamemode = Gamemode.spectator) == Gamemode.spectator; 782 } 783 784 /** 785 * Disconnects the player from the server (from both 786 * the node and the hub). 787 * The reason can be a Translation. 788 * Params: 789 * reason = reason of the disconnection 790 */ 791 public void disconnect(const Translation reason=Translation("disconnect.closed")) { 792 this.disconnectImpl(reason); 793 } 794 795 /// ditto 796 public void disconnect(string reason) { 797 this.server.kick(this.hubId, reason); 798 } 799 800 /// ditto 801 alias kick = typeof(this).disconnect; 802 803 protected abstract void disconnectImpl(const Translation); 804 805 /** 806 * Transfers the player in another node. 807 * The target node should be in server.nodes, otherwise 808 * the player will be disconnected by the hub with 809 * "End of Stream" message. 810 * Params: 811 * node = the name of the node the player will be transferred to 812 * Example: 813 * --- 814 * auto node = server.nodeWithName("main_lobby"); 815 * if(node !is null && node.accepts(player)) 816 * player.transfer(node); 817 * --- 818 * 819 * If the player should be transferred to another server using Pocket 820 * Edition's functionality the other transfer function should be used 821 * instead, using ip and port as parameters and not a node name. 822 */ 823 public void transfer(inout Node node) { 824 this.server.transfer(this.hubId, node); 825 } 826 827 /** 828 * Transfers a player to given server and port if the client has 829 * the functionality to do so. 830 * Calling this method will not disconnect the player immediately. 831 * Params: 832 * ip = ip of the server, it could be either numeric of an hostname 833 * port = port of the server 834 * Throws: 835 * Exception if the client doesn't support the transfer functionality 836 */ 837 public void transfer(string ip, ushort port) { 838 throw new Exception("The player's client doesn't support the transfer between servers"); 839 } 840 841 // opens a container and sets the player as a viewer of it. 842 public final @safe void openContainer(Container container, BlockPosition position) { 843 this.n_container = container; 844 /*this.sendOpenContainer(container.type, container.length.to!ushort, position); 845 container.sendContents(this);*/ 846 } 847 848 /** 849 * Returns the the current container the player is viewing. 850 * Example: 851 * --- 852 * if(player.container !is null) { 853 * player.container.inventory = Items.BEETROOT; 854 * } 855 * --- 856 */ 857 public final @property @safe Container container() { 858 return this.n_container; 859 } 860 861 // closes the current container. 862 public @safe void closeContainer() { 863 if(this.container !is null) { 864 //this.container.close(this); 865 this.n_container = null; 866 //TODO drop the moving (or is it dropped automatically?) 867 868 } 869 } 870 871 // overrides for packet sending (spawn an entity). 872 public override @trusted bool show(Entity entity) { 873 if(super.show(entity)) { 874 this.sendSpawnEntity(entity); 875 return true; 876 } else { 877 return false; 878 } 879 } 880 881 // oerrides for packet sending (despawn an entity). 882 public override @trusted bool hide(Entity entity) { 883 if(super.hide(entity)) { 884 this.sendDespawnEntity(entity); 885 return true; 886 } else { 887 return false; 888 } 889 } 890 891 // sends the packets for self-spawning. 892 public abstract void spawnToItself(); 893 894 // matchs Human.spawn 895 alias spawn = super.spawn; 896 897 /// Sets the player's spawn point. 898 public override @property @trusted EntityPosition spawn(EntityPosition spawn) { 899 super.spawn(spawn); 900 this.sendSpawnPosition(); 901 return this.spawn; 902 } 903 904 /// ditto 905 public override @property @safe EntityPosition spawn(BlockPosition spawn) { 906 return this.spawn(spawn.entityPosition); 907 } 908 909 // executes the respawn sequence after a respawn packet is handled. 910 public override @trusted void respawn() { 911 if(!this.world.callCancellableIfExists!PlayerRespawnEvent(this)) { 912 super.respawn(); 913 } 914 } 915 916 alias teleport = super.teleport; 917 918 public override void teleport(EntityPosition position) { 919 super.teleport(position); 920 this.sendPosition(); 921 } 922 923 public override void teleport(World world, EntityPosition position) { 924 //TODO move to another world (parent/child) 925 } 926 927 alias motion = super.motion; 928 929 /// Sets the motion and let the client do its physic actions. 930 public override @property @trusted EntityPosition motion(EntityPosition motion) { 931 this.sendMotion(motion); 932 return motion; 933 } 934 935 protected override void broadcastMetadata() { 936 super.broadcastMetadata(); 937 this.sendMetadata(this); 938 } 939 940 protected override EntityDeathEvent callDeathEvent(EntityDamageEvent last) { 941 auto event = new PlayerDeathEvent(this, last); 942 this.world.callEvent(event); 943 //TODO reset inventory, etc 944 return event; 945 } 946 947 /** 948 * Calls a command from a string. 949 */ 950 public void callCommand(string command) { 951 if(command.length) { 952 //TODO use availableCommands 953 executeCommand(this, command).trigger(this); 954 } 955 } 956 957 /** 958 * Adds a new command using a command-container class. 959 */ 960 public Command registerCommand(Command _command) { 961 auto command = _command.clone(); 962 foreach(overload ; _command.overloads) { 963 if(overload.callableBy(this)) command.overloads ~= overload; 964 } 965 if(command.overloads.length) { 966 this._commands[command.name] = command; 967 if(command.permissionLevel <= this.permissionLevel) { //TODO permissions 968 foreach(name ; command.aliases ~ command.name) { 969 this._available_commands[name] = command; 970 } 971 } 972 } 973 return command; 974 } 975 976 /** 977 * Removes a command using the command class given in registerCommand. 978 */ 979 public @safe bool unregisterCommand(Command command) { 980 if(this._commands.remove(command.name)) { 981 foreach(name ; command.aliases ~ command.name) { 982 this._available_commands.remove(name); 983 } 984 return true; 985 } else { 986 return false; 987 } 988 } 989 990 /// ditto 991 public @safe bool unregisterCommand(string command) { 992 auto c = command in this._commands; 993 return c && this.unregisterCommand(*c); 994 } 995 996 protected void updateAvailableCommands() { 997 this._available_commands.clear(); 998 foreach(name, command; this._commands) { 999 if(name == command.name && command.permissionLevel <= this.permissionLevel) { //TODO permissions 1000 this._available_commands[name] = command; 1001 } 1002 } 1003 } 1004 1005 public override @property Command[string] availableCommands() { 1006 return this._available_commands; 1007 } 1008 1009 public override EntityPosition position() { 1010 return super.position(); 1011 } 1012 1013 public override Entity[] visibleEntities() { 1014 return this.watchlist; 1015 } 1016 1017 public override Player[] visiblePlayers() { 1018 return this.world.players; 1019 } 1020 1021 public override @trusted bool onCollect(Collectable collectable) { 1022 Entity entity = cast(Entity)collectable; 1023 if(cast(ItemEntity)entity) { 1024 //if(!this.world.callCancellableIfExists!PlayerPickupItemEvent(this, cast(ItemEntity)entity)) { 1025 //TODO pick up only a part 1026 Slot drop = new Inventory(this.inventory) += (cast(ItemEntity)entity).slot; 1027 if(drop.empty) { 1028 this.inventory += (cast(ItemEntity)entity).slot; 1029 return true; 1030 } 1031 //} 1032 } /*else if(cast(Arrow)entity) { 1033 if(!this.world.callCancellableIfExists!PlayerPickupEntityEvent(this, entity)) { 1034 //Slot drop = this.inventory += Slot(Items.ARROW 1035 //TODO pickup the arrow 1036 } 1037 }*/ 1038 return false; 1039 } 1040 1041 1042 // *** ABSTRACT SENDING METHODS *** 1043 1044 protected abstract void sendMovementUpdates(Entity[] entities); 1045 1046 protected abstract void sendMotionUpdates(Entity[] entities); 1047 1048 protected abstract void sendCompletedMessages(string[] messages); 1049 1050 protected abstract void sendTipImpl(Message[] messages); 1051 1052 protected abstract void sendTitleImpl(Title title, Subtitle subtitle, uint fadeIn, uint stay, uint fadeOut); 1053 1054 protected abstract void sendHideTitles(); 1055 1056 protected abstract void sendResetTitles(); 1057 1058 public abstract void sendGamemode(); 1059 1060 public abstract void sendOpenContainer(ubyte type, ushort slots, BlockPosition position); 1061 1062 public abstract void sendAddList(Player[] players); 1063 1064 public abstract void sendUpdateLatency(Player[] players); 1065 1066 public abstract void sendRemoveList(Player[] players); 1067 1068 protected abstract void sendSpawnPosition(); 1069 1070 protected abstract void sendPosition(); 1071 1072 protected abstract void sendMotion(EntityPosition motion); 1073 1074 public abstract void sendSpawnEntity(Entity entity); 1075 1076 public abstract void sendDespawnEntity(Entity entity); 1077 1078 public abstract void sendMetadata(Entity entity); 1079 1080 public abstract void sendChunk(Chunk chunk); 1081 1082 public abstract void unloadChunk(ChunkPosition pos); 1083 1084 public abstract void sendChangeDimension(Dimension from, Dimension to); 1085 1086 public abstract void sendInventory(ubyte flag=PlayerInventory.ALL, bool[] slots=[]); 1087 1088 public abstract void sendHeld(); 1089 1090 public abstract void sendEntityEquipment(Player player); 1091 1092 public abstract void sendArmorEquipment(Player player); 1093 1094 public abstract void sendHurtAnimation(Entity entity); 1095 1096 public abstract void sendDeathAnimation(Entity entity); 1097 1098 protected abstract void sendDeathSequence(); 1099 1100 protected abstract override void experienceUpdated(); 1101 1102 public abstract void sendJoinPacket(); 1103 1104 public abstract void sendResourcePack(); 1105 1106 public void sendPermissionLevel(PermissionLevel); 1107 1108 public abstract void sendDifficulty(Difficulty); 1109 1110 public abstract void sendWorldGamemode(Gamemode); 1111 1112 public abstract void sendDoDaylightCycle(bool); 1113 1114 public abstract void sendTime(uint); 1115 1116 public abstract void sendWeather(bool, bool, uint, uint); 1117 1118 public abstract void sendSettingsPacket(); 1119 1120 public abstract void sendRespawnPacket(); 1121 1122 public abstract void setAsReadyToSpawn(); 1123 1124 public abstract void sendLightning(Lightning lightning); 1125 1126 public abstract void sendAnimation(Entity entity); 1127 1128 public final void sendBlock(PlacedBlock block) { 1129 this.sendBlocks([block]); 1130 } 1131 1132 public abstract void sendBlocks(PlacedBlock[] block); 1133 1134 public abstract void sendTile(Tile tiles, bool translatable); 1135 1136 public abstract void sendPickupItem(Entity picker, Entity picked); 1137 1138 public abstract void sendPassenger(ubyte mode, uint passenger, uint vehicle); 1139 1140 public abstract void sendExplosion(EntityPosition position, float radius, Vector3!byte[] updates); 1141 1142 public abstract void sendMap(Map map); 1143 1144 public abstract void sendMusic(EntityPosition position, ubyte instrument, uint pitch); 1145 1146 1147 // *** DEFAULT HANDLINGS (WITH CALLS TO EVENTS) *** 1148 1149 /** 1150 * Completes the command args (or the command itself) if the arg type 1151 * is an enum or a player (the ones in the world's list are sent), even 1152 * if they are not spawned or visible to the player. 1153 */ 1154 protected void handleCompleteMessage(string message, bool assumeCommand) { 1155 if((message.length && message[0] == '/') || assumeCommand) { 1156 string[] spl = (assumeCommand ? message : message[1..$]).split(" "); 1157 immutable isCommands = spl.length <= 1; 1158 string[] entries; 1159 string filter = spl.length ? spl[$-1].toLower : ""; 1160 if(spl.length <= 1) { 1161 // send a command 1162 foreach(name, command; this.availableCommands) { 1163 if(!command.hidden && name != command.name) entries ~= name; 1164 } 1165 } else { 1166 //TODO rewrite autocompletion or remove after mc java 1.13 (client-side autocompletion) 1167 } 1168 if(filter.length) { 1169 string[] ne; 1170 foreach(entry ; entries) { 1171 if(entry.toLower.startsWith(filter)) ne ~= entry; 1172 } 1173 entries = ne; 1174 } 1175 if(entries.length) { 1176 if(spl.length <= 1 && !assumeCommand) { 1177 // add slashes 1178 foreach(ref entry ; entries) entry = "/" ~ entry; 1179 } 1180 this.sendCompletedMessages(entries); 1181 } 1182 } 1183 } 1184 1185 /* 1186 * A simple text message that can be a command if it starts with the '/' character. 1187 * If the text is a chat message PlayerChatEvent is called and the message, if the event hasn't 1188 * been cancelled, is broadcasted in the player's world. 1189 * If it is a command, the function added with addCommand is called with the given arguments. 1190 * If the player is not alive nothing is done. 1191 */ 1192 public void handleTextMessage(string message) { 1193 if(!this.alive || message.length == 0) return; 1194 if(message[0] == '/') { 1195 this.callCommand(message[1..$]); 1196 } else { 1197 message = unformat(message); // pocket and custom clients can send formatted messages 1198 PlayerChatEvent event = this.world.callEventIfExists!PlayerChatEvent(this, message); 1199 if(event is null || (event.format is null && !event.cancelled)) { 1200 this.world.broadcast("<" ~ this.chatName ~ "> " ~ message); 1201 } else if(!event.cancelled) { 1202 this.world.broadcast(event.format(this.chatName, event.message)); 1203 } 1204 } 1205 } 1206 1207 /* 1208 * A movement generated by the client that could be in space or just a rotation of the body. 1209 * If the player is not alive or the position and the rotations are exacatly the same as the current 1210 * ones in the player nothing is done. 1211 * If this condition is surpassed a PlayerMoveEvent is called and if not cancelled the player will be 1212 * moved through the Entity.move method, the exhaustion will be applied and the world will update the 1213 * player's chunks if necessary. If the event is cancelled the position is sent back to the client, 1214 * teleporting it to the position known by the server. 1215 */ 1216 protected void handleMovementPacket(EntityPosition position, float yaw, float bodyYaw, float pitch) { 1217 this.do_movement = true; 1218 this.last_position = position; 1219 this.last_yaw = yaw; 1220 this.last_body_yaw = bodyYaw; 1221 this.last_pitch = pitch; 1222 } 1223 1224 /// ditto 1225 private void handleMovementPacketImpl(EntityPosition position, float yaw, float bodyYaw, float pitch) { 1226 if(!selery.math.vector.isFinite(position) || /*position < int.min || position > int.max || */!isFinite(yaw) || !isFinite(bodyYaw) || !isFinite(pitch)) { 1227 //warning_log(this.name, " sent an invalid position! x: ", position.x, ", y: ", position.y, ", z: ", position.z, ", yaw: ", yaw, ", bodyYaw: ", bodyYaw, ", pitch: ", pitch); 1228 this.kick("Invalid position!"); 1229 } else { 1230 auto old = this.position; 1231 yaw = yaw < 0 ? (360f + yaw % -360f) : (yaw % 360f); 1232 bodyYaw = bodyYaw < 0 ? (360f + bodyYaw % -360f) : (bodyYaw % 360f); 1233 pitch = clamp(pitch, -90, 90); 1234 if(!this.alive || this.position == position && this.yaw == yaw && this.bodyYaw == bodyYaw && this.pitch == pitch) return; 1235 if(this.world.callCancellableIfExists!PlayerMoveEvent(this, position, yaw, bodyYaw, pitch)) { 1236 //send the position back 1237 if(this.position == old) this.sendPosition(); 1238 } else { 1239 //exhaust //TODO swimming 1240 auto dis = distance(cast(Vector2!float)old, cast(Vector2!float)position); 1241 //TODO fix the distance! 1242 if(dis > 0) this.exhaust((this.sprinting ? Exhaustion.SPRINTING : (this.sneaking ? Exhaustion.SNEAKING : Exhaustion.WALKING)) * distance(cast(Vector2!float)this.position, cast(Vector2!float)position)); 1243 //update the position 1244 this.move(position, yaw, bodyYaw, pitch); 1245 if(dis > 0) this.world.playerUpdateRadius(this); 1246 } 1247 } 1248 } 1249 1250 /* 1251 * Starts breaking a generic block (not tapping). 1252 * If the player is alive and the target block exists and is not air the 'is_breaking' flag is set 1253 * to true. 1254 * If the player is in creative mode or the block's breaking time is 0, handleBlockBreaking is called. 1255 * Returns: 1256 * true id the player is digging a block, false otherwise 1257 */ 1258 protected bool handleStartBlockBreaking(BlockPosition position) { 1259 if(this.alive) { 1260 Block b = this.world[position]; 1261 if(!b.indestructible) { 1262 this.breaking = position; 1263 this.is_breaking = true; 1264 if(b.instantBreaking || this.creative || Effects.haste in this) { // TODO remove haste from here and add hardness 1265 this.handleBlockBreaking(); 1266 } 1267 } 1268 } 1269 return this.is_breaking; 1270 } 1271 1272 /* 1273 * Stops breaking the current block and sets the 'is_breaking' flag to false, without removing 1274 * it and consuming any tool. 1275 */ 1276 protected void handleAbortBlockBreaking() { 1277 this.is_breaking = false; 1278 } 1279 1280 /* 1281 * Stops breaking the block indicated in the variable 'breaking', calls the event, consumes the tool 1282 * and exhausts the player. 1283 */ 1284 protected bool handleBlockBreaking() { 1285 bool cancelitem = false; 1286 bool cancelblock = false; 1287 //log(!this.world.rules.immutableWorld, " ", this.alive, " ", this.is_breaking, " ", this.world[breaking] != Blocks.AIR); 1288 if(this.alive && this.is_breaking && !this.world[this.breaking].indestructible) { 1289 auto event = this.world.callEventIfExists!PlayerBreakBlockEvent(this, this.world[this.breaking], this.breaking); 1290 if(event !is null && event.cancelled) { 1291 cancelitem = true; 1292 cancelblock = true; 1293 } else { 1294 //consume the item 1295 if((event is null || event.consumeItem) && !this.inventory.held.empty && this.inventory.held.item.tool) { 1296 this.inventory.held.item.destroyOn(this, this.world[this.breaking], this.breaking); 1297 if(this.inventory.held.item.finished) { 1298 this.inventory.held = Slot(null); 1299 } 1300 } else { 1301 cancelitem = true; 1302 } 1303 if(event is null || event.drop) { 1304 foreach(Slot slot ; this.world[this.breaking].drops(this.world, this, this.inventory.held.item)) { 1305 this.world.drop(slot, this.breaking.entityPosition + .5); 1306 } 1307 } 1308 //if(event.particles) this.world.addParticle(new Particles.Destroy(this.breaking.entityPosition, this.world[this.breaking])); 1309 if(event is null || event.removeBlock) { 1310 this.world[this.breaking] = Blocks.air; 1311 } else { 1312 cancelblock = true; 1313 } 1314 this.exhaust(Exhaustion.BREAKING_BLOCK); 1315 } 1316 } else { 1317 cancelitem = true; 1318 cancelblock = true; 1319 } 1320 if(cancelblock && this.is_breaking && this.world[this.breaking] !is null) { 1321 this.sendBlock(PlacedBlock(this.breaking, this.world[this.breaking].data)); 1322 auto tile = this.world.tileAt(this.breaking); 1323 if(tile !is null) { 1324 this.sendTile(tile, false); 1325 } 1326 } 1327 if(cancelitem && !this.inventory.held.empty && this.inventory.held.item.tool) { 1328 this.inventory.update = PlayerInventory.HELD; 1329 } 1330 this.is_breaking = false; 1331 return !cancelblock; 1332 } 1333 1334 protected void handleBlockPlacing(BlockPosition tpos, uint tface) { 1335 /*BlockPosition position = tpos.face(tface); 1336 //TODO calling events on player and on block 1337 Block placed = this.inventory.held.item.place(this.world, position); 1338 if(placed !is null) { 1339 this.world[position] = placed; 1340 } else { 1341 //event cancelled or unavailable 1342 this.sendBlock(PlacedBlock(position, this.world[position])); 1343 }*/ 1344 if(this.world.callCancellableIfExists!PlayerPlaceBlockEvent(this, this.inventory.held, tpos, tface) || !this.inventory.held.item.onPlaced(this, tpos, tface)) { 1345 //no block placed! 1346 //sends the block back 1347 this.sendBlock(PlacedBlock(tpos.face(tface), this.world[tpos.face(tface)].data)); 1348 } 1349 } 1350 1351 protected void handleArmSwing() { 1352 this.do_animation = true; 1353 } 1354 1355 private void handleArmSwingImpl() { 1356 if(this.alive) { 1357 if(!this.world.callCancellableIfExists!PlayerAnimationEvent(this)) { 1358 if(!this.inventory.held.empty && this.inventory.held.item.onThrowed(this)) { 1359 this.actionFlag = true; 1360 this.broadcastMetadata(); 1361 } else { 1362 this.viewers!Player.call!"sendAnimation"(this); 1363 } 1364 } 1365 } 1366 } 1367 1368 protected void handleAttack(uint entity) { 1369 if(entity != this.id) this.handleAttack(this.world.entity(entity)); 1370 } 1371 1372 protected void handleAttack(Entity entity) { 1373 if(this.alive && entity !is null && (cast(Player)entity is null || this.world.pvp)) { 1374 if(cast(Player)entity ? !entity.attack(new PlayerAttackedByPlayerEvent(cast(Player)entity, this)).cancelled : !entity.attack(new EntityAttackedByPlayerEvent(entity, this)).cancelled) { 1375 this.exhaust(Exhaustion.ATTACKING); 1376 } 1377 } 1378 } 1379 1380 protected void handleInteract(uint entity) { 1381 if(entity != this.id) this.handleInteract(this.world.entity(entity)); 1382 } 1383 1384 protected void handleInteract(Entity entity) { 1385 //TODO 1386 if(this.alive && (!this.inventory.held.empty && this.inventory.held.item.onThrowed(this) && !this.creative)) { 1387 //remove one from inventory 1388 this.inventory.held.count--; 1389 if(this.inventory.held.empty) this.inventory.held = Slot(null); 1390 else this.inventory.update = 0; 1391 } 1392 } 1393 1394 protected void handleReleaseItem() { 1395 //TODO 1396 } 1397 1398 protected void handleStopSleeping() { 1399 //TODO 1400 } 1401 1402 protected void handleRespawn() { 1403 if(this.dead) { 1404 this.respawn(); 1405 } 1406 } 1407 1408 protected void handleJump() { 1409 if(this.alive) { 1410 // event 1411 this.world.callEventIfExists!PlayerJumpEvent(this); 1412 // exhaustion 1413 this.exhaust(this.sprinting ? Exhaustion.SPRINTED_JUMP : Exhaustion.JUMPING); 1414 } 1415 } 1416 1417 protected void handleSprinting(bool sprint) { 1418 if(this.alive && sprint ^ this.sprinting) { 1419 if(sprint) { 1420 this.world.callEventIfExists!PlayerStartSprintingEvent(this); 1421 } else { 1422 this.world.callEventIfExists!PlayerStopSprintingEvent(this); 1423 } 1424 this.sprinting = sprint; 1425 //if(this.pe) this.recalculateSpeed(); 1426 } 1427 } 1428 1429 protected void handleSneaking(bool sneak) { 1430 if(this.alive && sneak ^ this.sneaking) { 1431 //auto event = sneak ? new PlayerStartSneakingEvent(this) : new PlayerStopSneakingEvent(this); 1432 //this.world.callEvent(event); 1433 if(sneak) { 1434 this.world.callEventIfExists!PlayerStartSneakingEvent(this); 1435 } else { 1436 this.world.callEventIfExists!PlayerStopSneakingEvent(this); 1437 } 1438 this.sneaking = sneak; 1439 } 1440 } 1441 1442 protected void handleChangeDimension() { 1443 //TODO 1444 } 1445 1446 protected bool consumeItemInHand() { 1447 if(!this.inventory.held.empty && this.inventory.held.item.consumeable/* && this.hunger < 20*/) { 1448 Item ret = this.inventory.held.item.onConsumed(this); 1449 if(this.consumeTools) { 1450 if(ret is null) { 1451 this.inventory.held = Slot(this.inventory.held.item, to!ubyte(this.inventory.held.count - 1)); 1452 if(!this.inventory.held.empty) { 1453 //don't need to update the viewers 1454 this.inventory.update_viewers &= PlayerInventory.HELD ^ 0xF; 1455 } 1456 } else { 1457 this.inventory.held = Slot(ret, 1); 1458 } 1459 } 1460 return true; 1461 } else { 1462 this.inventory.update = PlayerInventory.HELD; 1463 return false; 1464 } 1465 } 1466 1467 protected final void handleRightClick(BlockPosition tpos, uint tface) { 1468 //called only when !inventory.held.empty 1469 BlockPosition position = tpos.face(tface); 1470 Block block = this.world[tpos]; 1471 if(block !is null) { 1472 //TODO call events 1473 if(this.inventory.held.item.useOnBlock(this, block, tpos, tface & 255)) { 1474 this.inventory.held = this.inventory.held.item.finished ? Slot(null) : this.inventory.held; 1475 } 1476 } 1477 } 1478 1479 protected final void handleMapRequest(ushort mapId) { 1480 /*if(!this.world.callCancellableIfExists!PlayerRequestMapEvent(this, mapId)) { 1481 auto map = this.world[mapId]; 1482 if(map !is null) { 1483 this.sendMap(map); 1484 } else { 1485 //TODO generate 1486 } 1487 }*/ 1488 } 1489 1490 protected final bool handleDrop(Slot slot) { 1491 if(!this.world.callCancellableIfExists!PlayerDropItemEvent(this, slot)) { 1492 this.drop(slot); 1493 return true; 1494 } else { 1495 return false; 1496 } 1497 } 1498 1499 1500 // *** ABSTRACT HANDLING *** 1501 1502 protected uint order; 1503 1504 public abstract void handle(ubyte id, ubyte[] data); 1505 1506 public abstract void flush(); 1507 1508 protected void sendPacketPayload(ubyte[] payload) { 1509 Handler.sharedInstance.send(new HncomPlayer.OrderedGamePacket(this.hubId, this.order++, payload).autoEncode()); 1510 } 1511 1512 private Tid compression; 1513 1514 protected void startCompression(T:Compression)(uint hubId) { 1515 static import std.concurrency; 1516 this.compression = std.concurrency.spawn(&startCompressionImpl!T, hubId); 1517 } 1518 1519 protected void stopCompression() { 1520 // send a stop message 1521 send(this.compression, uint.max, (immutable(ubyte)[]).init); 1522 } 1523 1524 protected void compress(ubyte[] payload) { 1525 send(this.compression, this.order++, payload.idup); 1526 } 1527 1528 protected static abstract class Compression { 1529 1530 public void start(uint hubId) { 1531 1532 auto handler = Handler.sharedInstance(); 1533 1534 while(true) { 1535 1536 auto data = receiveOnly!(uint, immutable(ubyte)[]); // tuple(order, uncompressed payload) 1537 1538 if(data[0] == uint.max) break; 1539 1540 handler.send(new HncomPlayer.OrderedGamePacket(hubId, data[0], this.compress(data[1].dup)).autoEncode()); 1541 1542 } 1543 1544 } 1545 1546 protected abstract ubyte[] compress(ubyte[] payload); 1547 1548 } 1549 1550 } 1551 1552 private void startCompressionImpl(T)(uint hubId) { 1553 Thread.getThis().name = "Compression@" ~ to!string(hubId); 1554 auto c = new T(); 1555 c.start(hubId); 1556 } 1557 1558 enum InputMode : ubyte { 1559 1560 keyboard = HncomPlayer.Add.KEYBOARD, 1561 touch = HncomPlayer.Add.TOUCH, 1562 controller = HncomPlayer.Add.CONTROLLER, 1563 1564 } 1565 1566 enum DeviceOS : ubyte { 1567 1568 unknown = 0, 1569 android = 1, 1570 ios = 2, 1571 osx = 3, 1572 fireos = 4, 1573 gearvr = 5, 1574 hololens = 6, 1575 win10 = 7, 1576 win32 = 8, 1577 dedicated = 9, 1578 orbis = 10, 1579 nx = 11 1580 1581 } 1582 1583 enum PermissionLevel : ubyte { 1584 1585 user, 1586 operator, 1587 host, 1588 automation, 1589 admin, 1590 1591 } 1592 1593 /** 1594 * Checks whether or not the given symbol is of a connected player class. 1595 * Returns: 1596 * true if the given symbol is or extends Player and not Puppet 1597 * Example: 1598 * --- 1599 * assert(isPlayer!Player); 1600 * assert(!isPlayer!Puppet); 1601 * assert(isPlayer!PocketPlayerBase); 1602 * assert(isPlayer!(MinecraftPlayer!210)); 1603 * assert(!isPlayer!(Projection!Puppet)); 1604 * --- 1605 */ 1606 enum isPlayer(T) = is(T : Player) && !is(T : Puppet); 1607 1608 /** 1609 * Checks if the given entity is an instance of a connected player. 1610 * Params: 1611 * entity = an instance of an entity 1612 * Returns: 1613 * true if the entity can be casted to Player and not to Puppet 1614 * Example: 1615 * --- 1616 * assert(isPlayerInstance(player!"steve")); 1617 * assert(!isPlayerInstance(new Puppet(player!"steve"))); 1618 * --- 1619 */ 1620 public @safe @nogc bool isPlayerInstance(Entity entity) { 1621 return cast(Player)entity && !cast(Puppet)entity; 1622 } 1623 1624 template SamePlayer(uint protocol, uint[] supported, uint[uint] same, alias T) if(supported.canFind(protocol)) { 1625 1626 static if(!!(protocol in same)) alias SamePlayer = T!(same[protocol]); 1627 else alias SamePlayer = T!protocol; 1628 1629 } 1630 1631 mixin template generateHandlers(E...) { 1632 1633 public override void handle(ubyte _id, ubyte[] _data) { 1634 switch(_id) { 1635 foreach(P ; E) { 1636 static if(P.SERVERBOUND && (is(typeof(mixin("handle" ~ P.stringof ~ "Packet"))) || is(typeof(P.variantField)))) { 1637 case P.ID: 1638 { 1639 P _packet = P.fromBuffer!false(_data); 1640 static if(!is(typeof(P.variantField))) { 1641 with(_packet) mixin("return this.handle" ~ P.stringof ~ "Packet(" ~ P.FIELDS.join(",") ~ ");"); 1642 } else { 1643 switch(mixin("_packet." ~ P.variantField)) { 1644 foreach(V ; P.Variants) { 1645 static if(is(typeof(mixin("handle" ~ P.stringof ~ V.stringof ~ "Packet")))) { 1646 mixin((){ import std..string:toUpper;return "case V." ~ P.variantField.toUpper ~ ":"; }()); //TODO convert to upper snake case 1647 { 1648 V _variant = _packet..new V(); 1649 _variant.decode(); 1650 with(_packet) { with(_variant) mixin("return this.handle" ~ P.stringof ~ V.stringof ~ "Packet(" ~ join((){ string[] f;foreach(fl;P.FIELDS){if(fl!=P.variantField){f~=fl;}}return f; }() ~ V.FIELDS, ",") ~ ");"); } 1651 } 1652 } 1653 } 1654 default: return; 1655 } 1656 } 1657 } 1658 } else version(ShowUnhandled) { 1659 static if(P.SERVERBOUND) pragma(msg, "Packet " ~ P.stringof ~ " is not handled"); 1660 } 1661 } 1662 default: 1663 version(ShowUnhandled) error_log("Unknown packet '", _id, "' with ", _data.length, " bytes"); 1664 break; 1665 } 1666 } 1667 1668 } 1669 1670 /** 1671 * Unconnected player for visualization. 1672 * In the world is registered as an entity and it will 1673 * not be found in the array of player obtained with 1674 * world.online!Player nor in the count obtained with 1675 * world.count!Player. 1676 * Example: 1677 * --- 1678 * //spawn a puppet 10 blocks over a player that follows it 1679 * class PuppetWorld : World { 1680 * 1681 * private Puppet[uint] puppets; 1682 * 1683 * public @event join(PlayerSpawnEvent event) { 1684 * this.puppets[event.player.id] = this.spawn!Puppet(event.player.position, event.player.name, event.player.skin, event.player.uuid); 1685 * } 1686 * 1687 * public @event left(PlayerDespawnEvent event) { 1688 * this.despawn(this.puppets[event.player.id]); 1689 * this.puppets.remove(event.player.id); 1690 * } 1691 * 1692 * public @event move(PlayerMoveEvent event) { 1693 * this.puppets[event.player.id].move(event.position, event.yaw, event.bodyYaw, event.pitch); 1694 * } 1695 * 1696 * } 1697 * --- 1698 * Example: 1699 * --- 1700 * // Unticked puppets will reduce the CPU usage 1701 * auto ticked = new Puppet(); 1702 * auto unticked = new Unticked!Puppet(); 1703 * --- 1704 */ 1705 class Puppet : Player { 1706 1707 public this(World world, EntityPosition position, string name, Skin skin, UUID uuid) { 1708 //TODO create a playerinfo 1709 super(null, world, position); 1710 } 1711 1712 public this(World world, EntityPosition position, string name, Skin skin) { 1713 this(world, position, name, skin, world.server.nextUUID); 1714 } 1715 1716 public this(World world, EntityPosition position, string name) { 1717 this(world, position, name, Skin.STEVE); 1718 } 1719 1720 public this(World world, Player from) { 1721 this(world, from.position, from.name, from.skin, from.uuid); 1722 } 1723 1724 protected override @safe @nogc void sendMovementUpdates(Entity[] entities) {} 1725 1726 protected override @safe @nogc void sendMotionUpdates(Entity[] entities) {} 1727 1728 protected override @safe @nogc void sendTipImpl(Message[] messages) {} 1729 1730 protected override @safe @nogc void sendTitleImpl(Title title, Subtitle subtitle, uint fadeIn, uint stay, uint fadeOut) {} 1731 1732 protected override @safe @nogc void sendHideTitles() {} 1733 1734 protected override @safe @nogc void sendResetTitles() {} 1735 1736 public override @safe @nogc void sendGamemode() {} 1737 1738 public override @safe @nogc void sendOpenContainer(ubyte type, ushort slots, BlockPosition position) {} 1739 1740 public override @safe @nogc void spawnToItself() {} 1741 1742 public override @safe @nogc void sendAddList(Player[] players) {} 1743 1744 public override @safe @nogc void sendRemoveList(Player[] players) {} 1745 1746 protected override @safe @nogc void sendSpawnPosition() {} 1747 1748 protected override @safe @nogc void sendPosition() {} 1749 1750 public override @safe @nogc void sendMetadata(Entity entity) {} 1751 1752 public override @safe @nogc void sendChunk(Chunk chunk) {} 1753 1754 public override @safe @nogc void unloadChunk(ChunkPosition pos) {} 1755 1756 public override @safe @nogc void sendInventory(ubyte flag=PlayerInventory.ALL, bool[] slots=[]) {} 1757 1758 public override @safe @nogc void sendHeld() {} 1759 1760 public override @safe @nogc void sendEntityEquipment(Player player) {} 1761 1762 public override @safe @nogc void sendArmorEquipment(Player player) {} 1763 1764 public override @safe @nogc void sendHurtAnimation(Entity entity) {} 1765 1766 public override @safe @nogc void sendDeathAnimation(Entity entity) {} 1767 1768 protected override @safe @nogc void sendDeathSequence() {} 1769 1770 protected override @safe @nogc void experienceUpdated() {} 1771 1772 public override @safe @nogc void sendJoinPacket() {} 1773 1774 public override @safe @nogc void sendResourcePack() {} 1775 1776 public override void sendPermissionLevel(PermissionLevel permissionLevel) {} 1777 1778 public override @safe @nogc void sendDifficulty(Difficulty difficulty) {} 1779 1780 public override @safe @nogc void sendWorldGamemode(Gamemode gamemode) {} 1781 1782 public override @safe @nogc void sendSettingsPacket() {} 1783 1784 public override @safe @nogc void sendRespawnPacket() {} 1785 1786 public override @safe @nogc void setAsReadyToSpawn() {} 1787 1788 public override @safe @nogc void sendLightning(Lightning lightning) {} 1789 1790 public override @safe @nogc void sendAnimation(Entity entity) {} 1791 1792 public override @safe @nogc void sendBlocks(PlacedBlock[] block) {} 1793 1794 public override @safe @nogc void sendTile(Tile tiles, bool translatable) {} 1795 1796 public override @safe @nogc void sendPickupItem(Entity picker, Entity picked) {} 1797 1798 public override @safe @nogc void sendPassenger(ubyte mode, uint passenger, uint vehicle) {} 1799 1800 public override @safe @nogc void sendExplosion(EntityPosition position, float radius, Vector3!byte[] updates) {} 1801 1802 public override @safe @nogc void sendMap(Map map) {} 1803 1804 public override @safe @nogc void handle(ubyte id, ubyte[] buffer) {} 1805 1806 } 1807 1808 struct TitleImpl { 1809 1810 Message[] message; 1811 1812 this(E...)(E args) { 1813 this.message = Message.convert(args); 1814 } 1815 1816 alias message this; 1817 1818 } 1819 1820 alias Title = TitleImpl; 1821 1822 alias Subtitle = TitleImpl;