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