diff --git a/README.md b/README.md
index 9878fa5a0..b8f8749b8 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-<img src="https://geysermc.org/img/geyserlogo.png" alt="Geyser" width="600"/>
+<img src="https://geysermc.org/img/geyser-1760-860.png" alt="Geyser" width="600"/>
 
 [![forthebadge made-with-java](http://ForTheBadge.com/images/badges/made-with-java.svg)](https://java.com/)
 
diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
index 24793839d..1cf519c0e 100644
--- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
+++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java
@@ -44,6 +44,7 @@ import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager;
 import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Path;
 import java.util.UUID;
 import java.util.logging.Level;
 
@@ -134,4 +135,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
     public IGeyserPingPassthrough getGeyserPingPassthrough() {
         return geyserBungeePingPassthrough;
     }
+
+    @Override
+    public Path getConfigFolder() {
+        return getDataFolder().toPath();
+    }
 }
diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
index d8f44f362..b5e2264a2 100644
--- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
+++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java
@@ -45,6 +45,7 @@ import us.myles.ViaVersion.api.Via;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.UUID;
 import java.util.logging.Level;
 
@@ -162,6 +163,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
         return this.geyserWorldManager;
     }
 
+    @Override
+    public Path getConfigFolder() {
+        return getDataFolder().toPath();
+    }
+
     public boolean isCompatible(String version, String whichVersion) {
         int[] currentVersion = parseVersion(version);
         int[] otherVersion = parseVersion(whichVersion);
@@ -195,4 +201,5 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
         }
         return temp;
     }
+
 }
diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
index 2b02ec0d4..79e7621cc 100644
--- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
+++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java
@@ -129,6 +129,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration {
         return node.getNode("allow-third-party-ears").getBoolean(false);
     }
 
+    @Override
+    public boolean isShowCooldown() {
+        return node.getNode("show-cooldown").getBoolean(true);
+    }
+
     @Override
     public String getDefaultLocale() {
         return node.getNode("default-locale").getString("en_us");
diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
index 2288fc674..c5f13b581 100644
--- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
+++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java
@@ -50,6 +50,7 @@ import org.spongepowered.api.plugin.Plugin;
 import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Path;
 import java.util.UUID;
 
 @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
@@ -147,6 +148,11 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
         return geyserSpongePingPassthrough;
     }
 
+    @Override
+    public Path getConfigFolder() {
+        return configDir.toPath();
+    }
+
     @Listener
     public void onServerStart(GameStartedServerEvent event) {
         onEnable();
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
index a1c472387..0fca35038 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java
@@ -37,6 +37,8 @@ import org.geysermc.platform.standalone.command.GeyserCommandManager;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.UUID;
 
 public class GeyserStandaloneBootstrap implements GeyserBootstrap {
@@ -100,4 +102,10 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
     public IGeyserPingPassthrough getGeyserPingPassthrough() {
         return geyserPingPassthrough;
     }
+
+    @Override
+    public Path getConfigFolder() {
+        // Return the current working directory
+        return Paths.get(System.getProperty("user.dir"));
+    }
 }
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
index ffb252b2e..7102d206e 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java
@@ -82,7 +82,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org
 
     @Override
     public void info(String message) {
-        log.info(printConsole(ChatColor.WHITE + message, colored));
+        log.info(printConsole(ChatColor.RESET + ChatColor.BOLD + message, colored));
     }
 
     @Override
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
index e2a4787b4..5abf3c230 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java
@@ -34,6 +34,7 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
 import com.velocitypowered.api.plugin.Plugin;
 
 import com.velocitypowered.api.proxy.ProxyServer;
+import lombok.Getter;
 import org.geysermc.common.PlatformType;
 import org.geysermc.connector.configuration.GeyserConfiguration;
 import org.geysermc.connector.GeyserConnector;
@@ -48,6 +49,8 @@ import org.slf4j.Logger;
 import java.io.File;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.UUID;
 
 @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC")
@@ -69,14 +72,16 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
 
     private GeyserConnector connector;
 
+    @Getter
+    private final Path configFolder = Paths.get("plugins/" + GeyserConnector.NAME + "-Velocity/");
+
     @Override
     public void onEnable() {
-        File configDir = new File("plugins/" + GeyserConnector.NAME + "-Velocity/");
-
         try {
-            if (!configDir.exists())
-                configDir.mkdir();
-            File configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()));
+            if (!configFolder.toFile().exists())
+                //noinspection ResultOfMethodCallIgnored
+                configFolder.toFile().mkdirs();
+            File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()));
             this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class);
         } catch (IOException ex) {
             logger.warn("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex);
@@ -101,7 +106,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
             return;
         }
 
-        geyserConfig.loadFloodgate(this, proxyServer, configDir);
+        geyserConfig.loadFloodgate(this, proxyServer, configFolder.toFile());
 
         this.connector = GeyserConnector.start(PlatformType.VELOCITY, this);
 
diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/window/button/FormImage.java
index b700b046b..72579f7ac 100644
--- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java
+++ b/common/src/main/java/org/geysermc/common/window/button/FormImage.java
@@ -32,14 +32,14 @@ public class FormImage {
 
     @Getter
     @Setter
-    private FormImageType type;
+    private String type;
 
     @Getter
     @Setter
     private String data;
 
     public FormImage(FormImageType type, String data) {
-        this.type = type;
+        this.type = type.getName();
         this.data = data;
     }
 
diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java
index 50a5c631a..f972d5906 100644
--- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java
+++ b/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java
@@ -25,9 +25,17 @@
 
 package org.geysermc.common.window.component;
 
+import lombok.Getter;
+import lombok.Setter;
+
 public class ToggleComponent extends FormComponent {
 
+    @Getter
+    @Setter
     private String text;
+
+    @Getter
+    @Setter
     private boolean defaultValue;
 
     public ToggleComponent(String text) {
diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
index abd48bc29..d201656ad 100644
--- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
+++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
@@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
 import com.nukkitx.protocol.bedrock.BedrockServer;
 import com.nukkitx.protocol.bedrock.v390.Bedrock_v390;
 import lombok.Getter;
+import lombok.Setter;
 import org.geysermc.common.AuthType;
 import org.geysermc.common.PlatformType;
 import org.geysermc.connector.bootstrap.GeyserBootstrap;
@@ -79,6 +80,7 @@ public class GeyserConnector {
     private static GeyserConnector instance;
 
     private RemoteServer remoteServer;
+    @Setter
     private AuthType authType;
 
     private boolean shuttingDown = false;
diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java
index 5cc6d06f3..8683f80cd 100644
--- a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java
+++ b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java
@@ -33,6 +33,8 @@ import org.geysermc.connector.command.CommandManager;
 import org.geysermc.connector.network.translators.world.CachedChunkManager;
 import org.geysermc.connector.network.translators.world.WorldManager;
 
+import java.nio.file.Path;
+
 public interface GeyserBootstrap {
 
     CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager();
@@ -83,4 +85,11 @@ public interface GeyserBootstrap {
     default WorldManager getWorldManager() {
         return DEFAULT_CHUNK_MANAGER;
     }
+
+    /**
+     * Return the data folder where files get stored
+     *
+     * @return Path location of data folder
+     */
+    Path getConfigFolder();
 }
diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
index 88b9e795d..8b1d0bc78 100644
--- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
+++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java
@@ -75,7 +75,7 @@ public abstract class CommandManager {
             args = new String[0];
         } else {
             label = command.substring(0, command.indexOf(" ")).toLowerCase();
-            String argLine = command.substring(command.indexOf(" " + 1));
+            String argLine = command.substring(command.indexOf(" ") + 1);
             args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine };
         }
 
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
index 8e59644e9..5ea942c1a 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java
@@ -62,6 +62,8 @@ public interface GeyserConfiguration {
 
     boolean isAllowThirdPartyEars();
 
+    boolean isShowCooldown();
+
     String getDefaultLocale();
 
     Path getFloodgateKeyFile();
diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
index a1d0db217..576e5ed0d 100644
--- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
+++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java
@@ -75,6 +75,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
     @JsonProperty("allow-third-party-capes")
     private boolean allowThirdPartyCapes;
 
+    @JsonProperty("show-cooldown")
+    private boolean showCooldown = true;
+
     @JsonProperty("allow-third-party-ears")
     private boolean allowThirdPartyEars;
 
diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java
index 8f79526de..a59cd08fc 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java
@@ -44,19 +44,28 @@ public class BoatEntity extends Entity {
     private final float ROWING_SPEED = 0.05f;
 
     public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
-        super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(0, 0, 90));
+        super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(90, 0, 90));
     }
 
     @Override
     public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
-        // Rotation is basically only called when entering/exiting a boat.
         // We don't include the rotation (y) as it causes the boat to appear sideways
-        super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(0, 0, rotation.getZ() + 90), isOnGround, teleported);
+        super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(rotation.getX() + 90, 0, rotation.getX() + 90), isOnGround, teleported);
     }
 
     @Override
     public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
-        super.moveRelative(session, relX, relY, relZ, Vector3f.from(0, 0, rotation.getZ()), isOnGround);
+        super.moveRelative(session, relX, relY, relZ, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround);
+    }
+
+    @Override
+    public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
+        moveRelative(session, moveX, moveY, moveZ, yaw + 90, pitch, isOnGround);
+    }
+
+    @Override
+    public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) {
+        moveRelative(session, 0, 0, 0, Vector3f.from(yaw + 90, 0, 0), isOnGround);
     }
 
     @Override
diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
index c5fcde9ef..d5ae391c0 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java
@@ -78,6 +78,11 @@ public class Entity {
      */
     protected Vector3f rotation;
 
+    /**
+     * Saves if the entity should be on the ground. Otherwise entities like parrots are flapping when rotating
+     */
+    protected boolean onGround;
+
     protected float scale = 1;
 
     protected EntityType entityType;
@@ -150,11 +155,12 @@ public class Entity {
     }
 
     public void moveRelative(GeyserSession session, double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
-        moveRelative(session, relX, relY, relZ, Vector3f.from(yaw, pitch, yaw), isOnGround);
+        moveRelative(session, relX, relY, relZ, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround);
     }
 
     public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
         setRotation(rotation);
+        setOnGround(isOnGround);
         this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
 
         MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
@@ -168,12 +174,13 @@ public class Entity {
     }
 
     public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) {
-        moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround, teleported);
+        moveAbsolute(session, position, Vector3f.from(yaw, pitch, this.rotation.getZ()), isOnGround, teleported);
     }
 
     public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
         setPosition(position);
         setRotation(rotation);
+        setOnGround(isOnGround);
 
         MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
         moveEntityPacket.setRuntimeEntityId(geyserId);
@@ -185,6 +192,52 @@ public class Entity {
         session.sendUpstreamPacket(moveEntityPacket);
     }
 
+    /**
+     * Teleports an entity to a new location. Used in JavaEntityTeleportTranslator.
+     * @param session GeyserSession.
+     * @param position The new position of the entity.
+     * @param yaw The new yaw of the entity.
+     * @param pitch The new pitch of the entity.
+     * @param isOnGround Whether the entity is currently on the ground.
+     */
+    public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) {
+        moveAbsolute(session, position, yaw, pitch, isOnGround, false);
+    }
+
+    /**
+     * Updates an entity's head position. Used in JavaEntityHeadLookTranslator.
+     * @param session GeyserSession.
+     * @param headYaw The new head rotation of the entity.
+     */
+    public void updateHeadLookRotation(GeyserSession session, float headYaw) {
+        moveRelative(session, 0, 0, 0, Vector3f.from(headYaw, rotation.getY(), rotation.getZ()), onGround);
+    }
+
+    /**
+     * Updates an entity's position and rotation. Used in JavaEntityPositionRotationTranslator.
+     * @param session GeyserSession
+     * @param moveX The new X offset of the current position.
+     * @param moveY The new Y offset of the current position.
+     * @param moveZ The new Z offset of the current position.
+     * @param yaw The new yaw of the entity.
+     * @param pitch The new pitch of the entity.
+     * @param isOnGround Whether the entity is currently on the ground.
+     */
+    public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
+        moveRelative(session, moveX, moveY, moveZ, Vector3f.from(rotation.getX(), pitch, yaw), isOnGround);
+    }
+
+    /**
+     * Updates an entity's rotation. Used in JavaEntityRotationTranslator.
+     * @param session GeyserSession.
+     * @param yaw The new yaw of the entity.
+     * @param pitch The new pitch of the entity.
+     * @param isOnGround Whether the entity is currently on the ground.
+     */
+    public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) {
+        updatePositionAndRotation(session, 0, 0, 0, yaw, pitch, isOnGround);
+    }
+
     public void updateBedrockAttributes(GeyserSession session) {
         if (!valid) return;
 
diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java
index 1711fd38c..00cfc8b54 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java
@@ -62,6 +62,11 @@ public class PaintingEntity extends Entity {
         session.getConnector().getLogger().debug("Spawned painting on " + position);
     }
 
+    @Override
+    public void updateHeadLookRotation(GeyserSession session, float headYaw) {
+        // Do nothing, as head look messes up paintings
+    }
+
     public Vector3f fixOffset(boolean toBedrock) {
         if (toBedrock) {
             Vector3f position = super.position;
diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
index aa7848da2..594f139a0 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java
@@ -57,7 +57,6 @@ public class PlayerEntity extends LivingEntity {
     private String username;
     private long lastSkinUpdate = -1;
     private boolean playerList = true;
-    private boolean onGround;
     private final EntityEffectCache effectCache;
 
     private Entity leftParrot;
@@ -144,7 +143,7 @@ public class PlayerEntity extends LivingEntity {
         setPosition(position);
         setRotation(rotation);
 
-        this.onGround = isOnGround;
+        setOnGround(isOnGround);
 
         MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
         movePlayerPacket.setRuntimeEntityId(geyserId);
@@ -171,7 +170,7 @@ public class PlayerEntity extends LivingEntity {
         setRotation(rotation);
         this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ);
 
-        this.onGround = isOnGround;
+        setOnGround(isOnGround);
 
         MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
         movePlayerPacket.setRuntimeEntityId(geyserId);
@@ -188,6 +187,35 @@ public class PlayerEntity extends LivingEntity {
         }
     }
 
+    @Override
+    public void updateHeadLookRotation(GeyserSession session, float headYaw) {
+        moveRelative(session, 0, 0, 0, Vector3f.from(rotation.getX(), rotation.getY(), headYaw), onGround);
+        MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
+        movePlayerPacket.setRuntimeEntityId(geyserId);
+        movePlayerPacket.setPosition(position);
+        movePlayerPacket.setRotation(getBedrockRotation());
+        movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION);
+        session.sendUpstreamPacket(movePlayerPacket);
+    }
+
+    @Override
+    public void updatePositionAndRotation(GeyserSession session, double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
+        moveRelative(session, moveX, moveY, moveZ, yaw, pitch, isOnGround);
+    }
+
+    @Override
+    public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) {
+        super.updateRotation(session, yaw, pitch, isOnGround);
+        // Both packets need to be sent or else player head rotation isn't correctly updated
+        MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
+        movePlayerPacket.setRuntimeEntityId(geyserId);
+        movePlayerPacket.setPosition(position);
+        movePlayerPacket.setRotation(getBedrockRotation());
+        movePlayerPacket.setOnGround(isOnGround);
+        movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION);
+        session.sendUpstreamPacket(movePlayerPacket);
+    }
+
     @Override
     public void setPosition(Vector3f position) {
         this.position = position.add(0, entityType.getOffset(), 0);
@@ -227,7 +255,7 @@ public class PlayerEntity extends LivingEntity {
         }
 
         // Parrot occupying shoulder
-        if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) {
+        if ((entityMetadata.getId() == 18 && leftParrot == null) || (entityMetadata.getId() == 19 && rightParrot == null)) { // null check since this code just creates the parrot
             CompoundTag tag = (CompoundTag) entityMetadata.getValue();
             if (tag != null && !tag.isEmpty()) {
                 // The parrot is a separate entity in Bedrock, but part of the player entity in Java
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java
index b28ad99f5..fd9fd999f 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PigEntity.java
@@ -27,21 +27,58 @@ package org.geysermc.connector.entity.living.animal;
 
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
 import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.protocol.bedrock.data.Attribute;
 import com.nukkitx.protocol.bedrock.data.EntityFlag;
+import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
+import org.geysermc.connector.entity.attribute.AttributeType;
 import org.geysermc.connector.entity.type.EntityType;
 import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.utils.AttributeUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 public class PigEntity extends AnimalEntity {
 
+    // For updating the pig heart visual easier
+    private float health = 20f;
+
     public PigEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
         super(entityId, geyserId, entityType, position, motion, rotation);
     }
 
     @Override
     public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
+        if (entityMetadata.getId() == 8) {
+            health = (float) entityMetadata.getValue();
+            updateBedrockAttributes(session);
+        }
+
         if (entityMetadata.getId() == 16) {
             metadata.getFlags().setFlag(EntityFlag.SADDLED, (boolean) entityMetadata.getValue());
         }
         super.updateBedrockMetadata(entityMetadata, session);
     }
+
+    @Override
+    public void updateBedrockAttributes(GeyserSession session) {
+        if (!valid) return;
+
+        float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f;
+
+        List<Attribute> attributesLocal = new ArrayList<>();
+        for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> entry : this.attributes.entrySet()) {
+            if (!entry.getValue().getType().isBedrockAttribute())
+                continue;
+
+            attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
+        }
+        attributesLocal.add(new Attribute("minecraft:health", 0.0f, maxHealth, health, maxHealth));
+
+        UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
+        updateAttributesPacket.setRuntimeEntityId(geyserId);
+        updateAttributesPacket.setAttributes(attributesLocal);
+        session.sendUpstreamPacket(updateAttributesPacket);
+    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java
index 63a67a0a7..de9bcb4ec 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java
@@ -38,6 +38,11 @@ public class CatEntity extends TameableEntity {
         super(entityId, geyserId, entityType, position, motion, rotation);
     }
 
+    @Override
+    public void updateRotation(GeyserSession session, float yaw, float pitch, boolean isOnGround) {
+        moveRelative(session, 0, 0, 0, Vector3f.from(this.rotation.getX(), pitch, yaw), isOnGround);
+    }
+
     @Override
     public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
         if (entityMetadata.getId() == 18) {
diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java
index ddeb31bd1..11028b79e 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/AbstractMerchantEntity.java
@@ -28,10 +28,16 @@ package org.geysermc.connector.entity.living.merchant;
 import com.nukkitx.math.vector.Vector3f;
 import org.geysermc.connector.entity.living.AgeableEntity;
 import org.geysermc.connector.entity.type.EntityType;
+import org.geysermc.connector.network.session.GeyserSession;
 
 public class AbstractMerchantEntity extends AgeableEntity {
 
     public AbstractMerchantEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
         super(entityId, geyserId, entityType, position, motion, rotation);
     }
+
+    @Override
+    public void teleport(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) {
+        super.teleport(session, position, yaw - 180, pitch, isOnGround);
+    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
index 5491d6e5d..d2b87af14 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
@@ -47,6 +47,8 @@ import com.nukkitx.protocol.bedrock.BedrockPacket;
 import com.nukkitx.protocol.bedrock.BedrockServerSession;
 import com.nukkitx.protocol.bedrock.data.*;
 import com.nukkitx.protocol.bedrock.packet.*;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2LongMap;
 import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 import lombok.Getter;
@@ -57,7 +59,6 @@ import org.geysermc.connector.GeyserConnector;
 import org.geysermc.connector.command.CommandSender;
 import org.geysermc.connector.entity.Entity;
 import org.geysermc.connector.entity.PlayerEntity;
-import org.geysermc.connector.entity.attribute.AttributeType;
 import org.geysermc.connector.inventory.PlayerInventory;
 import org.geysermc.connector.network.remote.RemoteServer;
 import org.geysermc.connector.network.session.auth.AuthData;
@@ -108,6 +109,9 @@ public class GeyserSession implements CommandSender {
     @Setter
     private TeleportCache teleportCache;
 
+    @Getter
+    private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = new Long2ObjectOpenHashMap<>();
+
     /**
      * A map of Vector3i positions to Java entity IDs.
      * Used for translating Bedrock block actions to Java entity actions.
@@ -175,6 +179,18 @@ public class GeyserSession implements CommandSender {
     @Setter
     private long lastInteractedVillagerEid;
 
+    /**
+     * The current attack speed of the player. Used for sending proper cooldown timings.
+     */
+    @Setter
+    private double attackSpeed;
+    /**
+     * The time of the last hit. Used to gauge how long the cooldown is taking.
+     * This is a session variable in order to prevent more scheduled threads than necessary.
+     */
+    @Setter
+    private long lastHitTime;
+
     private MinecraftProtocol protocol;
 
     public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java
index bfd4a90a5..84fb309e4 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java
@@ -26,6 +26,7 @@
 package org.geysermc.connector.network.translators.bedrock;
 
 import com.nukkitx.protocol.bedrock.data.EntityData;
+import com.nukkitx.protocol.bedrock.data.EntityDataMap;
 import com.nukkitx.protocol.bedrock.data.EntityFlag;
 import org.geysermc.connector.entity.Entity;
 import org.geysermc.connector.network.session.GeyserSession;
@@ -74,33 +75,43 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
                     Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId());
                     if (interactEntity == null)
                         return;
+                    EntityDataMap entityMetadata = interactEntity.getMetadata();
 
                     String interactiveTag;
                     switch (interactEntity.getEntityType()) {
-                        case PIG:
-                            if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.SADDLED)) {
-                                interactiveTag = "action.interact.mount";
-                            } else interactiveTag = "";
+                        case BOAT:
+                            interactiveTag = "action.interact.ride.boat";
                             break;
-                        case HORSE:
-                        case SKELETON_HORSE:
-                        case ZOMBIE_HORSE:
                         case DONKEY:
-                        case MULE:
+                        case HORSE:
                         case LLAMA:
+                        case MULE:
+                        case SKELETON_HORSE:
                         case TRADER_LLAMA:
-                            if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) {
+                        case ZOMBIE_HORSE:
+                            if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
                                 interactiveTag = "action.interact.ride.horse";
                             } else {
                                 interactiveTag = "action.interact.mount";
                             }
                             break;
-                        case BOAT:
-                            interactiveTag = "action.interact.ride.boat";
-                            break;
                         case MINECART:
                             interactiveTag = "action.interact.ride.minecart";
                             break;
+                        case PIG:
+                            if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
+                                interactiveTag = "action.interact.mount";
+                            } else interactiveTag = "";
+                            break;
+                        case VILLAGER:
+                            if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0
+                            && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby
+                                interactiveTag = "action.interact.trade";
+                            } else interactiveTag = "";
+                            break;
+                        case WANDERING_TRADER:
+                            interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably.
+                            break;
                         default:
                             return; // No need to process any further since there is no interactive tag
                     }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java
index 6395f0a13..08ad10bfb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLevelSoundEventTranslator.java
@@ -25,10 +25,12 @@
 
 package org.geysermc.connector.network.translators.bedrock;
 
+import com.nukkitx.protocol.bedrock.data.SoundEvent;
 import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.utils.CooldownUtils;
 
 @Translator(packet = LevelSoundEventPacket.class)
 public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoundEventPacket> {
@@ -37,5 +39,12 @@ public class BedrockLevelSoundEventTranslator extends PacketTranslator<LevelSoun
     public void translate(LevelSoundEventPacket packet, GeyserSession session) {
         // lol what even :thinking:
         session.sendUpstreamPacket(packet);
+
+        // Yes, what even, but thankfully we can hijack this packet to send the cooldown
+        if (packet.getSound() == SoundEvent.ATTACK_NODAMAGE || packet.getSound() == SoundEvent.ATTACK || packet.getSound() == SoundEvent.ATTACK_STRONG) {
+            // Send a faux cooldown since Bedrock has no cooldown support
+            // Sent here because Java still sends a cooldown if the player doesn't hit anything but Bedrock always sends a sound
+            CooldownUtils.sendCooldown(session);
+        }
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java
new file mode 100644
index 000000000..3c7efa18b
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMapInfoRequestTranslator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.bedrock;
+
+import com.nukkitx.protocol.bedrock.packet.MapInfoRequestPacket;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+
+import java.util.concurrent.TimeUnit;
+
+@Translator(packet = MapInfoRequestPacket.class)
+public class BedrockMapInfoRequestTranslator extends PacketTranslator<MapInfoRequestPacket> {
+
+    @Override
+    public void translate(MapInfoRequestPacket packet, GeyserSession session) {
+        long mapID = packet.getUniqueMapId();
+
+        if (session.getStoredMaps().containsKey(mapID)) {
+            // Delay the packet 100ms to prevent the client from ignoring the packet
+            GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> {
+                session.sendUpstreamPacket(session.getStoredMaps().get(mapID));
+                session.getStoredMaps().remove(mapID);
+            }, 100, TimeUnit.MILLISECONDS);
+        }
+    }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
index 5fc7f41ae..1dfa27743 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java
@@ -32,6 +32,7 @@ import org.geysermc.connector.network.translators.Translator;
 import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket;
 import com.nukkitx.protocol.bedrock.data.ContainerId;
 import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
+import org.geysermc.connector.utils.CooldownUtils;
 
 @Translator(packet = MobEquipmentPacket.class)
 public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipmentPacket> {
@@ -47,5 +48,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
 
         ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
         session.sendDownstreamPacket(changeHeldItemPacket);
+
+        // Java sends a cooldown indicator whenever you switch an item
+        CooldownUtils.sendCooldown(session);
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
index cb8613c8c..1962f62ec 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
@@ -28,8 +28,8 @@ package org.geysermc.connector.network.translators.item;
 
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
 import com.github.steveice10.mc.protocol.data.message.Message;
-import com.nukkitx.nbt.CompoundTagBuilder;
 import com.github.steveice10.opennbt.tag.builtin.*;
+import com.nukkitx.nbt.CompoundTagBuilder;
 import com.nukkitx.nbt.tag.CompoundTag;
 import com.nukkitx.nbt.tag.Tag;
 import com.nukkitx.protocol.bedrock.data.ItemData;
@@ -41,11 +41,7 @@ import org.geysermc.connector.network.translators.ItemRemapper;
 import org.geysermc.connector.utils.MessageUtils;
 import org.reflections.Reflections;
 
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 public abstract class ItemTranslator {
@@ -126,12 +122,20 @@ public abstract class ItemTranslator {
 
         ItemEntry bedrockItem = ItemRegistry.getItem(stack);
 
-        ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), stack.getNbt() != null ? stack.getNbt().clone() : null);
+        com.github.steveice10.opennbt.tag.builtin.CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null;
 
-        if (itemStack.getNbt() != null) {
+        // This is a fallback for maps with no nbt
+        if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) {
+            nbt = new com.github.steveice10.opennbt.tag.builtin.CompoundTag("");
+            nbt.put(new IntTag("map", 0));
+        }
+
+        ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), nbt);
+
+        if (nbt != null) {
             for (NbtItemStackTranslator translator : NBT_TRANSLATORS) {
                 if (translator.acceptItem(bedrockItem)) {
-                    translator.translateToBedrock(itemStack.getNbt(), bedrockItem);
+                    translator.translateToBedrock(nbt, bedrockItem);
                 }
             }
         }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
index 8c418c0f2..51029b83d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java
@@ -57,4 +57,9 @@ public class MapItemTranslator extends NbtItemStackTranslator {
             itemTag.remove("map_uuid");
         }
     }
+
+    @Override
+    public boolean acceptItem(ItemEntry itemEntry) {
+        return itemEntry.getJavaIdentifier().equals("minecraft:filled_map");
+    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java
index e26a5880f..115cae55e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityHeadLookTranslator.java
@@ -25,17 +25,12 @@
 
 package org.geysermc.connector.network.translators.java.entity;
 
+import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket;
 import org.geysermc.connector.entity.Entity;
-import org.geysermc.connector.entity.type.EntityType;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
 
-import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityHeadLookPacket;
-import com.nukkitx.math.vector.Vector3f;
-import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
-import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
-
 @Translator(packet = ServerEntityHeadLookPacket.class)
 public class JavaEntityHeadLookTranslator extends PacketTranslator<ServerEntityHeadLookPacket> {
 
@@ -48,21 +43,6 @@ public class JavaEntityHeadLookTranslator extends PacketTranslator<ServerEntityH
 
         if (entity == null) return;
 
-        entity.setRotation(Vector3f.from(entity.getRotation().getX(), entity.getRotation().getY(), packet.getHeadYaw()));
-
-        if (entity.getEntityType() != EntityType.PLAYER && entity.getEntityType() != EntityType.PAINTING) {
-            MoveEntityAbsolutePacket moveEntityAbsolutePacket = new MoveEntityAbsolutePacket();
-            moveEntityAbsolutePacket.setRuntimeEntityId(entity.getGeyserId());
-            moveEntityAbsolutePacket.setPosition(entity.getPosition());
-            moveEntityAbsolutePacket.setRotation(entity.getBedrockRotation());
-            session.sendUpstreamPacket(moveEntityAbsolutePacket);
-        } else {
-            MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
-            movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
-            movePlayerPacket.setPosition(entity.getPosition());
-            movePlayerPacket.setRotation(entity.getBedrockRotation());
-            movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION);
-            session.sendUpstreamPacket(movePlayerPacket);
-        }
+        entity.updateHeadLookRotation(session, packet.getHeadYaw());
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java
index 477c8f261..c4bb799e6 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java
@@ -25,14 +25,12 @@
 
 package org.geysermc.connector.network.translators.java.entity;
 
+import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket;
 import org.geysermc.connector.entity.Entity;
-import org.geysermc.connector.entity.type.EntityType;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
 
-import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPositionRotationPacket;
-
 @Translator(packet = ServerEntityPositionRotationPacket.class)
 public class JavaEntityPositionRotationTranslator extends PacketTranslator<ServerEntityPositionRotationPacket> {
 
@@ -43,10 +41,7 @@ public class JavaEntityPositionRotationTranslator extends PacketTranslator<Serve
             entity = session.getPlayerEntity();
         }
         if (entity == null) return;
-        if (entity.getEntityType() == EntityType.BOAT) {
-            entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw() - 90, packet.getPitch(), packet.isOnGround());
-        } else {
-            entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround());
-        }
+
+        entity.updatePositionAndRotation(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround());
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java
index d08bb1afb..744b11e96 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java
@@ -25,6 +25,8 @@
 
 package org.geysermc.connector.network.translators.java.entity;
 
+import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
+import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket;
 import org.geysermc.connector.entity.Entity;
 import org.geysermc.connector.entity.attribute.AttributeType;
 import org.geysermc.connector.network.session.GeyserSession;
@@ -32,17 +34,16 @@ import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
 import org.geysermc.connector.utils.AttributeUtils;
 
-import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
-import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket;
-
 @Translator(packet = ServerEntityPropertiesPacket.class)
 public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntityPropertiesPacket> {
 
     @Override
     public void translate(ServerEntityPropertiesPacket packet, GeyserSession session) {
+        boolean isSessionPlayer = false;
         Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
         if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) {
             entity = session.getPlayerEntity();
+            isSessionPlayer = true;
         }
         if (entity == null) return;
 
@@ -54,6 +55,13 @@ public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntit
                 case GENERIC_ATTACK_DAMAGE:
                     entity.getAttributes().put(AttributeType.ATTACK_DAMAGE, AttributeType.ATTACK_DAMAGE.getAttribute((float) AttributeUtils.calculateValue(attribute)));
                     break;
+                case GENERIC_ATTACK_SPEED:
+                    if (isSessionPlayer) {
+                        // Get attack speed value for use in sending the faux cooldown
+                        double attackSpeed = AttributeUtils.calculateValue(attribute);
+                        session.setAttackSpeed(attackSpeed);
+                    }
+                    break;
                 case GENERIC_FLYING_SPEED:
                     entity.getAttributes().put(AttributeType.FLYING_SPEED, AttributeType.FLYING_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute)));
                     entity.getAttributes().put(AttributeType.MOVEMENT_SPEED, AttributeType.MOVEMENT_SPEED.getAttribute((float) AttributeUtils.calculateValue(attribute)));
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java
index fbeb19692..c1d3e5787 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityRotationTranslator.java
@@ -25,17 +25,12 @@
 
 package org.geysermc.connector.network.translators.java.entity;
 
+import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket;
 import org.geysermc.connector.entity.Entity;
-import org.geysermc.connector.entity.type.EntityType;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
 
-import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityRotationPacket;
-import com.nukkitx.math.vector.Vector3f;
-import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
-import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
-
 @Translator(packet = ServerEntityRotationPacket.class)
 public class JavaEntityRotationTranslator extends PacketTranslator<ServerEntityRotationPacket> {
 
@@ -47,23 +42,6 @@ public class JavaEntityRotationTranslator extends PacketTranslator<ServerEntityR
         }
         if (entity == null) return;
 
-        // entity.moveRelative(packet.getMovementX(), packet.getMovementY(), packet.getMovementZ(), packet.getYaw(), packet.getPitch());
-        entity.setRotation(Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw()));
-
-        if (entity.getEntityType() != EntityType.PLAYER) {
-            MoveEntityAbsolutePacket moveEntityAbsolutePacket = new MoveEntityAbsolutePacket();
-            moveEntityAbsolutePacket.setRuntimeEntityId(entity.getGeyserId());
-            moveEntityAbsolutePacket.setPosition(entity.getPosition());
-            moveEntityAbsolutePacket.setRotation(entity.getBedrockRotation());
-            session.sendUpstreamPacket(moveEntityAbsolutePacket);
-        } else {
-            MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
-            movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
-            movePlayerPacket.setPosition(entity.getPosition());
-            movePlayerPacket.setRotation(entity.getBedrockRotation());
-            movePlayerPacket.setOnGround(packet.isOnGround());
-            movePlayerPacket.setMode(MovePlayerPacket.Mode.ROTATION);
-            session.sendUpstreamPacket(movePlayerPacket);
-        }
+        entity.updateRotation(session, packet.getYaw(), packet.getPitch(), packet.isOnGround());
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java
index 9a4686728..cf01d214d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java
@@ -44,6 +44,6 @@ public class JavaEntityTeleportTranslator extends PacketTranslator<ServerEntityT
         }
         if (entity == null) return;
 
-        entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround(), false);
+        entity.teleport(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), packet.isOnGround());
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java
index 920969a75..742fa97e9 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java
@@ -31,10 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData;
 import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
 import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket;
 import com.nukkitx.math.vector.Vector3f;
-import org.geysermc.connector.entity.Entity;
-import org.geysermc.connector.entity.FallingBlockEntity;
-import org.geysermc.connector.entity.FishingHookEntity;
-import org.geysermc.connector.entity.ItemFrameEntity;
+import org.geysermc.connector.entity.*;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
 import org.geysermc.connector.network.translators.Translator;
@@ -73,6 +70,10 @@ public class JavaSpawnEntityTranslator extends PacketTranslator<ServerSpawnEntit
                 // Fishing bobbers need the owner for the line
                 entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
                         type, position, motion, rotation, (ProjectileData) packet.getData());
+            } else if (packet.getType() == EntityType.BOAT) {
+                // Initial rotation is incorrect
+                entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
+                        type, position, motion, Vector3f.from(packet.getYaw(), 0, packet.getYaw()));
             } else {
                 Constructor<? extends Entity> entityConstructor = entityClass.getConstructor(long.class, long.class, org.geysermc.connector.entity.type.EntityType.class,
                         Vector3f.class, Vector3f.class, Vector3f.class);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java
index c8be3a56e..5a3ebabaf 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java
@@ -42,6 +42,7 @@ public class JavaMapDataTranslator extends PacketTranslator<ServerMapDataPacket>
     @Override
     public void translate(ServerMapDataPacket packet, GeyserSession session) {
         ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket();
+        boolean shouldStore = false;
 
         mapItemDataPacket.setUniqueMapId(packet.getMapId());
         mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension());
@@ -55,6 +56,11 @@ public class JavaMapDataTranslator extends PacketTranslator<ServerMapDataPacket>
             mapItemDataPacket.setWidth(data.getColumns());
             mapItemDataPacket.setHeight(data.getRows());
 
+            // We have a full map image, this usually only happens on spawn for the initial image
+            if (mapItemDataPacket.getWidth() == 128 && mapItemDataPacket.getHeight() == 128) {
+                shouldStore = true;
+            }
+
             // Every int entry is an ABGR color
             int[] colors = new int[data.getData().length];
 
@@ -76,6 +82,12 @@ public class JavaMapDataTranslator extends PacketTranslator<ServerMapDataPacket>
             id++;
         }
 
-        session.getUpstream().getSession().sendPacket(mapItemDataPacket);
+        // Store the map to send when the client requests it, as bedrock expects the data after a MapInfoRequestPacket
+        if (shouldStore) {
+            session.getStoredMaps().put(mapItemDataPacket.getUniqueMapId(), mapItemDataPacket);
+        }
+
+        // Send anyway just in case
+        session.sendUpstreamPacket(mapItemDataPacket);
     }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java
index a69a5f197..943fd2394 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/AttributeUtils.java
@@ -70,7 +70,12 @@ public class AttributeUtils {
         return new com.nukkitx.protocol.bedrock.data.Attribute(type.getBedrockIdentifier(), attribute.getMinimum(), attribute.getMaximum(), attribute.getValue(), attribute.getDefaultValue());
     }
 
-    //https://minecraft.gamepedia.com/Attribute#Modifiers
+    /**
+     * Retrieve the base attribute value with all modifiers applied.
+     * https://minecraft.gamepedia.com/Attribute#Modifiers
+     * @param attribute The attribute to calculate the total value.
+     * @return The finished attribute with all modifiers applied.
+     */
     public static double calculateValue(com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute attribute) {
         double base = attribute.getValue();
         for (AttributeModifier modifier : attribute.getModifiers()) {
diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java
new file mode 100644
index 000000000..9444fc2a4
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to deal
+ *  in the Software without restriction, including without limitation the rights
+ *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ *  copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ *  THE SOFTWARE.
+ *
+ *  @author GeyserMC
+ *  @link https://github.com/GeyserMC/Geyser
+ *
+ */
+
+package org.geysermc.connector.utils;
+
+import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.network.session.GeyserSession;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages the sending of a cooldown indicator to the Bedrock player as there is no cooldown indicator in Bedrock.
+ * Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind
+ */
+public class CooldownUtils {
+
+    private final static boolean SHOW_COOLDOWN;
+
+    static {
+        SHOW_COOLDOWN = GeyserConnector.getInstance().getConfig().isShowCooldown();
+    }
+
+    /**
+     * Starts sending the fake cooldown to the Bedrock client.
+     * @param session GeyserSession
+     */
+    public static void sendCooldown(GeyserSession session) {
+        if (!SHOW_COOLDOWN) return;
+        if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used
+        // Needs to be sent or no subtitle packet is recognized by the client
+        SetTitlePacket titlePacket = new SetTitlePacket();
+        titlePacket.setType(SetTitlePacket.Type.SET_TITLE);
+        titlePacket.setText(" ");
+        session.sendUpstreamPacket(titlePacket);
+        session.setLastHitTime(System.currentTimeMillis());
+        long lastHitTime = session.getLastHitTime(); // Used later to prevent multiple scheduled cooldown threads
+        computeCooldown(session, lastHitTime);
+    }
+
+    /**
+     * Keeps updating the cooldown until the bar is complete.
+     * @param session GeyserSession
+     * @param lastHitTime The time of the last hit. Used to gauge how long the cooldown is taking.
+     */
+    private static void computeCooldown(GeyserSession session, long lastHitTime) {
+        if (session.isClosed()) return; // Don't run scheduled tasks if the client left
+        if (lastHitTime != session.getLastHitTime()) return; // Means another cooldown has started so there's no need to continue this one
+        SetTitlePacket titlePacket = new SetTitlePacket();
+        titlePacket.setType(SetTitlePacket.Type.SET_SUBTITLE);
+        titlePacket.setText(getTitle(session));
+        titlePacket.setFadeInTime(0);
+        titlePacket.setFadeOutTime(5);
+        titlePacket.setStayTime(2);
+        session.sendUpstreamPacket(titlePacket);
+        if (hasCooldown(session)) {
+            session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50
+        } else {
+            SetTitlePacket removeTitlePacket = new SetTitlePacket();
+            removeTitlePacket.setType(SetTitlePacket.Type.SET_SUBTITLE);
+            removeTitlePacket.setText(" ");
+            session.sendUpstreamPacket(removeTitlePacket);
+        }
+    }
+
+    private static boolean hasCooldown(GeyserSession session) {
+        long time = System.currentTimeMillis() - session.getLastHitTime();
+        double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1.5);
+        return cooldown < 1.1;
+    }
+
+
+    private static double restrain(double x, double max) {
+        if (x < 0d)
+            return 0d;
+        return Math.min(x, max);
+    }
+
+    private static String getTitle(GeyserSession session) {
+        long time = System.currentTimeMillis() - session.getLastHitTime();
+        double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1);
+
+        int darkGrey = (int) Math.floor(10d * cooldown);
+        int grey = 10 - darkGrey;
+        StringBuilder builder = new StringBuilder("§8");
+        while (darkGrey > 0) {
+            builder.append("˙");
+            darkGrey--;
+        }
+        builder.append("§7");
+        while (grey > 0) {
+            builder.append("˙");
+            grey--;
+        }
+        return builder.toString();
+    }
+
+}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
index f55cb261e..e213fe6cc 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
@@ -33,6 +33,7 @@ import org.geysermc.connector.GeyserConnector;
 
 import java.io.*;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.*;
 import java.util.zip.ZipFile;
@@ -49,7 +50,8 @@ public class LocaleUtils {
 
     static {
         // Create the locales folder
-        File localesFolder = new File("locales/");
+        File localesFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile();
+        //noinspection ResultOfMethodCallIgnored
         localesFolder.mkdir();
 
         // Download the latest asset list and cache it
@@ -132,7 +134,7 @@ public class LocaleUtils {
      * @param locale Locale to download
      */
     private static void downloadLocale(String locale) {
-        File localeFile = new File("locales/" + locale + ".json");
+        File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile();
 
         // Check if we have already downloaded the locale file
         if (localeFile.exists()) {
@@ -149,7 +151,7 @@ public class LocaleUtils {
 
         // Get the hash and download the locale
         String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
-        WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json");
+        WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
     }
 
     /**
@@ -205,10 +207,11 @@ public class LocaleUtils {
             GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL);
 
             // Download the smallest JAR (client or server)
-            WebUtils.downloadFile(smallestURL, "tmp_locale.jar");
+            Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar");
+            WebUtils.downloadFile(smallestURL, tmpFilePath.toString());
 
             // Load in the JAR as a zip and extract the file
-            ZipFile localeJar = new ZipFile("tmp_locale.jar");
+            ZipFile localeJar = new ZipFile(tmpFilePath.toString());
             InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
             FileOutputStream outputStream = new FileOutputStream(localeFile);
 
@@ -227,7 +230,7 @@ public class LocaleUtils {
             localeJar.close();
 
             // Delete the nolonger needed client/server jar
-            Files.delete(Paths.get("tmp_locale.jar"));
+            Files.delete(tmpFilePath);
         } catch (Exception e) {
             throw new AssertionError("Unable to download and extract en_us locale!", e);
         }
diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml
index 931e0a8d4..e0e0771da 100644
--- a/connector/src/main/resources/config.yml
+++ b/connector/src/main/resources/config.yml
@@ -8,7 +8,8 @@
 # --------------------------------
 
 bedrock:
-  # The IP address that will listen for connections
+  # The IP address that will listen for connections.
+  # There is no reason to change this unless you want to limit what IPs can connect to your server.
   address: 0.0.0.0
   # The port that will listen for connections
   port: 19132
@@ -74,6 +75,9 @@ allow-third-party-capes: true
 # MinecraftCapes
 allow-third-party-ears: false
 
+# Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat
+show-cooldown: true
+
 # The default locale if we dont have the one the client requested
 default-locale: en_us