From 8f98162c69c10a58879b1edbd2877ede588cdc7c Mon Sep 17 00:00:00 2001
From: David Choo <davchoo@users.noreply.github.com>
Date: Wed, 21 Jul 2021 21:48:59 -0400
Subject: [PATCH] Fix Item drop animation (#2406)

---
 .../geysermc/connector/entity/ItemEntity.java | 78 +++++++++++++++----
 .../entity/JavaEntityVelocityTranslator.java  |  7 ++
 .../world/block/BlockStateValues.java         | 22 ++++++
 3 files changed, 91 insertions(+), 16 deletions(-)

diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java
index 1615ac8d8..3dcfb0d93 100644
--- a/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java
+++ b/connector/src/main/java/org/geysermc/connector/entity/ItemEntity.java
@@ -28,31 +28,23 @@ package org.geysermc.connector.entity;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
 import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
 import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
 import com.nukkitx.protocol.bedrock.packet.AddItemEntityPacket;
 import org.geysermc.connector.entity.type.EntityType;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.item.ItemTranslator;
+import org.geysermc.connector.network.translators.world.block.BlockStateValues;
 
-public class ItemEntity extends Entity {
+public class ItemEntity extends ThrowableEntity {
 
     protected ItemData item;
 
+    private int waterLevel = -1;
+
     public ItemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
-        super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation);
-    }
-
-    @Override
-    public void setMotion(Vector3f motion) {
-        if (isOnGround())
-            motion = Vector3f.from(motion.getX(), 0, motion.getZ());
-
-        super.setMotion(motion);
-    }
-
-    @Override
-    public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
-        super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported);
+        super(entityId, geyserId, entityType, position, motion, rotation);
     }
 
     @Override
@@ -63,14 +55,63 @@ public class ItemEntity extends Entity {
         valid = true;
         AddItemEntityPacket itemPacket = new AddItemEntityPacket();
         itemPacket.setRuntimeEntityId(geyserId);
+        itemPacket.setUniqueEntityId(geyserId);
         itemPacket.setPosition(position.add(0d, this.entityType.getOffset(), 0d));
         itemPacket.setMotion(motion);
-        itemPacket.setUniqueEntityId(geyserId);
         itemPacket.setFromFishing(false);
         itemPacket.setItemInHand(item);
+        itemPacket.getMetadata().putAll(metadata);
         session.sendUpstreamPacket(itemPacket);
     }
 
+    @Override
+    public void tick(GeyserSession session) {
+        if (isInWater(session)) {
+            return;
+        }
+        if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
+            float gravity = getGravity(session);
+            motion = motion.down(gravity);
+            moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
+            float drag = getDrag(session);
+            motion = motion.mul(drag, 0.98f, drag);
+        }
+    }
+
+    @Override
+    protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
+        float offset = entityType.getOffset();
+        if (waterLevel == 0) { // Item is in a full block of water
+            // Move the item entity down so it doesn't float above the water
+            offset = -entityType.getOffset();
+        }
+        super.moveAbsoluteImmediate(session, position.add(0, offset, 0), Vector3f.ZERO, isOnGround, teleported);
+        this.position = position;
+
+        int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
+        waterLevel = BlockStateValues.getWaterLevel(block);
+    }
+
+    @Override
+    protected float getGravity(GeyserSession session) {
+        if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater(session)) {
+            // Gravity can change if the item is in water/lava, but
+            // the server calculates the motion & position for us
+            return 0.04f;
+        }
+        return 0;
+    }
+
+    @Override
+    protected float getDrag(GeyserSession session) {
+        if (onGround) {
+            Vector3i groundBlockPos = position.toInt().down(1);
+            int blockState = session.getConnector().getWorldManager().getBlockAt(session, groundBlockPos);
+            return BlockStateValues.getSlipperiness(blockState) * 0.98f;
+        }
+        return 0.98f;
+    }
+
     @Override
     public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
         if (entityMetadata.getId() == 8) {
@@ -81,4 +122,9 @@ public class ItemEntity extends Entity {
 
         super.updateBedrockMetadata(entityMetadata, session);
     }
+
+    @Override
+    protected boolean isInWater(GeyserSession session) {
+        return waterLevel != -1;
+    }
 }
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java
index 1c628503a..3f0b69cdb 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java
@@ -26,6 +26,7 @@
 package org.geysermc.connector.network.translators.java.entity;
 
 import org.geysermc.connector.entity.Entity;
+import org.geysermc.connector.entity.ItemEntity;
 import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity;
 import org.geysermc.connector.network.session.GeyserSession;
 import org.geysermc.connector.network.translators.PacketTranslator;
@@ -54,6 +55,12 @@ public class JavaEntityVelocityTranslator extends PacketTranslator<ServerEntityV
             return;
         }
 
+        if (entity instanceof ItemEntity) {
+            // Don't bother sending entity motion packets for items
+            // since the client doesn't seem to care
+            return;
+        }
+
         SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
         entityMotionPacket.setRuntimeEntityId(entity.getGeyserId());
         entityMotionPacket.setMotion(entity.getMotion());
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
index 3be8752dd..d66f8592d 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java
@@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.world.block;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import it.unimi.dsi.fastutil.ints.*;
+import org.geysermc.connector.registry.BlockRegistries;
 
 import java.util.Map;
 import java.util.function.BiFunction;
@@ -301,4 +302,25 @@ public class BlockStateValues {
     public static int getWaterLevel(int state) {
         return WATER_LEVEL.getOrDefault(state, -1);
     }
+
+    /**
+     * Get the slipperiness of a block.
+     * This is used in ItemEntity to calculate the friction on an item as it slides across the ground
+     *
+     * @param state BlockState of the block
+     * @return The block's slipperiness
+     */
+    public static float getSlipperiness(int state) {
+        String blockIdentifier = BlockRegistries.JAVA_BLOCKS.get(state).getJavaIdentifier();
+        switch (blockIdentifier) {
+            case "minecraft:slime_block":
+                return 0.8f;
+            case "minecraft:ice":
+            case "minecraft:packed_ice":
+                return 0.98f;
+            case "minecraft:blue_ice":
+                return 0.989f;
+        }
+        return 0.6f;
+    }
 }