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 * Copyright: 2017-2018 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/player/bedrock.d, selery/player/bedrock.d) 28 */ 29 module selery.player.bedrock; 30 31 import std.algorithm : min, sort, canFind; 32 import std.array : Appender; 33 import std.base64 : Base64; 34 import std.conv : to; 35 import std.digest.digest : toHexString; 36 import std.digest.sha : sha256Of; 37 import std.json; 38 import std.string : split, join, startsWith, replace, strip, toLower; 39 import std.system : Endian; 40 import std.typecons : Tuple; 41 import std.uuid : UUID; 42 import std.zlib : Compress, HeaderFormat; 43 44 import sel.nbt.stream; 45 import sel.nbt.tags; 46 47 import selery.about; 48 import selery.block.block : Block, PlacedBlock; 49 import selery.block.tile : Tile; 50 import selery.command.command : Command, Position, Target; 51 import selery.command.util : PocketType; 52 import selery.config : Gamemode, Difficulty, Dimension, Files; 53 import selery.effect : Effect, Effects; 54 import selery.entity.entity : Entity; 55 import selery.entity.human : Skin; 56 import selery.entity.living : Living; 57 import selery.entity.metadata : SelMetadata = Metadata; 58 import selery.entity.noai : Lightning, ItemEntity; 59 import selery.inventory.inventory; 60 import selery.item.slot : Slot; 61 import selery.lang : Translation; 62 import selery.log : Format, Message; 63 import selery.math.vector; 64 import selery.node.info : PlayerInfo; 65 import selery.player.player; 66 import selery.plugin : Description; 67 import selery.world.chunk : Chunk; 68 import selery.world.map : Map; 69 import selery.world.world : World; 70 71 import sul.utils.var : varuint; 72 73 abstract class BedrockPlayer : Player { 74 75 protected static ubyte[][] resourcePackChunks; 76 protected static size_t resourcePackSize; 77 protected static string resourcePackId; 78 protected static string resourcePackHash; 79 80 public static void updateResourcePacks(UUID uuid, void[] rp) { 81 for(size_t i=0; i<rp.length; i+=4096) { 82 resourcePackChunks ~= cast(ubyte[])rp[i..min($, i+4096)]; 83 } 84 resourcePackSize = rp.length; 85 resourcePackId = uuid.toString(); 86 resourcePackHash = toLower(toHexString(sha256Of(rp))); 87 } 88 89 private bool n_edu; 90 private long n_xuid; 91 private ubyte n_os; 92 private string n_device_model; 93 94 private BlockPosition[] broken_by_this; 95 96 protected bool send_commands; 97 98 public this(shared PlayerInfo info, World world, EntityPosition position) { 99 super(info, world, position); 100 if(resourcePackId.length == 0) { 101 // no resource pack 102 this.hasResourcePack = true; 103 } 104 } 105 106 /** 107 * Gets the player's XBOX user id. 108 * It's always the same value for the same user, if authenticated. 109 * It's 0 if the server is not in online mode. 110 * This value can be used to retrieve more informations about the 111 * player using the XBOX live services. 112 */ 113 public final pure nothrow @property @trusted @nogc long xuid() { 114 return this.info.xuid; 115 } 116 117 /** 118 * Gets the player's operative system, as indicated by the client 119 * in the login packet. 120 * Example: 121 * --- 122 * if(player.os != PlayerOS.android) { 123 * player.kick("Only android players are allowed"); 124 * } 125 * --- 126 */ 127 public final pure nothrow @property @trusted @nogc DeviceOS deviceOs() { 128 return this.info.deviceOs; 129 } 130 131 /** 132 * Gets the player's device model (name and identifier) as indicated 133 * by the client in the login packet. 134 * Example: 135 * --- 136 * if(!player.deviceModel.toLower.startsWith("oneplus")) { 137 * player.kick("This server is reserved for oneplus users"); 138 * } 139 */ 140 public final pure nothrow @property @trusted @nogc string deviceModel() { 141 return this.info.deviceModel; 142 } 143 144 public final override void disconnectImpl(const Translation translation) { 145 if(translation.translatable.bedrock.length) { 146 this.server.kick(this.hubId, translation.translatable.bedrock, translation.parameters); 147 } else { 148 this.disconnect(this.server.lang.translate(translation, this.language)); 149 } 150 } 151 152 alias operator = super.operator; 153 154 public override @property bool operator(bool op) { 155 if(super.operator(op) == op && this.send_commands) { 156 this.sendCommands(); 157 } 158 return op; 159 } 160 161 /** 162 * Encodes a Message[] into a string that can be parsed by the client. 163 * Every instance of Translation is translated server-side. 164 */ 165 public string encodeServerMessage(Message[] messages) { 166 Appender!string appender; 167 foreach(message ; messages) { 168 final switch(message.type) { 169 case Message.FORMAT: 170 appender.put(cast(string)message.format); 171 break; 172 case Message.TEXT: 173 appender.put(message.text); 174 break; 175 case Message.TRANSLATION: 176 appender.put(this.server.lang.translate(message.translation.translatable.default_, message.translation.parameters, this.language)); 177 break; 178 } 179 } 180 return appender.data; 181 } 182 183 /** 184 * Creates a string message that can be displayed by the client. 185 */ 186 protected override void sendMessageImpl(Message[] messages) { 187 Appender!string appender; 188 string[] params; 189 bool translation = false; 190 foreach(message ; messages) { 191 final switch(message.type) { 192 case Message.FORMAT: 193 appender.put(cast(string)message.format); 194 break; 195 case Message.TEXT: 196 appender.put(message.text); 197 break; 198 case Message.TRANSLATION: 199 if(message.translation.translatable.bedrock.length) { 200 //TODO check whether it's possible to use multiple translations 201 appender.put("%"); 202 appender.put(message.translation.translatable.bedrock); 203 translation = true; 204 } else { 205 appender.put(this.server.lang.translate(message.translation.translatable.default_, message.translation.parameters, this.language)); 206 } 207 break; 208 } 209 } 210 if(!translation) this.sendRawMessageImpl(appender.data); 211 else this.sendTranslationMessageImpl(appender.data, params); 212 } 213 214 protected abstract void sendRawMessageImpl(string message); 215 216 protected abstract void sendTranslationMessageImpl(string message, string[] params); 217 218 public override @trusted Command registerCommand(Command command) { 219 super.registerCommand(command); 220 if(this.send_commands) { 221 this.sendCommands(); 222 } 223 return command; 224 } 225 226 public override @trusted bool unregisterCommand(Command command) { 227 immutable ret = super.unregisterCommand(command); 228 if(ret && this.send_commands) { 229 this.sendCommands(); 230 } 231 return ret; 232 } 233 234 protected void sendCommands(); 235 236 237 // generic PE handlings 238 239 protected override bool handleBlockBreaking() { 240 BlockPosition position = this.breaking; 241 if(super.handleBlockBreaking()) { 242 this.broken_by_this ~= position; 243 return true; 244 } else { 245 return false; 246 } 247 } 248 249 } 250 251 // send function are overwritten with static ifs 252 // handle functions are created for every version using static ifs 253 class BedrockPlayerImpl(uint __protocol) : BedrockPlayer if(supportedBedrockProtocols.canFind(__protocol)) { 254 255 mixin("import Types = sul.protocol.bedrock" ~ __protocol.to!string ~ ".types;"); 256 mixin("import Play = sul.protocol.bedrock" ~ __protocol.to!string ~ ".play;"); 257 258 mixin("import sul.attributes.bedrock" ~ __protocol.to!string ~ " : Attributes;"); 259 mixin("import sul.metadata.bedrock" ~ __protocol.to!string ~ " : Metadata;"); 260 261 private static __gshared ubyte[] creative_inventory; 262 263 public static bool loadCreativeInventory(const Files files) { 264 immutable cached = "creative_" ~ __protocol.to!string; 265 if(!files.hasTemp(cached)) { 266 immutable asset = "creative/" ~ __protocol.to!string ~ ".json"; 267 if(!files.hasAsset(asset)) return false; 268 static if(__protocol < 120) auto packet = new Play.ContainerSetContent(121, 0); 269 else auto packet = new Play.InventoryContent(121); 270 foreach(item ; parseJSON(cast(string)files.readAsset(asset))["items"].array) { 271 auto obj = item.object; 272 auto meta = "meta" in obj; 273 auto nbt = "nbt" in obj; 274 auto ench = "enchantments" in obj; 275 packet.slots ~= Types.Slot(obj["id"].integer.to!int, (meta ? (*meta).integer.to!int << 8 : 0) | 1, nbt && nbt.str.length ? Base64.decode(nbt.str) : []); 276 } 277 ubyte[] encoded = packet.encode(); 278 Compress c = new Compress(9); 279 creative_inventory = cast(ubyte[])c.compress(varuint.encode(encoded.length.to!uint) ~ encoded); 280 creative_inventory ~= cast(ubyte[])c.flush(); 281 files.writeTemp(cached, creative_inventory); 282 } else { 283 creative_inventory = cast(ubyte[])files.readTemp(cached); 284 } 285 return true; 286 } 287 288 protected Types.BlockPosition toBlockPosition(BlockPosition vector) { 289 return Types.BlockPosition(typeof(Types.BlockPosition.x)(vector.x), typeof(Types.BlockPosition.y)(vector.y), typeof(Types.BlockPosition.z)(vector.z)); 290 } 291 292 protected BlockPosition fromBlockPosition(Types.BlockPosition blockPosition) { 293 return BlockPosition(blockPosition.x, blockPosition.y, blockPosition.z); 294 } 295 296 protected Types.Slot toSlot(Slot slot) { 297 if(slot.empty) { 298 return Types.Slot(0); 299 } else { 300 auto stream = new ClassicStream!(Endian.littleEndian)(); 301 if(!slot.empty && slot.item.pocketCompound !is null) { 302 stream.writeTag(slot.item.pocketCompound); 303 } 304 return Types.Slot(slot.item.bedrockId, slot.item.bedrockMeta << 8 | slot.count, stream.buffer); 305 } 306 } 307 308 protected Slot fromSlot(Types.Slot slot) { 309 if(slot.id <= 0) { 310 return Slot(null); 311 } else { 312 auto item = this.world.items.fromBedrock(slot.id & ushort.max, (slot.metaAndCount >> 8) & ushort.max); 313 if(slot.nbt.length) { 314 auto stream = new ClassicStream!(Endian.littleEndian)(slot.nbt); 315 //TODO verify that this is right 316 auto tag = stream.readTag(); 317 if(cast(Compound)tag) item.parseBedrockCompound(cast(Compound)tag); 318 } 319 return Slot(item, slot.metaAndCount & 255); 320 } 321 } 322 323 protected Types.Slot[] toSlots(Slot[] slots) { 324 Types.Slot[] ret = new Types.Slot[slots.length]; 325 foreach(i, slot; slots) { 326 ret[i] = toSlot(slot); 327 } 328 return ret; 329 } 330 331 protected Slot[] fromSlots(Types.Slot[] slots) { 332 Slot[] ret = new Slot[slots.length]; 333 foreach(i, slot; slots) { 334 ret[i] = this.fromSlot(slot); 335 } 336 return ret; 337 } 338 339 public static Types.McpeUuid toUUID(UUID uuid) { 340 ubyte[8] msb, lsb; 341 foreach(i ; 0..8) { 342 msb[i] = uuid.data[i]; 343 lsb[i] = uuid.data[i+8]; 344 } 345 import std.bitmanip : bigEndianToNative; 346 return Types.McpeUuid(bigEndianToNative!long(msb), bigEndianToNative!long(lsb)); 347 } 348 349 public static uint convertGamemode(uint gamemode) { 350 if(gamemode == 3) return 1; 351 else return gamemode; 352 } 353 354 public static Metadata metadataOf(SelMetadata metadata) { 355 mixin("return metadata.bedrock" ~ __protocol.to!string ~ ";"); 356 } 357 358 private bool has_creative_inventory = false; 359 360 private Tuple!(string, PocketType)[][][string] sent_commands; // [command][overload] = [(name, type), (name, type), ...] 361 362 private ubyte[][] queue; 363 private size_t total_queue_length = 0; 364 365 public this(shared PlayerInfo info, World world, EntityPosition position) { 366 super(info, world, position); 367 this.startCompression!Compression(hubId); 368 } 369 370 protected void sendPacket(T)(T packet) if(is(T == ubyte[]) || is(typeof(T.encode))) { 371 static if(is(T == ubyte[])) { 372 alias buffer = packet; 373 } else { 374 ubyte[] buffer = packet.encode(); 375 } 376 this.queue ~= buffer; 377 this.total_queue_length += buffer.length; 378 } 379 380 public override void flush() { 381 enum padding = [ubyte(0), ubyte(0)]; 382 // since protocol 110 everything is compressed 383 if(this.queue.length) { 384 ubyte[] payload; 385 size_t total; 386 size_t total_bytes = 0; 387 foreach(ubyte[] packet ; this.queue) { 388 static if(__protocol >= 120) packet = packet[0] ~ padding ~ packet[1..$]; 389 total++; 390 total_bytes += packet.length; 391 payload ~= varuint.encode(packet.length.to!uint); 392 payload ~= packet; 393 if(payload.length > 1048576) { 394 // do not compress more than 1 MiB 395 break; 396 } 397 } 398 this.queue = this.queue[total..$]; 399 this.compress(payload); 400 this.total_queue_length -= total_bytes; 401 if(this.queue.length) this.flush(); 402 } 403 } 404 405 alias world = super.world; 406 407 public override @property World world(World world) { 408 this.send_commands = false; // world-related commands are removed but no packet is needed as they are updated at respawn 409 return super.world(world); 410 } 411 412 public override void transfer(string ip, ushort port) { 413 this.sendPacket(new Play.Transfer(ip, port)); 414 } 415 416 public override void firstspawn() { 417 super.firstspawn(); 418 this.recalculateSpeed(); 419 } 420 421 protected override void sendRawMessageImpl(string message) { 422 this.sendPacket(new Play.Text().new Raw(message)); 423 } 424 425 protected override void sendTranslationMessageImpl(string message,string[] params) { 426 this.sendPacket(new Play.Text().new Translation(message, params)); 427 } 428 429 protected override void sendCompletedMessages(string[] messages) { 430 // unsupported 431 } 432 433 protected override void sendTipImpl(Message[] messages) { 434 this.sendPacket(new Play.SetTitle(Play.SetTitle.SET_ACTION_BAR, this.encodeServerMessage(messages))); 435 } 436 437 protected override void sendTitleImpl(Title title, Subtitle subtitle, uint fadeIn, uint stay, uint fadeOut) { 438 this.sendPacket(new Play.SetTitle(Play.SetTitle.SET_TITLE, this.encodeServerMessage(title))); 439 if(subtitle.length) this.sendPacket(new Play.SetTitle(Play.SetTitle.SET_SUBTITLE, this.encodeServerMessage(subtitle))); 440 this.sendPacket(new Play.SetTitle(Play.SetTitle.SET_TIMINGS, "", fadeIn, stay, fadeOut)); 441 } 442 443 protected override void sendHideTitles() { 444 this.sendPacket(new Play.SetTitle(Play.SetTitle.HIDE)); 445 } 446 447 protected override void sendResetTitles() { 448 this.sendPacket(new Play.SetTitle(Play.SetTitle.RESET)); 449 } 450 451 public override void sendMovementUpdates(Entity[] entities) { 452 foreach(Entity entity ; entities) { 453 this.sendPacket(new Play.MoveEntity(entity.id, (cast(Vector3!float)(entity.position + [0, entity.eyeHeight, 0])).tuple, entity.anglePitch, entity.angleYaw, cast(Living)entity ? (cast(Living)entity).angleBodyYaw : entity.angleYaw, entity.onGround)); 454 } 455 } 456 457 public override void sendMotionUpdates(Entity[] entities) { 458 foreach(Entity entity ; entities) { 459 this.sendPacket(new Play.SetEntityMotion(entity.id, (cast(Vector3!float)entity.motion).tuple)); 460 } 461 } 462 463 public override void spawnToItself() { 464 //this.sendAddList([this]); 465 } 466 467 public override void sendGamemode() { 468 this.sendPacket(new Play.SetPlayerGameType(this.gamemode == 3 ? 1 : this.gamemode)); 469 if(this.creative) { 470 if(!this.has_creative_inventory) { 471 this.sendPacketPayload(creative_inventory); 472 this.has_creative_inventory = true; 473 } 474 } else if(this.spectator) { 475 if(has_creative_inventory) { 476 //TODO remove armor and inventory 477 static if(__protocol < 120) this.sendPacket(new Play.ContainerSetContent(121, this.id)); 478 else this.sendPacket(new Play.InventoryContent(121)); 479 this.has_creative_inventory = false; 480 } 481 } 482 this.sendSettingsPacket(); 483 } 484 485 public override void sendSpawnPosition() { 486 this.sendPacket(new Play.SetSpawnPosition(0, toBlockPosition(cast(Vector3!int)this.spawn), true)); 487 } 488 489 public override void sendAddList(Player[] players) { 490 Types.PlayerList[] list; 491 foreach(Player player ; players) { 492 list ~= Types.PlayerList(toUUID(player.uuid), player.id, player.displayName, Types.Skin(player.skin.name, player.skin.data.dup, player.skin.cape.dup, player.skin.geometryName, player.skin.geometryData.dup)); 493 } 494 if(list.length) this.sendPacket(new Play.PlayerList().new Add(list)); 495 } 496 497 public override void sendUpdateLatency(Player[] players) {} 498 499 public override void sendRemoveList(Player[] players) { 500 Types.McpeUuid[] uuids; 501 foreach(Player player ; players) { 502 uuids ~= toUUID(player.uuid); 503 } 504 if(uuids.length) this.sendPacket(new Play.PlayerList().new Remove(uuids)); 505 } 506 507 public override void sendMetadata(Entity entity) { 508 this.sendPacket(new Play.SetEntityData(entity.id, metadataOf(entity.metadata))); 509 } 510 511 public override void sendChunk(Chunk chunk) { 512 513 Types.ChunkData data; 514 515 auto sections = chunk.sections; 516 size_t[] keys = sections.keys; 517 sort(keys); 518 ubyte top = keys.length ? to!ubyte(keys[$-1] + 1) : 0; 519 foreach(size_t i ; 0..top) { 520 Types.Section section; 521 auto section_ptr = i in sections; 522 if(section_ptr) { 523 auto s = *section_ptr; 524 foreach(ubyte x ; 0..16) { 525 foreach(ubyte z ; 0..16) { 526 foreach(ubyte y ; 0..16) { 527 auto ptr = s[x, y, z]; 528 if(ptr) { 529 Block block = *ptr; 530 section.blockIds[x << 8 | z << 4 | y] = block.bedrockId != 0 ? block.bedrockId : ubyte(248); 531 if(block.bedrockMeta != 0) section.blockMetas[x << 7 | z << 3 | y >> 1] |= to!ubyte(block.bedrockMeta << (y % 2 == 1 ? 4 : 0)); 532 } 533 } 534 } 535 } 536 static if(__protocol < 120) { 537 section.skyLight = s.skyLight; 538 section.blockLight = s.blocksLight; 539 } 540 } else { 541 static if(__protocol < 120) { 542 section.skyLight = 255; 543 section.blockLight = 0; 544 } 545 } 546 data.sections ~= section; 547 } 548 //data.heights = chunk.lights; 549 foreach(i, biome; chunk.biomes) { 550 data.biomes[i] = biome.id; 551 } 552 //TODO extra data 553 554 auto stream = new NetworkStream!(Endian.littleEndian)(); 555 foreach(tile ; chunk.tiles) { 556 if(tile.pocketCompound !is null) { 557 auto compound = tile.pocketCompound.dup; 558 compound["id"] = new String(tile.pocketSpawnId); 559 compound["x"] = new Int(tile.position.x); 560 compound["y"] = new Int(tile.position.y); 561 compound["z"] = new Int(tile.position.z); 562 stream.writeTag(compound); 563 } 564 } 565 data.blockEntities = stream.buffer; 566 567 this.sendPacket(new Play.FullChunkData(chunk.position.tuple, data)); 568 569 /*if(chunk.translatable_tiles.length > 0) { 570 foreach(Tile tile ; chunk.translatable_tiles) { 571 if(tile.tags) this.sendTile(tile, true); 572 } 573 }*/ 574 } 575 576 public override void unloadChunk(ChunkPosition pos) { 577 // no UnloadChunk packet :( 578 } 579 580 public override void sendChangeDimension(Dimension from, Dimension to) { 581 //if(from == to) this.sendPacket(new Play.ChangeDimension((to + 1) % 3, typeof(Play.ChangeDimension.position)(0, 128, 0), true)); 582 if(from != to) this.sendPacket(new Play.ChangeDimension(to)); 583 } 584 585 public override void sendInventory(ubyte flag=PlayerInventory.ALL, bool[] slots=[]) { 586 //slot only 587 foreach(ushort index, bool slot; slots) { 588 if(slot) { 589 //TODO if slot is in the hotbar the third argument should not be 0 590 static if(__protocol < 120) this.sendPacket(new Play.ContainerSetSlot(0, index, 0, toSlot(this.inventory[index]))); 591 //else this.sendPacket(new Play.InventorySlot(0, index, 0, toSlot(this.inventory[index]))); 592 } 593 } 594 //normal inventory 595 if((flag & PlayerInventory.INVENTORY) > 0) { 596 static if(__protocol < 120) this.sendPacket(new Play.ContainerSetContent(0, this.id, toSlots(this.inventory[]), [9, 10, 11, 12, 13, 14, 15, 16, 17])); 597 else this.sendPacket(new Play.InventoryContent(0, toSlots(this.inventory[]))); 598 } 599 //armour 600 if((flag & PlayerInventory.ARMOR) > 0) { 601 static if(__protocol < 120) this.sendPacket(new Play.ContainerSetContent(120, this.id, toSlots(this.inventory.armor[]), new int[0])); 602 else this.sendPacket(new Play.InventoryContent(120, toSlots(this.inventory.armor[]))); 603 } 604 //held item 605 if((flag & PlayerInventory.HELD) > 0) this.sendHeld(); 606 } 607 608 public override void sendHeld() { 609 static if(__protocol < 120) this.sendPacket(new Play.ContainerSetSlot(0, this.inventory.hotbar[this.inventory.selected] + 9, this.inventory.selected, toSlot(this.inventory.held))); 610 //else this.sendPacket(new Play.InventorySlot(0, this.inventory.hotbar[this.inventory.selected] + 9, this.inventory.selected, toSlot(this.inventory.held))); 611 } 612 613 public override void sendEntityEquipment(Player player) { 614 this.sendPacket(new Play.MobEquipment(player.id, toSlot(player.inventory.held), cast(ubyte)0, cast(ubyte)0, cast(ubyte)0)); 615 } 616 617 public override void sendArmorEquipment(Player player) { 618 this.sendPacket(new Play.MobArmorEquipment(player.id, [toSlot(player.inventory.helmet), toSlot(player.inventory.chestplate), toSlot(player.inventory.leggings), toSlot(player.inventory.boots)])); 619 } 620 621 public override void sendOpenContainer(ubyte type, ushort slots, BlockPosition position) { 622 //TODO 623 //this.sendPacket(new PocketContainerOpen(to!ubyte(type + 1), type, slots, position)); 624 } 625 626 public override void sendHurtAnimation(Entity entity) { 627 this.sendEntityEvent(entity, Play.EntityEvent.HURT_ANIMATION); 628 } 629 630 public override void sendDeathAnimation(Entity entity) { 631 this.sendEntityEvent(entity, Play.EntityEvent.DEATH_ANIMATION); 632 } 633 634 private void sendEntityEvent(Entity entity, typeof(Play.EntityEvent.eventId) evid) { 635 this.sendPacket(new Play.EntityEvent(entity.id, evid)); 636 } 637 638 protected override void sendDeathSequence() { 639 this.sendPacket(new Play.SetHealth(0)); 640 this.sendRespawnPacket(); 641 } 642 643 protected override @trusted void experienceUpdated() { 644 auto attributes = [ 645 Types.Attribute(Attributes.experience.min, Attributes.experience.max, this.experience, Attributes.experience.def, Attributes.experience.name), 646 Types.Attribute(Attributes.level.min, Attributes.level.max, this.level, Attributes.level.def, Attributes.level.name) 647 ]; 648 this.sendPacket(new Play.UpdateAttributes(this.id, attributes)); 649 } 650 651 protected override void sendPosition() { 652 this.sendPacket(new Play.MovePlayer(this.id, (cast(Vector3!float)this.position).tuple, this.pitch, this.bodyYaw, this.yaw, Play.MovePlayer.TELEPORT, this.onGround)); 653 } 654 655 protected override void sendMotion(EntityPosition motion) { 656 this.sendPacket(new Play.SetEntityMotion(this.id, (cast(Vector3!float)motion).tuple)); 657 } 658 659 public override void sendSpawnEntity(Entity entity) { 660 if(cast(Player)entity) this.sendAddPlayer(cast(Player)entity); 661 else if(cast(ItemEntity)entity) this.sendAddItemEntity(cast(ItemEntity)entity); 662 else if(entity.bedrock) this.sendAddEntity(entity); 663 } 664 665 public override void sendDespawnEntity(Entity entity) { 666 this.sendPacket(new Play.RemoveEntity(entity.id)); 667 } 668 669 protected void sendAddPlayer(Player player) { 670 this.sendPacket(new Play.AddPlayer(toUUID(player.uuid), player.name, player.id, player.id, (cast(Vector3!float)player.position).tuple, (cast(Vector3!float)player.motion).tuple, player.pitch, player.bodyYaw, player.yaw, toSlot(player.inventory.held), metadataOf(player.metadata))); 671 } 672 673 protected void sendAddItemEntity(ItemEntity item) { 674 this.sendPacket(new Play.AddItemEntity(item.id, item.id, toSlot(item.item), (cast(Vector3!float)item.position).tuple, (cast(Vector3!float)item.motion).tuple, metadataOf(item.metadata))); 675 } 676 677 protected void sendAddEntity(Entity entity) { 678 this.sendPacket(new Play.AddEntity(entity.id, entity.id, entity.bedrockId, (cast(Vector3!float)entity.position).tuple, (cast(Vector3!float)entity.motion).tuple, entity.pitch, entity.yaw, new Types.Attribute[0], metadataOf(entity.metadata), typeof(Play.AddEntity.links).init)); 679 } 680 681 public override @trusted void healthUpdated() { 682 super.healthUpdated(); 683 auto attributes = [ 684 Types.Attribute(Attributes.health.min, this.maxHealthNoAbs, this.healthNoAbs, Attributes.health.def, Attributes.health.name), 685 Types.Attribute(Attributes.absorption.min, this.maxAbsorption, this.absorption, Attributes.absorption.def, Attributes.absorption.name) 686 ]; 687 this.sendPacket(new Play.UpdateAttributes(this.id, attributes)); 688 } 689 690 public override @trusted void hungerUpdated() { 691 super.hungerUpdated(); 692 auto attributes = [ 693 Types.Attribute(Attributes.hunger.min, Attributes.hunger.max, this.hunger, Attributes.hunger.def, Attributes.hunger.name), 694 Types.Attribute(Attributes.saturation.min, Attributes.saturation.max, this.saturation, Attributes.saturation.def, Attributes.saturation.name) 695 ]; 696 this.sendPacket(new Play.UpdateAttributes(this.id, attributes)); 697 } 698 699 protected override void onEffectAdded(Effect effect, bool modified) { 700 if(effect.bedrock) this.sendPacket(new Play.MobEffect(this.id, modified ? Play.MobEffect.MODIFY : Play.MobEffect.ADD, effect.bedrock.id, effect.level, true, cast(int)effect.duration)); 701 } 702 703 protected override void onEffectRemoved(Effect effect) { 704 if(effect.bedrock) this.sendPacket(new Play.MobEffect(this.id, Play.MobEffect.REMOVE, effect.bedrock.id, effect.level)); 705 } 706 707 public override void recalculateSpeed() { 708 super.recalculateSpeed(); 709 this.sendPacket(new Play.UpdateAttributes(this.id, [Types.Attribute(Attributes.speed.min, Attributes.speed.max, this.speed, Attributes.speed.def, Attributes.speed.name)])); 710 } 711 712 public override void sendJoinPacket() { 713 //TODO send thunders 714 auto packet = new Play.StartGame(this.id, this.id); 715 packet.gamemode = convertGamemode(this.gamemode); 716 packet.position = (cast(Vector3!float)this.position).tuple; 717 packet.yaw = this.yaw; 718 packet.pitch = this.pitch; 719 packet.seed = this.world.seed; 720 packet.dimension = this.world.dimension; 721 packet.generator = this.world.type=="flat" ? 2 : 1; 722 packet.worldGamemode = convertGamemode(this.world.gamemode); 723 packet.difficulty = this.world.difficulty; 724 packet.spawnPosition = (cast(Vector3!int)this.spawn).tuple; 725 packet.time = this.world.time.to!uint; 726 packet.vers = this.server.config.hub.edu; 727 packet.rainLevel = this.world.weather.raining ? this.world.weather.intensity : 0; 728 packet.commandsEnabled = true; 729 static if(__protocol >= 120) packet.permissionLevel = this.op ? 1 : 0; 730 packet.levelId = Software.display; 731 packet.worldName = this.server.name; 732 this.sendPacket(packet); 733 } 734 735 public override void sendResourcePack() {} 736 737 public override void sendPermissionLevel(PermissionLevel) { 738 this.sendSettingsPacket(); 739 } 740 741 public override void sendDifficulty(Difficulty difficulty) { 742 this.sendPacket(new Play.SetDifficulty(difficulty)); 743 } 744 745 public override void sendWorldGamemode(Gamemode gamemode) { 746 this.sendPacket(new Play.SetDefaultGameType(convertGamemode(gamemode))); 747 } 748 749 public override void sendDoDaylightCycle(bool cycle) { 750 this.sendGamerule(Types.Rule.DO_DAYLIGHT_CYCLE, cycle); 751 } 752 753 public override void sendTime(uint time) { 754 this.sendPacket(new Play.SetTime(time)); 755 } 756 757 public override void sendWeather(bool raining, bool thunderous, uint time, uint intensity) { 758 if(raining) { 759 this.sendLevelEvent(Play.LevelEvent.START_RAIN, EntityPosition(0), intensity * 24000); 760 if(thunderous) this.sendLevelEvent(Play.LevelEvent.START_THUNDER, EntityPosition(0), time); 761 else this.sendLevelEvent(Play.LevelEvent.STOP_THUNDER, EntityPosition(0), 0); 762 } else { 763 this.sendLevelEvent(Play.LevelEvent.STOP_RAIN, EntityPosition(0), 0); 764 this.sendLevelEvent(Play.LevelEvent.STOP_THUNDER, EntityPosition(0), 0); 765 } 766 } 767 768 public override void sendSettingsPacket() { 769 uint flags = Play.AdventureSettings.EVP_DISABLED; // player vs environment is disabled and the animation is done by server 770 if(this.adventure || this.spectator) flags |= Play.AdventureSettings.IMMUTABLE_WORLD; 771 if(!this.world.pvp || this.spectator) flags |= Play.AdventureSettings.PVP_DISABLED; 772 if(this.spectator) flags |= Play.AdventureSettings.PVM_DISABLED; 773 if(this.creative || this.spectator) flags |= Play.AdventureSettings.ALLOW_FLIGHT; 774 if(this.spectator) flags |= Play.AdventureSettings.NO_CLIP; 775 if(this.spectator) flags |= Play.AdventureSettings.FLYING; 776 uint abilities; 777 if(this.hasPermission("minecraft.build_and_mine")) abilities |= Play.AdventureSettings.BUILD_AND_MINE; 778 if(this.hasPermission("minecraft.doors_and_switches")) abilities |= Play.AdventureSettings.DOORS_AND_SWITCHES; 779 if(this.hasPermission("minecraft.open_containers")) abilities |= Play.AdventureSettings.OPEN_CONTAINERS; 780 if(this.hasPermission("minecraft.attack_players")) abilities |= Play.AdventureSettings.ATTACK_PLAYERS; 781 if(this.hasPermission("minecraft.attack_mobs")) abilities |= Play.AdventureSettings.ATTACK_MOBS; 782 if(this.operator) abilities |= Play.AdventureSettings.OP; 783 if(this.hasPermission("minecraft.teleport")) abilities |= Play.AdventureSettings.TELEPORT; 784 this.sendPacket(new Play.AdventureSettings(flags, this.permissionLevel, abilities)); 785 } 786 787 public override void sendRespawnPacket() { 788 this.sendPacket(new Play.Respawn((cast(Vector3!float)(this.spawn + [0, this.eyeHeight, 0])).tuple)); 789 } 790 791 public override void setAsReadyToSpawn() { 792 this.sendPacket(new Play.PlayStatus(Play.PlayStatus.SPAWNED)); 793 if(!this.hasResourcePack) { 794 // require custom texture 795 this.sendPacket(new Play.ResourcePacksInfo(true, new Types.PackWithSize[0], [Types.PackWithSize(resourcePackId, Software.fullVersion, resourcePackSize)])); 796 } else if(resourcePackChunks.length == 0) { 797 // no resource pack 798 this.sendPacket(new Play.ResourcePacksInfo(false)); 799 } 800 this.send_commands = true; 801 this.sendCommands(); 802 } 803 804 private void sendLevelEvent(typeof(Play.LevelEvent.eventId) evid, EntityPosition position, uint data) { 805 this.sendPacket(new Play.LevelEvent(evid, (cast(Vector3!float)position).tuple, data)); 806 } 807 808 public override void sendLightning(Lightning lightning) { 809 this.sendAddEntity(lightning); 810 } 811 812 public override void sendAnimation(Entity entity) { 813 this.sendPacket(new Play.Animate(Play.Animate.BREAKING, entity.id)); 814 } 815 816 public override void sendBlocks(PlacedBlock[] blocks) { 817 foreach(PlacedBlock block ; blocks) { 818 this.sendPacket(new Play.UpdateBlock(toBlockPosition(block.position), block.bedrock.id, 176 | block.bedrock.meta)); 819 } 820 this.broken_by_this.length = 0; 821 } 822 823 public override void sendTile(Tile tile, bool translatable) { 824 if(translatable) { 825 //TODO 826 //tile.to!ITranslatable.translateStrings(this.lang); 827 } 828 auto packet = new Play.BlockEntityData(toBlockPosition(tile.position)); 829 if(tile.pocketCompound !is null) { 830 auto stream = new NetworkStream!(Endian.littleEndian)(); 831 stream.writeTag(tile.pocketCompound); 832 packet.nbt = stream.buffer; 833 } else { 834 packet.nbt ~= NBT_TYPE.END; 835 } 836 this.sendPacket(packet); 837 /*if(translatable) { 838 tile.to!ITranslatable.untranslateStrings(); 839 }*/ 840 } 841 842 public override void sendPickupItem(Entity picker, Entity picked) { 843 this.sendPacket(new Play.TakeItemEntity(picked.id, picker.id)); 844 } 845 846 public override void sendPassenger(ubyte mode, uint passenger, uint vehicle) { 847 this.sendPacket(new Play.SetEntityLink(passenger, vehicle, mode)); 848 } 849 850 public override void sendExplosion(EntityPosition position, float radius, Vector3!byte[] updates) { 851 Types.BlockPosition[] upd; 852 foreach(Vector3!byte u ; updates) { 853 upd ~= toBlockPosition(cast(Vector3!int)u); 854 } 855 this.sendPacket(new Play.Explode((cast(Vector3!float)position).tuple, radius, upd)); 856 } 857 858 public override void sendMap(Map map) { 859 //TODO implement this! 860 //this.sendPacket(map.pecompression.length > 0 ? new PocketBatch(map.pecompression) : map.pocketpacket); 861 } 862 863 public override void sendMusic(EntityPosition position, ubyte instrument, uint pitch) { 864 this.sendPacket(new Play.LevelSoundEvent(Play.LevelSoundEvent.NOTE, (cast(Vector3!float)position).tuple, instrument, pitch, false)); 865 } 866 867 protected override void sendCommands() { 868 this.sent_commands.clear(); 869 auto packet = new Play.AvailableCommands(); 870 ushort addValue(string value) { 871 foreach(ushort i, v; packet.enumValues) { 872 if(v == value) return i; 873 } 874 packet.enumValues ~= value; 875 return cast(ushort)(packet.enumValues.length - 1); 876 } 877 uint addEnum(string name, inout(string)[] values) { 878 foreach(uint i, enum_; packet.enums) { 879 if(enum_.name == name) return i; 880 } 881 auto enum_ = Types.Enum(name); 882 foreach(value ; values) { 883 enum_.valuesIndexes ~= addValue(value); 884 } 885 packet.enums ~= enum_; 886 return packet.enums.length.to!uint - 1; 887 } 888 foreach(command ; this.availableCommands) { 889 if(!command.hidden) { 890 Types.Command pc; 891 pc.name = command.name; 892 if(command.description.type == Description.TEXT) pc.description = command.description.text; 893 else if(command.description.type == Description.TRANSLATABLE) { 894 if(command.description.translatable.bedrock.length) pc.description = command.description.translatable.bedrock; 895 else pc.description = this.server.lang.translate(command.description.translatable.default_, this.language); 896 } 897 if(command.aliases.length) { 898 pc.aliasesEnum = addEnum(command.name ~ ".aliases", command.aliases); 899 } 900 foreach(overload ; command.overloads) { 901 Types.Overload po; 902 foreach(i, name; overload.params) { 903 auto parameter = Types.Parameter(name, Types.Parameter.VALID, i >= overload.requiredArgs); 904 parameter.type |= { 905 final switch(overload.pocketTypeOf(i)) with(Types.Parameter) { 906 case PocketType.integer: return INT; 907 case PocketType.floating: return FLOAT; 908 case PocketType.target: return TARGET; 909 case PocketType..string: return STRING; 910 case PocketType.blockpos: return POSITION; 911 case PocketType.rawtext: return RAWTEXT; 912 case PocketType.stringenum: return ENUM | addEnum(overload.typeOf(i), overload.enumMembers(i)); 913 case PocketType.boolean: return ENUM | addEnum("bool", ["true", "false"]); 914 } 915 }(); 916 po.parameters ~= parameter; 917 } 918 pc.overloads ~= po; 919 } 920 packet.commands ~= pc; 921 } 922 } 923 if(packet.enumValues.length > 0 && packet.enumValues.length < 257) packet.enumValues.length = 257; //TODO fix protocol 924 this.sendPacket(packet); 925 } 926 927 // generic 928 929 private void sendGamerule(const string name, bool value) { 930 this.sendPacket(new Play.GameRulesChanged([Types.Rule(name, Types.Rule.BOOLEAN, value)])); 931 } 932 933 mixin generateHandlers!(Play.Packets); 934 935 protected void handleResourcePackClientResponsePacket(ubyte status, string[] packIds) { 936 if(resourcePackId.length) { 937 // only handle if the server has a resource pack to serve 938 if(status == Play.ResourcePackClientResponse.SEND_PACKS) { 939 this.sendPacket(new Play.ResourcePackDataInfo(resourcePackId, 4096u, resourcePackChunks.length.to!uint, resourcePackSize, resourcePackHash)); 940 foreach(uint i, chunk; resourcePackChunks) { 941 this.sendPacket(new Play.ResourcePackChunkData(resourcePackId, i, i*4096u, chunk)); 942 } 943 } else { 944 //TODO 945 } 946 } 947 } 948 949 protected void handleResourcePackChunkDataRequestPacket(string id, uint index) { 950 //TODO send chunk 951 } 952 953 protected void handleTextChatPacket(bool unknown1, string sender, string message, string xuid) { 954 this.handleTextMessage(message); 955 } 956 957 protected void handleMovePlayerPacket(long eid, typeof(Play.MovePlayer.position) position, float pitch, float bodyYaw, float yaw, ubyte mode, bool onGround, long unknown7, int unknown8, int unknown9) { 958 position.y -= this.eyeHeight; 959 this.handleMovementPacket(cast(EntityPosition)Vector3!float(position), yaw, bodyYaw, pitch); 960 } 961 962 protected void handleRiderJumpPacket(long eid) {} 963 964 //protected void handleLevelSoundEventPacket(ubyte sound, typeof(Play.LevelSoundEvent.position) position, uint volume, int pitch, bool u1, bool u2) {} 965 966 protected void handleEntityEventPacket(long eid, ubyte evid, int unknown) { 967 if(evid == Play.EntityEvent.USE_ITEM) { 968 //TODO 969 } 970 } 971 972 protected void handleMobEquipmentPacket(long eid, Types.Slot item, ubyte inventorySlot, ubyte hotbarSlot, ubyte unknown) { 973 /+if(hotbarSlot < 9) { 974 if(inventorySlot == 255) { 975 // empty 976 this.inventory.hotbar[hotbarSlot] = 255; 977 } else { 978 inventorySlot -= 9; 979 if(inventorySlot < this.inventory.length) { 980 if(this.inventory.hotbar.hotbar.canFind(hotbarSlot)) { 981 // switch item 982 auto s = this.inventory.hotbar[hotbarSlot]; 983 log("switching ", s, " with ", inventorySlot); 984 if(s == inventorySlot) { 985 // just selecting 986 } else { 987 // idk what to do 988 } 989 } else { 990 // just move 991 this.inventory.hotbar[hotbarSlot] = inventorySlot; 992 } 993 } 994 } 995 this.inventory.selected = hotbarSlot; 996 } 997 foreach(i ; this.inventory.hotbar) { 998 log(i == 255 ? "null" : to!string(this.inventory[i])); 999 }+/ 1000 } 1001 1002 //protected void handleMobArmorEquipmentPacket(long eid, Types.Slot[4] armor) {} 1003 1004 protected void handleInteractPacket(ubyte action, long target, typeof(Play.Interact.targetPosition) position) { 1005 switch(action) { 1006 case Play.Interact.LEAVE_VEHICLE: 1007 //TODO 1008 break; 1009 case Play.Interact.HOVER: 1010 //TODO 1011 break; 1012 default: 1013 break; 1014 } 1015 } 1016 1017 //protected void handleUseItemPacket(Types.BlockPosition blockPosition, uint hotbarSlot, uint face, typeof(Play.UseItem.facePosition) facePosition, typeof(Play.UseItem.position) position, int slot, Types.Slot item) {} 1018 1019 protected void handlePlayerActionPacket(long eid, typeof(Play.PlayerAction.action) action, Types.BlockPosition position, int face) { 1020 switch(action) { 1021 case Play.PlayerAction.START_BREAK: 1022 this.handleStartBlockBreaking(fromBlockPosition(position)); 1023 break; 1024 case Play.PlayerAction.ABORT_BREAK: 1025 this.handleAbortBlockBreaking(); 1026 break; 1027 case Play.PlayerAction.STOP_BREAK: 1028 this.handleBlockBreaking(); 1029 break; 1030 case Play.PlayerAction.STOP_SLEEPING: 1031 this.handleStopSleeping(); 1032 break; 1033 case Play.PlayerAction.RESPAWN: 1034 this.handleRespawn(); 1035 break; 1036 case Play.PlayerAction.JUMP: 1037 this.handleJump(); 1038 break; 1039 case Play.PlayerAction.START_SPRINT: 1040 this.handleSprinting(true); 1041 if(Effects.speed in this) this.recalculateSpeed(); 1042 break; 1043 case Play.PlayerAction.STOP_SPRINT: 1044 this.handleSprinting(false); 1045 if(Effects.speed in this) this.recalculateSpeed(); 1046 break; 1047 case Play.PlayerAction.START_SNEAK: 1048 this.handleSneaking(true); 1049 break; 1050 case Play.PlayerAction.STOP_SNEAK: 1051 this.handleSneaking(false); 1052 break; 1053 case Play.PlayerAction.START_GLIDING: 1054 //TODO 1055 break; 1056 case Play.PlayerAction.STOP_GLIDING: 1057 //TODO 1058 break; 1059 default: 1060 break; 1061 } 1062 } 1063 1064 //protected void handlePlayerFallPacket(float distance) {} 1065 1066 protected void handleAnimatePacket(uint action, long eid, float unknown2) { 1067 if(action == Play.Animate.BREAKING) this.handleArmSwing(); 1068 } 1069 1070 //protected void handleDropItemPacket(ubyte type, Types.Slot slot) {} 1071 1072 //protected void handleInventoryActionPacket(uint action, Types.Slot item) {} 1073 1074 //protected void handleContainerSetSlotPacket(ubyte window, uint slot, uint hotbar_slot, Types.Slot item, ubyte unknown) {} 1075 1076 //protected void handleCraftingEventPacket(ubyte window, uint type, UUID uuid, Types.Slot[] input, Types.Slot[] output) {} 1077 1078 protected void handleAdventureSettingsPacket(uint flags, uint unknown1, uint permissions, uint permissionLevel, uint customPermissions, long eid) { 1079 if(flags & Play.AdventureSettings.FLYING) { 1080 if(!this.creative && !this.spectator) this.kick("Flying is not enabled on this server"); 1081 //TODO set as flying 1082 } 1083 } 1084 1085 //protected void handlePlayerInputPacket(typeof(Play.PlayerInput.motion) motion, ushort flags, bool unknown) {} 1086 1087 protected void handleSetPlayerGameTypePacket(int gamemode) { 1088 if(this.op && gamemode >= 0 && gamemode <= 2) { 1089 this.gamemode = gamemode & 0b11; 1090 } else { 1091 this.sendGamemode(); 1092 } 1093 } 1094 1095 //protected void handleMapInfoRequestPacket(long mapId) {} 1096 1097 //protected void handleReplaceSelectedItemPacket(Types.Slot slot) {} 1098 1099 //protected void handleShowCreditsPacket(ubyte[] payload) {} 1100 1101 protected void handleCommandRequestPacket(string command, uint type, string requestId, uint playerId) { 1102 if(command.startsWith("/")) command = command[1..$]; 1103 if(command.length) { 1104 this.callCommand(command); 1105 } 1106 } 1107 1108 protected void handleCommandRequestPacket(string command, uint type, Types.McpeUuid uuid, string requestId, uint playerId, bool internal) { 1109 this.handleCommandRequestPacket(command, type, requestId, playerId); 1110 } 1111 1112 enum string stringof = "PocketPlayer!" ~ to!string(__protocol); 1113 1114 private static class Compression : Player.Compression { 1115 1116 protected override ubyte[] compress(ubyte[] payload) { 1117 ubyte[] data; 1118 Compress compress = new Compress(6, HeaderFormat.deflate); //TODO smaller level for smaller payloads 1119 data ~= cast(ubyte[])compress.compress(payload); 1120 data ~= cast(ubyte[])compress.flush(); 1121 return data; 1122 } 1123 1124 } 1125 1126 }