ovh.tgrhavoc.aibot.protocol.v4x.Protocol4X.java Source code

Java tutorial

Introduction

Here is the source code for ovh.tgrhavoc.aibot.protocol.v4x.Protocol4X.java

Source

/*******************************************************************************
 *     Copyright (C) 2015 Jordan Dalton (jordan.8474@gmail.com)
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License along
 *     with this program; if not, write to the Free Software Foundation, Inc.,
 *     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *******************************************************************************/
package ovh.tgrhavoc.aibot.protocol.v4x;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import ovh.tgrhavoc.aibot.ChatColor;
import ovh.tgrhavoc.aibot.MinecraftBot;
import ovh.tgrhavoc.aibot.auth.AuthService;
import ovh.tgrhavoc.aibot.auth.AuthenticationException;
import ovh.tgrhavoc.aibot.auth.InvalidSessionException;
import ovh.tgrhavoc.aibot.auth.Session;
import ovh.tgrhavoc.aibot.event.EventBus;
import ovh.tgrhavoc.aibot.event.EventHandler;
import ovh.tgrhavoc.aibot.event.EventListener;
import ovh.tgrhavoc.aibot.event.io.PacketProcessEvent;
import ovh.tgrhavoc.aibot.event.io.PacketReceivedEvent;
import ovh.tgrhavoc.aibot.event.io.PacketSentEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.ArmSwingEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BedLeaveEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BlockBreakCompleteEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BlockBreakEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BlockBreakStartEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BlockBreakStopEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.BlockPlaceEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.ChatSentEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.CrouchUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.EntityHitEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.EntityInteractEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.EntityUseEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.HandshakeEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.HeldItemChangeEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.HeldItemDropEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.InventoryChangeEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.InventoryCloseEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.ItemUseEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.PlayerUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.RequestDisconnectEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.RequestRespawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.client.SprintUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.BlockChangeEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.ChatReceivedEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.ChunkLoadEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EditTileEntityEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityCollectEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityDeathEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityDespawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityDismountEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityEatEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityHeadRotateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityHurtEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityMetadataUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityMountEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityMoveEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityRotateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntitySpawnEvent.SpawnLocation;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityStopEatingEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityTeleportEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.EntityVelocityEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.ExpOrbSpawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.HealthUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.KickEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.LivingEntitySpawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.LivingEntitySpawnEvent.LivingEntitySpawnData;
import ovh.tgrhavoc.aibot.event.protocol.server.LivingEntitySpawnEvent.LivingEntitySpawnLocation;
import ovh.tgrhavoc.aibot.event.protocol.server.LoginEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.ObjectEntitySpawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.ObjectEntitySpawnEvent.ObjectSpawnData;
import ovh.tgrhavoc.aibot.event.protocol.server.ObjectEntitySpawnEvent.ThrownObjectSpawnData;
import ovh.tgrhavoc.aibot.event.protocol.server.PaintingSpawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.PaintingSpawnEvent.PaintingSpawnLocation;
import ovh.tgrhavoc.aibot.event.protocol.server.PlayerEquipmentUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.PlayerEquipmentUpdateEvent.EquipmentSlot;
import ovh.tgrhavoc.aibot.event.protocol.server.PlayerListRemoveEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.PlayerListUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.PlayerSpawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.RespawnEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.RotatedEntitySpawnEvent.RotatedSpawnLocation;
import ovh.tgrhavoc.aibot.event.protocol.server.SignUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.SleepEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.TeleportEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.TileEntityUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.TimeUpdateEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.WindowCloseEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.WindowOpenEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.WindowSlotChangeEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.WindowTransactionCompleteEvent;
import ovh.tgrhavoc.aibot.event.protocol.server.WindowUpdateEvent;
import ovh.tgrhavoc.aibot.protocol.AbstractPacketX;
import ovh.tgrhavoc.aibot.protocol.AbstractProtocolX;
import ovh.tgrhavoc.aibot.protocol.ConnectionHandler;
import ovh.tgrhavoc.aibot.protocol.EncryptionUtil;
import ovh.tgrhavoc.aibot.protocol.Packet;
import ovh.tgrhavoc.aibot.protocol.PacketLengthHeader;
import ovh.tgrhavoc.aibot.protocol.ProtocolProvider;
import ovh.tgrhavoc.aibot.protocol.ReadablePacket;
import ovh.tgrhavoc.aibot.protocol.v4x.handshake.PacketHC00_Handshake;
import ovh.tgrhavoc.aibot.protocol.v4x.login.client.PacketLC00_LoginStart;
import ovh.tgrhavoc.aibot.protocol.v4x.login.client.PacketLC01_EncryptionResponse;
import ovh.tgrhavoc.aibot.protocol.v4x.login.server.PacketLS00_Disconnect;
import ovh.tgrhavoc.aibot.protocol.v4x.login.server.PacketLS01_EncryptionRequest;
import ovh.tgrhavoc.aibot.protocol.v4x.login.server.PacketLS02_LoginSuccess;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC00_KeepAlive;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC01_Chat;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC02_UseEntity;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC03_PlayerUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC04_PositionUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC05_RotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC06_PositionRotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC07_BlockDig;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC08_BlockPlace;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC09_HeldItemChange;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC0A_Animation;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC0B_EntityAction;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC0D_CloseWindow;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC0E_ClickWindow;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC0F_ConfirmTransaction;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC15_ClientSettings;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC15_ClientSettings.ChatMode;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC15_ClientSettings.ViewDistance;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC16_ClientStatus;
import ovh.tgrhavoc.aibot.protocol.v4x.play.client.PacketC17_PluginMessage;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS00_KeepAlive;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS01_JoinGame;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS02_ChatMessage;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS03_TimeUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS04_EntityEquipment;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS05_SpawnLocation;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS06_UpdateHealth;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS07_Respawn;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS08_Teleport;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS09_ChangeHeldItem;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0A_EnterBed;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0B_Animation;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0B_Animation.Animation;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0C_SpawnPlayer;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0D_CollectItem;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0E_SpawnObject;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS0F_SpawnMob;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS10_SpawnPainting;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS11_SpawnExperienceOrb;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS12_EntityVelocityUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS13_DespawnEntities;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS14_EntityUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS15_EntityRelativeMovementUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS16_EntityRotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS17_EntityRelativeMovementRotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS18_EntityPositionRotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS19_EntityHeadRotationUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1A_EntityStatusUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1B_EntityAttachmentUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1C_EntityMetadataUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1D_EntityEffectUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1E_EntityRemoveEffect;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS1F_ExperienceUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS20_EntityPropertyUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS21_ChunkData;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS21_ChunkData.ChunkData;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS22_MultiBlockUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS23_BlockUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS24_BlockAction;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS25_BlockBreakAnimation;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS26_MultiChunkData;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS2D_OpenWindow;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS2E_CloseWindow;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS2F_SetSlot;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS30_WindowItems;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS31_WindowProperty;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS32_ConfirmTransaction;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS33_SignUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS34_MapUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS35_TileEntityUpdate;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS36_OpenTileEditor;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS37_Statistics;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS38_PlayerListItem;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS39_PlayerAbilities;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS3A_TabCompletion;
import ovh.tgrhavoc.aibot.protocol.v4x.play.server.PacketS40_Disconnect;
import ovh.tgrhavoc.aibot.world.Difficulty;
import ovh.tgrhavoc.aibot.world.Dimension;
import ovh.tgrhavoc.aibot.world.entity.MainPlayerEntity;
import ovh.tgrhavoc.aibot.world.item.BasicItemStack;
import ovh.tgrhavoc.aibot.world.item.ItemStack;

public final class Protocol4X extends AbstractProtocolX implements EventListener {
    public final class LengthPacket extends AbstractPacketX implements ReadablePacket {
        private final int length;

        private byte[] data;

        protected LengthPacket(int id, int length) {
            super(id, Protocol4X.this.getState(), Direction.DOWNSTREAM);

            this.length = length;
        }

        @Override
        public void readData(DataInputStream in) throws IOException {
            byte[] data = new byte[length];
            in.readFully(data);
            this.data = data;
        }

        public byte[] getData() {
            return data;
        }
    }

    public static final int VERSION = 4;
    public static final String VERSION_NAME = "1.7.2";

    private static final double STANCE_CONSTANT = 1.620000004768372;
    private static final BigDecimal STANCE_CONSTANT_PRECISE = BigDecimal.valueOf(STANCE_CONSTANT);

    private final MinecraftBot bot;
    private final Map<String, String> lang;

    private boolean positionSet = false;

    public Protocol4X(MinecraftBot bot) {
        super(VERSION);
        this.bot = bot;

        Map<String, String> lang = new HashMap<>();
        try (InputStream in = getClass().getResourceAsStream("en_US.lang")) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));

            String line;
            while ((line = reader.readLine()) != null) {
                int index = line.indexOf('=');
                if (index == -1)
                    continue;
                String key = line.substring(0, index);
                String value = line.substring(index + 1);
                lang.put(key, value);
            }
        } catch (Exception exception) {
            lang.clear();
        }
        this.lang = Collections.unmodifiableMap(lang);

        register(State.LOGIN, PacketLS00_Disconnect.class);
        register(State.LOGIN, PacketLS01_EncryptionRequest.class);
        register(State.LOGIN, PacketLS02_LoginSuccess.class);

        register(State.PLAY, PacketS00_KeepAlive.class);
        register(State.PLAY, PacketS01_JoinGame.class);
        register(State.PLAY, PacketS02_ChatMessage.class);
        register(State.PLAY, PacketS03_TimeUpdate.class);
        register(State.PLAY, PacketS04_EntityEquipment.class);
        register(State.PLAY, PacketS05_SpawnLocation.class);
        register(State.PLAY, PacketS06_UpdateHealth.class);
        register(State.PLAY, PacketS07_Respawn.class);
        register(State.PLAY, PacketS08_Teleport.class);
        register(State.PLAY, PacketS09_ChangeHeldItem.class);
        register(State.PLAY, PacketS0A_EnterBed.class);
        register(State.PLAY, PacketS0B_Animation.class);
        register(State.PLAY, PacketS0C_SpawnPlayer.class);
        register(State.PLAY, PacketS0D_CollectItem.class);
        register(State.PLAY, PacketS0E_SpawnObject.class);
        register(State.PLAY, PacketS0F_SpawnMob.class);
        register(State.PLAY, PacketS10_SpawnPainting.class);
        register(State.PLAY, PacketS11_SpawnExperienceOrb.class);
        register(State.PLAY, PacketS12_EntityVelocityUpdate.class);
        register(State.PLAY, PacketS13_DespawnEntities.class);
        register(State.PLAY, PacketS14_EntityUpdate.class);
        register(State.PLAY, PacketS15_EntityRelativeMovementUpdate.class);
        register(State.PLAY, PacketS16_EntityRotationUpdate.class);
        register(State.PLAY, PacketS17_EntityRelativeMovementRotationUpdate.class);
        register(State.PLAY, PacketS18_EntityPositionRotationUpdate.class);
        register(State.PLAY, PacketS19_EntityHeadRotationUpdate.class);
        register(State.PLAY, PacketS1A_EntityStatusUpdate.class);
        register(State.PLAY, PacketS1B_EntityAttachmentUpdate.class);
        register(State.PLAY, PacketS1C_EntityMetadataUpdate.class);
        register(State.PLAY, PacketS1D_EntityEffectUpdate.class);
        register(State.PLAY, PacketS1E_EntityRemoveEffect.class);
        register(State.PLAY, PacketS1F_ExperienceUpdate.class);
        register(State.PLAY, PacketS20_EntityPropertyUpdate.class);
        register(State.PLAY, PacketS21_ChunkData.class);
        register(State.PLAY, PacketS22_MultiBlockUpdate.class);
        register(State.PLAY, PacketS23_BlockUpdate.class);
        register(State.PLAY, PacketS24_BlockAction.class);
        register(State.PLAY, PacketS25_BlockBreakAnimation.class);
        register(State.PLAY, PacketS26_MultiChunkData.class);

        register(State.PLAY, PacketS2D_OpenWindow.class);
        register(State.PLAY, PacketS2E_CloseWindow.class);
        register(State.PLAY, PacketS2F_SetSlot.class);
        register(State.PLAY, PacketS30_WindowItems.class);
        register(State.PLAY, PacketS31_WindowProperty.class);
        register(State.PLAY, PacketS32_ConfirmTransaction.class);
        register(State.PLAY, PacketS33_SignUpdate.class);
        register(State.PLAY, PacketS34_MapUpdate.class);
        register(State.PLAY, PacketS35_TileEntityUpdate.class);
        register(State.PLAY, PacketS36_OpenTileEditor.class);
        register(State.PLAY, PacketS37_Statistics.class);
        register(State.PLAY, PacketS38_PlayerListItem.class);
        register(State.PLAY, PacketS39_PlayerAbilities.class);
        register(State.PLAY, PacketS3A_TabCompletion.class);

        register(State.PLAY, PacketS40_Disconnect.class);

        bot.getEventBus().register(this);
    }

    @Override
    public Packet createPacket(PacketLengthHeader header) {
        if (!(header instanceof PacketLengthHeader))
            return new LengthPacket(header.getId(), 0);
        PacketLengthHeader plHeader = (PacketLengthHeader) header;
        Packet packet = super.createPacket(header);
        if (packet != null)
            return packet;
        // System.out.println("Could not find packet for " + header);
        return new LengthPacket(header.getId(),
                plHeader.getLength() - AbstractPacketX.varIntLength(header.getId()));
    }

    @EventHandler
    public void onHandshake(HandshakeEvent event) {
        bot.getConnectionHandler()
                .sendPacket(new PacketHC00_Handshake(VERSION, event.getServer(), event.getPort(), State.LOGIN));
        bot.getConnectionHandler().sendPacket(new PacketLC00_LoginStart(event.getSession().getUsername()));
    }

    @EventHandler
    public void onInventoryChange(InventoryChangeEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        int windowId = event.getInventory().getWindowId();
        int mode = event.isShiftHeld() ? 1 : 0;
        PacketC0E_ClickWindow packet = new PacketC0E_ClickWindow(windowId, event.getSlot(), event.getButton(),
                event.getTransactionId(), mode, event.getItem());
        handler.sendPacket(packet);
    }

    @EventHandler
    public void onInventoryClose(InventoryCloseEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC0D_CloseWindow(event.getInventory().getWindowId()));
    }

    @EventHandler
    public void onHeldItemDrop(HeldItemDropEvent event) {
        if (positionSet) {
            ConnectionHandler handler = bot.getConnectionHandler();
            PacketC07_BlockDig.Action action = event.isEntireStack() ? PacketC07_BlockDig.Action.DROP_ITEM_STACK
                    : PacketC07_BlockDig.Action.DROP_ITEM;
            handler.sendPacket(new PacketC07_BlockDig(action, 0, 0, 0, 0));
        } else
            event.setCancelled(true);
    }

    @EventHandler
    public void onHeldItemChange(HeldItemChangeEvent event) {
        if (positionSet) {
            ConnectionHandler handler = bot.getConnectionHandler();
            handler.sendPacket(new PacketC09_HeldItemChange(event.getNewSlot()));
        } else
            event.setCancelled(true);
    }

    @EventHandler
    public void onEntityInteract(EntityInteractEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        int mode;
        if (event instanceof EntityHitEvent)
            mode = 1;
        else if (event instanceof EntityUseEvent)
            mode = 0;
        else
            return;
        handler.sendPacket(new PacketC02_UseEntity(event.getEntity().getId(), mode));
    }

    @EventHandler
    public void onArmSwing(ArmSwingEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(
                new PacketC0A_Animation(bot.getPlayer().getId(), PacketC0A_Animation.Animation.SWING_ARM));
    }

    @EventHandler
    public void onCrouchUpdate(CrouchUpdateEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC0B_EntityAction(bot.getPlayer().getId(),
                event.isCrouching() ? PacketC0B_EntityAction.Action.CROUCH : PacketC0B_EntityAction.Action.UNCROUCH,
                0));
    }

    @EventHandler
    public void onSprintUpdate(SprintUpdateEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC0B_EntityAction(bot.getPlayer().getId(),
                event.isSprinting() ? PacketC0B_EntityAction.Action.START_SPRINTING
                        : PacketC0B_EntityAction.Action.STOP_SPRINTING,
                0));
    }

    @EventHandler
    public void onBedLeave(BedLeaveEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(
                new PacketC0B_EntityAction(bot.getPlayer().getId(), PacketC0B_EntityAction.Action.LEAVE_BED, 0));
    }

    @EventHandler
    public void onChatSent(ChatSentEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC01_Chat(event.getMessage()));
    }

    @EventHandler
    public void onBlockBreak(BlockBreakEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        PacketC07_BlockDig.Action action;
        if (event instanceof BlockBreakStartEvent)
            action = PacketC07_BlockDig.Action.START_DIGGING;
        else if (event instanceof BlockBreakStopEvent)
            action = PacketC07_BlockDig.Action.CANCEL_DIGGING;
        else if (event instanceof BlockBreakCompleteEvent)
            action = PacketC07_BlockDig.Action.FINISH_DIGGING;
        else
            return;
        handler.sendPacket(
                new PacketC07_BlockDig(action, event.getX(), event.getY(), event.getZ(), event.getFace()));
    }

    @EventHandler
    public void onBlockPlace(BlockPlaceEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC08_BlockPlace(event.getX(), event.getY(), event.getZ(), event.getFace(),
                event.getItem(), event.getXOffset(), event.getYOffset(), event.getZOffset()));
    }

    @EventHandler
    public void onPlayerUpdate(PlayerUpdateEvent event) {
        MainPlayerEntity player = event.getEntity();
        double x = player.getX(), y = player.getY(), z = player.getZ(), yaw = player.getYaw(),
                pitch = player.getPitch();
        double eyeY = BigDecimal.valueOf(y).add(STANCE_CONSTANT_PRECISE).doubleValue();
        boolean move = x != player.getLastX() || y != player.getLastY() || z != player.getLastZ();
        boolean rotate = yaw != player.getLastYaw() || pitch != player.getLastPitch();
        boolean onGround = player.isOnGround();
        PacketC03_PlayerUpdate packet;
        if (move && rotate)
            packet = new PacketC06_PositionRotationUpdate(x, y, z, eyeY, yaw, pitch, onGround);
        else if (move)
            packet = new PacketC04_PositionUpdate(x, y, z, eyeY, onGround);
        else if (rotate)
            packet = new PacketC05_RotationUpdate(yaw, pitch, onGround);
        else
            packet = new PacketC03_PlayerUpdate(onGround);
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(packet);
    }

    @EventHandler
    public void onItemUse(ItemUseEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        ItemStack item = event.getItem();
        handler.sendPacket(new PacketC08_BlockPlace(-1, -1, -1, item != null && item.getId() == 346 ? 255 : -1,
                item, 0, 0, 0));
    }

    @EventHandler
    public void onRequestRespawn(RequestRespawnEvent event) {
        System.out.println("Respawn requested! :o");
        ConnectionHandler handler = bot.getConnectionHandler();
        handler.sendPacket(new PacketC16_ClientStatus(0));
    }

    @EventHandler
    public void onRequestDisconnect(RequestDisconnectEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        // Ensures sending of previously queued packets now that there is no
        // disconnect packet included
        handler.sendPacket(new PacketC04_PositionUpdate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, false));
        if (handler.isConnected())
            handler.disconnect(event.getReason());
    }

    @EventHandler
    public void onPacketReceived(PacketReceivedEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        Packet packet = event.getPacket();
        if (packet instanceof PacketLS01_EncryptionRequest) {
            handler.pauseReading();
            handleEncryption((PacketLS01_EncryptionRequest) packet);
        } else if (packet instanceof PacketLS02_LoginSuccess) {
            handler.pauseReading();
        } else if (packet instanceof PacketS32_ConfirmTransaction) {
            PacketS32_ConfirmTransaction transactionPacket = (PacketS32_ConfirmTransaction) packet;
            handler.sendPacket(new PacketC0F_ConfirmTransaction(transactionPacket.getWindowId(),
                    transactionPacket.getActionId(), true));
            bot.getEventBus().fire(new WindowTransactionCompleteEvent(transactionPacket.getWindowId(),
                    (short) transactionPacket.getActionId(), transactionPacket.isAccepted()));
        }
    }

    @EventHandler
    public void onPacketSent(PacketSentEvent event) {
        ConnectionHandler handler = bot.getConnectionHandler();
        Packet packet = event.getPacket();

        if (packet instanceof PacketHC00_Handshake) {
            setState(((PacketHC00_Handshake) packet).getNextState());
        } else if (packet instanceof PacketLC01_EncryptionResponse) {
            if (!handler.supportsEncryption()) {
                handler.disconnect("ConnectionHandler does not support encryption!");
                return;
            }
            if (handler.getSharedKey() != null) {
                handler.disconnect("Shared key already installed!");
                return;
            }
            if (!handler.isEncrypting()) {
                handler.setSharedKey(((PacketLC01_EncryptionResponse) packet).getSecretKey());
                handler.enableEncryption();
            }
            if (handler.getSharedKey() == null) {
                handler.disconnect("No shared key!");
                return;
            }
            if (!handler.isDecrypting()) {
                handler.enableDecryption();
                handler.resumeReading();
            }
        }
    }

    @EventHandler
    public void onPacketProcess(PacketProcessEvent event) {
        Packet packet = event.getPacket();
        ConnectionHandler connectionHandler = bot.getConnectionHandler();
        EventBus eventBus = bot.getEventBus();

        switch (getState()) {
        case LOGIN:
            switch (packet.getId()) {
            case 0x00: {
                connectionHandler.disconnect(((PacketLS00_Disconnect) packet).getData());
                break;
            }
            case 0x01: {
                break;
            }
            case 0x02: {
                connectionHandler.resumeReading();
                setState(State.PLAY);
                break;
            }
            }
            break;
        case PLAY:
            switch (packet.getId()) {
            case 0x00: {
                connectionHandler.sendPacket(new PacketC00_KeepAlive(((PacketS00_KeepAlive) packet).getPingId()));
                break;
            }
            case 0x01: {
                PacketS01_JoinGame joinPacket = (PacketS01_JoinGame) packet;
                eventBus.fire(new LoginEvent(joinPacket.getPlayerId(), joinPacket.getWorldType(),
                        joinPacket.getGameMode(), joinPacket.getDimension(), joinPacket.getDifficulty(), 256,
                        joinPacket.getMaxPlayers()));
                connectionHandler.sendPacket(new PacketC15_ClientSettings("en_US", ViewDistance.FAR,
                        ChatMode.ENABLED, Difficulty.NORMAL, true, true));
                try {
                    connectionHandler
                            .sendPacket(new PacketC17_PluginMessage("MC|Brand", "vanilla".getBytes("UTF-8")));
                } catch (UnsupportedEncodingException exception) {
                    throw new RuntimeException(exception);
                }
                break;
            }
            case 0x02: {
                PacketS02_ChatMessage chatPacket = (PacketS02_ChatMessage) packet;
                String message = chatPacket.getMessage();
                try {
                    JSONParser parser = new JSONParser();
                    JSONObject json = (JSONObject) parser.parse(message);

                    String text = parseChatMessage(json);
                    System.out.println("Parsing chat message: " + message + " -> " + text);
                    eventBus.fire(new ChatReceivedEvent(text));
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
                break;
            }
            case 0x03: {
                PacketS03_TimeUpdate timePacket = (PacketS03_TimeUpdate) packet;
                eventBus.fire(new TimeUpdateEvent(timePacket.getTime(), timePacket.getWorldAge()));
                break;
            }
            case 0x04: {
                PacketS04_EntityEquipment equipmentPacket = (PacketS04_EntityEquipment) packet;
                eventBus.fire(new PlayerEquipmentUpdateEvent(equipmentPacket.getEntityId(),
                        EquipmentSlot.fromId(equipmentPacket.getSlot().ordinal()), equipmentPacket.getItem()));
                break;
            }
            case 0x06: {
                PacketS06_UpdateHealth healthPacket = (PacketS06_UpdateHealth) packet;
                int health = (int) Math.ceil(healthPacket.getHealth());
                System.out.println("Received health: " + healthPacket.getHealth() + " " + healthPacket.getFood()
                        + " " + healthPacket.getFoodSaturation());
                eventBus.fire(new HealthUpdateEvent(health, healthPacket.getFood(),
                        (float) healthPacket.getFoodSaturation()));
                break;
            }
            case 0x07: {
                PacketS07_Respawn respawnPacket = (PacketS07_Respawn) packet;
                eventBus.fire(new RespawnEvent(respawnPacket.getDimension(), respawnPacket.getDifficulty(),
                        respawnPacket.getGameMode(), respawnPacket.getWorldType(), 256));
                break;
            }
            case 0x08: {
                PacketS08_Teleport teleportPacket = (PacketS08_Teleport) packet;

                double x = teleportPacket.getX(), eyeY = teleportPacket.getY(), z = teleportPacket.getZ();
                double yaw = teleportPacket.getYaw(), pitch = teleportPacket.getPitch();
                boolean grounded = teleportPacket.isGrounded();
                // Computers can't arithmetic
                double actualY = BigDecimal.valueOf(eyeY).subtract(STANCE_CONSTANT_PRECISE).doubleValue();

                PacketC06_PositionRotationUpdate clientUpdatePacket = new PacketC06_PositionRotationUpdate(x,
                        actualY, z, eyeY, yaw, pitch, grounded);
                connectionHandler.sendPacket(clientUpdatePacket);
                positionSet = true;

                eventBus.fire(new TeleportEvent(x, actualY, z, (float) yaw, (float) pitch));
                break;
            }
            case 0x09: {
                PacketS09_ChangeHeldItem heldItemPacket = (PacketS09_ChangeHeldItem) packet;
                eventBus.fire(
                        new ovh.tgrhavoc.aibot.event.protocol.server.ChangeHeldItemEvent(heldItemPacket.getSlot()));
                break;
            }
            case 0x0A: {
                PacketS0A_EnterBed sleepPacket = (PacketS0A_EnterBed) packet;
                eventBus.fire(new SleepEvent(sleepPacket.getEntityId(), sleepPacket.getBedX(),
                        sleepPacket.getBedY(), sleepPacket.getBedZ()));
                break;
            }
            case 0x0B: {
                PacketS0B_Animation animationPacket = (PacketS0B_Animation) packet;
                if (animationPacket.getAnimation() == Animation.EAT_FOOD)
                    eventBus.fire(new EntityEatEvent(animationPacket.getPlayerId()));
                break;
            }
            case 0x0C: {
                PacketS0C_SpawnPlayer spawnPacket = (PacketS0C_SpawnPlayer) packet;
                RotatedSpawnLocation location = new RotatedSpawnLocation(spawnPacket.getX(), spawnPacket.getY(),
                        spawnPacket.getZ(), spawnPacket.getYaw(), spawnPacket.getPitch());
                ItemStack heldItem = new BasicItemStack(spawnPacket.getHeldItemId(), 1, 0);
                eventBus.fire(new PlayerSpawnEvent(spawnPacket.getEntityId(), spawnPacket.getName(), heldItem,
                        location, spawnPacket.getMetadata()));
                break;
            }
            case 0x0D: {
                PacketS0D_CollectItem collectPacket = (PacketS0D_CollectItem) packet;
                eventBus.fire(new EntityCollectEvent(collectPacket.getItemEntityId(),
                        collectPacket.getCollectorEntityId()));
                break;
            }
            case 0x0E: {
                PacketS0E_SpawnObject spawnPacket = (PacketS0E_SpawnObject) packet;
                RotatedSpawnLocation location = new RotatedSpawnLocation(spawnPacket.getX(), spawnPacket.getY(),
                        spawnPacket.getZ(), spawnPacket.getYaw(), spawnPacket.getPitch());
                ObjectSpawnData spawnData;
                if (spawnPacket.getData() != 0)
                    spawnData = new ThrownObjectSpawnData(spawnPacket.getType(), spawnPacket.getData(),
                            spawnPacket.getVelocityX(), spawnPacket.getVelocityY(), spawnPacket.getVelocityZ());
                else
                    spawnData = new ObjectSpawnData(spawnPacket.getType());
                eventBus.fire(new ObjectEntitySpawnEvent(spawnPacket.getEntityId(), location, spawnData));
                break;
            }
            case 0x0F: {
                PacketS0F_SpawnMob spawnPacket = (PacketS0F_SpawnMob) packet;
                LivingEntitySpawnLocation location = new LivingEntitySpawnLocation(spawnPacket.getX(),
                        spawnPacket.getY(), spawnPacket.getZ(), spawnPacket.getYaw(), spawnPacket.getPitch(),
                        spawnPacket.getHeadYaw());
                LivingEntitySpawnData data = new LivingEntitySpawnData(spawnPacket.getType(),
                        spawnPacket.getVelocityX(), spawnPacket.getVelocityY(), spawnPacket.getVelocityZ());
                eventBus.fire(new LivingEntitySpawnEvent(spawnPacket.getEntityId(), location, data,
                        spawnPacket.getMetadata()));
                break;
            }
            case 0x10: {
                PacketS10_SpawnPainting spawnPacket = (PacketS10_SpawnPainting) packet;
                PaintingSpawnLocation location = new PaintingSpawnLocation(spawnPacket.getX(), spawnPacket.getY(),
                        spawnPacket.getZ(), spawnPacket.getFace());
                eventBus.fire(new PaintingSpawnEvent(spawnPacket.getEntityId(), location, spawnPacket.getTitle()));
                break;
            }
            case 0x11: {
                PacketS11_SpawnExperienceOrb spawnPacket = (PacketS11_SpawnExperienceOrb) packet;
                SpawnLocation location = new SpawnLocation(spawnPacket.getX(), spawnPacket.getY(),
                        spawnPacket.getZ());
                eventBus.fire(new ExpOrbSpawnEvent(spawnPacket.getEntityId(), location, spawnPacket.getCount()));
                break;
            }
            case 0x12: {
                PacketS12_EntityVelocityUpdate velocityPacket = (PacketS12_EntityVelocityUpdate) packet;
                eventBus.fire(new EntityVelocityEvent(velocityPacket.getEntityId(), velocityPacket.getVelocityX(),
                        velocityPacket.getVelocityY(), velocityPacket.getVelocityZ()));
                break;
            }
            case 0x13: {
                PacketS13_DespawnEntities despawnPacket = (PacketS13_DespawnEntities) packet;
                for (int id : despawnPacket.getEntityIds())
                    eventBus.fire(new EntityDespawnEvent(id));
                break;
            }
            case 0x15: {
                PacketS15_EntityRelativeMovementUpdate updatePacket = (PacketS15_EntityRelativeMovementUpdate) packet;
                eventBus.fire(new EntityMoveEvent(updatePacket.getEntityId(), updatePacket.getDX(),
                        updatePacket.getDY(), updatePacket.getDZ()));
                break;
            }
            case 0x16: {
                PacketS16_EntityRotationUpdate updatePacket = (PacketS16_EntityRotationUpdate) packet;
                eventBus.fire(new EntityRotateEvent(updatePacket.getEntityId(), updatePacket.getYaw(),
                        updatePacket.getPitch()));
                break;
            }
            case 0x17: {
                PacketS17_EntityRelativeMovementRotationUpdate updatePacket = (PacketS17_EntityRelativeMovementRotationUpdate) packet;
                eventBus.fire(new EntityMoveEvent(updatePacket.getEntityId(), updatePacket.getDX(),
                        updatePacket.getDY(), updatePacket.getDZ()));
                eventBus.fire(new EntityRotateEvent(updatePacket.getEntityId(), updatePacket.getYaw(),
                        updatePacket.getPitch()));
                break;
            }
            case 0x18: {
                PacketS18_EntityPositionRotationUpdate updatePacket = (PacketS18_EntityPositionRotationUpdate) packet;
                eventBus.fire(new EntityTeleportEvent(updatePacket.getEntityId(), updatePacket.getX(),
                        updatePacket.getY(), updatePacket.getZ(), updatePacket.getYaw(), updatePacket.getPitch()));
                break;
            }
            case 0x19: {
                PacketS19_EntityHeadRotationUpdate updatePacket = (PacketS19_EntityHeadRotationUpdate) packet;
                eventBus.fire(new EntityHeadRotateEvent(updatePacket.getEntityId(), updatePacket.getHeadYaw()));
                break;
            }
            case 0x1A: {
                PacketS1A_EntityStatusUpdate updatePacket = (PacketS1A_EntityStatusUpdate) packet;
                if (updatePacket.getStatus() == 2)
                    eventBus.fire(new EntityHurtEvent(updatePacket.getEntityId()));
                else if (updatePacket.getStatus() == 3)
                    eventBus.fire(new EntityDeathEvent(updatePacket.getEntityId()));
                else if (updatePacket.getStatus() == 9)
                    eventBus.fire(new EntityStopEatingEvent(updatePacket.getEntityId()));
                break;
            }
            case 0x1B: {
                PacketS1B_EntityAttachmentUpdate updatePacket = (PacketS1B_EntityAttachmentUpdate) packet;
                if (updatePacket.isWithLeash())
                    break;
                if (updatePacket.getAttachedEntityId() != -1)
                    eventBus.fire(
                            new EntityMountEvent(updatePacket.getEntityId(), updatePacket.getAttachedEntityId()));
                else
                    eventBus.fire(new EntityDismountEvent(updatePacket.getEntityId()));
                break;
            }
            case 0x1C: {
                PacketS1C_EntityMetadataUpdate updatePacket = (PacketS1C_EntityMetadataUpdate) packet;
                eventBus.fire(
                        new EntityMetadataUpdateEvent(updatePacket.getEntityId(), updatePacket.getMetadata()));
                break;
            }
            case 0x21: {
                if (bot.isMovementDisabled())
                    return;
                PacketS21_ChunkData chunkPacket = (PacketS21_ChunkData) packet;
                processChunk(chunkPacket.getChunk(), bot.getWorld().getDimension() == Dimension.OVERWORLD,
                        chunkPacket.hasBiomes());
                break;
            }
            case 0x22: {
                PacketS22_MultiBlockUpdate blockPacket = (PacketS22_MultiBlockUpdate) packet;
                if (blockPacket.getBlockData() == null)
                    return;
                int chunkX = blockPacket.getX() * 16, chunkZ = blockPacket.getZ() * 16;
                int[] blockData = blockPacket.getBlockData();
                for (int i = 0; i < blockData.length; i++) {
                    int data = blockData[i];

                    int id = (data >> 4) & 0xFFF;
                    int metadata = data & 0xF;

                    int x = chunkX + (data >> 28) & 0xF;
                    int y = (data >> 16) & 0xFF;
                    int z = chunkZ + (data >> 24) & 0xF;

                    eventBus.fire(new BlockChangeEvent(id, metadata, x, y, z));
                }
                break;
            }
            case 0x23: {
                PacketS23_BlockUpdate blockPacket = (PacketS23_BlockUpdate) packet;
                eventBus.fire(new BlockChangeEvent(blockPacket.getBlockId(), blockPacket.getBlockMetadata(),
                        blockPacket.getX(), blockPacket.getY(), blockPacket.getZ()));
                break;
            }
            case 0x26: {
                if (bot.isMovementDisabled())
                    return;
                PacketS26_MultiChunkData chunkPacket = (PacketS26_MultiChunkData) packet;
                for (ChunkData chunk : chunkPacket.getChunks())
                    processChunk(chunk, chunkPacket.hasSkylight(), true);
                break;
            }
            case 0x2D: {
                PacketS2D_OpenWindow windowOpenPacket = (PacketS2D_OpenWindow) packet;
                eventBus.fire(
                        new WindowOpenEvent(windowOpenPacket.getWindowId(), windowOpenPacket.getInventoryType(),
                                windowOpenPacket.useWindowTitle() ? windowOpenPacket.getWindowTitle() : "",
                                windowOpenPacket.getSlotCount()));
                break;
            }
            case 0x2E: {
                PacketS2E_CloseWindow windowClosePacket = (PacketS2E_CloseWindow) packet;
                eventBus.fire(new WindowCloseEvent(windowClosePacket.getWindowId()));
                break;
            }
            case 0x2F: {
                PacketS2F_SetSlot slotPacket = (PacketS2F_SetSlot) packet;
                eventBus.fire(new WindowSlotChangeEvent(slotPacket.getWindowId(), slotPacket.getSlot(),
                        slotPacket.getItem()));
                break;
            }
            case 0x30: {
                PacketS30_WindowItems windowItemsPacket = (PacketS30_WindowItems) packet;
                eventBus.fire(new WindowUpdateEvent(windowItemsPacket.getWindowId(), windowItemsPacket.getItems()));
                break;
            }
            // TODO 0x31 WindowProperty
            case 0x33: {
                PacketS33_SignUpdate signUpdatePacket = (PacketS33_SignUpdate) packet;
                eventBus.fire(new SignUpdateEvent(signUpdatePacket.getX(), signUpdatePacket.getY(),
                        signUpdatePacket.getZ(), signUpdatePacket.getLines()));
                break;
            }
            // TODO 0x34 MapUpdate
            case 0x35: {
                PacketS35_TileEntityUpdate tilePacket = (PacketS35_TileEntityUpdate) packet;
                eventBus.fire(new TileEntityUpdateEvent(tilePacket.getX(), tilePacket.getY(), tilePacket.getZ(),
                        tilePacket.getAction(), tilePacket.getData()));
                break;
            }
            case 0x36: {
                PacketS36_OpenTileEditor tileEditorPacket = (PacketS36_OpenTileEditor) packet;
                eventBus.fire(new EditTileEntityEvent(tileEditorPacket.getX(), tileEditorPacket.getY(),
                        tileEditorPacket.getZ()));
                break;
            }
            case 0x38: {
                PacketS38_PlayerListItem playerListEvent = (PacketS38_PlayerListItem) packet;
                if (playerListEvent.isOnline())
                    eventBus.fire(
                            new PlayerListUpdateEvent(playerListEvent.getPlayerName(), playerListEvent.getPing()));
                else
                    eventBus.fire(new PlayerListRemoveEvent(playerListEvent.getPlayerName()));
                break;
            }
            // TODO 0x39 PlayerAbilities
            case 0x40: {
                PacketS40_Disconnect disconnectPacket = (PacketS40_Disconnect) packet;
                eventBus.fire(new KickEvent(disconnectPacket.getReason()));
            }
            }
            break;
        default:
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void handleEncryption(PacketLS01_EncryptionRequest request) {
        String serverId = request.getServerId().trim();
        PublicKey publicKey;
        try {
            publicKey = EncryptionUtil.generatePublicKey(request.getPublicKey());
        } catch (GeneralSecurityException exception) {
            throw new Error("Unable to generate public key", exception);
        }
        SecretKey secretKey = EncryptionUtil.generateSecretKey();
        ConnectionHandler connectionHandler = bot.getConnectionHandler();

        if (!serverId.equals("-")) {
            try {
                AuthService service = bot.getAuthService();
                Session session = bot.getSession();

                String hash = new BigInteger(EncryptionUtil.encrypt(serverId, publicKey, secretKey)).toString(16);
                service.authenticate(service.validateSession(session), hash, bot.getLoginProxy());
            } catch (InvalidSessionException exception) {
                connectionHandler.disconnect("Session invalid: " + exception);
            } catch (NoSuchAlgorithmException | UnsupportedEncodingException exception) {
                connectionHandler.disconnect("Unable to hash: " + exception);
            } catch (AuthenticationException | IOException exception) {
                connectionHandler.disconnect("Unable to authenticate: " + exception);
            }
        }

        connectionHandler
                .sendPacket(new PacketLC01_EncryptionResponse(secretKey, publicKey, request.getVerifyToken()));
    }

    public String parseChatMessage(JSONObject messageData) {
        StringBuffer message = new StringBuffer();
        if (messageData.get("bold") == Boolean.TRUE)
            message.append(ChatColor.BOLD);
        if (messageData.get("italic") == Boolean.TRUE)
            message.append(ChatColor.ITALIC);
        if (messageData.get("underlined") == Boolean.TRUE)
            message.append(ChatColor.UNDERLINE);
        if (messageData.get("strikethrough") == Boolean.TRUE)
            message.append(ChatColor.STRIKETHROUGH);
        if (messageData.get("obfuscated") == Boolean.TRUE)
            message.append(ChatColor.OBFUSCATED);
        if (messageData.containsKey("color"))
            message.append(ChatColor.valueOf(messageData.get("color").toString().toUpperCase()));

        if (messageData.containsKey("translate")) {
            String key = (String) messageData.get("translate");

            String text = lang.get(key);
            if (text == null)
                text = key;

            if (messageData.containsKey("with")) {
                JSONArray array = (JSONArray) messageData.get("with");
                String[] translationValues = new String[array.size()];
                for (int i = 0; i < translationValues.length; i++) {
                    Object object = array.get(i);

                    String value;
                    if (object instanceof JSONObject)
                        value = parseChatMessage((JSONObject) object);
                    else
                        value = (String) object;
                    translationValues[i] = value;
                }

                text = String.format(text, (Object[]) translationValues);
            }

            message.append(text);
        } else if (messageData.containsKey("text"))
            message.append(messageData.get("text"));

        if (messageData.containsKey("extra")) {
            JSONArray extra = (JSONArray) messageData.get("extra");
            for (Object object : extra)
                if (object instanceof JSONObject)
                    message.append(parseChatMessage((JSONObject) object));
                else
                    message.append(object);
        }

        return message.toString();
    }

    private void processChunk(ChunkData chunk, boolean addSkylight, boolean addBiomes) {
        if (chunk == null)
            return;
        int x = chunk.getX(), z = chunk.getZ();
        int bitmask = chunk.getPrimaryBitmask();
        byte[] data = chunk.getData();

        int chunksChanged = 0;
        for (int i = 0; i < 16; i++)
            if ((bitmask & (1 << i)) != 0)
                chunksChanged++;
        if (chunksChanged == 0)
            return;
        int[] yValues = new int[chunksChanged];
        byte[][] allBlocks = new byte[chunksChanged][], allMetadata = new byte[chunksChanged][],
                allLight = new byte[chunksChanged][], allSkylight = new byte[chunksChanged][];
        byte[] biomes = new byte[256];
        int i = 0;
        for (int y = 0; y < 16; y++) {
            if ((bitmask & (1 << y)) == 0)
                continue;
            yValues[i] = y;
            int dataIndex = i * 4096;
            byte[] blocks = Arrays.copyOfRange(data, dataIndex, dataIndex + 4096);
            dataIndex += ((chunksChanged - i) * 4096) + (i * 2048);
            byte[] metadata = Arrays.copyOfRange(data, dataIndex, dataIndex + 2048);
            dataIndex += chunksChanged * 2048;
            byte[] light = Arrays.copyOfRange(data, dataIndex, dataIndex + 2048);
            dataIndex += chunksChanged * 2048;
            byte[] skylight = null;
            if (addSkylight)
                skylight = Arrays.copyOfRange(data, dataIndex, dataIndex + 2048);

            byte[] perBlockMetadata = new byte[4096];
            byte[] perBlockLight = new byte[4096];
            byte[] perBlockSkylight = new byte[4096];

            for (int j = 0; j < 2048; j++) {
                int k = j * 2;
                perBlockMetadata[k] = (byte) (metadata[j] & 0x0F);
                perBlockLight[k] = (byte) (light[j] & 0x0F);
                if (addSkylight)
                    perBlockSkylight[k] = (byte) (skylight[j] & 0x0F);
                k++;
                perBlockMetadata[k] = (byte) (metadata[j] >> 4);
                perBlockLight[k] = (byte) (light[j] >> 4);
                if (addSkylight)
                    perBlockSkylight[k] = (byte) (skylight[j] >> 4);
            }

            allBlocks[i] = blocks;
            allMetadata[i] = perBlockMetadata;
            allLight[i] = perBlockLight;
            allSkylight[i] = perBlockSkylight;
            i++;
        }
        System.arraycopy(data, data.length - 256, biomes, 0, 256);
        EventBus eventBus = bot.getEventBus();
        for (i = 0; i < chunksChanged; i++) {
            ChunkLoadEvent event = new ChunkLoadEvent(x, yValues[i], z, allBlocks[i], allMetadata[i], allLight[i],
                    allSkylight[i], biomes.clone());
            eventBus.fire(event);
        }
    }

    public static final class Provider extends ProtocolProvider<Protocol4X> {
        @Override
        public Protocol4X getProtocolInstance(MinecraftBot bot) {
            return new Protocol4X(bot);
        }

        @Override
        public int getSupportedVersion() {
            return VERSION;
        }

        @Override
        public String getMinecraftVersion() {
            return VERSION_NAME;
        }
    }
}