diff options
-rw-r--r-- | src/Protocol/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/Protocol/ProtocolRecognizer.cpp | 7 | ||||
-rw-r--r-- | src/Protocol/ProtocolRecognizer.h | 7 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_12.cpp | 1509 | ||||
-rw-r--r-- | src/Protocol/Protocol_1_12.h | 66 |
5 files changed, 1589 insertions, 3 deletions
diff --git a/src/Protocol/CMakeLists.txt b/src/Protocol/CMakeLists.txt index dd24c710a..f21c81f83 100644 --- a/src/Protocol/CMakeLists.txt +++ b/src/Protocol/CMakeLists.txt @@ -11,6 +11,7 @@ SET (SRCS Protocol_1_9.cpp Protocol_1_10.cpp Protocol_1_11.cpp + Protocol_1_12.cpp ProtocolRecognizer.cpp ) @@ -24,6 +25,7 @@ SET (HDRS Protocol_1_9.h Protocol_1_10.h Protocol_1_11.h + Protocol_1_12.h ProtocolRecognizer.h ) @@ -32,6 +34,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set_source_files_properties(Protocol_1_8.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") set_source_files_properties(Protocol_1_10.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") set_source_files_properties(Protocol_1_11.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") + set_source_files_properties(Protocol_1_12.cpp PROPERTIES COMPILE_FLAGS "-Wno-error=switch") endif() if (NOT MSVC) diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index 011070998..4487cdf6b 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -11,6 +11,7 @@ #include "Protocol_1_9.h" #include "Protocol_1_10.h" #include "Protocol_1_11.h" +#include "Protocol_1_12.h" #include "Packetizer.h" #include "../ClientHandle.h" #include "../Root.h" @@ -57,6 +58,7 @@ AString cProtocolRecognizer::GetVersionTextFromInt(int a_ProtocolVersion) case PROTO_VERSION_1_10_0: return "1.10"; case PROTO_VERSION_1_11_0: return "1.11"; case PROTO_VERSION_1_11_1: return "1.11.1"; + case PROTO_VERSION_1_12: return "1.12"; } ASSERT(!"Unknown protocol version"); return Printf("Unknown protocol (%d)", a_ProtocolVersion); @@ -1085,6 +1087,11 @@ bool cProtocolRecognizer::TryRecognizeLengthedProtocol(UInt32 a_PacketLengthRema m_Protocol = new cProtocol_1_11_1(m_Client, ServerAddress, ServerPort, NextState); return true; } + case PROTO_VERSION_1_12: + { + m_Protocol = new cProtocol_1_12(m_Client, ServerAddress, ServerPort, NextState); + return true; + } default: { LOGD("Client \"%s\" uses an unsupported protocol (lengthed, version %u (0x%x))", diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 921efcea7..1a638fb21 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -18,9 +18,9 @@ // Adjust these if a new protocol is added or an old one is removed: -#define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x, 1.10.x, 1.11.x" -#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210, 315, 316" -#define MCS_LATEST_PROTOCOL_VERSION 316 +#define MCS_CLIENT_VERSIONS "1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12" +#define MCS_PROTOCOL_VERSIONS "47, 107, 108, 109, 110, 210, 315, 316, 335" +#define MCS_LATEST_PROTOCOL_VERSION 335 @@ -42,6 +42,7 @@ public: PROTO_VERSION_1_10_0 = 210, PROTO_VERSION_1_11_0 = 315, PROTO_VERSION_1_11_1 = 316, + PROTO_VERSION_1_12 = 335, } ; cProtocolRecognizer(cClientHandle * a_Client); diff --git a/src/Protocol/Protocol_1_12.cpp b/src/Protocol/Protocol_1_12.cpp new file mode 100644 index 000000000..8d56a95bd --- /dev/null +++ b/src/Protocol/Protocol_1_12.cpp @@ -0,0 +1,1509 @@ + +// Protocol_1_12.cpp + +/* +Implements the 1.12 protocol classes: +- release 1.12 protocol (#335) +*/ + +#include "Globals.h" +#include "Protocol_1_12.h" +#include "ProtocolRecognizer.h" +#include "Packetizer.h" + +#include "../Entities/Boat.h" +#include "../Entities/Minecart.h" +#include "../Entities/Pickup.h" +#include "../Entities/Player.h" +#include "../Entities/ItemFrame.h" +#include "../Entities/ArrowEntity.h" +#include "../Entities/FireworkEntity.h" +#include "../Entities/SplashPotionEntity.h" + +#include "../Mobs/IncludeAllMonsters.h" + +#include "../Root.h" +#include "../Server.h" +#include "../ClientHandle.h" +#include "../Bindings/PluginManager.h" + + + + + +// The disabled error is intended, since the Metadata have overlapping indexes +// based on the type of the Entity. +// +// IMPORTANT: The enum is used to automate the sequential counting of the +// Metadata indexes. Adding a new enum value causes the following values to +// increase their index. Therefore the ordering of the enum values is VERY important! +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wduplicate-enum" +#endif + +namespace Metadata +{ + enum Metadata_Index + { + // Entity + ENTITY_FLAGS, + ENTITY_AIR, + ENTITY_CUSTOM_NAME, + ENTITY_CUSTOM_NAME_VISIBLE, + ENTITY_SILENT, + ENTITY_NO_GRAVITY, + _ENTITY_NEXT, // Used by descendants + + // Potion + POTION_THROWN = _ENTITY_NEXT, + + // FallingBlock + FALLING_BLOCK_POSITION = _ENTITY_NEXT, + + // AreaEffectCloud + AREA_EFFECT_CLOUD_RADIUS = _ENTITY_NEXT, + AREA_EFFECT_CLOUD_COLOR, + AREA_EFFECT_CLOUD_SINGLE_POINT_EFFECT, + AREA_EFFECT_CLOUD_PARTICLE_ID, + AREA_EFFECT_CLOUD_PARTICLE_PARAMETER1, + AREA_EFFECT_CLOUD_PARTICLE_PARAMETER2, + + // Arrow + ARROW_CRITICAL = _ENTITY_NEXT, + _ARROW_NEXT, + + // TippedArrow + TIPPED_ARROW_COLOR = _ARROW_NEXT, + + // Boat + BOAT_LAST_HIT_TIME = _ENTITY_NEXT, + BOAT_FORWARD_DIRECTION, + BOAT_DAMAGE_TAKEN, + BOAT_TYPE, + BOAT_RIGHT_PADDLE_TURNING, + BOAT_LEFT_PADDLE_TURNING, + + // EnderCrystal + ENDER_CRYSTAL_BEAM_TARGET = _ENTITY_NEXT, + ENDER_CRYSTAL_SHOW_BOTTOM, + + // Fireball + _FIREBALL_NEXT = _ENTITY_NEXT, + + // WitherSkull + WITHER_SKULL_INVULNERABLE = _FIREBALL_NEXT, + + // Fireworks + FIREWORK_INFO = _ENTITY_NEXT, + FIREWORK_BOOSTED_ENTITY_ID, // 1.11.1 only + + // Hanging + _HANGING_NEXT = _ENTITY_NEXT, + + // ItemFrame + ITEM_FRAME_ITEM = _HANGING_NEXT, + ITEM_FRAME_ROTATION, + + // Item + ITEM_ITEM = _ENTITY_NEXT, + + // Living + LIVING_ACTIVE_HAND = _ENTITY_NEXT, + LIVING_HEALTH, + LIVING_POTION_EFFECT_COLOR, + LIVING_POTION_EFFECT_AMBIENT, + LIVING_NUMBER_OF_ARROWS, + _LIVING_NEXT, + + // Player + PLAYER_ADDITIONAL_HEARTHS = _LIVING_NEXT, + PLAYER_SCORE, + PLAYER_DISPLAYED_SKIN_PARTS, + PLAYER_MAIN_HAND, + + // ArmorStand + ARMOR_STAND_STATUS = _LIVING_NEXT, + ARMOR_STAND_HEAD_ROTATION, + ARMOR_STAND_BODY_ROTATION, + ARMOR_STAND_LEFT_ARM_ROTATION, + ARMOR_STAND_RIGHT_ARM_ROTATION, + ARMOR_STAND_LEFT_LEG_ROTATION, + ARMOR_STAND_RIGHT_LEG_ROTATION, + + // Insentient + INSENTIENT_STATUS = _LIVING_NEXT, + _INSENTIENT_NEXT, + + // Ambient + _AMBIENT_NEXT = _INSENTIENT_NEXT, + + // Bat + BAT_HANGING = _AMBIENT_NEXT, + + // Creature + _CREATURE_NEXT = _INSENTIENT_NEXT, + + // Ageable + AGEABLE_BABY = _CREATURE_NEXT, + _AGEABLE_NEXT, + + // PolarBear + POLAR_BEAR_STANDING = _AGEABLE_NEXT, + + // Animal + _ANIMAL_NEXT = _AGEABLE_NEXT, + + // Abstract horse + ABSTRACT_HORSE_STATUS = _ANIMAL_NEXT, + ABSTRACT_HORSE_OWNER, + _ABSTRACT_HORSE_NEXT, + + // Horse + HORSE_VARIANT = _ABSTRACT_HORSE_NEXT, + HORSE_ARMOR, + + // Chested horse + CHESTED_HORSE_CHESTED = _ABSTRACT_HORSE_NEXT, + _CHESTED_HORSE_NEXT, + + // Llama + LLAMA_STRENGTH = _CHESTED_HORSE_NEXT, + LLAMA_CARPET_COLOR, + LLAMA_VARIANT, + + // Pig + PIG_HAS_SADDLE = _ANIMAL_NEXT, + PIG_TOTAL_CARROT_ON_A_STICK_BOOST, // 1.11.1 only + + // Rabbit + RABBIT_TYPE = _ANIMAL_NEXT, + + // Sheep + SHEEP_STATUS = _ANIMAL_NEXT, + + // TameableAnimal + TAMEABLE_ANIMAL_STATUS = _ANIMAL_NEXT, + TAMEABLE_ANIMAL_OWNER, + _TAMEABLE_NEXT, + + // Ocelot + OCELOT_TYPE = _TAMEABLE_NEXT, + + // Wolf + WOLF_DAMAGE_TAKEN = _TAMEABLE_NEXT, + WOLF_BEGGING, + WOLF_COLLAR_COLOR, + + // Villager + VILLAGER_PROFESSION = _AGEABLE_NEXT, + + // Golem + _GOLEM_NEXT = _CREATURE_NEXT, + + // IronGolem + IRON_GOLEM_PLAYER_CREATED = _GOLEM_NEXT, + + // Shulker + SHULKER_FACING_DIRECTION = _GOLEM_NEXT, + SHULKER_ATTACHMENT_FALLING_BLOCK_POSITION, + SHULKER_SHIELD_HEIGHT, + + // Monster + _MONSTER_NEXT = _CREATURE_NEXT, + + // Blaze + BLAZE_ON_FIRE = _MONSTER_NEXT, + + // Creeper + CREEPER_STATE = _MONSTER_NEXT, + CREEPER_POWERED, + CREEPER_IGNITED, + + // Guardian + GUARDIAN_STATUS = _MONSTER_NEXT, + GUARDIAN_TARGET, + + // Abstract Skeleton + ABSTRACT_SKELETON_ARMS_SWINGING = _MONSTER_NEXT, + + // Spider + SPIDER_CLIMBING = _MONSTER_NEXT, + + // Witch + WITCH_AGGRESIVE = _MONSTER_NEXT, + + // Wither + WITHER_FIRST_HEAD_TARGET = _MONSTER_NEXT, + WITHER_SECOND_HEAD_TARGET, + WITHER_THIRD_HEAD_TARGET, + WITHER_INVULNERABLE_TIMER, + + // Zombie + ZOMBIE_IS_BABY = _MONSTER_NEXT, + ZOMBIE_UNUSED, // Was type + ZOMBIE_HANDS_RISED_UP, + _ZOMBIE_NEXT, + + // Zombie villager + ZOMBIE_VILLAGER_CONVERTING = _ZOMBIE_NEXT, + ZOMBIE_VILLAGER_PROFESSION, + + // Enderman + ENDERMAN_CARRIED_BLOCK = _MONSTER_NEXT, + ENDERMAN_SCREAMING, + + // Evocation illager + EVOKER_SPELL = _MONSTER_NEXT, + + // Vex + VEX_FLAGS = _MONSTER_NEXT, + + // Vindication illager + VINDICATOR_FLAGS = _MONSTER_NEXT, + + // EnderDragon + ENDER_DRAGON_DRAGON_PHASE = _INSENTIENT_NEXT, + + // Flying + _FLYING_NEXT = _INSENTIENT_NEXT, + + // Ghast + GHAST_ATTACKING = _FLYING_NEXT, + + // Slime + SLIME_SIZE = _INSENTIENT_NEXT, + + // Minecart + MINECART_SHAKING_POWER = _ENTITY_NEXT, + MINECART_SHAKING_DIRECTION, + MINECART_SHAKING_MULTIPLIER, + MINECART_BLOCK_ID_META, + MINECART_BLOCK_Y, + MINECART_SHOW_BLOCK, + _MINECART_NEXT, + + // MinecartCommandBlock + MINECART_COMMAND_BLOCK_COMMAND = _MINECART_NEXT, + MINECART_COMMAND_BLOCK_LAST_OUTPUT, + + // MinecartFurnace + MINECART_FURNACE_POWERED = _MINECART_NEXT, + + // TNTPrimed + TNT_PRIMED_FUSE_TIME = _ENTITY_NEXT, + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop // Restore ignored clang errors +#endif + + + + + +#define HANDLE_READ(ByteBuf, Proc, Type, Var) \ + Type Var; \ + if (!ByteBuf.Proc(Var))\ + {\ + return;\ + } + + + + + +cProtocol_1_12::cProtocol_1_12(cClientHandle * a_Client, const AString & a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State) : + super(a_Client, a_ServerAddress, a_ServerPort, a_State) +{ +} + + + + + +void cProtocol_1_12::SendSpawnMob(const cMonster & a_Mob) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x03); // Spawn Mob packet + Pkt.WriteVarInt32(a_Mob.GetUniqueID()); + // TODO: Bad way to write a UUID, and it's not a true UUID, but this is functional for now. + Pkt.WriteBEUInt64(0); + Pkt.WriteBEUInt64(a_Mob.GetUniqueID()); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Mob.GetMobType())); + Pkt.WriteBEDouble(a_Mob.GetPosX()); + Pkt.WriteBEDouble(a_Mob.GetPosY()); + Pkt.WriteBEDouble(a_Mob.GetPosZ()); + Pkt.WriteByteAngle(a_Mob.GetPitch()); + Pkt.WriteByteAngle(a_Mob.GetHeadYaw()); + Pkt.WriteByteAngle(a_Mob.GetYaw()); + Pkt.WriteBEInt16(static_cast<Int16>(a_Mob.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast<Int16>(a_Mob.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast<Int16>(a_Mob.GetSpeedZ() * 400)); + WriteEntityMetadata(Pkt, a_Mob); + Pkt.WriteBEUInt8(0xff); // Metadata terminator +} + + + + + +void cProtocol_1_12::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) +{ + int BlockX, BlockY, BlockZ; + if (!a_ByteBuffer.ReadPosition64(BlockX, BlockY, BlockZ)) + { + return; + } + + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Face); + HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorX); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorY); + HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorZ); + m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), m_Client->GetPlayer()->GetEquippedItem()); +} + + + + + +void cProtocol_1_12::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) +{ + cServer * Server = cRoot::Get()->GetServer(); + AString ServerDescription = Server->GetDescription(); + int NumPlayers = Server->GetNumPlayers(); + int MaxPlayers = Server->GetMaxPlayers(); + AString Favicon = Server->GetFaviconData(); + cRoot::Get()->GetPluginManager()->CallHookServerPing(*m_Client, ServerDescription, NumPlayers, MaxPlayers, Favicon); + + // Version: + Json::Value Version; + Version["name"] = "Cuberite 1.12"; + Version["protocol"] = cProtocolRecognizer::PROTO_VERSION_1_12; + + // Players: + Json::Value Players; + Players["online"] = NumPlayers; + Players["max"] = MaxPlayers; + // TODO: Add "sample" + + // Description: + Json::Value Description; + Description["text"] = ServerDescription.c_str(); + + // Create the response: + Json::Value ResponseValue; + ResponseValue["version"] = Version; + ResponseValue["players"] = Players; + ResponseValue["description"] = Description; + if (!Favicon.empty()) + { + ResponseValue["favicon"] = Printf("data:image/png;base64,%s", Favicon.c_str()); + } + + // Serialize the response into a packet: + Json::FastWriter Writer; + cPacketizer Pkt(*this, 0x00); // Response packet + Pkt.WriteString(Writer.write(ResponseValue)); +} + + + + + +void cProtocol_1_12::WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) +{ + using namespace Metadata; + + // Common metadata: + Int8 Flags = 0; + if (a_Entity.IsOnFire()) + { + Flags |= 0x01; + } + if (a_Entity.IsCrouched()) + { + Flags |= 0x02; + } + if (a_Entity.IsSprinting()) + { + Flags |= 0x08; + } + if (a_Entity.IsRclking()) + { + Flags |= 0x10; + } + if (a_Entity.IsInvisible()) + { + Flags |= 0x20; + } + a_Pkt.WriteBEUInt8(ENTITY_FLAGS); // Index + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); // Type + a_Pkt.WriteBEInt8(Flags); + + switch (a_Entity.GetEntityType()) + { + case cEntity::etPlayer: + { + auto & Player = reinterpret_cast<const cPlayer &>(a_Entity); + + // TODO Set player custom name to their name. + // Then it's possible to move the custom name of mobs to the entities + // and to remove the "special" player custom name. + a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME); + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(Player.GetName()); + + a_Pkt.WriteBEUInt8(LIVING_HEALTH); + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast<float>(Player.GetHealth())); + + a_Pkt.WriteBEUInt8(PLAYER_DISPLAYED_SKIN_PARTS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEUInt8(static_cast<UInt8>(Player.GetSkinParts())); + + a_Pkt.WriteBEUInt8(PLAYER_MAIN_HAND); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEUInt8(static_cast<UInt8>(Player.GetMainHand())); + break; + } + case cEntity::etPickup: + { + a_Pkt.WriteBEUInt8(ITEM_ITEM); + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast<const cPickup &>(a_Entity).GetItem()); + break; + } + case cEntity::etMinecart: + { + a_Pkt.WriteBEUInt8(MINECART_SHAKING_POWER); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + + // The following expression makes Minecarts shake more with less health or higher damage taken + auto & Minecart = reinterpret_cast<const cMinecart &>(a_Entity); + auto maxHealth = a_Entity.GetMaxHealth(); + auto curHealth = a_Entity.GetHealth(); + a_Pkt.WriteVarInt32(static_cast<UInt32>((maxHealth - curHealth) * Minecart.LastDamage() * 4)); + + a_Pkt.WriteBEUInt8(MINECART_SHAKING_DIRECTION); // (doesn't seem to effect anything) + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(1); + + a_Pkt.WriteBEUInt8(MINECART_SHAKING_MULTIPLIER); // or damage taken + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast<float>(Minecart.LastDamage() + 10)); + + if (Minecart.GetPayload() == cMinecart::mpNone) + { + auto & RideableMinecart = reinterpret_cast<const cRideableMinecart &>(Minecart); + const cItem & MinecartContent = RideableMinecart.GetContent(); + if (!MinecartContent.IsEmpty()) + { + a_Pkt.WriteBEUInt8(MINECART_BLOCK_ID_META); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Content = MinecartContent.m_ItemType; + Content |= MinecartContent.m_ItemDamage << 8; + a_Pkt.WriteVarInt32(static_cast<UInt32>(Content)); + + a_Pkt.WriteBEUInt8(MINECART_BLOCK_Y); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(RideableMinecart.GetBlockHeight())); + + a_Pkt.WriteBEUInt8(MINECART_SHOW_BLOCK); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(true); + } + } + else if (Minecart.GetPayload() == cMinecart::mpFurnace) + { + a_Pkt.WriteBEUInt8(MINECART_FURNACE_POWERED); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(reinterpret_cast<const cMinecartWithFurnace &>(Minecart).IsFueled()); + } + break; + } // case etMinecart + + case cEntity::etProjectile: + { + auto & Projectile = reinterpret_cast<const cProjectileEntity &>(a_Entity); + switch (Projectile.GetProjectileKind()) + { + case cProjectileEntity::pkArrow: + { + a_Pkt.WriteBEUInt8(ARROW_CRITICAL); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(reinterpret_cast<const cArrowEntity &>(Projectile).IsCritical() ? 1 : 0); + break; + } + case cProjectileEntity::pkFirework: + { + a_Pkt.WriteBEUInt8(FIREWORK_INFO); // Firework item used for this firework + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast<const cFireworkEntity &>(Projectile).GetItem()); + + // FIREWORK_BOOSTED_ENTITY_ID, in 1.11.1 only + break; + } + case cProjectileEntity::pkSplashPotion: + { + a_Pkt.WriteBEUInt8(POTION_THROWN); // Potion item which was thrown + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, reinterpret_cast<const cSplashPotionEntity &>(Projectile).GetItem()); + } + default: + { + break; + } + } + break; + } // case etProjectile + + case cEntity::etMonster: + { + WriteMobMetadata(a_Pkt, reinterpret_cast<const cMonster &>(a_Entity)); + break; + } + + case cEntity::etBoat: + { + auto & Boat = reinterpret_cast<const cBoat &>(a_Entity); + + a_Pkt.WriteBEInt8(BOAT_LAST_HIT_TIME); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Boat.GetLastDamage())); + + a_Pkt.WriteBEInt8(BOAT_FORWARD_DIRECTION); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Boat.GetForwardDirection())); + + a_Pkt.WriteBEInt8(BOAT_DAMAGE_TAKEN); + a_Pkt.WriteBEInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(Boat.GetDamageTaken()); + + a_Pkt.WriteBEInt8(BOAT_TYPE); + a_Pkt.WriteBEInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Boat.GetMaterial())); + + a_Pkt.WriteBEInt8(BOAT_RIGHT_PADDLE_TURNING); + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsRightPaddleUsed()); + + a_Pkt.WriteBEInt8(BOAT_LEFT_PADDLE_TURNING); + a_Pkt.WriteBEInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Boat.IsLeftPaddleUsed()); + + break; + } // case etBoat + + case cEntity::etItemFrame: + { + auto & Frame = reinterpret_cast<const cItemFrame &>(a_Entity); + a_Pkt.WriteBEUInt8(ITEM_FRAME_ITEM); + a_Pkt.WriteBEUInt8(METADATA_TYPE_ITEM); + WriteItem(a_Pkt, Frame.GetItem()); + a_Pkt.WriteBEUInt8(ITEM_FRAME_ROTATION); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Frame.GetItemRotation()); + break; + } // case etItemFrame + + default: + { + break; + } + } +} + + + + + +void cProtocol_1_12::WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) +{ + using namespace Metadata; + + // Living Enitiy Metadata + if (a_Mob.HasCustomName()) + { + // TODO: As of 1.9 _all_ entities can have custom names; should this be moved up? + a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME); + a_Pkt.WriteBEUInt8(METADATA_TYPE_STRING); + a_Pkt.WriteString(a_Mob.GetCustomName()); + + a_Pkt.WriteBEUInt8(ENTITY_CUSTOM_NAME_VISIBLE); // Custom name always visible + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(a_Mob.IsCustomNameAlwaysVisible()); + } + + a_Pkt.WriteBEUInt8(LIVING_HEALTH); + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast<float>(a_Mob.GetHealth())); + + switch (a_Mob.GetMobType()) + { + case mtBat: + { + auto & Bat = reinterpret_cast<const cBat &>(a_Mob); + a_Pkt.WriteBEUInt8(BAT_HANGING); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(Bat.IsHanging() ? 1 : 0); + break; + } // case mtBat + + case mtCreeper: + { + auto & Creeper = reinterpret_cast<const cCreeper &>(a_Mob); + a_Pkt.WriteBEUInt8(CREEPER_STATE); // (idle or "blowing") + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Creeper.IsBlowing() ? 1 : static_cast<UInt32>(-1)); + + a_Pkt.WriteBEUInt8(CREEPER_POWERED); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Creeper.IsCharged()); + + a_Pkt.WriteBEUInt8(CREEPER_IGNITED); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Creeper.IsBurnedWithFlintAndSteel()); + break; + } // case mtCreeper + + case mtEnderman: + { + auto & Enderman = reinterpret_cast<const cEnderman &>(a_Mob); + a_Pkt.WriteBEUInt8(ENDERMAN_CARRIED_BLOCK); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BLOCKID); + UInt32 Carried = 0; + Carried |= static_cast<UInt32>(Enderman.GetCarriedBlock() << 4); + Carried |= Enderman.GetCarriedMeta(); + a_Pkt.WriteVarInt32(Carried); + + a_Pkt.WriteBEUInt8(ENDERMAN_SCREAMING); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Enderman.IsScreaming()); + break; + } // case mtEnderman + + case mtGhast: + { + auto & Ghast = reinterpret_cast<const cGhast &>(a_Mob); + a_Pkt.WriteBEUInt8(GHAST_ATTACKING); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Ghast.IsCharging()); + break; + } // case mtGhast + + case mtHorse: + { + // XXX This behaves incorrectly with different varients; horses have different entity IDs now + + // Abstract horse + auto & Horse = reinterpret_cast<const cHorse &>(a_Mob); + Int8 Flags = 0; + if (Horse.IsTame()) + { + Flags |= 0x02; + } + if (Horse.IsSaddled()) + { + Flags |= 0x04; + } + if (Horse.IsChested()) + { + Flags |= 0x08; + } + if (Horse.IsEating()) + { + Flags |= 0x20; + } + if (Horse.IsRearing()) + { + Flags |= 0x40; + } + if (Horse.IsMthOpen()) + { + Flags |= 0x80; + } + a_Pkt.WriteBEUInt8(ABSTRACT_HORSE_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(Flags); + + // This doesn't exist any more; it'll cause horses to all be the normal type + // a_Pkt.WriteBEUInt8(HORSE_TYPE); + // a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + // a_Pkt.WriteVarInt32(static_cast<UInt32>(Horse.GetHorseType())); + + // Regular horses + a_Pkt.WriteBEUInt8(HORSE_VARIANT); // Color / style + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + int Appearance = 0; + Appearance = Horse.GetHorseColor(); + Appearance |= Horse.GetHorseStyle() << 8; + a_Pkt.WriteVarInt32(static_cast<UInt32>(Appearance)); + + a_Pkt.WriteBEUInt8(HORSE_ARMOR); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Horse.GetHorseArmour())); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Horse.IsBaby()); + break; + } // case mtHorse + + case mtMagmaCube: + { + auto & MagmaCube = reinterpret_cast<const cMagmaCube &>(a_Mob); + a_Pkt.WriteBEUInt8(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(MagmaCube.GetSize())); + break; + } // case mtMagmaCube + + case mtOcelot: + { + auto & Ocelot = reinterpret_cast<const cOcelot &>(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Ocelot.IsBaby()); + break; + } // case mtOcelot + + case mtCow: + { + auto & Cow = reinterpret_cast<const cCow &>(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Cow.IsBaby()); + break; + } // case mtCow + + case mtChicken: + { + auto & Chicken = reinterpret_cast<const cChicken &>(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Chicken.IsBaby()); + break; + } // case mtChicken + + case mtPig: + { + auto & Pig = reinterpret_cast<const cPig &>(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Pig.IsBaby()); + + a_Pkt.WriteBEUInt8(PIG_HAS_SADDLE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Pig.IsSaddled()); + + // PIG_TOTAL_CARROT_ON_A_STICK_BOOST in 1.11.1 only + break; + } // case mtPig + + case mtSheep: + { + auto & Sheep = reinterpret_cast<const cSheep &>(a_Mob); + + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Sheep.IsBaby()); + + a_Pkt.WriteBEUInt8(SHEEP_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + Int8 SheepMetadata = 0; + SheepMetadata = static_cast<Int8>(Sheep.GetFurColor()); + if (Sheep.IsSheared()) + { + SheepMetadata |= 0x10; + } + a_Pkt.WriteBEInt8(SheepMetadata); + break; + } // case mtSheep + + case mtRabbit: + { + auto & Rabbit = reinterpret_cast<const cRabbit &>(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Rabbit.IsBaby()); + + a_Pkt.WriteBEUInt8(RABBIT_TYPE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Rabbit.GetRabbitType())); + break; + } // case mtRabbit + + case mtSkeleton: + { + // XXX Skeletons are separate entities; all skeletons are currently treated as regular ones + + // auto & Skeleton = reinterpret_cast<const cSkeleton &>(a_Mob); + // a_Pkt.WriteBEUInt8(SKELETON_TYPE); + // a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + // a_Pkt.WriteVarInt32(Skeleton.IsWither() ? 1 : 0); + break; + } // case mtSkeleton + + case mtSlime: + { + auto & Slime = reinterpret_cast<const cSlime &>(a_Mob); + a_Pkt.WriteBEUInt8(SLIME_SIZE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Slime.GetSize())); + break; + } // case mtSlime + + case mtVillager: + { + auto & Villager = reinterpret_cast<const cVillager &>(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Villager.IsBaby()); + + a_Pkt.WriteBEUInt8(VILLAGER_PROFESSION); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Villager.GetVilType())); + break; + } // case mtVillager + + case mtWitch: + { + auto & Witch = reinterpret_cast<const cWitch &>(a_Mob); + a_Pkt.WriteBEUInt8(WITCH_AGGRESIVE); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Witch.IsAngry()); + break; + } // case mtWitch + + case mtWither: + { + auto & Wither = reinterpret_cast<const cWither &>(a_Mob); + a_Pkt.WriteBEUInt8(WITHER_INVULNERABLE_TIMER); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(Wither.GetWitherInvulnerableTicks()); + + // TODO: Use boss bar packet for health + break; + } // case mtWither + + case mtWolf: + { + auto & Wolf = reinterpret_cast<const cWolf &>(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Wolf.IsBaby()); + + Int8 WolfStatus = 0; + if (Wolf.IsSitting()) + { + WolfStatus |= 0x1; + } + if (Wolf.IsAngry()) + { + WolfStatus |= 0x2; + } + if (Wolf.IsTame()) + { + WolfStatus |= 0x4; + } + a_Pkt.WriteBEUInt8(TAMEABLE_ANIMAL_STATUS); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BYTE); + a_Pkt.WriteBEInt8(WolfStatus); + + a_Pkt.WriteBEUInt8(WOLF_DAMAGE_TAKEN); + a_Pkt.WriteBEUInt8(METADATA_TYPE_FLOAT); + a_Pkt.WriteBEFloat(static_cast<float>(a_Mob.GetHealth())); // TODO Not use the current health + + a_Pkt.WriteBEUInt8(WOLF_BEGGING); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Wolf.IsBegging()); + + a_Pkt.WriteBEUInt8(WOLF_COLLAR_COLOR); + a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + a_Pkt.WriteVarInt32(static_cast<UInt32>(Wolf.GetCollarColor())); + break; + } // case mtWolf + + case mtZombie: + { + // XXX Zombies were also split into new sublcasses; this doesn't handle that. + auto & Zombie = reinterpret_cast<const cZombie &>(a_Mob); + a_Pkt.WriteBEUInt8(ZOMBIE_IS_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(Zombie.IsBaby()); + + // These don't exist + // a_Pkt.WriteBEUInt8(ZOMBIE_TYPE); + // a_Pkt.WriteBEUInt8(METADATA_TYPE_VARINT); + // a_Pkt.WriteVarInt32(Zombie.IsVillagerZombie() ? 1 : 0); + + // a_Pkt.WriteBEUInt8(ZOMBIE_CONVERTING); + // a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + // a_Pkt.WriteBool(Zombie.IsConverting()); + break; + } // case mtZombie + + case mtZombiePigman: + { + auto & ZombiePigman = reinterpret_cast<const cZombiePigman &>(a_Mob); + a_Pkt.WriteBEUInt8(AGEABLE_BABY); + a_Pkt.WriteBEUInt8(METADATA_TYPE_BOOL); + a_Pkt.WriteBool(ZombiePigman.IsBaby()); + break; + } // case mtZombiePigman + } // switch (a_Mob.GetType()) +} + + + + + +void cProtocol_1_12::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x26); // Entity Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // TODO: 1.9 changed these from chars to shorts, meaning that there can be more percision and data. Other code needs to be updated for that. + Pkt.WriteBEInt16(a_RelX * 128); + Pkt.WriteBEInt16(a_RelY * 128); + Pkt.WriteBEInt16(a_RelZ * 128); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_12::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x27); // Entity Look And Relative Move packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // TODO: 1.9 changed these from chars to shorts, meaning that there can be more percision and data. Other code needs to be updated for that. + Pkt.WriteBEInt16(a_RelX * 128); + Pkt.WriteBEInt16(a_RelY * 128); + Pkt.WriteBEInt16(a_RelZ * 128); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_12::SendEntityLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x28); // Entity Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_12::SendDestroyEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x31); // Destroy Entities packet + Pkt.WriteVarInt32(1); + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_12::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x32); // Remove entity effect packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast<UInt8>(a_EffectID)); +} + + + + + +void cProtocol_1_12::SendRespawn(eDimension a_Dimension) +{ + cPacketizer Pkt(*this, 0x34); // Respawn packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEInt32(static_cast<Int32>(a_Dimension)); + Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) + Pkt.WriteBEUInt8(static_cast<Byte>(Player->GetEffectiveGameMode())); + Pkt.WriteString("default"); +} + + + + + +void cProtocol_1_12::SendEntityHeadLook(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x35); // Entity Head Look packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteByteAngle(a_Entity.GetHeadYaw()); +} + + + + + +void cProtocol_1_12::SendCameraSetTo(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x38); // Camera Packet (Attach the camera of a player at another entity in spectator mode) + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_12::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3a); // Display scoreboard packet + Pkt.WriteBEUInt8(static_cast<UInt8>(a_Display)); + Pkt.WriteString(a_Objective); +} + + + + + +void cProtocol_1_12::SendEntityMetadata(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3b); // Entity Metadata packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityMetadata(Pkt, a_Entity); + Pkt.WriteBEUInt8(0xff); // The termination byte +} + + + + + +void cProtocol_1_12::SendEntityVelocity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3d); // Entity Velocity packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // 400 = 8000 / 20 ... Conversion from our speed in m / s to 8000 m / tick + Pkt.WriteBEInt16(static_cast<Int16>(a_Entity.GetSpeedX() * 400)); + Pkt.WriteBEInt16(static_cast<Int16>(a_Entity.GetSpeedY() * 400)); + Pkt.WriteBEInt16(static_cast<Int16>(a_Entity.GetSpeedZ() * 400)); +} + + + + + +void cProtocol_1_12::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3e); // Entity Equipment packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + // Needs to be adjusted due to the insertion of offhand at slot 1 + if (a_SlotNum > 0) + { + a_SlotNum++; + } + Pkt.WriteVarInt32(static_cast<UInt32>(a_SlotNum)); + WriteItem(Pkt, a_Item); +} + + + + + +void cProtocol_1_12::SendExperience(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x3f); // Experience Packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEFloat(Player->GetXpPercentage()); + Pkt.WriteVarInt32(static_cast<UInt32>(Player->GetXpLevel())); + Pkt.WriteVarInt32(static_cast<UInt32>(Player->GetCurrentXp())); +} + + + + + +void cProtocol_1_12::SendHealth(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x40); // Update Health packet + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteBEFloat(static_cast<float>(Player->GetHealth())); + Pkt.WriteVarInt32(static_cast<UInt32>(Player->GetFoodLevel())); + Pkt.WriteBEFloat(static_cast<float>(Player->GetFoodSaturationLevel())); +} + + + + + +void cProtocol_1_12::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x41); // Scoreboard objective packet + Pkt.WriteString(a_Name); + Pkt.WriteBEUInt8(a_Mode); + if ((a_Mode == 0) || (a_Mode == 2)) + { + Pkt.WriteString(a_DisplayName); + Pkt.WriteString("integer"); + } +} + + + + + +void cProtocol_1_12::SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x42); // Set passangers packet + Pkt.WriteVarInt32(a_Vehicle.GetUniqueID()); + Pkt.WriteVarInt32(1); // 1 passenger + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + +void cProtocol_1_12::SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle) +{ + ASSERT(m_State == 3); // In game mode? + cPacketizer Pkt(*this, 0x42); // Set passangers packet + Pkt.WriteVarInt32(a_PreviousVehicle.GetUniqueID()); + Pkt.WriteVarInt32(0); // No passangers +} + + + + + +void cProtocol_1_12::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x44); // Update score packet + Pkt.WriteString(a_Player); + Pkt.WriteBEUInt8(a_Mode); + Pkt.WriteString(a_Objective); + + if (a_Mode != 1) + { + Pkt.WriteVarInt32(static_cast<UInt32>(a_Score)); + } +} + + + + + +void cProtocol_1_12::SendLogin(const cPlayer & a_Player, const cWorld & a_World) +{ + // Send the Join Game packet: + { + cServer * Server = cRoot::Get()->GetServer(); + cPacketizer Pkt(*this, 0x23); // Join Game packet + Pkt.WriteBEUInt32(a_Player.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast<UInt8>(a_Player.GetEffectiveGameMode()) | (Server->IsHardcore() ? 0x08 : 0)); // Hardcore flag bit 4 + Pkt.WriteBEInt32(static_cast<Int32>(a_World.GetDimension())); + Pkt.WriteBEUInt8(2); // TODO: Difficulty (set to Normal) + Pkt.WriteBEUInt8(static_cast<UInt8>(Clamp<int>(Server->GetMaxPlayers(), 0, 255))); + Pkt.WriteString("default"); // Level type - wtf? + Pkt.WriteBool(false); // Reduced Debug Info - wtf? + } + + // Send the spawn position: + { + cPacketizer Pkt(*this, 0x45); // Spawn Position packet + Pkt.WritePosition64(FloorC(a_World.GetSpawnX()), FloorC(a_World.GetSpawnY()), FloorC(a_World.GetSpawnZ())); + } + + // Send the server difficulty: + { + cPacketizer Pkt(*this, 0x0d); // Server difficulty packet + Pkt.WriteBEInt8(1); + } + + // Send player abilities: + SendPlayerAbilities(); +} + + + + + +void cProtocol_1_12::SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) +{ + ASSERT(m_State == 3); // In game mode? + if (!a_DoDaylightCycle) + { + // When writing a "-" before the number the client ignores it but it will stop the client-side time expiration. + a_TimeOfDay = std::min(-a_TimeOfDay, -1LL); + } + + cPacketizer Pkt(*this, 0x46); // Time update packet + Pkt.WriteBEInt64(a_WorldAge); + Pkt.WriteBEInt64(a_TimeOfDay); +} + + + + + +void cProtocol_1_12::SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x47); // Title packet + Pkt.WriteVarInt32(3); // Set title display times + Pkt.WriteBEInt32(a_FadeInTicks); + Pkt.WriteBEInt32(a_DisplayTicks); + Pkt.WriteBEInt32(a_FadeOutTicks); +} + + + + + +void cProtocol_1_12::SendHideTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x47); // Title packet + Pkt.WriteVarInt32(4); // Hide title +} + + + + + +void cProtocol_1_12::SendResetTitle(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x47); // Title packet + Pkt.WriteVarInt32(5); // Reset title +} + + + + + +void cProtocol_1_12::SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4a); // Collect Item packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteVarInt32(a_Player.GetUniqueID()); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Count)); +} + + + + + +void cProtocol_1_12::SendTeleportEntity(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4b); // Entity teleport packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEDouble(a_Entity.GetPosX()); + Pkt.WriteBEDouble(a_Entity.GetPosY()); + Pkt.WriteBEDouble(a_Entity.GetPosZ()); + Pkt.WriteByteAngle(a_Entity.GetYaw()); + Pkt.WriteByteAngle(a_Entity.GetPitch()); + Pkt.WriteBool(a_Entity.IsOnGround()); +} + + + + + +void cProtocol_1_12::SendEntityProperties(const cEntity & a_Entity) +{ + ASSERT(m_State == 3); // In game mode? + + + cPacketizer Pkt(*this, 0x4d); // Entity Properties packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + WriteEntityProperties(Pkt, a_Entity); +} + + + + + +void cProtocol_1_12::SendPlayerMaxSpeed(void) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4d); // Entity Properties + cPlayer * Player = m_Client->GetPlayer(); + Pkt.WriteVarInt32(Player->GetUniqueID()); + Pkt.WriteBEInt32(1); // Count + Pkt.WriteString("generic.movementSpeed"); + // The default game speed is 0.1, multiply that value by the relative speed: + Pkt.WriteBEDouble(0.1 * Player->GetNormalMaxSpeed()); + if (Player->IsSprinting()) + { + Pkt.WriteVarInt32(1); // Modifier count + Pkt.WriteBEUInt64(0x662a6b8dda3e4c1c); + Pkt.WriteBEUInt64(0x881396ea6097278d); // UUID of the modifier + Pkt.WriteBEDouble(Player->GetSprintingMaxSpeed() - Player->GetNormalMaxSpeed()); + Pkt.WriteBEUInt8(2); + } + else + { + Pkt.WriteVarInt32(0); // Modifier count + } +} + + + + + +void cProtocol_1_12::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) +{ + ASSERT(m_State == 3); // In game mode? + + cPacketizer Pkt(*this, 0x4e); // Entity Effect packet + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); + Pkt.WriteBEUInt8(static_cast<UInt8>(a_EffectID)); + Pkt.WriteBEUInt8(static_cast<UInt8>(a_Amplifier)); + Pkt.WriteVarInt32(static_cast<UInt32>(a_Duration)); + Pkt.WriteBool(false); // Hide particles +} + + + + + +bool cProtocol_1_12::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) +{ + switch (m_State) + { + case 1: + { + // Status + switch (a_PacketType) + { + case 0x00: HandlePacketStatusRequest(a_ByteBuffer); return true; + case 0x01: HandlePacketStatusPing(a_ByteBuffer); return true; + } + break; + } + + case 2: + { + // Login + switch (a_PacketType) + { + case 0x00: HandlePacketLoginStart(a_ByteBuffer); return true; + case 0x01: HandlePacketLoginEncryptionResponse(a_ByteBuffer); return true; + } + break; + } + + case 3: + { + // Game + switch (a_PacketType) + { + case 0x00: HandleConfirmTeleport(a_ByteBuffer); return true; + case 0x01: break; // Prepare Crafting Grid, not yet implemented + case 0x02: HandlePacketTabComplete(a_ByteBuffer); return true; + case 0x03: HandlePacketChatMessage(a_ByteBuffer); return true; + case 0x04: HandlePacketClientStatus(a_ByteBuffer); return true; + case 0x05: HandlePacketClientSettings(a_ByteBuffer); return true; + case 0x06: break; // Confirm transaction - not used in Cuberite + case 0x07: HandlePacketEnchantItem(a_ByteBuffer); return true; + case 0x08: HandlePacketWindowClick(a_ByteBuffer); return true; + case 0x09: HandlePacketWindowClose(a_ByteBuffer); return true; + case 0x0a: HandlePacketPluginMessage(a_ByteBuffer); return true; + case 0x0b: HandlePacketUseEntity(a_ByteBuffer); return true; + case 0x0c: HandlePacketKeepAlive(a_ByteBuffer); return true; + case 0x0d: HandlePacketPlayer(a_ByteBuffer); return true; + case 0x0e: HandlePacketPlayerPos(a_ByteBuffer); return true; + case 0x0f: HandlePacketPlayerPosLook(a_ByteBuffer); return true; + case 0x10: HandlePacketPlayerLook(a_ByteBuffer); return true; + case 0x11: HandlePacketVehicleMove(a_ByteBuffer); return true; + case 0x12: HandlePacketBoatSteer(a_ByteBuffer); return true; + case 0x13: HandlePacketPlayerAbilities(a_ByteBuffer); return true; + case 0x14: HandlePacketBlockDig(a_ByteBuffer); return true; + case 0x15: HandlePacketEntityAction(a_ByteBuffer); return true; + case 0x16: HandlePacketSteerVehicle(a_ByteBuffer); return true; + case 0x17: break; // Crafting Book Data - not yet implemented + case 0x18: break; // Resource pack status - not yet implemented + case 0x19: break; // Advancement Tab - not yet implemented + case 0x1a: HandlePacketSlotSelect(a_ByteBuffer); return true; + case 0x1b: HandlePacketCreativeInventoryAction(a_ByteBuffer); return true; + case 0x1c: HandlePacketUpdateSign(a_ByteBuffer); return true; + case 0x1d: HandlePacketAnimation(a_ByteBuffer); return true; + case 0x1e: HandlePacketSpectate(a_ByteBuffer); return true; + case 0x1f: HandlePacketBlockPlace(a_ByteBuffer); return true; + case 0x20: HandlePacketUseItem(a_ByteBuffer); return true; + } + break; + } + default: + { + // Received a packet in an unknown state, report: + LOGWARNING("Received a packet in an unknown protocol state %d. Ignoring further packets.", m_State); + + // Cannot kick the client - we don't know this state and thus the packet number for the kick packet + + // Switch to a state when all further packets are silently ignored: + m_State = 255; + return false; + } + case 255: + { + // This is the state used for "not processing packets anymore" when we receive a bad packet from a client. + // Do not output anything (the caller will do that for us), just return failure + return false; + } + } // switch (m_State) + + // Unknown packet type, report to the ClientHandle: + m_Client->PacketUnknown(a_PacketType); + return false; +} diff --git a/src/Protocol/Protocol_1_12.h b/src/Protocol/Protocol_1_12.h new file mode 100644 index 000000000..934a751da --- /dev/null +++ b/src/Protocol/Protocol_1_12.h @@ -0,0 +1,66 @@ + +// Protocol_1_12.h + +/* +Declares the 1.12 protocol classes: + - cProtocol_1_12 + - release 1.12 protocol (#335) +(others may be added later in the future for the 1.12 release series) +*/ + + + + + +#pragma once + +#include "Protocol_1_11.h" + + + + + +class cProtocol_1_12 : + public cProtocol_1_11_1 +{ + typedef cProtocol_1_11_1 super; + +public: + cProtocol_1_12(cClientHandle * a_Client, const AString &a_ServerAddress, UInt16 a_ServerPort, UInt32 a_State); + + virtual void SendAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle) override; + virtual void SendCameraSetTo(const cEntity & a_Entity) override; + virtual void SendCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count) override; + virtual void SendDestroyEntity(const cEntity & a_Entity) override; + virtual void SendDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle); + virtual void SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display) override; + virtual void SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration) override; + virtual void SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item) override; + virtual void SendEntityHeadLook(const cEntity & a_Entity) override; + virtual void SendEntityLook(const cEntity & a_Entity) override; + virtual void SendEntityMetadata(const cEntity & a_Entity) override; + virtual void SendEntityProperties(const cEntity & a_Entity) override; + virtual void SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ)override; + virtual void SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ)override; + virtual void SendEntityVelocity(const cEntity & a_Entity) override; + virtual void SendExperience(void) override; + virtual void SendHealth(void) override; + virtual void SendHideTitle(void) override; + virtual void SendLogin(const cPlayer & a_Player, const cWorld & a_World) override; + virtual void SendPlayerMaxSpeed(void) override; + virtual void SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID) override; + virtual void SendResetTitle(void) override; + virtual void SendRespawn(eDimension a_Dimension) override; + virtual void SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode) override; + virtual void SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override; + virtual void SendSpawnMob(const cMonster & a_Mob) override; + virtual void SendTeleportEntity(const cEntity & a_Entity) override; + virtual void SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay, bool a_DoDaylightCycle) override; + virtual void SendTitleTimes(int a_FadeInTicks, int a_DisplayTicks, int a_FadeOutTicks) override; +protected: + virtual bool HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) override; + virtual void HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer) override; + virtual void HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) override; + virtual void WriteEntityMetadata(cPacketizer & a_Pkt, const cEntity & a_Entity) override; + virtual void WriteMobMetadata(cPacketizer & a_Pkt, const cMonster & a_Mob) override; +}; |