summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Protocol/CMakeLists.txt3
-rw-r--r--src/Protocol/ProtocolRecognizer.cpp7
-rw-r--r--src/Protocol/ProtocolRecognizer.h7
-rw-r--r--src/Protocol/Protocol_1_12.cpp1509
-rw-r--r--src/Protocol/Protocol_1_12.h66
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;
+};